# Code Storage
Source: https://docs.pulsy.app/atria/architecture/code-storage
Learn where feed code lives in Atria.
# Code Storage
Atria stores feed code separately from feed metadata.
## What Is Stored
* Filter JavaScript.
* Optional function JavaScript.
* File paths linked from feed records.
## Storage Backends
Deployments can use local file storage or object storage depending on configuration.
## Why It Matters
Keeping code in file storage allows feeds to keep metadata in PostgreSQL while loading executable code only when needed for testing or deployment.
For how feed definitions point to code files, see [feed manifest](/atria/core-concepts/feed-manifest).
# Delivery
Source: https://docs.pulsy.app/atria/architecture/delivery
See how Atria delivers matching feed results.
# Delivery
The Delivery service picks up feed results and invokes the feed's configured outputs.
## Delivery Flow
```mermaid theme={null}
flowchart TB
Runtime[Runtime publishes feed result] --> Stream[Feed result stream]
Stream --> Delivery[Delivery service reads result]
Delivery --> Config[Load configured outputs]
Config --> Invoke[Invoke output]
Invoke --> Success{Output succeeds?}
Success -->|Yes| Ack[Acknowledge result]
Success -->|No| Retry[Retry delivery]
Retry --> Pause[Pause feed after repeated failures]
```
## Webhooks
Webhook delivery is the supported output type available today. Each webhook output includes a URL, HTTP method, headers, and timeout settings. Headers can be used to pass authentication or other verification values your endpoint requires.
## Retries
The Delivery service retries failed webhook sends. If delivery keeps failing, it can request that the feed be paused.
## Decoupling
Delivery is decoupled from feed execution. This lets the Runtime publish matched results while the Delivery service invokes the configured outputs separately.
# Ingestion
Source: https://docs.pulsy.app/atria/architecture/ingestion
Learn how Atria reads blockchain data for feeds.
# Ingestion
The Ingestor connects to configured blockchain networks, reads block data, and writes normalized payloads into Atria's block store. EVM networks are supported today, with additional chain families on the roadmap.
## What It Reads
* Blocks with transactions.
* Blocks with logs.
* Debug traces.
## Network Flow
```mermaid theme={null}
flowchart LR
RPC[HTTP RPC] --> Ingestor
WS[WebSocket blocks] --> Ingestor
Ingestor --> Blocks[Block data store]
Ingestor --> State[Chain state store]
```
## Realtime and Fallback
The Ingestor can listen for new blocks over WebSocket and uses polling fallback so feeds can continue processing when a WebSocket signal is delayed or unavailable.
For chain reorganizations, see [reorg handling](/atria/architecture/reorg-handling).
# Leases and Cursors
Source: https://docs.pulsy.app/atria/architecture/leases-and-cursors
Understand how Atria coordinates work and progress.
# Leases and Cursors
Atria uses leases to decide which service instance owns work, and cursors to track feed progress. A lease is a temporary ownership claim. It is not a user-facing feed setting. It is an internal coordination mechanism that helps Atria avoid duplicate processing.
## Runtime Lease
A runtime lease marks which runtime instance owns a feed. This prevents multiple runtime instances from processing the same feed at the same time.
## Delivery Lease
A delivery lease coordinates delivery workers so feed outputs are not delivered by multiple workers at once.
## Cursor
The cursor stores the next block number a feed should process.
```mermaid theme={null}
flowchart LR
Runtime[Runtime instance] --> Lease[Acquire feed lease]
Lease --> Cursor[Read cursor]
Cursor --> Process[Process next block]
Process --> Save[Save next cursor]
```
See [cursors and block delay](/atria/core-concepts/cursors-and-block-delay).
# Orchestrator
Source: https://docs.pulsy.app/atria/architecture/orchestrator
Learn how Atria coordinates deployments and feed health.
# Orchestrator
The Orchestrator coordinates feed deployment, status transitions, delivery configuration requests, and runtime health.
## Responsibilities
* Handles feed deploy requests.
* Tracks `Pending`, `Running`, `Paused`, `Error`, and `Completed` transitions.
* Confirms runtime deployment events.
* Responds to delivery services with output configuration.
* Provisions feeds and outputs from manifests.
## Control Flow
```mermaid theme={null}
sequenceDiagram
participant Control as Control plane
participant Orchestrator
participant Runtime
Control->>Orchestrator: feed.deploy.req
Orchestrator->>Runtime: deploy feed
Runtime->>Orchestrator: feed.deployed
Orchestrator->>Control: update feed status
```
# Reorg Handling
Source: https://docs.pulsy.app/atria/architecture/reorg-handling
Learn how Atria handles chain reorganizations.
# Reorg Handling
Blockchain reorganizations can replace previously observed blocks. Atria tracks block hashes and parent hashes so the Ingestor can detect when the local chain view diverges from the network.
## Detection
For each new block, the Ingestor compares the block parent hash with the stored hash of the previous block. A mismatch indicates a possible reorg.
## Response
When a reorg is detected, Atria searches back to a common ancestor, rewinds stored state, and publishes a reorg event.
```mermaid theme={null}
flowchart LR
NewBlock[New block] --> Compare[Compare parent hash]
Compare --> Match{Matches stored hash?}
Match -->|Yes| Store[Store block]
Match -->|No| Rewind[Find ancestor and rewind]
Rewind --> Event[Publish reorg event]
```
## Feed Impact
Feeds receive metadata that includes `isReorg`. Feed authors can use this field when they need reorg-aware behavior.
For payload shapes, see [data types](/atria/core-concepts/data-types).
# Runtime
Source: https://docs.pulsy.app/atria/architecture/runtime
Learn how Atria runs feed logic.
# Runtime
The Runtime executes deployed feeds. It claims feed leases, reads block data, runs filters, optionally runs functions, and publishes feed results. A lease is a temporary ownership claim for a feed, used to keep execution coordinated when more than one runtime instance exists.
## Runtime Flow
```mermaid theme={null}
flowchart LR
Deploy[Deploy request] --> Lease[Runtime lease]
Lease --> Cursor[Load cursor]
Cursor --> Blocks[Read blocks]
Blocks --> Filter[Run filter]
Filter --> Function[Optional function]
Function --> Results[Publish result]
Results --> Cursor
```
## Filter Execution
Filters run in a V8 JavaScript environment. They are compiled once per feed deployment and executed for each payload.
Bundled modules include:
* `ethers`
* `@atria/sdk`
* `@atria/kv`
For more details, see [filters](/atria/core-concepts/filters).
## Function Execution
If a feed includes a function, the Runtime runs it after the filter emits a result. The function receives the filter result and can perform post-filter work such as external lookups, managed database reads, heavier business logic, or action-specific preparation. In the current runtime, functions run in a Fission-based serverless environment.
If the filter returns `null` or `undefined`, the feed does not emit and the function is not called. If the feed has no function, the filter result becomes the feed result.
For more details, see [functions](/atria/core-concepts/functions).
## Cursors
The Runtime stores a cursor per feed. When a feed restarts, it resumes from the last stored block unless a new start block is configured.
For coordination details, see [leases and cursors](/atria/architecture/leases-and-cursors).
## Limits
Execution time, heap size, stack usage, and output size are bounded by runtime settings. See [security and sandboxing](/atria/architecture/security-and-sandboxing).
# Security and Sandboxing
Source: https://docs.pulsy.app/atria/architecture/security-and-sandboxing
See how Atria keeps feed execution constrained.
# Security and Sandboxing
Atria runs feed filters in a constrained JavaScript environment. The goal is to let teams write useful feed logic while limiting runtime risk.
## Sandbox Controls
The JavaScript wrapper and runtime disable or constrain risky behavior:
* `eval` is disabled.
* The `Function` constructor is blocked.
* WebAssembly, shared memory primitives, and atomics are removed.
* Common prototypes are frozen.
* Console output is suppressed.
* Execution timeout is enforced.
* Heap, stack, and output size limits are enforced.
## Module Loading
Filters can only `require` modules made available by the runtime configuration. Current bundled modules include `ethers`, `@atria/sdk`, and `@atria/kv`.
# Storage and Messaging
Source: https://docs.pulsy.app/atria/architecture/storage-and-messaging
Explore the storage and messaging layers behind Atria.
# Storage and Messaging
Atria uses PostgreSQL for product metadata and NATS for event streams, block storage, leases, cursors, and chain state.
## PostgreSQL
PostgreSQL stores:
* Feeds.
* Outputs.
* Tags.
* Feed-output links.
* Deploy records.
* Status history.
## NATS JetStream and KV
NATS is used for:
* Feed deploy requests.
* Feed deployed events.
* Pause events.
* Feed result streams.
* Block data buckets.
* Chain state.
* Runtime and delivery leases.
* Feed cursors.
## High-Level Map
```mermaid theme={null}
flowchart TB
Backend[Management backend] --> PG[(PostgreSQL metadata)]
Orchestrator --> PG
Ingestor --> KV[(NATS KV block data)]
Runtime --> KV
Runtime --> JS[(JetStream results)]
Delivery --> JS
```
For feed result delivery, see [delivery](/atria/architecture/delivery).
# System Overview
Source: https://docs.pulsy.app/atria/architecture/system-overview
Meet the main services that make up Atria.
# System Overview
Atria is composed of focused services that separate control, ingestion, execution, and delivery.
## Services
* **Dashboard**: Web UI for creating and monitoring feeds.
* **Management backend**: Coordinates feed metadata, outputs, tags, configuration, deploys, and results for the Dashboard and automation.
* **Orchestrator**: Deployment coordination, lease checks, status updates, and local provisioning.
* **Ingestor**: Blockchain connectivity and block data ingestion.
* **Runtime**: Feed execution, cursor management, filters, and optional functions.
* **Delivery**: Result consumption and webhook delivery.
```mermaid theme={null}
flowchart TB
Blockchain[Blockchain networks] --> Ingestor
Dashboard --> Backend[Management backend]
Backend --> Database[(PostgreSQL)]
Backend --> Orchestrator
Ingestor --> NATS[(NATS JetStream and KV)]
Runtime --> NATS
NATS --> Runtime
Orchestrator --> Runtime
Runtime --> Delivery
Delivery --> Webhooks[Webhook destinations]
```
## Infrastructure
Atria uses PostgreSQL for core metadata and NATS JetStream/KV for messaging, leases, cursors, chain state, and block data.
See [storage and messaging](/atria/architecture/storage-and-messaging).
For runtime ownership and progress tracking, see [leases and cursors](/atria/architecture/leases-and-cursors).
# Atria 0.8.23
Source: https://docs.pulsy.app/atria/changelog/0-8-23
Share a feed with a public link, let anyone start from it, and read per-feed metrics in the dashboard.
**Released July 24, 2026**
This release adds public sharing for feeds. You can send someone a link to a feed and they can read its code and watch its events arrive live, without an account. It also gives every feed a Metrics tab, and fixes a set of problems on small screens.
## Share a feed with a link
Atria Cloud only
Open a feed and click **Share** in the feed header, then **Enable sharing**. You get a link like `https://atria.pulsy.app/feed/share/abc123`.
Anyone with that link can open the feed without signing in. They see:
* The feed name, description and status.
* The network it reads and the data type it handles.
* The filter code.
* Results arriving live, the same stream you watch in your own workspace.
* The feed's metrics, including how much data it processed and how much it produced.
Outputs, webhooks, tags and deploy history stay private. They are never on the shared page.
Sharing is off until you turn it on, and turning it off stops the link from opening the feed.
**Read your code before you enable a link.** Whatever is hard-coded in your filter is visible to anyone who opens it. The dialog says the same thing.
See [share a feed](/atria/dashboard/share-a-feed) for the full detail.
### Start from a shared feed
A shared feed has a **Use this data** button. It copies the feed into your own workspace as a new feed, with the settings and the code already in place. You then add your own output before you test or deploy, since outputs are not part of what gets shared.
The button works for people who do not have an account yet. They sign up, and the feed is waiting for them when they land.
So a link is not only something to read. Send someone a feed that already does the thing, and they start from it instead of from an empty editor.
### Link previews
Paste a shared link anywhere that unfurls links, like Slack or X, and it renders a preview card with the feed name and its network.
## Feed metrics
Every feed now has a **Metrics** tab.
Health:
* Blocks processed.
* Outputs produced.
* Failures.
* Successfully delivered.
Volume:
* Data reduction, how much smaller your output is than the raw block data it was built from.
* Data volume over time, input processed against output produced.
Choose a range of the last hour, 24 hours or 7 days.
This answers a question you could not answer before: is the feed keeping up, and are its deliveries landing. The dashboard already showed the cursor and the chain head, so you could see how far behind a feed was, but not whether its output was getting through.
## Fixes
* The dashboard sets a viewport meta tag, so it scales properly on a phone.
* Fixed dialogs and feed tables on small screens.
* Fixed the chat panel on small screens. Atria Cloud only
* Feed tables now look and behave the same across the app.
* Tidied up the feed workspace layout, header and navigation.
# Atria Changelog
Source: https://docs.pulsy.app/atria/changelog/overview
New features, fixes and API changes in Atria.
What's new in Atria. Each release is listed here with a short summary. Open a release for the full detail.
## [Atria 0.8.23](/atria/changelog/0-8-23)
Share a feed with a public link, let anyone start from it with one click, and read per-feed metrics in the dashboard.
[Read the full release](/atria/changelog/0-8-23)
# Cursors and Block Delay
Source: https://docs.pulsy.app/atria/core-concepts/cursors-and-block-delay
Learn how feeds track progress through blocks.
# Cursors and Block Delay
Atria uses cursors and block delay to process chain data in order.
These two concepts are central to running feeds reliably. The cursor answers “where should this feed continue from?” Block delay answers “how far behind the chain head should this feed process?”
## Cursor
The cursor records the next block a feed should process. When a running feed restarts, the runtime resumes from the saved cursor.
## Start Block
If a feed defines `startBlock`, Atria begins from that block. If no start block is provided, the feed begins from the current chain head.
## End Block
If a feed defines `endBlock`, processing completes once the feed reaches that block.
## Block Delay
Block delay tells the runtime to wait until the chain has advanced beyond the target block. This can reduce the chance of acting on data that may be affected by a reorg.
Low block delay gives faster detection. Higher block delay gives the chain more time to settle. The right value depends on the network, the business impact of acting too early, and whether the downstream workflow can tolerate reorg-aware updates.
For operational behavior, see [running feeds](/atria/operations/running-feeds).
# Data Types
Source: https://docs.pulsy.app/atria/core-concepts/data-types
Explore the blockchain payloads feeds can process.
# Data Types
Each feed subscribes to one blockchain payload type. The selected data type determines the shape passed to `main(stream)` in your filter.
## `BlockWithTransactions`
Use this for native token transfers and transaction-level monitoring.
```json theme={null}
{
"metadata": { "networkId": "ethereum-mainnet", "blockNumber": "123", "isReorg": false },
"block": {
"number": "0x7b",
"hash": "0x...",
"timestamp": "0x...",
"transactions": [
{ "hash": "0x...", "from": "0x...", "to": "0x...", "value": "0x..." }
]
}
}
```
## `BlockWithLogs`
Use this for contract events, including ERC-20 `Transfer` logs.
```json theme={null}
{
"metadata": { "networkId": "ethereum-mainnet", "blockNumber": "123", "isReorg": false },
"logs": [
{ "address": "0x...", "transactionHash": "0x...", "data": "0x...", "topics": ["0x..."] }
]
}
```
## `BlockWithTraces`
Use this for debug trace workflows.
```json theme={null}
{
"metadata": { "networkId": "ethereum-mainnet", "blockNumber": "123", "isReorg": false },
"traces": []
}
```
## Choosing a Type
* Use transactions for native transfers.
* Use logs for emitted smart contract events.
* Use traces for internal calls and execution-level analysis.
# Feed Lifecycle
Source: https://docs.pulsy.app/atria/core-concepts/feed-lifecycle
Understand how feeds move through their running states.
# Feed Lifecycle
A feed moves through a small set of statuses as it is created, deployed, run, paused, or completed.
## Statuses
* `Draft`: The feed exists but is not running.
* `Pending`: A deployment request has been created.
* `Running`: A runtime instance has claimed the feed and is processing blocks.
* `Paused`: Processing was stopped by a user or by the system.
* `Error`: The system paused the feed because deployment or processing failed.
* `Completed`: The feed reached its configured end block.
## Lifecycle Flow
```mermaid theme={null}
stateDiagram-v2
[*] --> Draft
Draft --> Pending: start
Pending --> Running: deployed
Running --> Paused: pause
Running --> Error: unrecovered failure
Running --> Completed: end block reached
Paused --> Pending: start again
Error --> Pending: retry
```
## Operational Details
The [Orchestrator](/atria/architecture/orchestrator) tracks deployment state. The [Runtime](/atria/architecture/runtime) owns active execution through a lease, which is a temporary claim that prevents two runtime instances from processing the same feed at the same time. The runtime also stores the cursor and resumes from the last processed block.
For more detail, see [leases and cursors](/atria/architecture/leases-and-cursors).
# Feed Manifest
Source: https://docs.pulsy.app/atria/core-concepts/feed-manifest
Understand the definition behind a feed.
# Feed Manifest
A feed manifest is the declarative definition behind every feed. It captures the feed's identity, source data, runtime code, and connected outputs in a form Atria can provision and operate.
## How Atria Uses It
The Orchestrator uses the manifest definition to validate the required fields and turn the feed definition into an executable feed record.
```mermaid theme={null}
flowchart LR
Manifest[Feed manifest] --> Validate[Validate required fields]
Validate --> Feed[Create or update feed]
Feed --> Runtime[Runtime executes feed]
Feed --> Delivery[Outputs connect actions]
```
The manifest is not runtime state. It does not store the current cursor, feed status, recent results, delivery attempts, or deployment history. Those are managed by Atria after the feed exists.
## Example
```json theme={null}
{
"name": "Large Native Transfers",
"version": "1.0.0",
"description": "Emits when native transfers match the configured criteria.",
"author": "Your Team",
"config": {
"source": {
"networkId": "ethereum-mainnet",
"dataType": "BlockWithTransactions",
"startBlock": null,
"endBlock": null
},
"runtime": {
"errorHandling": "ContinueOnError",
"filter": {
"path": "filter.js"
}
},
"destination": {
"outputs": ["operations-webhook"],
"errorHandling": "ContinueOnError"
}
}
}
```
## Main Fields
* `name`: Human-readable feed name.
* `version`: Feed definition version.
* `description`: Short explanation of what the feed detects or enables.
* `author`: Optional owner or team label.
* `config.source.networkId`: Network identifier, such as `ethereum-mainnet`.
* `config.source.dataType`: Payload type passed to the filter. See [data types](/atria/core-concepts/data-types).
* `config.source.startBlock`: Optional first block to process.
* `config.source.endBlock`: Optional last block to process.
* `config.runtime.filter.path`: Path to the filter code used by the feed.
* `config.runtime.function.path`: Optional path to post-filter function code when a function is used.
* `config.runtime.errorHandling`: Runtime error strategy for feed execution.
* `config.destination.outputs`: Optional list of output names to connect to the feed.
* `config.destination.errorHandling`: Delivery-side error strategy.
## What Gets Provisioned
When Atria creates or updates a feed from a manifest, the feed receives the manifest's name, version, description, network, data type, start and end block, filter path, optional function path, and connected outputs.
Outputs are resolved by name. For example, if `config.destination.outputs` contains `operations-webhook`, Atria looks for an output with that name and connects it to the feed.
## Manifest and Code Files
The manifest points to code, but it does not contain the code itself. The filter path tells Atria which file to load for the feed's [filter](/atria/core-concepts/filters). If the feed uses a [function](/atria/core-concepts/functions), the function path points to that code as well.
For practical examples of feed manifests, see the [Atria Library](/atria/library/overview).
# Filters
Source: https://docs.pulsy.app/atria/core-concepts/filters
Learn how filters select and shape feed emissions.
# Filters
A filter is the per-feed event handling step that runs against each payload from the feed's selected data type. It is not the whole feed and it is not the whole workflow. It is the part of the feed that inspects the current `stream`, decides whether this payload should emit, and can shape the object that gets emitted.
In the current runtime, filters are authored in JavaScript. A filter receives `stream` and returns either a result object or no output.
## Required Shape
Every filter must define `main(stream)`. Atria calls this function for each payload assigned to the feed. Your code goes inside `main`.
```javascript theme={null}
function main(stream) {
// 1. Inspect the incoming payload.
const blockNumber = stream.metadata?.blockNumber;
// 2. Decide whether this payload should emit.
if (!blockNumber) return null;
// 3. Return the payload shape you want the feed to emit.
return {
metadata: stream.metadata,
blockNumber
};
}
```
## Return Behavior
* Return `null` or `undefined` to emit no output.
* Return a JSON-serializable object to emit a result.
* Avoid returning very large objects. The default filter output limit is 8 MB, controlled by the runtime `MaxOutputSizeKB` setting.
This return behavior is important because it makes a feed selective and programmable at the event boundary. Atria does not treat every block, transaction, or log as something that must be emitted. Your filter is where the feed answers two questions: should this payload emit, and what should the emitted payload look like?
## Available Modules
Filters can use bundled runtime modules:
* `ethers` for EVM encoding, decoding, units, and ABI helpers.
* `@atria/sdk` for Atria helper functions such as EVM log decoding.
* `@atria/kv` for feed-accessible key-value storage when enabled.
See [JavaScript modules](/atria/core-concepts/javascript-modules) for more detail.
## Execution Model
Filters run inside an embedded V8 engine. The runtime serializes input to JSON, calls `main`, then deserializes the returned value.
The filter should be deterministic and quick. It should parse the incoming payload, check the event condition, and return a compact object that the next workflow step can understand. Keep heavier enrichment, integration-specific formatting, or optional business-specific computation in a [function](/atria/core-concepts/functions) when that separation makes the feed easier to operate.
Learn more in [runtime architecture](/atria/architecture/runtime) and [security and sandboxing](/atria/architecture/security-and-sandboxing).
# Functions
Source: https://docs.pulsy.app/atria/core-concepts/functions
Learn when optional functions should extend a feed.
# Functions
A function is optional logic that runs after a filter. It receives the filter result and prepares the feed output for the action, integration, or system that comes next.
## When to Use a Function
Use a function when you need to:
* Keep the filter focused on event matching and first-pass shaping.
* Add a separate post-filter step for heavier business logic.
* Call an external API or managed database after an event has already matched.
* Prepare action-specific context without hiding the trigger condition.
* Isolate integration-specific work from the core feed logic.
A good mental model is: the filter answers "does this payload matter, and what should the feed emit first?" A function answers "after the feed has emitted, what extra work is needed before the next action can use it?" That extra work may involve service lookups, database reads, action-specific preparation, or logic that should run outside the filter's tight execution path.
## Flow
```mermaid theme={null}
flowchart LR
Input[Blockchain payload] --> Filter[Filter]
Filter --> Function[Optional function]
Function --> Output[Action-ready feed output]
```
## Runtime Note
Function execution is an optional deployment capability. In the current runtime, functions use the Fission-based serverless path for post-filter execution.
If a feed has no function, the filter result becomes the feed output.
## Design Guidance
Do not use a function just because the output needs a different field name or a smaller object. Filters can already shape the emitted result.
Use a function when the work is meaningfully separate from matching the blockchain payload. Team members should be able to read the filter and understand why a feed emits. The function can then handle post-filter responsibilities such as enrichment from another service, lookup-driven decisions, action preparation, or integration logic.
# JavaScript Modules
Source: https://docs.pulsy.app/atria/core-concepts/javascript-modules
See the JavaScript helpers available inside filters.
# JavaScript Modules
In the current runtime, filters are authored in JavaScript and can use a small set of bundled modules. These modules are available through `require(...)` inside the filter.
Only modules registered by the runtime can be imported. A filter cannot install packages dynamically or import arbitrary npm dependencies.
## `ethers`
Use `ethers` for EVM-specific parsing, encoding, decoding, and unit conversion.
```javascript theme={null}
const ethers = require("ethers");
function main(stream) {
// 0xde0b6b3a7640000 is 1 ETH in wei, encoded as a hex quantity.
const valueEth = ethers.formatEther("0xde0b6b3a7640000");
return {
metadata: stream.metadata,
valueEth
};
}
```
Common uses:
* Build an ABI interface with `new ethers.Interface(...)`.
* Parse event logs with `iface.parseLog(...)`.
* Read an event topic with `iface.getEvent("Transfer").topicHash`.
* Convert native token units with `ethers.parseEther(...)` and `ethers.formatEther(...)`.
* Convert token units with `ethers.parseUnits(...)` and `ethers.formatUnits(...)`.
* Work with large integer values safely through `BigInt`-compatible values.
Use `ethers` when the feed needs low-level EVM handling or precise token amount conversion.
## `@atria/sdk`
Use `@atria/sdk` for Atria-provided helpers that sit above raw `ethers` usage.
```javascript theme={null}
const atria = require("@atria/sdk");
function main(stream) {
const decoded = atria.evm.decodeEVMLogs(stream.logs || [], [
[
"event Transfer(address indexed from, address indexed to, uint256 value)"
]
]);
if (decoded.length === 0) return null;
return {
metadata: stream.metadata,
transfers: decoded
};
}
```
Current helper:
* `atria.evm.decodeEVMLogs(data, abis)`
`decodeEVMLogs` accepts either an array of logs or an array of receipt-like objects with `logs`. It tries to decode each log against the provided ABIs. Logs that do not match are ignored. Decoded logs include a `decodedLog` object with `name`, `signature`, and named `args`.
Use `@atria/sdk` when you want a feed to decode EVM logs without writing the full parse loop yourself.
## `@atria/kv`
Use `@atria/kv` for lightweight feed state when KV access is enabled for the feed runtime.
```javascript theme={null}
const kv = require("@atria/kv");
async function main(stream) {
const seen = kv.bucket("seen-blocks");
const key = stream.metadata.blockNumber;
if (await seen.get(key)) return null;
await seen.add(key, true);
return {
metadata: stream.metadata,
firstSeen: key
};
}
```
Use KV for compact workflow state, lookup values, and simple deduplication. Do not use it as a warehouse, analytics store, or large event archive.
For the full list of bucket operations, see [KV storage](/atria/core-concepts/kv-storage).
## Module Boundaries
Filters can only import the modules that Atria bundles into the runtime. For example, `require("ethers")` works, but `require("lodash")` fails unless that module has been added to the runtime. If you need a small helper, define it in the filter file itself.
See [filters](/atria/core-concepts/filters), [KV storage](/atria/core-concepts/kv-storage), and [security and sandboxing](/atria/architecture/security-and-sandboxing).
# KV Storage
Source: https://docs.pulsy.app/atria/core-concepts/kv-storage
Use lightweight state in Atria workflows.
# KV Storage
Atria includes key-value storage for lightweight workflow state.
KV storage is useful when feed logic needs a small amount of memory across executions. It is not required for most feeds. Many feeds can stay stateless and simply return a result when a block, transaction, or log matches the filter condition.
## Use Cases
* Store small lookup tables.
* Track recently seen identifiers.
* Keep compact feed state.
* Share simple values across executions.
## Filter Access
Filters can use the `@atria/kv` module when KV access is enabled.
```javascript theme={null}
const kv = require("@atria/kv");
async function main(stream) {
const bucket = kv.bucket("seen-transfers");
await bucket.add(stream.metadata.blockNumber, true);
return null;
}
```
## Bucket Operations
Create a bucket handle with `kv.bucket(name)`, then call operations on that bucket.
### Add Values
```javascript theme={null}
await bucket.add("key", { value: 1 });
await bucket.addMany([
{ key: "a", value: { value: 1 } },
{ key: "b", value: { value: 2 } }
]);
```
* `add(key, value)`: stores one value by key.
* `addMany(items)`: stores multiple key-value items in one call.
### Read Values
```javascript theme={null}
const item = await bucket.get("key");
const items = await bucket.getMany(["a", "b"]);
```
* `get(key)`: returns one value, or `null` when no value exists.
* `getMany(keys)`: returns multiple values for the provided keys.
### Remove Values
```javascript theme={null}
await bucket.remove("key");
await bucket.removeMany(["a", "b"]);
```
* `remove(key)`: removes one key.
* `removeMany(keys)`: removes multiple keys.
### List Values
```javascript theme={null}
const page = await bucket.list({
limit: 100,
cursor: ""
});
```
* `list({ limit, cursor })`: returns a page of bucket values.
* `limit` controls the maximum number of values returned.
* `cursor` is used to continue from a previous page.
## Use Carefully
KV is best for compact workflow state and lookup data. It should not replace an analytical database, a warehouse, or a long-term event archive.
If a workflow needs large joins, historical scans, or complex reporting, send feed results to a downstream data store through an output instead of trying to keep that state inside the feed runtime.
# Outputs
Source: https://docs.pulsy.app/atria/core-concepts/outputs
Learn how feed results trigger workflow outputs.
# Outputs
Outputs define where Atria sends matching feed results. A feed can be connected to one or more outputs.
## Webhook Outputs
A webhook output connects a feed to an HTTP endpoint. When the feed emits a result, Atria sends a webhook request so the connected service can react to the on-chain event.
Webhook is currently the only supported output type. Additional output connectors are on the roadmap.
Webhook outputs are commonly used to trigger internal services, automation endpoints, alerting flows, risk checks, operations processes, or integration layers. The endpoint decides what action to take from the feed result it receives.
## Webhook Payload
When a feed emits a result, Atria wraps the user-defined feed output before sending the webhook. The fields returned by the filter, or by the optional function when one is configured, appear inside `data`.
```json theme={null}
{
"feedId": "...",
"outputIds": ["..."],
"data": {
"userDefinedField": "...",
"anotherUserDefinedField": "...",
"nestedUserDefinedObject": {}
},
"isTestExecution": false,
"blockNumber": "123"
}
```
In this shape, everything inside `data` is defined by your feed logic. Atria adds the outer wrapper fields such as `feedId`, `outputIds`, `isTestExecution`, and `blockNumber`.
Fields from the original blockchain payload only appear in the webhook if the feed returns them inside `data`. For example, reorg metadata is available to filters as `stream.metadata.isReorg`; if the feed returns `metadata`, the webhook can include it as `data.metadata.isReorg`.
## Delivery Behavior
The Delivery service picks up feed results from the result stream and invokes configured webhook targets. By default, if a webhook call fails, Atria retries it after 30 seconds. After 30 failed delivery attempts for the same result, Atria pauses the feed with a delivery failure reason.
Webhook-connected actions should be designed to accept retried or delayed messages. Blockchain networks can reorganize, connected systems can time out, and delivery can retry. The connected system should use a stable idempotency key built from the fields that matter for that action, such as `feedId`, `blockNumber`, transaction or log identifiers, and reorg metadata when the feed exposes it.
See [delivery architecture](/atria/architecture/delivery) for how output delivery works after a feed emits a result.
# Results
Source: https://docs.pulsy.app/atria/core-concepts/results
Understand what a feed emits when it matches.
# Results
A result is the structured object emitted by a feed after its filter and optional function complete.
## When Results Are Created
Atria creates a result only when feed logic returns a JSON-safe value. Returning `null` or `undefined` means no result is published.
## Result Wrapper
```json theme={null}
{
"feedId": "...",
"outputIds": ["..."],
"data": {},
"isTestExecution": false,
"blockNumber": "123"
}
```
## `data`
The `data` field contains the object returned by the feed. Keep this shape stable once a feed is in use, so the systems or actions connected to the feed can rely on consistent fields.
For example, a native transfer feed might return `nativeTransfers`, while an ERC-20 transfer feed might return `transfers`. Those names become part of the contract between the feed and the action connected to it, so avoid changing them casually once a feed is in use.
## Where Results Go
Results are published to the feed result stream and picked up by the [Delivery service](/atria/architecture/delivery). In the Dashboard, open a feed and use the **Live Preview** tab to inspect the latest result and stream new ones in real time.
Results are not meant to be a replacement for a full warehouse or analytics store. They are the operational output of a feed: the payload used to trigger an action, alert, workflow step, or integration.
For action connectors, see [outputs](/atria/core-concepts/outputs).
# Tags
Source: https://docs.pulsy.app/atria/core-concepts/tags
Keep feeds and outputs organized with tags.
# Tags
Tags help teams organize feeds and outputs by project, protocol, chain, team, environment, or workflow type.
Tags do not change how a feed runs. They make the system easier to operate as the number of feeds grows. A deployment with ten feeds can survive with loose naming. A deployment with hundreds of feeds needs consistent tags so teams can find, filter, and review the right workflows quickly.
## Common Uses
* Group feeds by protocol.
* Separate production and test workflows.
* Mark feeds owned by a team.
* Find all outputs related to a business process.
## Feed Tags
Feed tags make it easier to search, filter, and operate groups of feeds.
## Output Tags
Output tags make delivery destinations easier to manage as deployments grow.
Tags are part of the feed and output management model. Use them consistently from the start so larger deployments remain easy to navigate.
# What Is a Feed
Source: https://docs.pulsy.app/atria/core-concepts/what-is-a-feed
Learn the core primitive behind Atria workflows.
# What Is a Feed
A feed is Atria's core primitive for building event-driven workflows. It listens to a specific type of blockchain data, decides what matters, and emits a structured payload when the feed logic matches.
## Anatomy
* **Source**: Network, data type, and optional block range.
* **Filter**: Feed logic that decides whether to emit and can shape the payload it returns. In the current runtime, filters are authored in JavaScript.
* **Function**: Optional post-filter logic for heavier enrichment, integration-specific formatting, or complex transformation.
* **Outputs**: Connectors that use the emitted payload to trigger the next action.
* **Cursor**: Stored progress that tells the runtime which block to process next.
* **Metadata**: Name, version, tags, description, status, and deployment history.
```mermaid theme={null}
flowchart LR
Source[Source] --> Filter[Filter]
Filter --> Function[Optional function]
Function --> Outputs[Outputs]
```
## Feed Input
Feed input comes from one [data type](/atria/core-concepts/data-types), such as `BlockWithTransactions` or `BlockWithLogs`.
## Feed Output
If the feed returns `null` or `undefined`, Atria emits no result. If it returns an object, Atria wraps it as feed output data and sends it to configured outputs.
## Related Pages
* [Feed lifecycle](/atria/core-concepts/feed-lifecycle)
* [Cursors and block delay](/atria/core-concepts/cursors-and-block-delay)
* [Filters](/atria/core-concepts/filters)
* [Results](/atria/core-concepts/results)
* [Outputs](/atria/core-concepts/outputs)
* [Feed manifest](/atria/core-concepts/feed-manifest)
# Create an Output
Source: https://docs.pulsy.app/atria/dashboard/create-an-output
Create a reusable action connector for Atria feeds.
# Create an Output
Outputs are reusable connectors that let feeds trigger actions outside Atria. A webhook output can call an internal service, automation endpoint, alerting workflow, or integration layer when a feed matches the on-chain event you care about.
Webhook is the currently supported output type in the dashboard. Additional connector types are on the roadmap.
Create an output before creating a feed if you already know what should happen when a feed matches. You can also create feeds first and attach outputs later.
## Use the AI Assistant
Atria Cloud only
Give the AI assistant the output name, webhook URL, request method, timeout, and any headers, and it will create the output.
Example prompt:
```text theme={null}
Create a new webhook output named "New Output".
Use "https://webhook.site/01c55a61-f7a3-490e-a7ba-3ed15b355dff" as webhook.
Send requests with "POST".
Set the timeout to 30 seconds.
Add an "X-API-KEY" header with "e796a340-47b4-4591-b3de-7f04e3958023".
```
Once the assistant creates the output, it appears on the **Outputs** page.
After the AI assistant creates the output, review it before assigning it to a feed. Make sure the name, type, endpoint, method, timeout, and headers match the output you want to use.
## Create an Output Manually
Use the manual flow when you want to configure each field yourself.
### Open the Output Form
1. Go to **Outputs**.
2. Select **Add Output**.
### Enter Basic Information
In **Basic Information**:
1. Enter an **Output Name**.
2. Set **Output Type** to **Webhook**.
3. Add an optional **Description** that explains the action or integration this output represents.
4. Add or create tags to organize outputs in the way your team works, for example by project, protocol, environment, workflow, or owner.
### Configure the Webhook
In **Webhook Configuration**:
1. Enter the **Webhook URL**.
2. Choose the **HTTP Method**.
3. Set the **Timeout** in seconds.
4. Add HTTP headers if your endpoint requires authentication or custom metadata.
Use a stable endpoint that can safely handle repeated calls. Atria may retry a webhook when the connected system is temporarily unavailable, so the receiving service should be idempotent for the action it performs.
### Save the Output
Select **Create Output**. After the output is created, it appears on the **Outputs** page and can be selected from the **Output** tab in a feed workspace.
The same output can be reused by multiple feeds. For example, several risk-monitoring feeds can call the same incident workflow, or several protocol feeds can call the same operations service.
## Next Step
Create a feed and attach this output from the feed workspace so matching on-chain events can trigger the action.
Continue with [Create, Test, and Deploy a Feed](/atria/dashboard/create-test-and-deploy-a-feed).
# Create, Test, and Deploy a Feed
Source: https://docs.pulsy.app/atria/dashboard/create-test-and-deploy-a-feed
Build a feed, test it against a block, and deploy it.
# Create, Test, and Deploy a Feed
A feed listens to the blockchain data you define, decides what matters, and emits a structured payload when the feed logic matches. In the dashboard, you can create a feed with the AI assistant or configure each part manually.
## Use the AI Assistant
Atria Cloud only
Give the AI assistant the feed name, source, stream behavior, filter logic, outputs, and test block, and it will create the feed.
Example prompt:
```text theme={null}
Create a feed named "Large Transfer Monitor".
Use Ethereum Mainnet with the "Block With Logs" dataset.
Run continuously from the latest block with no block delay.
Filter for ERC-20 Transfer events above 100,000 tokens.
Attach the "New Output" webhook output.
Test it on block 19876543.
```
After the AI assistant creates the feed, review the settings, filter, output, and test result before connecting it to your systems.
## Create a Feed Manually
Use the manual flow when you want to configure each part yourself.
### Create a Feed
1. Go to **Feeds**.
2. Select **Add Feed**.
The dashboard opens a mode selection screen. Select **Advanced Mode** to create the feed manually.
After you select **Advanced Mode**, the dashboard opens a new feed workspace with the **Settings** tab selected.
### Configure Settings
In **Settings**, review the **Basics** section:
1. Keep the generated **Feed Name** or enter your own.
2. Set **Version** using semantic versioning, such as `1.0.0`.
3. Add an optional **Description**.
4. Add tags to organize the feed in the way your team works, for example by use case, chain, protocol, or owner.
### Choose the Source
In **Configuration**, select the pencil icon on **Source**.
1. Choose a **Network**.
2. Choose an **Environment**.
3. Choose a **Dataset**.
4. Select **Done**.
### Configure the Stream
Select the pencil icon on **Stream**.
1. Choose whether the feed should start from the **Latest block** or a **Specific block**.
2. Choose whether the feed should **Run continuously** or stop at a **Specific block**.
3. Choose **Realtime** for no block delay, or **Custom** to wait for a number of confirmations.
4. Select **Done**.
Use custom block ranges when you want the feed to process a defined section of chain history instead of running only from the latest block onward.
### Set Error Handling
In **Error Handling**, choose the strategy for runtime errors. The default flow is to continue on error when possible so one bad event does not stop the whole feed.
### Add Filter Logic
Select **Filter** in the workspace navigation. The dashboard opens a code editor and inserts a starter template based on the selected dataset.
The filter must define `main(stream)`. Return:
* A JSON-safe object when the feed should emit a result.
* `null` when the feed should skip the current block or event.
You can also upload a `.js` or `.ts` file from the upload button in the editor header.
### Attach Outputs
Select **Output** in the workspace navigation.
1. Use **Add Output** to select one or more existing outputs.
2. Select an output badge to review its read-only configuration.
3. Remove an output with the close icon if it should not receive feed results.
If you only want to test logic first, you can leave outputs empty and attach them later.
### Test the Feed
Open **Test Console** at the bottom of the workspace.
1. Enter a **Block Number**, or use the refresh icon to load the latest block for the selected environment.
2. Enable **Execute Outputs** only when you want the test run to call attached outputs.
3. Select **Test Feed**.
4. Review the console output.
If the filter and optional function return no errors, the console shows **Passed** and the primary action changes from **Test Feed** to **Deploy**.
### Deploy and Start
Select **Deploy**.
For a new feed, the dashboard creates the feed and then starts it. For an existing feed, the dashboard updates the feed and then starts it.
### Review Live Results
For an existing deployed feed, open **Live Preview** to watch real-time results. Results appear with block number, timestamp, size, and JSON data.
### Review Deploy History
For an existing feed, open **Deploy History** to review deployment status, version, creation time, and update time.
# Manage Feeds
Source: https://docs.pulsy.app/atria/dashboard/manage-feeds
Review status, pause, resume, edit, and delete feeds from the dashboard.
# Manage Feeds
Use the **Feeds** page to operate feeds after they have been created.
## Read Feed Status
The **Status** column shows the feed state. When live stream data is available, the status also shows whether the feed is in sync or behind the chain head.
## Pause or Resume a Feed
Use the toggle in the feed row.
* If the feed is **Running**, the toggle pauses it.
* If the feed is **Paused**, the toggle resumes it.
* The toggle is disabled when the feed is not in an active state that can be paused or resumed.
## Edit a Feed
Select the edit action in the feed row to open the feed workspace.
From there you can change settings, filter logic, outputs, and tags. After a change, run the test flow again before deploying the updated feed.
## Share a Feed
Open a feed and select **Share** in the feed header to publish a read-only link. Anyone with the link can view the feed's code, its live events and its metrics without an Atria account. Outputs, webhooks, tags and deploy history stay private.
See [share a feed](/atria/dashboard/share-a-feed).
## Delete a Feed
Select the delete action in the feed row. Deletion cannot be undone.
# Share a Feed
Source: https://docs.pulsy.app/atria/dashboard/share-a-feed
Publish a read-only link to a feed so anyone can view its code, live events and metrics.
# Share a Feed
Atria Cloud only
Sharing turns a feed into a public read-only page. Anyone with the link can open it without an Atria account.
## Turn Sharing On
Open the feed and select **Share** in the feed header, then **Enable sharing**.
You get a link in the form `https://atria.pulsy.app/feed/share/`. Use the copy action next to it.
Once sharing is on, the header button reads **Shared via link**.
## What a Visitor Sees
* The feed name, description and status.
* The network it reads and the data type it handles.
* The filter code.
* **Live Preview**, the same stream of results you watch in your own workspace.
* **Metrics** for the feed, including data reduction, input processed, output produced and failures.
## What Stays Private
Outputs, webhooks, tags and deploy history are never included on the shared page.
Your filter code is visible to anyone who opens the link. Review it for hard-coded secrets before you enable sharing.
## Start From a Shared Feed
A shared feed has a **Use this data** button. It copies the feed into the visitor's own workspace as a new feed, with the settings and the filter code already in place.
Outputs are not copied, because they are not part of what gets shared. The new feed opens on its output step so you can add your own before you test or deploy.
The button also works for someone without an account. They are taken through sign up first, and the copied feed is waiting for them when they land.
## Link Previews
A shared link renders a preview card when it is pasted somewhere that unfurls links, such as Slack or X. The card carries the feed name and its network.
## Turn Sharing Off
Open **Share** again and select **Turn off sharing**. The link stops opening the feed.
The link keeps its code, so turning sharing back on gives the same URL rather than a new one.
# How Atria Works
Source: https://docs.pulsy.app/atria/getting-started/how-atria-works
Learn how Atria turns blockchain events into actions.
# How Atria Works
Atria separates blockchain ingestion, feed execution, lifecycle management, and delivery into focused services. This keeps feed logic small while the platform handles the operational work around it.
## End-to-End Flow
```mermaid theme={null}
flowchart LR
Chain[Blockchain] --> Ingestor[Ingestor]
Ingestor --> Store[Block store]
Dashboard[Dashboard] --> Orchestrator[Orchestrator]
Orchestrator --> Runtime[Runtime]
Store --> Runtime
Runtime --> Match[Matched event payload]
Match --> Output[Feed output]
Output --> Action[External workflow action]
```
## Steps
1. The [Ingestor](/atria/architecture/ingestion) reads blocks, logs, and traces from configured networks.
2. The [Runtime](/atria/architecture/runtime) picks up data for each running feed.
3. The feed [filter](/atria/core-concepts/filters) decides whether to emit and can shape the payload it returns.
4. An optional [function](/atria/core-concepts/functions) adds a post-filter step for heavier enrichment, integration-specific formatting, or keeping complex transformation logic out of the filter.
5. The feed output triggers the next action in an external workflow.
6. The Dashboard, management backend, and [Orchestrator](/atria/architecture/orchestrator) manage creation, deployment, pausing, and health.
## Why This Matters
Teams write the event logic that matters to them. Atria handles chain connectivity, block storage, execution isolation, cursors, retries, delivery, and operational state.
# Key Use Cases
Source: https://docs.pulsy.app/atria/getting-started/key-use-cases
Explore common use cases for Atria.
# Key Use Cases
Atria is useful whenever on-chain activity should trigger monitoring, enrichment, or operational action. It can also route matched events to the right system or team, for example sending treasury movements to finance tooling, liquidation risk events to a risk engine, or protocol events to an internal service.
## Protocol Monitoring
Track DEX swaps, liquidity changes, lending positions, collateral updates, liquidations, governance events, derivatives activity, and other protocol-specific events.
Start with [data types](/atria/core-concepts/data-types) and [filters](/atria/core-concepts/filters).
## Wallet and Treasury Monitoring
Monitor native token transfers, ERC-20 transfers, treasury wallets, stablecoin mint or burn activity, and large movements across known addresses.
See the [native transfer template](/atria/library/native-transfers) and [ERC-20 transfer template](/atria/library/erc20-transfers).
## Bridge and Cross-Chain Operations
Watch bridge-related transactions, deposits, withdrawals, or liquidity events. Atria can power custom workflows, while [XFlow](/xflow/getting-started/overview) provides broader bridge intelligence.
## Automation and Alerts
Send feed results to webhooks that connect with alerting, internal services, risk engines, or data pipelines.
Learn about [outputs](/atria/core-concepts/outputs) and [results](/atria/core-concepts/results).
## Historical Replay and Analysis
Atria feeds are designed around block cursors and block ranges.
# Atria Overview
Source: https://docs.pulsy.app/atria/getting-started/overview
Start here to learn what Atria is and where it fits.
# Atria
Atria is Pulsy's off-chain backend for event-driven blockchain workflows. It turns on-chain events into real-time actions by running feeds that read blockchain data, apply custom logic, and deliver structured outputs.
Build feeds in the hosted dashboard. Pulsy runs the infrastructure.
Run Atria in your own infrastructure. Full source available.
## Quick Start
Build a feed with AI or manually, test it against chain data, and start it.
Set up a reusable connector with AI or manual configuration.
Use Atria when your team needs to monitor wallets, contracts, protocols, treasuries, bridge flows, DEX activity, lending risk, stablecoin movements, or other on-chain events without building the full ingestion and runtime layer yourself.
## What Atria Does
* Reads blockchain data from configured networks.
* Runs feed logic.
* Emits structured results only when your conditions match.
* Delivers matching results to outputs.
* Supports cloud, self-managed, private, and on-prem deployment models.
## The Core Idea
Atria is built around a [feed](/atria/core-concepts/what-is-a-feed). A feed is the primitive you use to build workflows: it selects a data source, runs event logic, optionally reshapes the payload, and triggers the next action through an output.
```mermaid theme={null}
flowchart LR
Chain[Blockchain data] --> Feed[Atria feed]
Feed --> Logic[Filter and optional function]
Logic --> Output[Output connector]
Output --> Action[Next action]
```
## Where to Go Next
* Learn [how Atria works](/atria/getting-started/how-atria-works).
* Review common [use cases](/atria/getting-started/key-use-cases).
* Understand [feeds](/atria/core-concepts/what-is-a-feed).
* Explore the [Atria Library](/atria/library/overview).
# EVM ERC-20 Transfers
Source: https://docs.pulsy.app/atria/library/erc20-transfers
Track ERC-20 transfers with a library template.
# EVM ERC-20 Transfers
The EVM ERC-20 Transfers template tracks ERC-20 `Transfer(address,address,uint256)` events.
Source: [`evm-erc-20-transfers`](https://github.com/Pulsy-Global/atria-library/tree/main/library/evm-erc-20-transfers)
## Data Type
This template uses [`BlockWithLogs`](/atria/core-concepts/data-types). It reads `stream.logs`.
## Configuration
* `TOKEN_ADDRESS`: Optional token contract filter.
* `FROM_ADDRESS`: Optional sender address filter.
* `TO_ADDRESS`: Optional recipient address filter.
* `MIN_VALUE`: Optional minimum amount.
* `MAX_VALUE`: Optional maximum amount.
* `DECIMALS`: Optional token decimals for human-readable thresholds.
## Logic
The filter:
* Matches the ERC-20 `Transfer` event topic.
* Parses logs with `ethers.Interface`.
* Compares token amount thresholds.
* Treats min and max as raw base units if `DECIMALS` is omitted.
* Returns `null` when no transfer matches.
## Output
When at least one log matches, the feed returns a result with the matching ERC-20 transfers inside `transfers`.
```json theme={null}
{
"metadata": {
"networkId": "ethereum-mainnet",
"blockNumber": "123",
"isReorg": false
},
"count": 1,
"transfers": [
{
"hash": "0x...",
"from": "0x...",
"to": "0x...",
"value": "1000000000000000000",
"address": "0x..."
}
]
}
```
`value` is the raw token amount from the ERC-20 event. If no log matches the configured token, sender, recipient, or value range, the filter returns `null` and the feed does not emit a result.
# EVM Native Transfers
Source: https://docs.pulsy.app/atria/library/native-transfers
Track native token transfers with a library template.
# EVM Native Transfers
The EVM Native Transfers template tracks native token transfers, such as ETH, POL, BNB, AVAX, or other chain gas assets.
Source: [`evm-native-transfers`](https://github.com/Pulsy-Global/atria-library/tree/main/library/evm-native-transfers)
## Data Type
This template uses [`BlockWithTransactions`](/atria/core-concepts/data-types). It reads `stream.block.transactions`.
## Configuration
* `FROM_ADDRESS`: Optional sender address filter.
* `TO_ADDRESS`: Optional recipient address filter.
* `MIN_VALUE`: Optional minimum native token amount.
* `MAX_VALUE`: Optional maximum native token amount.
## Logic
The filter:
* Skips zero-value transactions.
* Converts configured thresholds with `ethers.parseEther`.
* Compares transaction values as `BigInt`.
* Returns `null` when no transfer matches.
## Output
When at least one transaction matches, the feed returns a result with the matching transfers inside `nativeTransfers`.
```json theme={null}
{
"metadata": {
"networkId": "ethereum-mainnet",
"blockNumber": "123",
"isReorg": false
},
"timestamp": "0x...",
"count": 1,
"nativeTransfers": [
{
"hash": "0x...",
"from": "0x...",
"to": "0x...",
"valueWei": "0xde0b6b3a7640000",
"valueEth": "1.0"
}
]
}
```
If no transaction matches the configured sender, recipient, or value range, the filter returns `null` and the feed does not emit a result.
# Atria Library
Source: https://docs.pulsy.app/atria/library/overview
Explore reusable feed templates for Atria.
# Atria Library
The Atria Library is a curated collection of reusable feed templates. Each template packages a manifest, JavaScript filter template, and configuration schema.
Repository: [Pulsy-Global/atria-library](https://github.com/Pulsy-Global/atria-library)
## Current Templates
* [EVM Native Transfers](/atria/library/native-transfers)
* [EVM ERC-20 Transfers](/atria/library/erc20-transfers)
## Template Structure
```text theme={null}
library//
manifest.json
filter.js.hbs
filter-config.json
```
## Why It Exists
Templates help teams start from known feed patterns instead of writing every feed definition from scratch. They are also useful references for AI agents generating new Atria feed logic.
For the manifest format, see [feed manifest](/atria/core-concepts/feed-manifest).
# Delivery Failures
Source: https://docs.pulsy.app/atria/operations/delivery-failures
Troubleshoot failed output delivery.
# Delivery Failures
Delivery failures happen when Atria cannot send a feed result to a configured output.
## Common Causes
* Webhook URL is unavailable.
* Destination times out.
* Destination rejects the request.
* Required headers are missing or invalid.
* Payload shape does not match the destination expectation.
## System Behavior
Atria retries failed delivery. By default, it retries after 30 seconds and allows up to 30 delivery attempts for the same result. If delivery still fails, Atria stops retrying that result and moves the feed to `Error` with a delivery failure reason.
## What to Check
* Confirm the webhook URL.
* Check headers and authentication.
* Check the logs of the service that receives the webhook.
* Review recent feed results.
* Test the feed on a known block that should produce the expected result.
See [outputs](/atria/core-concepts/outputs) and [delivery architecture](/atria/architecture/delivery).
# Deployment Options
Source: https://docs.pulsy.app/atria/operations/deployment-options
Choose the right way to run Atria.
# Deployment Options
Atria is designed for several deployment models.
The right deployment model depends on how much infrastructure your team wants to operate and how strict your data, security, or latency requirements are. The feed model stays the same across deployment types: a feed still reads blockchain data, runs logic, and triggers an output when the condition matches.
## Pulsy Cloud
Pulsy manages infrastructure, runtime operations, and product access. This is the simplest path for teams that want to build feeds without operating the backend.
## Self-Managed
Teams can run Atria in their own infrastructure using the open-source runtime and supporting services.
## Private or On-Prem
For teams with stricter operational requirements, Atria can be deployed into private infrastructure with tailored setup and support.
## Self-Managed Dependencies
In a self-managed deployment, your team deploys and manages the Atria services and the supporting infrastructure they depend on:
* Atria Dashboard and management backend.
* Ingestor.
* Runtime.
* Delivery service.
* PostgreSQL for Atria metadata.
* NATS for streams, KV storage, leases, cursors, and block data.
* Fission-based function runtime when feeds use post-filter functions.
See [self-hosting](/atria/operations/self-hosting).
## Choosing a Model
Use Pulsy Cloud when speed and low operational overhead matter most. Use self-managed or private deployments when your team needs direct control over infrastructure, network access, security boundaries, or custom runtime behavior.
# Local Development
Source: https://docs.pulsy.app/atria/operations/local-development
Set up a practical local development flow.
# Local Development
Local development is useful for learning the system, testing feed logic, and validating self-managed deployment settings.
For the local quick start, see the [Atria GitHub repository](https://github.com/Pulsy-Global/atria).
## Typical Local Stack
* PostgreSQL.
* NATS.
* Management backend.
* Dashboard.
* Ingestor.
* Runtime.
* Delivery.
## Recommended Flow
1. Start infrastructure.
2. Configure one network.
3. Confirm block ingestion.
4. Create a simple feed.
5. Test the filter on a known block.
6. Attach a webhook output.
7. Start the feed and inspect results.
For testing logic, see [testing feeds](/atria/operations/testing-feeds).
# Network Configuration
Source: https://docs.pulsy.app/atria/operations/network-configuration
Configure the chains Atria should read from.
# Network Configuration
Atria reads network configuration from deployment settings. Each network entry tells the Ingestor how to connect to an EVM-compatible chain.
Network configuration is not just a chain list. It defines the data boundary for feeds: which chain can be read, which RPC transport is used, and whether advanced payloads such as traces are available.
## Typical Fields
* Network identifier, such as `ethereum-mainnet`.
* Chain ID.
* HTTP RPC endpoint.
* WebSocket endpoint when available.
* Feature flags such as trace support.
* Reorg and cache behavior.
## Cloud and Local Differences
Atria Cloud supports a curated set of EVM networks. Self-managed deployments can add any EVM-compatible chain by supplying reliable RPC endpoints.
For local or private deployments, start with one chain and verify that blocks, logs, and any required trace data are available before adding more networks. Different RPC providers expose different capabilities, especially for debug tracing.
## Current Atria Cloud Networks
* Ethereum
* Arbitrum One
* Optimism
* Base
* Polygon
* BNB Chain
* Avalanche C-Chain
For payload selection, see [data types](/atria/core-concepts/data-types).
# Running Feeds
Source: https://docs.pulsy.app/atria/operations/running-feeds
Understand what happens while feeds are running.
# Running Feeds
When a feed is running, the Runtime claims the feed with a lease, reads block data, executes logic, and advances the feed cursor. The lease is the runtime's temporary ownership marker, so another runtime instance does not process the same feed at the same time.
## Start Block
If a feed has a `startBlock`, processing begins there. If not, the runtime can start from the current chain head.
## Block Delay
A feed can use a block delay so it processes blocks only after the chain has advanced beyond them. This can reduce reorg risk.
## Cursor
The cursor stores the next block to process. If the feed restarts, it resumes from the cursor.
See [cursors and block delay](/atria/core-concepts/cursors-and-block-delay).
## Metrics
A running feed has a **Metrics** tab in its workspace. It reports what the feed has actually done over the last hour, 24 hours or 7 days.
Health:
* **Blocks processed** and **Outputs produced**, so you can see the feed is keeping up.
* **Successfully delivered**, the volume that reached your outputs.
* **Failures**.
Volume:
* **Data reduction**, how much smaller your output is than the raw block data it was built from.
* **Data volume over time**, input processed against output produced.
The cursor tells you how far behind the chain head a feed is. Metrics tell you whether its output is getting through.
## Pausing
Feeds can be paused manually or by the system after repeated runtime or delivery failures.
See [feed lifecycle](/atria/core-concepts/feed-lifecycle).
# Self-Hosting
Source: https://docs.pulsy.app/atria/operations/self-hosting
Learn what it takes to run Atria yourself.
# Self-Hosting
Self-hosting Atria means operating the services and infrastructure that power feed creation, ingestion, execution, and delivery.
This model is best for teams that already run production infrastructure and want Atria close to their own systems, secrets, RPC endpoints, and monitoring stack.
## What You Run
* Dashboard and management backend for feed operations.
* Ingestor for blockchain data collection.
* Runtime for feed execution.
* Delivery for webhooks.
* PostgreSQL for metadata.
* NATS for streams, KV, leases, and cursors.
## Configuration Areas
* Network RPC and WebSocket endpoints.
* Runtime limits.
* File storage for filter and function code.
* Messaging connection settings.
* Database connection settings.
* Webhook output configuration.
## Practical Advice
Start with a small set of networks and feeds. Confirm ingestion, feed testing, runtime deployment, and webhook delivery before scaling to more chains or higher throughput.
Treat RPC quality as part of the deployment. Atria can only ingest what the configured network providers can serve reliably. For production workflows, use endpoints with stable latency, sufficient rate limits, archive or trace capabilities when needed, and clear operational ownership.
For networks, see [network configuration](/atria/operations/network-configuration).
# Testing Feeds
Source: https://docs.pulsy.app/atria/operations/testing-feeds
Test feed logic before it goes live.
# Testing Feeds
Atria supports testing feed logic against block data before a feed goes live. This helps teams validate filters, output shape, and edge cases before deployment.
Testing matters because feeds often become operational contracts. Once a destination starts relying on a payload shape, even a small field change can break an alert, pipeline, or internal service.
## What to Test
* The selected [data type](/atria/core-concepts/data-types).
* Whether `main(stream)` returns `null` when it should.
* Whether matching output is JSON-safe.
* Address casing and token unit handling.
* Error behavior for empty logs, missing fields, or malformed events.
## Test Flow
```mermaid theme={null}
flowchart LR
Block[Selected block] --> Filter[Run filter]
Filter --> Result[Test result]
Result --> Review[Review output or error]
```
## Testing Surface
In the Dashboard, you can test a feed against a selected block and review the returned result or error before starting the feed.
When testing feeds, use blocks that are known to contain the event type you care about. For example, test an ERC-20 transfer feed against a block with matching token transfer logs, not just a random recent block.
# Authentication
Source: https://docs.pulsy.app/xflow/api-reference/auth
How to authenticate with the XFlow external API.
## How authentication works
External endpoints are protected by an API key that must be sent in the `Authorization` header using the `Basic` scheme. The value after `Basic` must be a Base64-encoded credential string.
The credential string is: `:`.
Where:
* `client_id` is your assigned client name or identifier.
* `api_key` is your secret key.
## Request format
1. Build the credential string: `:`.
2. Base64-encode the credential string.
3. Send it in the `Authorization` header using `Basic`.
### Example
```bash theme={null}
curl -H "Authorization: Basic " \
https://api.pulsy.app/v1/external/swaps
```
## Getting an API key
To request an API key for external access, contact [sales@pulsy.app](mailto:sales@pulsy.app).
# Get Supported Bridges
Source: https://docs.pulsy.app/xflow/api-reference/endpoints/get_bridges
GET /v1/external/bridges
Returns all bridges whose on-chain contracts XFlow tracks across supported chains, sorted by volume.
# Get Supported Chains
Source: https://docs.pulsy.app/xflow/api-reference/endpoints/get_chains
GET /v1/external/chains
Retrieves all blockchains tracked by XFlow.
# Get Swaps By Identifier
Source: https://docs.pulsy.app/xflow/api-reference/endpoints/get_swap_by_id
GET /v1/external/swaps/{identifier}
Retrieves cross-chain swaps by transaction hash or system id.
# Get Cross-Chain Swaps
Source: https://docs.pulsy.app/xflow/api-reference/endpoints/get_swaps
GET /v1/external/swaps
Retrieves cross-chain swaps detected within the requested timeframe. The timeframe between `from` and `to` must not exceed 30 minutes.
# Get Supported Tokens
Source: https://docs.pulsy.app/xflow/api-reference/endpoints/get_tokens
GET /v1/external/chains/{blockchainId}/tokens
Retrieves the tokens XFlow tracks for the specified blockchain.
# XFlow 1.24.0
Source: https://docs.pulsy.app/xflow/changelog/1-24-0
Solana coverage for four more bridges, a public explorer, and a delisted flag on the bridges endpoint.
**Released July 13, 2026**
This release adds Solana coverage for four more bridges, opens the XFlow explorer to the public, and adds a `delisted` flag to the external bridges endpoint so you can tell active bridges from retired ones.
## Explorer
The most recent 3 months of cross-chain transfers are now publicly available in the [XFlow explorer](https://xflow.pulsy.app). You no longer need an account to search and filter recent transfers.
Fixes in this release:
* Integrator filtering on the Analytics page no longer returns unrelated base protocol matches.
* Fixed a layout overflow on small screens.
* Fixed the arrow icon on the Top 10 Pairs panel.
* Fixed visibility of the Create Account button.
## Expanded coverage
Four more bridges now track transfers to and from Solana:
* [Sushi Swap](https://www.sushi.com/ethereum/cross-chain-swap)
* [WAN Bridge](https://bridge.wanchain.org)
* [1inch Fusion](https://app.1inch.io)
* [Bungee](https://www.bungee.exchange)
The full list of tracked bridges lives on the [Supported Bridges](/xflow/supported-bridges/overview) page and on the [XFlow home page](https://xflow.pulsy.app).
## Data accuracy
Improved tracking for [ThorSwap](https://app.thorswap.finance/swap), [LiFi Protocol](https://li.fi), [Mayan](https://swap.mayan.finance), [Bridgers](https://bridgers.xyz), [Pheasant](https://pheasant.network), [Relay](https://relay.link/bridge), and [AllBridge Core](https://core.allbridge.io) as part of our ongoing data accuracy refinement.
## Bridge delistings
The following bridges were delisted from active maintenance due to lack of on-chain activity:
* [Everclear](https://www.everclear.org)
* [zk Bridge](https://www.zkbridge.com/token)
* [Messina](https://messina.one/bridge)
* [Crowd Swap](https://app.crowdswap.org/exchange)
Historical data for these bridges remains available. You can still query their past transfers through the explorer and the API.
## API changes
The [Get Supported Bridges](/xflow/api-reference/endpoints/get_bridges) endpoint now returns a `delisted` flag on each bridge. It indicates whether the bridge has been delisted from active maintenance due to inactivity.
Delisted bridges stay in the response so historical lookups keep working.
# XFlow Changelog
Source: https://docs.pulsy.app/xflow/changelog/overview
New bridge coverage, explorer updates, and API changes in XFlow.
What's new in XFlow. Each release is listed here with a short summary. Open a release for the full detail.
## [XFlow 1.24.0](/xflow/changelog/1-24-0)
Solana coverage for four more bridges, the explorer opened to the public, and a `delisted` flag on the bridges endpoint.
[Read the full release](/xflow/changelog/1-24-0)
# How XFlow Works
Source: https://docs.pulsy.app/xflow/getting-started/how-xflow-works
Overview of how XFlow tracks cross-chain transfers.
## What XFlow is Tracking
XFlow tracks cross-chain transfers by observing activity on multiple blockchains and bridge protocols, then correlating the inbound and outbound legs into a single, normalized transfer record. The goal is to provide a consistent, chain-agnostic view of a cross-chain transfer, including:
* Source and destination chains.
* Source and destination transaction hashes.
* Sender and recipient addresses.
* Tokens and amounts (including USD valuation where available).
* Detection and completion timestamps.
* Protocol attribution.
## High-Level Architecture
XFlow’s pipeline is composed of three stages that run continuously:
1. Ingestion (on-chain data parsers).
2. Enrichment and completion (matching the two sides).
3. Publication and storage (normalized records for APIs and analytics).
Each stage is designed to be resilient to partial data, variable settlement times, and differences across chains and protocols.
```mermaid theme={null}
flowchart TB
A["On-chain data
"] --> B["Stage 1: Ingestion
Parse transfer candidates"]
B --> C["Raw datastore
Preliminary transfer records"]
C --> D["Stage 2: Enrichment & Completion
Match source/destination legs"]
D --> E["Normalized datastore
Completed transfer records"]
E --> F["Stage 3: Publication & Storage
APIs + analytics"]
```
## Stage 1: Ingestion
XFlow uses a set of protocol-specific parsers (“engines”). Each engine knows how to identify candidate transfers for a given bridge. Engines ingest data from a mix of on-chain sources, which can include:
* Transactions.
* Logs.
* Internal transaction traces.
* Debug data.
The ingestion phase produces a preliminary transfer object with whatever data is immediately available. This object is stored as an internal record in a raw datastore, along with associated metadata extracted from the blockchain.
## Stage 2: Enrichment and Completion
Cross-chain transfers rarely arrive as a fully complete record in a single pass. XFlow therefore runs a continuous completion service that attempts to fill in missing details for both sides of a transfer.
XFlow prioritizes fully deterministic matching of transfer legs. In rare cases where on-chain data alone is not sufficient, non-deterministic matching can be used, relying on expected timestamps and acceptable amount ranges.
The completion process operates as follows:
* It loads pending cross-chain transfers that are partially complete.
* It searches for related transactions using protocol-specific search terms.
* It applies engine-specific logic to map candidate transactions onto the correct “from” and “to” sides.
* It normalizes chain, network, token, and address data to a consistent schema.
* It marks the transfer as completed once both sides meet the completeness criteria.
XFlow supports cases where the destination transaction appears before the source transaction, and continues revisiting incomplete transfers until an expiration window is reached.
## Stage 3: Publication and Storage
Once a transfer is complete, XFlow persists a normalized record to its datastore for API consumers and analytics. Raw metadata remains available internally for auditability and further enrichment.
## Data Model Summary
At a high level, XFlow maintains two layers of data:
* Raw blocks and metadata: protocol-specific and generic, used for matching and audit.
* Normalized transfers: completed, de-duplicated, and ready for external consumption.
Each normalized transfer includes:
* Source/destination chains and networks.
* Source/destination transaction hashes.
* Source/destination addresses.
* Tokens and amounts (with decimals normalization).
* Protocol attribution.
* Detection and completion timestamps.
# USD Mismatches
Source: https://docs.pulsy.app/xflow/getting-started/in-out-usd-mismatch
How XFlow interprets USD differences between transfer legs.
## USD Mismatch Status
This document explains what USD mismatch status means for cross-chain transfers, how the system calculates it, and where external integrators can read it in the API. It also summarizes the inputs, thresholds, and edge cases that affect the final status so you can interpret it correctly in downstream systems.
XFlow matches swaps deterministically using on-chain data wherever possible. Our team investigates each protocol’s mechanics and source code to analyze cross-chain processing logic and ensure our matching uses available on-chain data. In rare cases, unknown or not-yet-implemented patterns can slip through, or bridges themselves process transfers in ways where the USD value on the deposit leg does not match the USD value on the withdrawal leg (for example, processing transfers in batches). We expose the USD mismatch status through the external API so clients can make their own assessments. XFlow does not assert fraud or failure, it simply indicates when the input and output USD values look inconsistent.
## Where It Appears In The External API
The external cross-chain transfer endpoints return these fields:
* `depositUsdAmount`: USD equivalent for the source/deposit leg.
* `withdrawnUsdAmount`: USD equivalent for the destination/withdrawal leg.
* `usdMismatchStatus`: enum string describing whether the USD amounts are within an acceptable range.
## Status Values
`usdMismatchStatus` can be one of:
* `Unprocessed`: USD amounts were not processed yet, so mismatch was not evaluated.
* `NotFound`: USD rates were unavailable for at least one side.
* `MismatchDetected`: the difference between deposited and withdrawn USD is unusually high.
* `OK`: difference is within the acceptable range.
## How USD Amounts Are Calculated
For each transfer side (source and destination), the system calculates the USD amount as:
`value`: `tokenAmount` × `usdRate`: token USD rate at the swap timestamp.
USD rates are retrieved from external pricing sources at or near the transfer timestamp. If the USD rate is unavailable or token information is missing, the USD amount remains `null` for that side, which leads to `NotFound`.
## Mismatch Detection
Step-by-step logic:
1. If either `depositUsdAmount` or `withdrawnUsdAmount` is `null`, status = `NotFound`.
2. If exactly one of the USD amounts is `0`, status = `MismatchDetected`. If both are `0`, status = `OK`.
3. Compute `diff = abs(depositUsdAmount - withdrawnUsdAmount)`. Mismatch is detected when `diff >= 100` and the relative difference is at least 10%.
If none of the mismatch conditions are met, status = `OK`.
# XFlow Overview
Source: https://docs.pulsy.app/xflow/getting-started/overview
Cross-Chain Explorer by Pulsy
# XFlow
XFlow is a cross-chain explorer for teams that need real-time visibility into bridge activity and cross-chain liquidity flows. Powered by the broader [Pulsy Tracking System](https://pulsy.app/#offerings), XFlow combines 70+ bridge integrations with seamless delivery options.
XFlow continuously watches 2,000+ on-chain bridge contracts and events, spanning both current and legacy bridge contracts. The system matches inbound and outbound legs, enriches them with sender and receiver accounts, identifies the bridged token, and timestamps everything so you can reconstruct a full transfer lifecycle.
On a daily basis, XFlow processes 100,000+ bridging events across supported bridges and chains.
Explore the [API](/xflow/api-reference/endpoints/get_swaps) to see the exact schemas, parameters, and filtering options exposed by XFlow, or download the [OpenAPI Spec](https://api.pulsy.app/swagger/ExternalAPI/swagger.json) directly.
## What XFlow Delivers
* **Real-Time Cross-Chain Tracking** – Monitor bridge transactions as they happen.
* **Wide Bridge Coverage** – Stay ahead with 70+ integrated bridges that continue to grow alongside the broader Pulsy ecosystem.
* **Extensive Chain Coverage** – Track cross-chain flows across major blockchains.
* **Integration-Ready** – Use the API to programmatically route events into dashboards, alerting, or automation.
* **Custom-Branded Explorer** – Offer your users a white-labeled bridge explorer powered by the same dataset the Pulsy team maintains.
Read more at [pulsy.app/xflow](https://pulsy.app/xflow).
## Explorer
Jump into the live explorer at [xflow.pulsy.app](https://xflow.pulsy.app) to search, analyse, and filter cross-chain swaps in real time.
The explorer also surfaces liquidity flows between supported chains, overall tracked dollar volumes, and cross-chain swap counts so you can see how traffic shifts across the ecosystems.
## Coverage
* **Bridge & Chain Coverage** – The home page of the XFlow app [xflow.pulsy.app](https://xflow.pulsy.app) lists the currently supported bridges and chains, so you always know which ecosystems are tracked.
* **Programmatic Lookup** – Use the [Get Bridges API](/xflow/api-reference/endpoints/get_bridges), [Get Chains API](/xflow/api-reference/endpoints/get_chains), and [Get Tokens API](/xflow/api-reference/endpoints/get_tokens) to retrieve the latest supported entities directly in your workflows.
## Bespoke Integrations
Need something beyond the standard explorer or API? Reach out through [sales@pulsy.app](mailto:sales@pulsy.app) to scope bespoke integrations powered by the broader Pulsy suite.
# Polling Data
Source: https://docs.pulsy.app/xflow/getting-started/polling-data
Polling-based XFlow data delivery
## Overview
This guide explains how to integrate with XFlow for real‑time cross-chain transfer data delivery and how to handle historical backfills.
XFlow provides data in two complementary ways:
* **Historical backfill:** delivered as CSV exports (split by year / month).
* **Real‑time API:** delivered via the external API polling endpoint.
The CSV schema mirrors the API response schema, so you can ingest once and use a single data model in your system.
## Access & Authentication
Reach out to [sales@pulsy.app](mailto:sales@pulsy.app) to coordinate an integration and request an API key. Once provisioned, follow the authentication instructions in the [Authentication guide](/xflow/api-reference/auth).
## API Base URL
Production: `https://api.pulsy.app`.
## Main Polling Endpoint
Transfers (swaps): `GET /v1/external/swaps`. See [Get Swaps](/xflow/api-reference/endpoints/get_swaps) for more details.
**Required query parameters:**
* `from`: ISO‑8601 timestamp (UTC recommended).
* `to`: ISO‑8601 timestamp (UTC recommended).
**Optional query parameter:**
* `filter`: one of `Withdrawn`, `Deposited`, `Completed`, `Updated`.
**Important constraint:**
* The time window between `from` and `to` must not exceed 30 minutes.
## Recommended Polling Strategy
For accurate, forward‑only, and idempotent polling, use `filter=Completed` and the `completedAt` field as your cursor.
**Recommended setup:**
* Job A (real‑time): poll with a small delay (5–10 minutes), as most cross‑chain transfers settle within that timeframe.
* Job B (delayed): poll with a 48‑hour delay to capture late‑arriving transfers.
**Cursor logic:**
1. Ingest the historical CSV export.
2. Start both pollers from `max(completedAt)` found in the CSV.
3. After each API response, advance your cursor to the max `completedAt` returned by that response.
`completedAt` is stable (it is set when a transfer record reaches its final state). This makes forward‑only polling safe.
## Historical Backfill via CSV
* CSV exports are provided for historical data (split by year / month).
* Use CSVs for backfill. The external API is intended for real‑time polling only.
* The polling API is not intended for deep historical backfill. Use CSV exports instead.
* We periodically regenerate historical exports as protocols and patterns evolve. Implement a re‑fill flow so you can re‑ingest refreshed exports when provided.
## Timestamp Fields (Polling & Reconciliation)
Key timestamps available in both CSV and API:
* `detectedAt`: when a transfer was detected by XFlow.
* `completedAt`: when a transfer was finalized and made available in the external API.
* `updatedAt`: when a transfer record was last changed.
* `depositedAt`: inbound on‑chain transaction time.
* `withdrawnAt`: outbound on‑chain transaction time.
## Rate Limits & Integration Tiers
Polling rate limits are set per integration tier and use case. Reach out to [sales@pulsy.app](mailto:sales@pulsy.app) to discuss your requirements and confirm limits.
# Bridges Tracked by XFlow
Source: https://docs.pulsy.app/xflow/supported-bridges/overview
Overview of bridges integrated in XFlow
This page contains a list of bridges currently tracked by XFlow. It is reviewed and updated regularly, but for the most up-to-date list, details on which chains are supported for each specific bridge, and visibility into which bridges are active vs inactive, refer to the XFlow home page at [xflow.pulsy.app](https://xflow.pulsy.app).
Bridges are sorted by processed volume to date, with higher cross-chain volume at the top and lower volume at the bottom.
Reach out to [hello@pulsy.app](mailto:hello@pulsy.app) if you want your bridge integrated into and listed on Pulsy XFlow.
## Active Bridges (48)
| Bridge | Explorer | X | How It Works |
| ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | -------------------------------------------------- | ------------ |
| [1inch Fusion](https://app.1inch.io) | — | [@1inch](https://x.com/1inch) | *Soon* |
| [Across](https://across.to/bridge) | [app.across.to](https://app.across.to/transfers) | [@AcrossProtocol](https://x.com/AcrossProtocol) | *Soon* |
| [AllBridge Core](https://core.allbridge.io) | [core.allbridge.io](https://core.allbridge.io/explorer) | [@Allbridge\_io](https://x.com/Allbridge_io) | *Soon* |
| [Arbitrum](https://bridge.arbitrum.io) | — | [@arbitrum](https://x.com/arbitrum) | *Soon* |
| [Avalanche](https://core.app/bridge) | — | [@coreapp](https://x.com/coreapp) | *Soon* |
| [Axelar](https://www.axelar.network) | [axelarscan.io](https://axelarscan.io) | [@axelar](https://x.com/axelar) | *Soon* |
| [Base](https://docs.base.org/base-chain/network-information/bridges-mainnet) | — | [@base](https://x.com/base) | *Soon* |
| [Bitget Wallet](https://web3.bitget.com/en/swap) | [web3.bitget.com](https://web3.bitget.com/en/explorer) | [@BitgetWallet](https://x.com/BitgetWallet) | *Soon* |
| [Bridgers](https://bridgers.xyz) | [explorer.bridgers.xyz](https://explorer.bridgers.xyz) | [@Bridgersxyz](https://x.com/Bridgersxyz) | *Soon* |
| [Bungee](https://www.bungee.exchange) | — | [@BungeeExchange](https://x.com/BungeeExchange) | *Soon* |
| [Butter Swap](https://www.butterswap.io/swap) | [explorer.butterswap.io](https://explorer.butterswap.io/en) | [@ButterNetworkio](https://x.com/ButterNetworkio) | *Soon* |
| [CCIP](https://chain.link/cross-chain) | [ccip.chain.link](https://ccip.chain.link) | [@chainlink](https://x.com/chainlink) | *Soon* |
| [CCTP](https://www.circle.com/en/cross-chain-transfer-protocol) | — | [@circle](https://x.com/circle) | *Soon* |
| [Celer](https://cbridge.celer.network) | [celerscan.com](https://celerscan.com) | [@CelerNetwork](https://x.com/CelerNetwork) | *Soon* |
| [Chain Port](https://app.chainport.io) | — | [@chain\_port](https://x.com/chain_port) | *Soon* |
| [Chainflip](https://swap.chainflip.io) | [scan.chainflip.io](https://scan.chainflip.io/swaps) | [@Chainflip](https://x.com/Chainflip) | *Soon* |
| [Circle Gateway](https://www.circle.com/gateway) | — | [@circle](https://x.com/circle) | *Soon* |
| [deBridge](https://app.debridge.finance) | [app.debridge.finance](https://app.debridge.finance/orders) | [@debridge](https://x.com/debridge) | *Soon* |
| [dePort](https://app.debridge.finance/deport) | [app.debridge.finance](https://app.debridge.finance/orders) | [@debridge](https://x.com/debridge) | *Soon* |
| [Hyperlane](https://www.usenexus.org) | [explorer.hyperlane.xyz](https://explorer.hyperlane.xyz) | [@hyperlane](https://x.com/hyperlane) | *Soon* |
| [Injective](https://bridge.injective.network) | — | [@injective](https://x.com/injective) | *Soon* |
| [Interport](https://interport.fi) | [explorer.interport.fi](https://explorer.interport.fi/transactions) | [@interportfi](https://x.com/interportfi) | *Soon* |
| [LayerZero](https://layerzero.network) | [layerzeroscan.com](https://layerzeroscan.com) | [@LayerZero\_Core](https://x.com/LayerZero_Core) | *Soon* |
| [LiFi Protocol](https://li.fi) | [jumper.exchange](https://jumper.exchange/scan) | [@lifiprotocol](https://x.com/lifiprotocol) | *Soon* |
| [Lombard](https://www.lombard.finance/app/bridge) | — | [@lombard\_finance](https://x.com/lombard_finance) | *Soon* |
| [Mayan](https://swap.mayan.finance) | [explorer.mayan.finance](https://explorer.mayan.finance) | [@mayan](https://x.com/mayan) | *Soon* |
| [Meson](https://meson.fi) | [explorer.meson.fi](https://explorer.meson.fi) | [@mesonfi](https://x.com/mesonfi) | *Soon* |
| [Metamask](https://portfolio.metamask.io/bridge) | — | [@metamask](https://x.com/metamask) | *Soon* |
| [Near Intents](https://near-intents.org) | [explorer.near-intents.org](https://explorer.near-intents.org) | — | *Soon* |
| [OmniBridge](https://app.omnibridge.pro) | [explorer.omnibridge.pro](https://explorer.omnibridge.pro/#/) | [@Bridgersxyz](https://x.com/Bridgersxyz) | *Soon* |
| [Optimism](https://app.optimism.io/bridge) | — | [@optimism](https://x.com/optimism) | *Soon* |
| [Pheasant](https://pheasant.network) | — | [@PheasantNetwork](https://x.com/PheasantNetwork) | *Soon* |
| [Polygon PoS](https://portal.polygon.technology/bridge) | — | [@0xPolygon](https://x.com/0xPolygon) | *Soon* |
| [Rango](https://rango.exchange) | [explorer.rango.exchange](https://explorer.rango.exchange) | [@RangoExchange](https://x.com/RangoExchange) | *Soon* |
| [Relay](https://relay.link/bridge) | [relay.link](https://relay.link/transactions) | [@RelayProtocol](https://x.com/RelayProtocol) | *Soon* |
| [Router Protocol](https://routerprotocol.com) | — | [@routerprotocol](https://x.com/routerprotocol) | *Soon* |
| [Rubic](https://rubic.exchange) | — | [@CryptoRubic](https://x.com/CryptoRubic) | *Soon* |
| [Socket Protocol](https://www.socket.tech) | [www.socketscan.io](https://www.socketscan.io) | [@SOCKETProtocol](https://x.com/SOCKETProtocol) | *Soon* |
| [Squid Router](https://www.squidrouter.com) | — | [@squidrouter](https://x.com/squidrouter) | *Soon* |
| [Sushi Swap](https://www.sushi.com/ethereum/cross-chain-swap) | — | [@sushiswap](https://x.com/sushiswap) | *Soon* |
| [Symbiosis](https://app.symbiosis.finance) | [explorer.symbiosis.finance](https://explorer.symbiosis.finance/transactions) | [@symbiosis\_fi](https://x.com/symbiosis_fi) | *Soon* |
| [ThorSwap](https://app.thorswap.finance/swap) | [thorchain.net](https://thorchain.net) | [@THORSwap](https://x.com/THORSwap) | *Soon* |
| [Threshold](https://dashboard.threshold.network/tBTC/mint) | [tbtcscan.com](https://tbtcscan.com) | [@TheTNetwork](https://x.com/TheTNetwork) | *Soon* |
| [Transit Swap](https://swap.transit.finance) | [explorer.transit.finance](https://explorer.transit.finance) | [@TransitFinance](https://x.com/TransitFinance) | *Soon* |
| [Unizen](https://zcx.com/trade) | — | [@unizen\_io](https://x.com/unizen_io) | *Soon* |
| [WAN Bridge](https://bridge.wanchain.org) | [bridge.wanchain.org](https://bridge.wanchain.org/Dashboard) | [@wanchain\_org](https://x.com/wanchain_org) | *Soon* |
| [Woo Fi](https://woofi.com/swap) | — | [@\_WOOFi](https://x.com/_WOOFi) | *Soon* |
| [Wormhole](https://www.portalbridge.com/#/transfer) | [wormholescan.io](https://wormholescan.io) | [@wormhole](https://x.com/wormhole) | *Soon* |
***
## Delisted Bridges (30)
The following bridges have been delisted from active maintenance but remain in our system as part of the historical record. All past transactions and transfer records for these bridges are still available and accessible.
| Bridge | Explorer | X |
| ------------------------------------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------- |
| [Altitude](https://www.altitudedefi.com/transfer) | — | [@altitudedefi](https://x.com/altitudedefi) |
| [Chainge Finance](https://dapp.chainge.finance) | — | [@FinanceChainge](https://x.com/FinanceChainge) |
| [Connext](https://bridge.connext.network) | [connextscan.io](https://connextscan.io) | [@connext](https://x.com/connext) |
| [Crosschain Bridge](https://app.crosschainbridge.org/bridge/tokens) | — | [@ccb\_bridge](https://x.com/ccb_bridge) |
| [CrossChain X](https://swap.swft.pro) | — | — |
| [Crowd Swap](https://app.crowdswap.org/exchange) | [scanner.crowdswap.org](https://scanner.crowdswap.org/scanner) | [@CrowdSwap\_App](https://x.com/CrowdSwap_App) |
| [Daimo Pay](https://pay.daimo.com) | — | [@daimo\_com](https://x.com/daimo_com) |
| [Everclear](https://www.everclear.org) | [explorer.everclear.org](https://explorer.everclear.org/intents) | [@everclearorg](https://x.com/everclearorg) |
| [Helix](https://app.helix.box) | [app.helixbox.ai](https://app.helixbox.ai/#/explorer) | [@helixofficialx](https://x.com/helixofficialx) |
| [Hop](https://app.hop.exchange) | [explorer.hop.exchange](https://explorer.hop.exchange) | [@hopprotocol](https://x.com/hopprotocol) |
| [Hyphen](https://hyphen.biconomy.io/pools) | [hyphen-info.biconomy.io](https://hyphen-info.biconomy.io) | — |
| [Layer Sync](https://layersync.org) | — | [@LayerSync](https://x.com/LayerSync) |
| [Messina](https://messina.one/bridge) | — | [@MessinaOne](https://x.com/MessinaOne) |
| [Mimic Finance](https://www.mimic.fi) | — | [@mimicfi](https://x.com/mimicfi) |
| [Multichain](https://app.multichain.org) | [scan.multichain.org](https://scan.multichain.org) | [@MultichainOrg](https://x.com/MultichainOrg) |
| [Nitro](https://routernitro.com) | [explorer.routernitro.com](https://explorer.routernitro.com/analytics) | [@nitrobyrouter](https://x.com/nitrobyrouter) |
| [Nomad](https://app.nomad.xyz) | — | [@nomadxyz\_](https://x.com/nomadxyz_) |
| [OKX](https://www.okx.com) | — | [@okx](https://x.com/okx) |
| [OMO Swap](https://app.omoswap.xyz) | — | [@OMOSwapX](https://x.com/OMOSwapX) |
| [Open Ocean](https://app.openocean.finance) | — | [@OpenOceanGlobal](https://x.com/OpenOceanGlobal) |
| [Orbitchain](https://bridge.orbitchain.io) | [bridge.orbitchain.io](https://bridge.orbitchain.io/explorer) | [@Orbit\_Chain](https://x.com/Orbit_Chain) |
| [Orion](https://trade.orion.xyz/bridge) | — | [@orion\_protocol](https://x.com/orion_protocol) |
| [Owlto Finance](https://owlto.finance) | — | [@Owlto\_Finance](https://x.com/Owlto_Finance) |
| [Plexus Exchange](https://www.plexus.app/swap) | — | — |
| [Synapse](https://synapseprotocol.com) | [explorer.synapseprotocol.com](https://explorer.synapseprotocol.com) | [@SynapseProtocol](https://x.com/SynapseProtocol) |
| [Union](https://union.build) | — | [@union\_build](https://x.com/union_build) |
| [Value Router](https://app.valuerouter.com) | — | [@ValueRouter](https://x.com/ValueRouter) |
| [WBTC Protocol](https://wbtc.network) | [wbtc.network](https://wbtc.network/proof-of-reserves/mint-burn-records) | [@WrappedBTC](https://x.com/WrappedBTC) |
| [XY Finance](https://app.xy.finance) | [app.xy.finance](https://app.xy.finance/explorer) | [@xyfinance](https://x.com/xyfinance) |
| [zk Bridge](https://www.zkbridge.com/token) | — | [@zkBridge](https://x.com/zkBridge) |