Account batches
Sign a split, merge, or withdrawal with your own account key and submit it yourself, one operation per batch.
An account batch is a split, merge, or withdrawal that you sign with your account's owner key and submit yourself. The split and merge endpoints create the same kind of batch and sign it for you.
Each batch carries exactly one operation. A batch with more operations is
refused with 422 invalid_batch. To run several operations, sign one batch for
each and queue them; see Queueing several batches.
The only exception is a cross-chain withdrawal, which sends its approval and its withdrawal together in one batch. You cannot sign one yourself: start it from the signed-in web flow, which builds and signs it from a current quote.
A batch executes as one on-chain transaction.
Provisional balance and position changes can appear while the batch is still
PENDING; do not rely on them yet. Use SUBMITTED as the conservative
client gate: it means the provisional account update completed and chain
submission is beginning or in progress. Treat those changes as provisional
until SETTLED.
API order placement
For API trading, use pre-signed orders.
Server-side signing may slow order ingestion. Follow the
one-time setup and grant
orders:place_signed for signed single-order and batch submissions.
When to use it
Reach for a batch when you hold your wallet's private key and want to submit a ready-made signed operation, the same way you already do with pre-signed orders.
If you would rather not sign, the split and merge endpoints in On-chain positions are simpler.
One-time setup
Batches are signed against your account (the deposit wallet), with the same owner key and export flow as pre-signed orders. Follow Pre-signed orders → One-time setup to export the key and note the account address. Two differences:
- Mint the PAT with the
batches:submitscope (Settings → API Tokens → Submit account batches), exchange set to AGARA. It is a dedicated scope: grantingorders:place_signeddoes not grant it. - You sign an
AgaraAccountbatch envelope, not an exchange order. The schema is in Signing a batch below.
The operations
Every quantity is an integer in micro units (one collateral unit = 1,000,000; outcome
shares use the same scale). condition_id is the market's 32-byte id
from Markets.
Split locks collateral and mints the full binary pair. AGARA neg-risk group members are ordinary binary CTF conditions and use this same operation:
{ "kind": "SPLIT", "market_id": "0b0e7f3a-…", "condition_id": "0x…", "shares_micro": 5000000 }Merge burns the full binary pair back to collateral for every AGARA market:
{ "kind": "MERGE", "market_id": "7c2d9e1b-…", "condition_id": "0x…", "shares_micro": 2000000 }Withdraw moves collateral out of the account:
{ "kind": "WITHDRAW", "destination": "0x5555…", "amount_micro": 1000000 }Withdrawals floor at 10,000 micro (0.01 collateral units), count toward the per-batch value cap, and must not target your own account or the collateral token's own address. The same-chain operation is available in every environment.
The operation becomes one on-chain call, and its events carry batch_index = 0.
Cross-chain batches use an exact two-operation approval and withdrawal pair
that the server builds and signs from a current quote in the signed-in web
flow. The wire kinds are ACROSS_SWAP_APPROVE followed by
WITHDRAW_ACROSS_SWAP_DEPOSIT, WITHDRAW_ACROSS_SWAP_DEPOSIT_V3, or
WITHDRAW_ACROSS_SWAP_CCTP, with equal input amounts. A batch you sign with
these kinds is refused with 422 invalid_batch. They still share your account
sequence: the approval is call 0 and the withdrawal call 1, so the withdrawal's
events carry batch_index = 1.
Submitting a batch
POST /trade/v1/batches
Scope: batches:submit
Request
{
"seq": 4,
"deadline_unix_seconds": 1784022000,
"signature": "0x5ee4523b…",
"ops": [
{ "kind": "SPLIT", "market_id": "0b0e7f3a-…", "condition_id": "0x…a1", "shares_micro": 5000000 }
]
}ops: a list holding the batch's one operation.seq: the sequence number this batch takes. Read your account's current one on-chain by callingseq()(selector0x6857ab40). A batch executes only when the account has reached its seq, and executing it consumes the seq, so a signed batch runs at most once. To queue several batches, give each the next seq up from the last — see Queueing several batches.deadline_unix_seconds: when the signature expires, enforced on-chain. It must be in the future and at most just under 24 hours ahead (a one-minute clock-skew margin is reserved at the top of the window). Keep it short, 10 to 30 minutes: the deadline is your safety valve, and a batch that cannot execute by then expires without consuming the seq. A transaction executed before the deadline can confirm afterward and still settle. Allow for execution, confirmation, and queue time: the deadline runs from when you sign, not from when the batch reaches the front.signature: the 65-byter‖s‖vfrom the account owner key over the batch digest (see Signing a batch).
Response
201 Created with the batch's hash, the same digest you signed and can
compute locally before you submit:
{ "batch_hash": "0x51cda675…" }If the response is lost, read the batch by the hash you computed before
submission. A byte-identical replay cannot create a second batch identity.
However, the current wallet, market, deadline, value, and signature checks are
reevaluated and can return a typed refusal instead of 201. The hash lookup,
not a promised replay status, is authoritative.
What the server checks
Failures use Problem Details. Match the
stable code, never the first word of detail.
| Status and code | Meaning and action |
|---|---|
409 batch_in_flight | A batch already owns this sequence or the queue is full; wait and read current state |
409 sequence_conflict | The sequence is no longer current; re-read seq() before signing again |
403 forbidden_scope | The PAT lacks batches:submit |
403 account_quarantined | Stop new batches until the account is available again |
404 wallet_not_registered | Register the AGARA wallet |
422 invalid_signature | Rebuild the canonical envelope and sign with the account owner |
422 insufficient_balance | The batch demand exceeds available coverage; reconcile and reduce it |
422 value_cap_exceeded | Reduce the batch value |
422 market_closed | The market is closed or resolved; split and merge no longer run there |
422 invalid_batch | Send one operation per batch, and correct the operation, deadline, route, or amount |
424 wallet_setup_incomplete | Finish wallet setup before submitting |
502 dependency_outcome_unknown | Follow recovery.resource.batch_hash; check that batch before another mutation |
502 dependency_credentials_rejected | Stop retrying, retain request_id, and contact support |
502 dependency_invalid_response | Do not retry automatically; retain request_id for investigation |
503 dependency_unavailable | Back off with jitter, then reconcile before resubmitting |
503 account_operations_paused | Retry in a few minutes: new batches are paused for maintenance. A heal is still accepted, and batches you already submitted still settle, so read their state rather than signing them again |
503 feature_not_configured | This deployment does not offer account batches |
Coverage is checked against your balance and positions before the batch. When one operation needs what another produces, sign the second batch after the first settles.
Tracking a batch
GET /trade/v1/batches/{batch_hash}
Scope: batches:submit
{
"batch_hash": "0x51cda675…",
"status": "SETTLED",
"seq": 4,
"deadline_unix_seconds": 1784022000,
"origin": "PRESIGNED",
"tx_hash": "0x5e0d…",
"executed_at": "2026-07-14T09:12:41Z",
"failure": null,
"superseded_by_batch_hash": null,
"heals_batch_hash": null,
"unwound_at": null,
"created_at": "2026-07-14T09:12:03Z"
}For a terminal failure, failure is a safe typed object:
{
"status": "FAILED",
"failure": {
"code": "sequence_conflict",
"title": "Sequence conflict",
"detail": "Order submission failed, please retry.",
"recovery": { "strategy": "none" }
},
"unwound_at": "2026-07-14T09:13:10Z"
}Branch on failure.code; do not parse title or detail.
Poll every 1 to 2 seconds until a final state. The states:
| status | final | meaning |
|---|---|---|
PENDING | no | accepted; provisional effects may already appear, but clients should not rely on them yet |
SUBMITTED | no | the provisional account update completed and chain submission is beginning or in progress; effects remain reversible until settlement |
SETTLED | yes | confirmed on-chain and recorded; tx_hash and executed_at are set |
FAILED | conditional | no successful execution is expected, but possibly applied effects can still be reversing; require unwound_at, and apply the stranded-higher-sequence exception below before trusting restored inventory |
FAILED_DIVERGENT | yes | the failure needs review; the account stays frozen until it is resolved |
Read FAILED together with unwound_at. For an isolated failed batch,
unwound_at normally means its balance and position effects were reversed.
When it is still null, treat the funds as not yet restored and keep polling.
A reversal that cannot complete can instead move the batch to
FAILED_DIVERGENT. There is one safety exception: when a lower sequence is
FAILED_DIVERGENT, a higher queued batch can be closed as FAILED with
sequence_conflict while its effects are still uncertain. For those higher
rows, unwound_at alone is not permission to reuse inventory. Keep account
mutations paused, reconcile every affected hash, the on-chain sequence,
balances, positions, and account events, and resolve the divergent batch first.
If you already listen to the
account stream, use its position and
collateral events to update your live account view. tokens_minted,
tokens_merged, collateral_withdrawn, and compensating
collateral_deposited events carry the (batch_hash, batch_index) pair when
available. The pair correlates an event with an operation; it is not a
deduplication key, and recovery can produce several events with the same pair.
Keep polling GET even after the stream shows the account change. In particular,
collateral_withdrawn means your available collateral was debited; only the
batch's final SETTLED status confirms the on-chain withdrawal. Portfolio REST
and the batch response remain authoritative after reconnects or missed events.
Queueing several batches
You can hold several signed batches at once. Give each the next seq up from the last, and the server runs them strictly in that order: a batch waits until your account reaches its seq, then executes.
Three rules make a queue work:
- Send them one at a time. Wait for each
201before signing the next, so you know which seq it took. - No gaps. Every seq between your account's current one and the batch you
are signing must already be held by a batch of yours. A seq above a gap is
refused with
sequence_conflict, because nothing would ever consume the gap. - There is a limit. Past it you get
batch_in_flight. It is set per deployment; treat a refusal as the signal rather than assuming a number.
Each batch is still independent. Coverage is what you hold now, minus what your
still-waiting batches will spend — so you cannot queue past your balance. Once a
batch starts executing it reads SUBMITTED, and from then on its proceeds count
toward coverage even though they are still provisional. Queueing on top of them
is accepted and is the risk described under
what you observe on failure. For a flow that
truly depends on an earlier result, wait for SETTLED.
A queued batch stays PENDING, and its deadline is running the whole time it
waits. Give a batch you expect to sit behind others a longer deadline, or send
it later. A deadline that lapses in the queue is only noticed when the batch
reaches the front, so it can read PENDING well past its own deadline.
Split, merge, and withdrawal operations you start from the app share the same sequence, so they queue alongside anything you signed yourself.
Automatic redemption does not use your account sequence. It starts only after
wallets with a position in the market have no PENDING or SUBMITTED account
batches, so a queue you keep continuously non-empty can delay that market's
redemption.
The platform-signed transfer that moves bridged funds does use your account sequence, but it is not inserted behind batches you already queued. It waits until that queue finishes. Keeping the queue continuously non-empty can therefore delay bridged funds indefinitely.
What you observe on failure
For an isolated FAILED batch, unwound_at normally means its split, merge,
or withdrawal effects were reversed. Do not apply that shortcut to higher
sequence_conflict rows stranded by a lower FAILED_DIVERGENT batch; keep the
account paused and use the full reconciliation described above.
Whenever recovery requires positions, retry the whole positions read while
unavailable_exchanges includes AGARA. Do not rebuild the queue from a
partial 200 response.
What happens to the rest of your queue depends on failure.code:
sequence_conflict— the sequence is no longer safe to attribute to this batch. Another transaction may have consumed it, a lower failed sequence may have stranded this batch, or recovery may have raced. Do not infer that later queued sequences can run from this code alone. Poll every affected hash until terminal and re-readseq(), balances, positions, and account events. If a lower batch isFAILED_DIVERGENT, do not trust a higher row'sunwound_ator rebuild the queue until that divergence is resolved.batch_superseded— a replacement or recovery batch took over. Followsuperseded_by_batch_hashand reconcile that batch.chain_execution_failed,insufficient_balance,insufficient_shares, orinternal_error— do not assume a later queued batch can run. Poll every affected hash until terminal, re-readseq(), and rebuild from authoritative balances and positions.settlement_divergent— stop submitting. The account needs review before more mutations are safe.
When a non-divergent failure strands higher sequences, poll those hashes until
each is terminal, then re-read seq() and reconcile balances, positions, and
account events before signing again. If the lower batch is
FAILED_DIVERGENT, stay paused instead. Allow at least a minute, usually one to
two: a transaction still in flight looks exactly like an abandoned seq at
first, so the server waits to be sure before failing anything.
You can also keep the queue alive while the stranded original is still
PENDING. Before it becomes terminal, call
POST /trade/v1/batches/{batch_hash}/supersede with a replacement signed at
the same sequence. Once the original reads FAILED, the supersede endpoint
only returns that terminal row; re-read seq(), balances, and positions, then
submit a fresh batch if the reconciled state permits it.
A batch that needs review (FAILED_DIVERGENT) is the exception to the waiting
period: it closes the rest of your queue immediately.
Freshly split or merged inventory is spendable during the in-flight window,
but using it can leave the automatic reversal short if the batch fails. Do not
commit a batch's proceeds to other fills or withdrawals until it reads
SETTLED.
Signing a batch
The signature is EIP-712 over the account's Batch struct. Get any
field wrong and the server's recompute won't match, so you get
422 invalid_signature.
Domain: bind to your own account, or every batch hash-mismatches:
EIP712Domain {
name: "AgaraAccount"
version: "1"
chainId: <environment chain id>
verifyingContract: <your account address>
}verifyingContract is your account address, not an exchange or registry.
Use chain ID 8453 for this deployment.
Types: mirror the Agara account contract's Call and Batch structs exactly:
Call(address target, uint256 value, bytes data)
Batch(address wallet, uint256 seq, uint256 deadline, Call[] calls)wallet is your account address (the same value as
verifyingContract). Each op becomes one Call, in op order, with
value always 0:
| op | target | calldata |
|---|---|---|
| SPLIT | ConditionalTokens | splitPosition(collateral, 0x00…00, condition_id, [1,2], shares_micro) |
| MERGE | ConditionalTokens | mergePositions(collateral, 0x00…00, condition_id, [1,2], shares_micro) |
| WITHDRAW | collateral token | transfer(destination, amount_micro) |
The Conditional Tokens address is 0xe12D566cE5Dd8d817488E85a81386878649e3A18 and the
collateral address is 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913. Verify them against
CTF tokens and Collateral.
batch_hash is the EIP-712 signing digest; sign it with the account
owner key (canonical low-s, EIP-2), no extra prefix.
Verify against this vector
Reproduce this before pointing a hand-rolled signer at a live environment. All inputs are illustrative; the signing key is the well-known Anvil test key 0, never for use outside a test.
account = 0x1111111111111111111111111111111111111111
ctf = 0x2222222222222222222222222222222222222222
collateral = 0x4444444444444444444444444444444444444444
chainId = 8453
seq = 4
deadline = 1784022000
key = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
ops = [ SPLIT condition 0xaa…a1 (62 a's, then a1), shares_micro 5000000 ]
batch_hash = 0x51cda675bcc49a94f2c4ace692aa542d182dcb43ffe000da5d343bb49a441574
signature = 0x5ee4523b8cfe07be0dce5a8f42d385b70c9d171f526a71c158f795b76bfd6b22
6a31706a3babc9fb4d5b4aa7676251c0daae344894c49cb753f71cbed88457da1cIf your digest doesn't match, your composition differs from the canonical one. The server only ever accepts the exact bytes this composition produces, so fix the digest before anything else.
Security considerations
- The account owner key authorises batches that move funds and positions directly. Anyone who reads it can drain the account. Store it as a high-impact signing credential, exactly as for pre-signed orders.
- The PAT alone can't forge a batch: every submission also needs a valid owner signature. A leaked PAT is far less dangerous here than on a server-signed path; revoke it in Settings → API Tokens to cut off submissions without touching the key.
- Verify a withdrawal
destinationbefore you sign: transfers are irreversible once the batch settles, and a withdrawal to your own account is rejected.