Skip to main content
A trade is one bridger taking one maker’s ad. These endpoints reserve the liquidity, build the deposit and lock calls for each wallet to send, and check the chain to see that each one landed. The relayer never moves funds. Settlement, the step after the lock, is on Settlement.

Endpoints

The flow

If the order deadline passes before settlement, the trade ends through cancels.

Who counts as a party

Every per-trade route admits only the trade’s parties: the bridger (by signed-in wallet), the maker (the ad’s creator, directly or through a linked account the chain says you own, or the order’s settlement signer), or an agent whose installed policy names the ad for that settlement signer. Anyone else gets 403. Each route then adds its own role: only the maker locks, and each party writes only its own half.

Trade statuses

A trade never moves backwards: once CLAIMED or DISPUTED, it only goes on to COMPLETED or CANCELLED. A deposit that lands late on an expired or replaced reservation brings it back to ACTIVE.

List trades

GET /v1/trades/all lists only your own trades: where you are the bridger, the ad creator or the settlement signer, or where an agent binding of yours names the ad. Filters narrow that set; a filter naming someone else’s address returns nothing of theirs.
A page can come back shorter than limit. Keep following nextCursor until it is null.
Dispute fields are explained on Disputes.

Trades your agent may act on

GET /v1/trades/assigned is the discovery route for an agent runtime. It takes your signed-in wallets as agent keys, finds the ads their installed policies name, and returns the trades on those ads that the live policy lets the agent open. It takes the same query as /all but ignores the address filters, and returns the same shape. With no installed policy you get { "data": [], "nextCursor": null }. A maker trading from their own wallet uses /all instead. See Agents.

One trade

GET /v1/trades/:id returns the trade in the list shape, plus:
  • pendingLockAttempt: true when a lock was quoted or a reservation hold exists.
  • Your own receive address, and only yours: bridgerDstAddress if you are the bridger (where you are paid on the ad chain), adCreatorDstAddress if you are the maker (where you are paid on the order chain). This is the only route that adds them.

The timeline

Every trade carries timeline: what has happened so far, oldest first. A step the chain recorded has source: "chain", the block’s close time, the chain and the transaction. A step only the relayer saw has source: "relayer" and the relayer’s clock. A step that has not happened is absent, and each step appears at most once. Ties are shown in table order.

Create a trade

POST /v1/trades/create reserves the slice of the ad you want to take and returns the exact order to send to the OrderPortal.
routeId is accepted but not read; the route always comes from the ad. bridgerDstAddress need not be a wallet you linked, but it is your identity for disputes on the ad chain: if you cannot sign from it, you cannot file or answer a dispute on this order (the cancel path still refunds you). It may not be the zero address, the ad’s creator or its settlement signer. The relayer checks the ad is ACTIVE with no pending change, scales your amount to ad-token decimals and checks the ad’s limits, checks that the maker’s settlement signer has a usable key on both chains, and checks free liquidity on chain. It then sets the deadline (see Deadlines and clocks) and writes the trade as INACTIVE with a hold on the ad.
Your wallet then calls OrderPortal.createOrder(orderParams) on chainId (approve amount first for an ERC-20; send exactly amount as value for a native token), and you call confirm.

The order

Every order is the same 19 fields, in this order, matching the on-chain order hash: orderChainToken, adChainToken, amount, bridger, orderChainId, orderPortal, orderRecipient, adChainId, adManager, adId, adCreator, adRecipient, salt, orderDecimals, adDecimals, deadline, adSettlementSigner, lpFeeBps, protocolFeeBps.
  • Addresses are bytes32 hex; an EVM address is left-padded with zeros.
  • salt is the trade id read as a 128-bit number, in decimal.
  • deadline is unix seconds; after it the co-signed payout is refused.
  • adSettlementSigner is read from the ad on chain when the trade is created.
  • lpFeeBps and protocolFeeBps are the ad chain’s fee split at reservation (see Fees); every order built for the trade carries the same values.
Each escrow takes 17 of them: the OrderPortal drops orderChainId and orderPortal (its own), and the AdManager drops adChainId and adManager and calls orderPortal srcOrderPortal. The relayer already sends the right set for each call. See Order hashing.

Reservations

  • A reservation holds the liquidity for 30 minutes. If the deposit has not landed by then, the trade becomes CANCELLED with cancelReason: "expired-reservation" and the hold is released. A deposit that lands later still revives it on confirm.
  • One pending reservation per bridger per ad: creating again replaces the earlier one.
  • You can hold only so many open reservations (reserved, deposit not landed, not expired) at once. A reservation being replaced does not count.
The 429 body carries limit. The route also has a strict per-IP rate limit.

Fetch the order again

POST /v1/trades/:id/create-data rebuilds the same createOrder call for a trade still INACTIVE, for when the wallet call failed or the page reloaded. It takes no body, makes no new reservation and does not extend the 30 minutes. The response is the same as create, byte for byte.

Deposit status

GET /v1/trades/:id/deposits reports how far each leg has got in the relayer’s record of the chain. Poll it after the lock is confirmed. When settlementGrade is true, /params serves the roots and co-signing can start.

Order parameters for signing

GET /v1/trades/:id/params returns the order plus everything both parties sign in the co-signature.
  • Roots. The first call after both legs are settlement grade pins the two roots and widths on the trade, so both parties sign the same pair even if more deposits land in between. A reorg that replaces a pinned leaf unpins them and clears stored co-signatures; read /params again and re-sign.
  • Maker key. makerKeyCommitment names the maker key. Once the order is locked it is the key in pinnedSlot, and it is absent if that slot holds no qualifying key.
  • Nullifiers. adLegNullifier and orderLegNullifier appear once both legs have a half on record. Until then a co-signature is refused with 409 nullifiers-not-ready.
  • secretCustody. Per leg: parties (the owning party sent its half), relayer (the half was made under the earlier scheme, for older trades) or pending (none yet). The ad leg is the bridger’s half, the order leg the maker’s. See Trade secrets.
Refusals are the same as for /deposits.

Get the maker’s lock

POST /v1/trades/:id/lock returns the lockForOrder call for the ad chain. Only the maker, or an agent the maker’s policy names, may call it. The body carries the maker’s half for the order leg.
The envelope is the same as in Send your half. A retried lock may send the same half again. The relayer checks maker authority, that the trade is ACTIVE and not locked, and that the hold matches the order amount. It then picks the slot the lock pins: the settlement signer’s ad-chain slot holding the newest key live on both trade chains that outlives the order. Then it stores the half, records the pending lock and returns the call.
Send lockForOrder(orderParams, signerSlot) from the maker’s wallet or account, then call confirm. The AdManager refuses the lock once the deadline is too close (Escrow__DeadlineTooSoon). If a lock you were given is already on chain but not confirmed, the answer is { "pendingConfirmation": true, "chainKind": "STELLAR" }: call confirm, do not lock again. A quoted lock is held for 10 minutes; after that a new request quotes a fresh one.

Send your half of the trade secret

POST /v1/trades/:id/half stores one party’s half of the per-trade secret. Each payout proves knowledge of one half, and both parties co-sign each leg’s nullifier (the hash of that half). Each party derives its own half from a root only it holds, so the same half comes back on every device. See Trade secrets. The bridger sends the ad leg’s half after create and before the deposit. The maker normally sends the order leg’s half with the lock, and may use this route instead.
Seal the half to the key published at GET /v1/keys/half-encryption, after checking that key’s signature against the key pinned in your build. Use sealHalf from @proofbridge/bls-encodings. The envelope binds the half to this order and leg, so it cannot be replayed elsewhere. Before sending, open your own envelope locally and check that the half hashes to the nullifier you send.
The first half per leg wins. Sending the same nullifier again answers created: false, even with a fresh ciphertext after a key rotation. A trade whose half never arrives cannot be co-signed and ends through the cancel path (cancelReason: "half-missing").

Confirm a deposit or lock

POST /v1/trades/:id/confirm reports that the trade’s pending createOrder or lockForOrder landed. The chain decides, not the body: the relayer reads the escrow’s order status at the chain head.
  • On chain: the trade moves to ACTIVE (deposit) or LOCKED (lock) and you get 200. The maker is notified of a deposit and the bridger of a lock.
  • Not on chain yet, txHash looks like a hash, still inside the window: 202 with { "success": false, "pending": true, "retry": true }. Call again. The window is 10 minutes from the lock quote, or 30 minutes from create for a deposit.
  • Not on chain and the window has passed, or no hash-shaped txHash: 400. A deposit that lands later still confirms, and the relayer also confirms late deposits on its own.