Skip to main content
Once both deposits are on chain, each party signs one short message with its BLS key. When the relayer holds both signatures it checks them, aggregates them, builds a zero-knowledge proof for each leg and sends the two payouts: the AdManager pays the bridger on the ad chain, the OrderPortal pays the maker on the order chain. The signatures and proofs are what the contracts check, so the finished packages are public and anyone can submit them. See Co-signing.

Endpoints

A client’s part is small: read GET /v1/trades/:id/params, sign, upload, then watch GET /v1/trades/:id/settlement (or the trade:status socket event) until Filled.

The bridge fee

The ad chain takes a fee from what the bridger receives: a share stays in the maker’s ad and a share goes to the protocol. The order chain takes nothing. The split is fixed per trade by lpFeeBps and protocolFeeBps in the order, so a later change does not affect it. Each payout emits FeeCharged(orderHash, lpFee, protocolFee). See Fees.

Upload a co-signature

POST /v1/trades/:id/cosign stores one party’s signature. Both parties upload the same auth; only signer, blsSig and blsPubKey differ.
auth is the SettlementAuth (v4): the domain tag plus eight fields. The signature is over their 288-byte concatenation, hashed to G2. Don’t build it by hand: use buildSettlementPreimage and signSettlement from @proofbridge/bls-encodings. What the signature binds:
  • The order. orderHash must be the trade’s. Both verifiers refuse a co-signature for another order.
  • The roots. They must be the pair /params pinned. A deposit landing between the two parties’ fetches cannot split them.
  • The maker key. A maker upload under any key other than makerKeyCommitment is refused, and so is a pair aggregated under another key.
  • The nullifiers. Each payout must consume the nullifier signed for its leg.
The maker signs as the order’s settlement signer. Your key must sit in a live registry slot on both trade chains, not expiring within the settlement horizon. You don’t send slot ids; the relayer finds them. See Keys.
A wrong signature is refused at upload and never stored, so it cannot block the right one. The second signature completes the pair: the relayer verifies, aggregates and queues both proofs inside the request, and only then answers Aggregated. Re-uploading the same bytes is a no-op. On any roots-not-pinned refusal, read /params again and sign once more.

Settlement status

GET /v1/trades/:id/settlement returns where the trade’s settlement stands. Only the trade’s parties may read it.
partyKeys and secretCustody are always present here. The cosign response and the trade:status push carry only tradeId, state, signaturesCollected, parties and updatedAt.

The collected pair

GET /v1/trades/:id/settlement/pair returns both co-signatures, so either party can keep its own claiming material and aggregate locally. It is served only once both halves exist: the first signature is never shown to the second signer before they sign.
Before completion you get complete: false. Later, once the relayer’s short-lived copy has expired, signatures is empty and aggSig, pkMaker and pkBridger carry the aggregated material instead. After both legs settle and the material is pruned, you get settled: true with auth: null.

Public leg package

GET /v1/trades/:id/settlement/legs/:side returns everything needed to submit one payout. It needs no auth: every field is public, and the payout is gated on chain by the proof and the co-signatures, not by who sends it. Anyone holding the trade id can finish the settlement if both parties and the relayer’s submitter are down. side is 0 (the AdManager payout on the ad chain, to the bridger) or 1 (the OrderPortal payout on the order chain, to the maker). A leg is served only when the relayer would release it itself: when the other leg’s package exists too, or the other leg already paid out, and (while neither has landed) the deadline is not too close.
From the package alone you build the co-signature data (version 3: the auth, the two slot ids, then both keys and the aggregate in EIP-2537 form) and call unlock(unlockParams, nullifierHash, targetRoot, proof, cosigData).

Settlement states

Claimed and Disputed take priority over Settling, so a leg that already paid shows only in the trade’s adCreatorClaimed and bridgerClaimed. If the trade leaves CLAIMED or DISPUTED without ending, the state is worked out again from the pair and the payouts. Between Aggregated and Settling the progress signal is legs.

Halting and resuming settlement

There is no endpoint for this. A maker halts co-signed payouts from their ads with their own transaction on the ad chain’s AdManager, from the account that owns the ads: One transaction covers every ad that maker owns on that AdManager, and takes effect in the same block or ledger. An agent cannot halt or resume. While halted, the co-signed AdManager payout reverts. Nothing else reads the halt: settlement evidence still pays the bridger, and new locks still work. A halt in force at or after the deadline makes a later cancel wait an extra grace period, so a maker already paid on the order chain cannot also take the lock back. The relayer checks the halt before every payout it sends, for both legs, and holds them while it stands. The status then shows halt: { "by": "maker", "since": … } with the state still Aggregated and both legs RELEASED. It clears at the next sweep after the maker resumes, when the leg lands, or when the deadline passes, after which the trade ends through the cancel path. For the chain’s own answer, read halted(maker) or halt_of(maker) directly. The public leg route keeps serving packages while halted.