Skip to main content
This page is the reference for the signing half of Self-custody trading: what each request asks the wallet to do, how to answer it, and what errors mean.

The request

Each entry in pendingSignatures[] looks like this:

What each type asks for

Send exactly one of signature and txHash per step. Signatures are 0x-prefixed hex. A fill funded entirely from a legacy Polymarket Safe asks for one safe_tx signature and never an approve or an EIP-7702 authorization. A fill funded from a Safe plus another wallet on the same chain also asks for the usual steps for that other wallet.

A complete signer

fillSelfCustody calls your signer once per request. Return a hex signature, or { txHash } for a transaction.
Throwing from your signer is not a rejection. fillSelfCustody stops at once and drops the signatures it had collected for that batch, but the server has no reject call: the request simply expires and the fill fails. If you know a wallet cannot produce a raw-digest EIP-7702 authorization, fill with approveMode: "user_broadcast" instead.
When transaction reaches the trader. Only when the fill has to bridge, the source token needs an on-chain approve rather than a permit signature, and the fill set approveMode: "user_broadcast". In the default mode the same approval is signed as eip712. Venue orders, the Polymarket wrap, and the Hyperliquid builder-fee approval are always signatures. The other transaction you may see is an app-paid referral payout addressed to your payer wallet. Check signerAddress before you prompt. See Fees. After a transaction, return the hash without waiting. AGG watches for the mined receipt and does not continue until it matches the request.

Timeouts, and what expiry means

Every request carries expiresAt. Read it; never hard-code a deadline. Steps in one fill can have different deadlines, and the shortest is tight: When a fill dies, GET /execution/status reports overallState failed or expired with an errorReason, and fillSelfCustody throws with that state in its message. An expired request disappears from the pending list rather than showing as expired, so an empty list never means “done”. Only terminal: true does. Submitting a signature for an expired request returns 400. There is no recovery: quote again. A user has one live execution at a time. If a request expires instead of being answered, a new fill does not start until the abandoned one has finished failing, so ask the user to complete or abandon deliberately.

How many wallet prompts the user sees

Requests parked together arrive in one batch, but the user approves each one. The total is the sum of three parts: the venue order, a Polymarket funding step, and the funding transfers. Venue order Polymarket funding step
The wrap prompt is not one-time. Funding arrives as USDC.e, which must be wrapped before a buy settles, so a returning user sees it on every freshly funded buy.
Funding transfers, per funding source. A route funded from two chains pays this twice. With a partner fee on an EVM-funded buy, the funding batch also carries the fee transfers. A user-paid referral is one more transfer in the same batch. Worked examples: a Hyperliquid buy from an existing Hyperliquid balance is 1 prompt (2 while the builder approval is outstanding). A Polymarket buy bridged from native USDC is 4 (one funding transfer, one wrap, two order signatures), and stays 4 on repeat. The same buy from a token needing an approval, before the first authorization, is 6. A Polymarket sell is 2, or 3 the first time that deposit wallet trades.

Driving the loop yourself

Without fillSelfCustody, run this loop:
  1. POST /execution/fill with quoteId and signingAddress. The response never carries pendingSignatures, even for self-custody.
  2. Poll GET /execution/status?quoteId=, waiting pollAfterMs between calls.
  3. For each new pendingSignatures[] entry, have the wallet named by signerAddress sign or send the payload.
  4. Submit the results to POST /execution/fill/{quoteId}/signatures as { "signatures": [{ "stepId": "...", "signature": "0x..." }] } (or txHash for a transaction). Up to 32 entries per call. The response has the same shape as the fill response.
  5. Repeat from step 2 until terminal is true.
Errors from the signatures endpoint: Resubmitting a step that already succeeded is ignored, so retrying a batch after a network error is safe. Do not send the same stepId twice in one body: duplicates collapse to the last entry, so one bad entry discards the good one and the request fails.