Bridges
A bridge moves a treasury balance from one chain to another. Sweeps consolidate within a chain; bridging is what gets those balances onto the one chain you want to hold them on.
ChainOS facilitates a bridge. It does not operate one. It selects the provider for a route, builds the unsigned source transaction, polls the attestation service, builds the unsigned destination transaction, tracks the state and records both legs in your ledger. It never holds your funds in transit, provides no liquidity and takes no custody at any point. Your Edge signs both legs, from keys we do not have.
That is also why provider selection is narrow: 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.
The in-flight window
Almost every burn-and-mint bridge is two transactions on two chains with an attestation between them:

Between the burn and the mint your funds exist in neither place. Everything else on this page follows from that.
The transfer status that matters is SOURCE_CONFIRMED. Before it, nothing is at risk and a
failure costs you a gas fee. After it, the funds exist only as a claim against an off-chain
service, and inFlightSince is stamped — every threshold, alert and exposure figure counts from
that moment rather than from when you started the transfer. Time spent building and signing
carries no risk, and counting it would make every alert wrong by however long your Edge took to
wake up.
Three refusals, all of them cheap
POST /v1/treasury/bridges/transfers checks three things before it builds anything, in this
order. All three leave your funds in your source treasury wallet. After the burn there are no
cheap refusals left.
1. Can the destination receive? A live on-chain check, not a configuration lookup. On Solana the associated token account must exist; on the XRP Ledger and Stellar a trust line must be established. Burning when the destination cannot receive leaves the funds attested and unredeemable — so this is checked at quote time and again immediately before the burn.
POST /v1/treasury/bridges/quote returns destinationReady and, when it is false, a
preparation array carrying the unsigned transaction that would fix it. Sign it through the
ordinary path and quote again.
RLUSD there is an issued currency, not a token contract. Your address needs a trust line to the issuer before it can hold any, and only the holder can sign one — the sender cannot create it for you. The trust line also locks a further 2 XRP of owner reserve, so an RLUSD-capable address needs 12 XRP rather than 10. An address funded to exactly the base reserve cannot open one.
2. Are you within your in-flight limits? Three caps on your bridge policy bound how much of your money can be burned and not yet minted at any moment:
{ "maxInFlightValue": "500000000000",
"maxInFlightCount": 20,
"maxSingleTransfer": "100000000000" }
Above any of them the transfer is refused with 409 IN_FLIGHT_LIMIT_EXCEEDED, naming what is
already in flight. This is the guard working rather than a failure: 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 an off-chain attestation service is not.
3. Is the cost within maxFeeRatioBps? It defaults to 100 bps, a third of a sweep policy's
300, and the difference is deliberate: a sweep's alternative is leaving funds stranded in a
deposit address, while a bridge's alternative is waiting until more has accumulated, which costs
nothing. Bridging 50 USDC out of Ethereum can cost several dollars across both legs.
The ratio is the protocol fee's share of the amount. Gas is reported separately in each chain's own currency and deliberately not folded in — comparing a fee in ETH against an amount in USDC needs a price, and ChainOS has no price feed and will not acquire one to answer this.
When something goes wrong
The failure states are not interchangeable, and treating them as one is the most expensive mistake you can make here.
| Status | What it means | What to do |
|---|---|---|
SOURCE_FAILED | The burn never landed. No funds were at risk. | Retry immediately. Nothing left your wallet. |
STALLED_ATTESTATION | Burned, not attested. Funds in flight. | Do not retry. See below. |
STALLED_RELAY | Attested, not minted. Funds in flight, recoverable. | POST /relay — submit the mint yourself. |
EXPIRED | The attestation window lapsed. | POST /reattest. Do not start a new transfer. |
DEST_FAILED | The mint reverted. The attestation is still valid. | Fix the cause, then POST /relay. |
A stalled bridge is alarming, and the instinct is to start another one. That would burn a second
amount against a message that is still perfectly valid. Every transfer carries a
recommendedAction field saying what to actually do; the bridge_stalled webhook carries the same
sentence. Show it to whoever is on call.
Stall thresholds come from the provider rather than from a global setting, because fifteen minutes is a stall for LayerZero and an ordinary finalized CCTP transfer out of Ethereum. Each provider declares its own expected timings; we warn at three times and alert at ten.
Relay modes
| Mode | Who submits the mint |
|---|---|
self_relay | You do. We build it, your Edge signs, we broadcast. |
auto_relay | The protocol's relayer network. We watch for completion. |
hybrid | Auto-relay, then self-relay after a timeout. |
hybrid is the default. Relayer networks stall, and without a fallback a stalled relay leaves
funds burned and unminted indefinitely. You can also POST /relay at any time once an attestation
is in hand — you should not have to wait for our timer to agree with you.
Watching your exposure
GET /v1/treasury/bridges/in-flight
{
"byToken": [
{ "token": "USDC", "count": 3, "value": "72100000000",
"oldestInFlightSeconds": 340 }
],
"stalled": [],
"maxInFlightValue": "500000000000",
"utilisationBps": 1684
}
oldestInFlightSeconds is the number to alert on. A total says how much is at risk; that one says
whether something is wrong.
The routes
| Token | Protocol | Notes |
|---|---|---|
| USDC, EURC | Circle CCTP | One integration, both assets. |
| USDT | LayerZero OFT | Verified chains only — see below. |
| RLUSD | Wormhole NTT | The XRPL leg needs a trust line. |
| cNGN | Issuer API | Permissioned; needs a relationship with the issuer. |
Native coins are never bridged. Moving ETH to Polygon needs either a wrapping bridge or a swap — a wrapped derivative or third-party liquidity — and both are excluded by design. Native assets are swept, not bridged.
Some gaps are deliberate and worth knowing about:
- USDC on BNB Smart Chain. Circle has a CCTP domain for BSC (domain 17), but does not issue USDC there — the domain carries USYC. A burn addressed to it could never be minted, so the route does not exist.
- USDT on Base. Native USDT does not exist on Base at all.
- USDT on TRON. TRON carries an enormous amount of legacy USDT and is not an EVM LayerZero endpoint, so the OFT standard does not reach it.
Ask for a route that does not exist and you get 422 NO_BRIDGE_ROUTE with the reason and any
alternatives that would serve the same purpose. You should never discover a gap by receiving an
error after building on top of it — which is why pipelines check routes at configuration time.
Rehearse it first
Everything on this page works with a sandbox key, and the sandbox is a simulator rather than a stub: your Edge really signs. What is simulated is the outside world — the attestation service, the gas price, the provider's availability.
GET /v1/sandbox/treasury/scenarios
Eight scenarios, each corresponding to a production incident that will eventually happen. Two matter more than the rest: stalled attestation and expired attestation. Both put your funds in the state where they exist in neither place, and the cheapest possible moment to find out whether your alerting fires and your team knows not to retry is now.
The sandbox route matrix is identical to production, so you cannot build something in sandbox that is impossible live.