One asset, many chains
A fintech takes USDC. Its customers hold USDC on whichever chain their exchange or wallet happens to use — Ethereum, Polygon, Base, Solana — and none of them think of those as different assets. The fintech's dashboard has to show one USDC balance, and its treasury has to be somewhere it can actually be spent from.
That is two problems wearing one coat, and ChainOS solves them with two different mechanisms.

A sweep consolidates within a chain. A bridge crosses between chains. They are separate because they fail differently: a sweep that fails costs a fee and leaves the money where it was, and a bridge that fails can leave money in neither place. Collapsing them into one verb would hide that difference at exactly the moment it matters.
Before anything: is one address per chain even right?
The natural design — and the one most diagrams show — is a separate deposit address per network. On ChainOS that is one more address than you need on the EVM side.
Ethereum, BSC, Polygon, Avalanche and Base share the derivation path m/44'/60'/0', so one
issued address receives on all five networks. What differs is which network the sender
broadcasts on, and the chain field on the webhook tells you which one it was.
| Networks | Addresses you issue per customer |
|---|---|
| Ethereum · BSC · Polygon · Avalanche · Base | one, shared |
| Solana | one |
| TRON, XRP, Stellar, Bitcoin | one each |
So the four-chain example above needs two addresses per customer, not four. Show the same EVM address under all five network names, with the network the payer must choose stated plainly beside it.
An EVM address is valid on all five networks. A customer who copies their Polygon address and sends on Ethereum has sent real money to a real address you control — so it is recoverable — but a customer who pastes an exchange's Ethereum deposit address into your Polygon withdrawal form has not. Say the network, in words, next to the address. Never just the asset.
1. What can actually cross
Check this before you design the rest, because it decides the destination chain.
GET /v1/treasury/routes/check?token=USDC&from=eth&to=polygon
ChainOS only uses issuer-operated burn-and-mint bridges, where the issuer of the token controls the contract on both sides. A third-party wrapper would hand you an asset nobody is obliged to honour, and no amount of liquidity makes that a good trade with customer money.
For USDC that means Circle's CCTP, and it reaches:
| Token | Protocol | Chains |
|---|---|---|
| USDC | Circle CCTP | Ethereum, Avalanche, Base, Polygon, Solana |
| EURC | Circle CCTP | Ethereum, Avalanche, Base, Solana |
| USDT | LayerZero OFT | Verified endpoints only — not Base, not TRON |
| RLUSD | Wormhole NTT | The XRPL leg needs a trust line first |
| cNGN | Issuer API | Permissioned; needs a relationship with the issuer |
Two gaps are worth knowing by heart because they look like configuration mistakes and are not:
- USDC on BNB Smart Chain. Circle has a CCTP domain for BSC — domain 17 — and does not issue USDC there. The domain carries USYC. A burn addressed to it could never be minted and the funds would be irrecoverable, so the route does not exist.
- USDT on Base and on TRON. Native USDT does not exist on Base at all, and TRON is not an EVM LayerZero endpoint despite carrying an enormous amount of legacy USDT.
Native coins are never bridged. Moving ETH to Polygon needs a wrapping bridge or a swap, and both are excluded by design. Native assets are swept, not bridged.
Choosing the destination
Pick the chain where you will actually spend. The considerations, in the order they usually decide it:
- Can every source reach it? Solana can reach Polygon and Base; it cannot reach a chain Circle does not issue on.
- What do your payouts cost there? This is where the money leaves, so the fee per withdrawal matters more than the fee per sweep.
- Can the treasury wallet be derived? On Bitcoin, Ethereum and the other secp256k1 chains, yes — which means your Edge verifies the destination by re-deriving it. On Solana and Stellar it cannot, and an external treasury address has to be allowlisted on the Edge by hand.
For the diagram above, Polygon and Base are both defensible. Ethereum is not: you would be paying L1 gas on every payout for the privilege of consolidating there.
2. A sweep policy per source chain
Each source chain consolidates into its own treasury wallet first. The bridge moves between treasury wallets, never between deposit addresses.
POST /v1/treasury/wallets
{ "chain": "eth", "source": "derived", "label": "USDC collection — Ethereum" }
POST /v1/treasury/sweeps/policies
{ "chain": "eth", "token": "USDC", "enabled": true,
"triggerMode": "THRESHOLD_AND_SCHEDULE",
"minAmount": "500000000", // 500 USDC. Ethereum gas is the reason
"scheduleCron": "0 0 2 * * *",
"treasuryWalletId": "…", "fundingWalletId": "…",
"gasFundingMode": "FUNDING_WALLET",
"maxFeeRatioBps": 300, "dustFloor": "20000000" }
Repeat per chain. The economics are not the same on each one and copying one policy across five chains is the commonest way to lose money here:
| Chain | A sane dustFloor for USDC | Why |
|---|---|---|
| Ethereum | tens of dollars | A token sweep plus a gas top-up is two L1 transactions |
| Polygon · Base · Avalanche | cents | Fees are small enough that the floor is about tidiness |
| Solana | cents, but watch rent | Leave the 890,880-lamport rent-exempt minimum |
Run POST /v1/treasury/sweeps/preview on each before enabling it, and read skipped. See
Sweeps.
Ed25519 admits no public-key-only derivation, so source: derived is unavailable on Solana
and Stellar. Nominate an address with source: external and add it to
EDGE_SWEEP_TREASURY_ALLOWLIST on the Edge — otherwise the Edge refuses to sign the sweep,
because there is nothing for it to re-derive and check against.
3. One bridge policy
POST /v1/treasury/bridges/policies
{
"token": "USDC",
"destinationChain": "polygon",
"destinationWalletId": "twl_polygon_main",
"maxInFlightValue": "500000000000",
"maxInFlightCount": 20,
"maxSingleTransfer": "100000000000",
"maxFeeRatioBps": 100,
"relayMode": "hybrid"
}
The three in-flight caps are the part to think about rather than copy.
Between the burn and the mint your funds exist in neither place. Not on the source chain,
not on the destination chain — they are a claim against an off-chain attestation service. The
caps bound how much of your money can be in that state at once, and going over them returns
409 IN_FLIGHT_LIMIT_EXCEEDED.
That refusal is the guard working. Refusing a bridge is always recoverable — the funds are still on the source chain and you can transfer in an hour. Accumulating unbounded exposure to somebody else's attestation service is not.
maxFeeRatioBps defaults to 100 here against a sweep policy's 300, deliberately: a sweep's
alternative is funds stranded in a deposit address, while a bridge's alternative is waiting
until more has accumulated, which costs nothing.
4. Name the whole thing with a pipeline
POST /v1/treasury/pipelines
{
"name": "USDC to Polygon treasury",
"token": "USDC",
"destinationChain": "polygon",
"destinationWalletId": "twl_polygon_main",
"bridgePolicyId": "brp_usdc_polygon",
"sweepPolicyIds": ["swp_eth_usdc", "swp_base_usdc", "swp_sol_usdc"],
"enabled": true
}
A pipeline owns nothing. It is the statement that these policies belong together, which is what lets one screen answer "where is my money and what is it doing" — a question neither half can answer alone. Delete it and the sweeps keep running.
Add the chains that cannot reach the destination too. A source with no route is kept and marked unsupported with the reason, not rejected. If you collect USDC on Stellar and XRP as well, you want those visible and greyed on the flow diagram, still sweeping into their own treasury, rather than silently missing from a list you submitted.
GET /v1/treasury/pipelines/{id}/flow
returns the stages left to right in the order money travels, each with a state — idle,
working, blocked, excluded. Render excluded in place with its reason beside it. A
customer who configured a chain and then cannot find it has a worse problem than one who can
see why it is greyed out.
POST /v1/treasury/pipelines/{id}/run triggers the sweeps and stops there, deliberately. The
sweeps have to confirm before their proceeds exist in the source treasury wallet, and
bridging now would burn a balance that has not arrived. The bridge policy's own trigger fires
when it has — which is the whole reason the two halves are policies rather than steps in a
script.
5. The number you show, and the one you must not hide
The holder sees 700.00 USDC. That number is not the destination treasury balance. It is:
one balance = destination treasury
+ every source treasury not yet bridged
+ every deposit address not yet swept
+ everything currently in flight between a burn and a mint
The last term is the one integrations drop, and it is the one that matters. During a CCTP transfer out of Ethereum — finalized, which is the default — there is a window of minutes where a real amount of real money is attested and unminted. A dashboard that sums only on-chain balances shows the holder a number that dipped by 100 USDC and came back, with no explanation, and generates a support ticket every single time.
GET /v1/treasury/bridges/in-flight
{ "byToken": [ { "token": "USDC", "count": 3, "value": "72100000000",
"oldestInFlightSeconds": 340 } ],
"stalled": [], "maxInFlightValue": "500000000000", "utilisationBps": 1684 }
Show the total as one figure and label the in-flight part in transit. Alert on
oldestInFlightSeconds, not on value — a total says how much is at risk, and that one says
whether something is wrong.
6. When a bridge does not complete
The failure states are not interchangeable and treating them as one is the most expensive mistake available on this page.
| Status | Where your money is | What to do |
|---|---|---|
SOURCE_FAILED | Still in your source treasury. Nothing was at risk | Retry immediately |
STALLED_ATTESTATION | Burned, not attested | Do not retry. Wait; the message is still valid |
STALLED_RELAY | Attested, not minted | POST /relay — submit the mint yourself |
EXPIRED | The attestation window lapsed | POST /reattest. Not a new transfer |
DEST_FAILED | The mint reverted; the attestation is still good | Fix the cause, then POST /relay |
A stalled bridge is alarming and the instinct is to try again. That would burn a second
amount against a message that is still perfectly valid. Every transfer carries a
recommendedAction field with the sentence to act on. Put it on the screen the on-call person
is looking at.
Rehearse both stall cases before you go live. GET /v1/sandbox/treasury/scenarios ships eight
rehearsals, each corresponding to a production incident that will eventually happen, and the
two worth doing first are stalled attestation and expired attestation — the two that
put funds in the state where they exist in neither place. Your Edge really signs in sandbox;
what is simulated is the outside world.
7. What this does not do
It does not convert. A customer who deposits USDT does not get USDC. There is no swap anywhere in ChainOS and no price feed on any path — "one asset, many chains" is exactly that, one asset. See Cross-network withdrawals for the shape of the problem when the asset differs too.
It does not chain bridges. A source with no direct route to the destination is unsupported, full stop; ChainOS will not route Ethereum → Base → Polygon around a missing route. Multi-hop doubles the in-flight window and the failure surface, and the in-flight window is the thing the entire treasury component is organised around minimising.
It does not take custody. ChainOS selects the provider, builds the unsigned legs, polls the attestation and records both sides in your ledger. Your Edge signs, from keys we do not have. No liquidity is provided and no custody is taken at any point.
A CCTP mint is built against MessageTransmitterV2, whose address differs per chain and comes
from the route's registry configuration rather than from a constant. If it is missing for your
destination, GET /v1/treasury/routes/check says so and the transfer is refused before
anything is burned — which is the cheap moment. Verify the exact source→destination pair you
intend to use, not just the token.
Next
- Bridges — the reference page, including relay modes and the stall thresholds.
- Pipelines — the flow diagram and what each stage state means.
- Treasury policy and yield — what may be done with the pool once it is in one place.