Data access · 10 min read

Stablecoin data: tracking cross-chain flows and peg health

A stablecoin is rarely one thing on one chain. The same brand is issued natively across many networks, and the interesting questions follow the money between them. This guide is about the data underneath that: transfers as the substrate of flow tracking, the per-chain address problem, lining up a window across chains by timestamp, and reading peg health from where stablecoins actually trade. Every query below is run against SQD's Portal, and every number is from a fixed block range so it stays reproducible.

Updated 2026-06-15 · By the SQD team

1. Flow data, and where it stops

Three different questions get filed under stablecoin data, and keeping them apart saves a lot of confusion. Flows are movements of value between addresses: who paid whom, how funds reach an exchange, where a treasury sends money. Volume is the aggregate size of those movements over time. Supply is how much of the token exists, which has to be reconstructed from issuance.

This guide is about flows and the related question of peg health. Transfer volume, and reconstructing circulating supply from mint and burn events across chains, are their own topic with their own reconciliation problems, and they are covered in the onchain analytics guide. One boundary is worth stating plainly up front: the data here is transfers and trades, the events a chain actually emits. It is not a balance lookup. Nothing below reconstructs an account balance at a point in time, because that is a different exercise built on different data.

2. Transfers are the substrate

Every movement of an ERC-20 stablecoin emits a Transfer event, so the flow of funds is exactly the log of that event filtered to the token's contract. A query against SQD's Portal for USDC transfers on Ethereum is one filter on the token address and the Transfer topic:

POST https://portal.sqd.dev/datasets/ethereum-mainnet/stream
Accept: application/x-ndjson

{
  "type": "evm",
  "fromBlock": 21000000,
  "toBlock": 21000100,
  "logs": [{
    "address": ["0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"],
    "topic0": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]
  }],
  "fields": {
    "block": { "number": true, "timestamp": true },
    "log": { "address": true, "topics": true, "data": true, "transactionHash": true }
  }
}

The Transfer signature puts the sender in topic1, the recipient in topic2, and the amount in data. One real result, at block 21,000,100, is an issuance:

from:  0x0000000000000000000000000000000000000000
to:    0xa6227fffd0f7e46ee9f513ca65acfd52667f4974
value: 750.969553 USDC

The sender is the zero address, which is what a mint looks like: new tokens entering circulation have to come from somewhere, and by convention that somewhere is address zero. A burn is the mirror image, a transfer to the zero address. Everything else in the stream is an ordinary movement between two real addresses. That single event type, read for one token, is the entire flow of funds for that stablecoin on that chain. Turning mints and burns into a supply series is the analytics step that lives in the onchain analytics guide; here the point is simply that issuance, redemption, and every payment are all the same queryable event.

3. Every chain, one query

A stablecoin does not live on one chain, and that is where flow tracking gets real. The same query shape runs on every network SQD indexes; the only change is the dataset name in the endpoint and the token's address on that chain. The catch is that the address is genuinely different each time. USDC is a separate native deployment per network:

Ethereum   0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48
Base       0x833589fcd6edb6e08f4c7c32d4f71b54bda02913
Arbitrum   0xaf88d065e77c8cc2239327c5edb3a432268e5831

Run the identical Transfer query against each dataset with the right address and the transfers come back from all three: one query model, one event signature, every chain a stablecoin is deployed on, with the per-chain address map as the only thing you carry between them. A flow that crosses chains, say a treasury moving USDC from Ethereum to Base, is two transfers in two datasets that you join on the addresses and amounts. One thing has to be right before the per-chain numbers mean anything, and that is the window they cover.

4. Aligning time across chains

Block numbers do not line up across chains. Block 22,606,143 on Ethereum and block 22,606,143 on Base are different moments, because the chains produce blocks at different rates and started at different times. So to compare the same window everywhere at once, you cannot reuse a block number. You translate the instant you care about into each chain's block first.

Portal resolves a timestamp to a block with a single GET against the dataset, passing the instant as a Unix timestamp in seconds. It returns the first block at or after that time:

GET https://portal.sqd.dev/datasets/base-mainnet/timestamps/1748736000/block

{ "block_number": 30973327 }

Change the dataset in the path and the same lookup works on every chain, returning an EVM block number or a Solana slot. Take a fixed ten-minute window, 2025-06-01 00:00:00 to 00:10:00 UTC (Unix 1748736000 to 1748736600), and resolve both ends on each chain:

                00:00:00 block   00:10:00 block   blocks in window
  Ethereum      22,606,143       22,606,192       49
  Base          30,973,327       30,973,627       300
  Arbitrum      342,607,969      342,610,336      2,367

The same ten minutes is 49 blocks on Ethereum and 2,367 on Arbitrum, which is exactly why a shared block number is meaningless across chains. One boundary detail matters: the lookup returns the first block at or after the instant, and Portal's toBlock is inclusive, so each window runs fromBlock the 00:00:00 block to toBlock the 00:10:00 block minus one. That last block already belongs to the next window. Over those 49, 300, and 2,367 blocks the USDC transfer counts compare directly:

USDC transfers, 2025-06-01 00:00:00 to 00:10:00 UTC
  Ethereum     1,304
  Base        10,483
  Arbitrum     3,649

In that window Base alone moved USDC close to eight times as often as Ethereum, and Arbitrum more than twice as often, the kind of comparison that holds only because the windows are time-aligned rather than block-aligned. The lookup works the same way across EVM, Solana, Bitcoin, and Substrate datasets. The alternative, with only an RPC endpoint, is a binary search over block timestamps, roughly twenty eth_getBlockByNumber calls per bound, per chain. Time alignment is the unglamorous part of cross-chain flow data, and it is most of the work.

5. Peg health from DEX trades

A stablecoin's peg is a market fact, not a contract field. The token does not report that it is worth a dollar; the market decides that by what people pay for it, and onchain that happens in DEX pools. So peg health is read from swap events: the executed price of the stablecoin against a reference asset, usually another stablecoin or the chain's wrapped native token, on its deepest pools. Compare that price to 1.00 and a sustained gap is a depeg signal.

The mechanics are the same as any DEX price work: filter the pool's Swap events, derive the executed price from the amounts, and bucket it over time. This is a reading of trade data, not an oracle feed and not a substitute for one; it tells you where the stablecoin changed hands, which is the right signal for monitoring rather than for settlement. The same transfer and swap streams that drive flow tracking also drive this, so peg monitoring is another view over data you are already pulling, not a separate integration.

6. Stablecoin data with SQD

SQD provides the transfer and trade data this all runs on, decoded and typed, from the Portal. The same query shape covers every network a stablecoin reaches, including newer stablecoin-focused chains such as Plasma, which SQD indexes the same way it indexes Ethereum, so adding a chain to a flow view is a one-line dataset change rather than a new integration.

For a product you would stream this rather than query by hand. The Pipes SDK pulls the same transfer and swap events into your own store, where you keep per-chain flow tables and a peg series that update as blocks arrive, with the timestamp-to-block lookup handling cross-chain windows. For transfer-volume metrics and cross-chain supply reconstruction, see the onchain analytics guide; for how this feeds a monitoring product, see the stablecoins solution page.

Frequently asked questions

What data do you need to track stablecoin flows?
Flows are movements of value between addresses, and onchain every movement of an ERC-20 stablecoin is a Transfer event. So the substrate is the token's Transfer log: sender, recipient, amount, and the block timestamp. Filtering that event for a stablecoin's contract gives you the full flow of funds for that token on that chain, and the same shape repeats on every chain the token is deployed on.
Why does the same stablecoin have a different contract address on each chain?
A stablecoin like USDC is issued natively on many chains, and each deployment is its own contract with its own address. USDC is 0xa0b8...eb48 on Ethereum, 0x8335...2913 on Base, and 0xaf88...5831 on Arbitrum. Any cross-chain flow query has to carry a per-chain address map; using one address everywhere silently returns nothing on the other chains.
How do you compare stablecoin activity at the same moment across chains?
Block numbers are not comparable across chains, because the same block height is a different wall-clock time on each one. To line up a window you resolve the timestamp to a block on each chain first. SQD's Portal resolves a timestamp to the first block at or after it with a single GET, /datasets/{chain}/timestamps/{unix_seconds}/block, on any chain; 2025-06-01 00:00:00 UTC resolves to block 22,606,143 on Ethereum, 30,973,327 on Base, and 342,607,969 on Arbitrum. Because the lookup returns the first block at or after the instant and toBlock is inclusive, the window runs from the start block to the 00:10:00 block minus one. Counting USDC transfers over that ten-minute window gives 1,304 on Ethereum, 10,483 on Base, and 3,649 on Arbitrum, a comparison that is only valid because the windows are time-aligned. With an RPC node you would binary-search block timestamps instead, roughly twenty calls per bound, per chain.
How do you measure a stablecoin's peg from onchain data?
Peg health is the market price of the stablecoin against a reference such as the US dollar or another stablecoin, and that price comes from where it trades: DEX swap events. You read the executed price from each swap on the stablecoin's main pools and compare it to 1.00; a sustained deviation is a depeg signal. This is a market-price reading from trade data, not a value reported by the token contract, and it is separate from an oracle feed.
How do you measure stablecoin supply onchain?
Supply is reconstructed from issuance and redemption: a mint is a Transfer from the zero address, a burn is a Transfer to the zero address, and circulating supply is the running total of mints minus burns across every chain the token lives on. That reconstruction, and transfer-volume metrics, are covered in the onchain analytics guide rather than here, because they need careful cross-chain reconciliation of mint and burn pairs.

Building stablecoin or payments tooling?

See how cross-chain flow tracking and peg monitoring run on SQD on the stablecoins solution page.