TaifoonTAIFOON
SIGN IN
Exchange deposits and withdrawals

Guide · Exchange

Exchange deposits and withdrawals

Check a deposit before you credit it. Check a chain before you send a withdrawal, then prove the withdrawal landed. One set of reads covers every chain the layer reads, so a new chain needs no new node and no new code on your side.

An exchange running many chains keeps a node, an indexer and a set of finality rules per chain. The layer reads each chain from at least two sources, applies that chain's own finality rule, and answers in one shape. You keep your wallets and your signing. Every call on this page is a read.

Thirty calls a minute are free without a key. For a higher budget, take a free key with POST /v1/register and send it as X-API-Key (see Buy and use a key).

The deposit flow

Collectors read each chain from two sources. The address watch finds transfers into your deposit addresses; the finality stream says which blocks are final under the chain's rule. The verdict joins both. Your ledger credits, keeps the evidence, or waits.

exchange.deposits · workflowcredited 0waiting 0final blocks 0
● deposit or withdrawal■ final block● heldruns in your systemsExample amounts; the nodes are the routes on this page.

The withdrawal flow

Preflight says whether the chain is moving and which node to send through. Your wallet signs and sends. The receipt proves the transaction landed in a final block, and that proof is what the user sees.

exchange.withdrawals · workflowdelivered 0held 0final blocks 0
● deposit or withdrawal■ final block● heldruns in your systemsExample amounts; the nodes are the routes on this page.

One call per decision: the desk

The desk routes return the verdict directly, in one record for every chain family. The sections after this one show the reads behind each verdict, so you can run them yourself.

Deposit verdict

shell
curl "https://coord.taifoon.dev/v1/desk/deposit/8453/0xb513abed23b9e21d75e330ea478272cdaf9af8245038c25f9710374648423b45?address=0x3574999dd4c96eb73bd6e11d4177010c83e14f5b"
answer (trimmed)
{
  "verdict": "CREDIT",
  "reason": "final: 1 transfer at proven under OP_DISPUTE_GAME",
  "records": [{
    "schema": "taifoon.deposit.v1",
    "chain": "eip155:8453",
    "asset": { "kind": "token", "id": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "symbol": "USDC", "decimals": 6 },
    "amount": "1000",
    "block": { "number": 52919297 },
    "level": "proven"
  }]
}

verdict is CREDIT, WAIT or REJECT. Anything the desk could not read is a WAIT. level climbs from seen to confirmed, final and proven.

Deposits into an address over a window

shell
curl "https://coord.taifoon.dev/v1/desk/deposits/8453/0x3574999dd4c96eb73bd6e11d4177010c83e14f5b?from=52919000&to=52919400"

Token and native-coin deposits, each as the same record. coverage says which blocks were read for which kind: native coin comes from block bodies, at most 100 blocks a call.

Withdrawal preflight and receipt

shell
curl https://coord.taifoon.dev/v1/desk/withdrawal/8453/preflight
curl https://coord.taifoon.dev/v1/desk/withdrawal/8453/0xb448dd374e718c20ea0bc56826ed8b249cc481c8e7525471d008d51053633792/receipt

Preflight answers SEND or HOLD with the reason and the best node. The receipt answers DELIVERED or NOT_YET.

Streams

shell
curl -N "https://coord.taifoon.dev/v1/desk/deposits/stream?chains=8453&addresses=0x3574999dd4c96eb73bd6e11d4177010c83e14f5b"
curl -N "https://coord.taifoon.dev/v1/desk/finality/stream?chains=8453,1"

The deposits stream sends each deposit once per level. The finality stream sends finalized for each new final block, and halt and resume when a chain stops or starts. Both resume from Last-Event-ID after a dropped connection.

Chains and lambdas

shell
curl https://coord.taifoon.dev/v1/desk/chains
curl https://coord.taifoon.dev/v1/desk/lambdas

desk/chains lists every chain the desk serves with its family, CAIP-2 id, finality rule and a recommendation: keep_open, watch, hold_credits or pause. A reading that is too old counts as no signal and holds credits. desk/lambdas lists the readers behind the desk, one per chain family. A venue registers its own lambda as data; a family with no reader yet answers 501 family_not_read.

1. Watch the deposit address

Token transfers into an address, over a window of blocks. The held block headers' blooms pick the blocks that may hold a match, and only those are read. Each row says whether its block is in the chain's block tree.

shell
curl "https://coord.taifoon.dev/v1/chain/8453/address/0x3574999dd4c96eb73bd6e11d4177010c83e14f5b/events?from=52919000&to=52919400"

The answer holds the USDC transfer of 1,000 base units (0.001 USDC) in block 52,919,297, with in_tree: true and final: true. A window wider than one call becomes a scan job (202) that you page. The scan sees logs, so a native-coin transfer that emits no log is not in it: read those from the block body with GET /v1/chain/:chain/block/:id.

2. Read the transaction

shell
curl "https://coord.taifoon.dev/v1/chain/tx?chain=8453&hashes=0xb513abed23b9e21d75e330ea478272cdaf9af8245038c25f9710374648423b45"
FieldWhat it means
statussuccess, reverted or not_found.
block, confirmationsWhere it landed and how deep it is under the tip.
finalThe block is final under the chain’s own rule.
events[]Decoded logs: for a token deposit, the Transfer with from, to and value in base units.

3. Prove it

shell
curl https://coord.taifoon.dev/v1/proof/tx/8453/0xb513abed23b9e21d75e330ea478272cdaf9af8245038c25f9710374648423b45
answer (trimmed)
{
  "proven": true,
  "proof_state": "proven",
  "is_finalized": true,
  "block_number": 52919297,
  "checks": {
    "receipt_found": true,
    "tx_succeeded": true,
    "proof_served": true,
    "block_hash_matches": true,
    "within_verifiable_range": true
  }
}

The first ask for a transaction builds its proof and can take about 10 seconds; later asks are quick. The answers that are not a proof are listed in Prove a transaction.

4. Decide

Credit only when three things hold: the transaction succeeded, a transfer in it pays your address, and the proof is final. Anything the layer could not read is a wait, never a credit.

shell
V1=https://coord.taifoon.dev/v1
CHAIN=8453
TX=0xb513abed23b9e21d75e330ea478272cdaf9af8245038c25f9710374648423b45
TO=0x3574999dd4c96eb73bd6e11d4177010c83e14f5b
curl -s "$V1/chain/tx?chain=$CHAIN&hashes=$TX" > tx.json
curl -s "$V1/proof/tx/$CHAIN/$TX" > proof.json
jq -n --slurpfile t tx.json --slurpfile p proof.json --arg to "$TO" '
  ($t[0].txs[0] // {}) as $x | $p[0] as $p
  | [($x.events // [])[] | select(.event == "Transfer" and ((.args.to // "") | ascii_downcase) == $to)
      | {token: .address, amount: .args.value}] as $paid
  | if $x.status != "success" then "REJECT: \($x.status // "not found")"
    elif ($paid | length) == 0 then "REJECT: nothing paid to \($to)"
    elif $p.proven == true and $p.is_finalized == true then "CREDIT \($paid | map("\(.amount) of \(.token)") | join(", "))"
    else "WAIT: proof \($p.proof_state // "pending")" end'
output
"CREDIT 1000 of 0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"

With any other address in TO the same transaction prints REJECT: nothing paid to …. Keep both answers with the credit: they are the evidence for it.

Before you send

shell
curl https://coord.taifoon.dev/v1/clock/8453
curl https://coord.taifoon.dev/v1/rpc/8453
FieldHold the withdrawal when
tips[] (clock)The newest block is older than six block times, or older than 60 seconds on a fast chain. Each source is listed with when it was read (at): a reading more than a few minutes old is no signal, so hold.
state, blocks_behindHow far the layer’s proofs trail the tip. Withdrawals can go out while this trails; credits wait for it.
healthy, best (rpc)No healthy endpoint is in the rotation. best names the endpoint to send through.

Prove the withdrawal landed

The same two reads as a deposit, on the transaction you sent. Filter its transfers by the address you sent from:

shell
curl -s "https://coord.taifoon.dev/v1/chain/tx?chain=8453&hashes=0xb448dd374e718c20ea0bc56826ed8b249cc481c8e7525471d008d51053633792" \
  | jq '[.txs[0].events[] | select(.event == "Transfer" and ((.args.from // "") | ascii_downcase) == "0x3574999dd4c96eb73bd6e11d4177010c83e14f5b")
        | {token: .address, to: .args.to, amount: .args.value}]'
curl -s https://coord.taifoon.dev/v1/proof/tx/8453/0xb448dd374e718c20ea0bc56826ed8b249cc481c8e7525471d008d51053633792 | jq '{proven, is_finalized, block_number, super_root_hash, checks}'

This transaction sent 1,000,000 base units in block 52,899,823, inside a batch with other transfers; the filter keeps only the leg you sent. proven: true and is_finalized: true make the receipt you show the user.

Check the proof yourself

A proof answer is a path of hashes, so you do not have to take the answer on trust. Ask for the block, walk its leaf to the chain's tree root and on to the superroot, and compare that with the published root.

shell
curl "https://coord.taifoon.dev/v1/proof/blocks/8453?blocks=52919297"
curl https://coord.taifoon.dev/v1/root/latest

The walk, line by line, is in Onboard a protocol. /proof runs the same check in your browser. Change one hash in the answer and the check fails.

Stay current: the root stream

One Server-Sent Events connection carries the superroot as it is recomputed, every 10 seconds. A new root means newly final blocks are provable; a root that stops moving is your signal to hold credits.

shell
curl -N "https://coord.taifoon.dev/v1/stream?lanes=root"
event
event: root
data: {"root":"05bb0e09…","at":1792677584,"leaf_count":21110614,"lane":"root"}

One encoding for every chain

Each chain is reduced to the same shape before it reaches you, so your credit rule is written once.

PartThe same on every chain
Chain idCAIP-2: eip155:8453 for Base, bip122:000000000019d6689c085ae165831e93 for Bitcoin, solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp for Solana, polkadot:fc41b9bd8ef8fe53d58c7ea67c794c7e for Acala.
BlockA 32-byte leaf in the chain’s block tree, indexed by block number.
FinalityOne finality type per chain, applied by its own rule: a PoS checkpoint, an L2 output root, a depth, GRANDPA, Tower BFT, DPoS.
AmountAn integer string in the asset’s base units. Never a float.
ProofOne six-layer V5 proof, from the superroot down to the event.

Transactions are read on 16 EVM chains today; the clock and the root cover more (GET /v1/clock lists them). A chain you list joins the same way: two independent sources with the chain's own finality rule, the common encoding, shared proof test vectors, a shadow run against your own node, then the same routes.