Orders
The struct, domainname and version are the same for both
generations, binary and neg-risk:
maker, in the struct and in the request body: your vault of that generation;domain.verifyingContract: the market’s exchange.
Cancels (
POST /api/orders/cancel, cancel-batch, cancel-all) are
keyed by your wallet: they cover orders of both vaults, before and after
the upgrade. GET /api/orders/open lists the orders of both vaults; each
order’s maker shows which vault it rests on.
The vault field
Withdraw, redeem and split / merge / convert take an optional vault:
one of your vaults[].vaultAddress.
Withdraw
POST /api/withdrawals/request (withdraw:write or vault:write), the
usual request plus vault. userSig is
WithdrawERC20 signed in the domain of that vault:
- Any other destination from a generation-2 vault:
400 withdrawal_destination_not_allowed(details: { destination, vaultAddress, generation }). It is checked before the 2FA code, so the code is not spent. Withdraw to your wallet and transfer from there. - A listed destination can be removed at any time. A refused destination is final for that request.
- 2FA and whitelist rules are unchanged. A TOTP code is single-use per 30-second step: a second withdrawal in the same step needs the next code or a backup code.
503 withdrawal_unknown: the withdrawal may have been created. Do not sign a new one; a new salt is a second withdrawal. Re-send the same body (samesalt,expiry,userSig,totpCode) for up to about 60 seconds, then checkGET /api/me/withdrawals. A stored request answers with its withdrawal (details.withdrawalId).503 withdrawal_temporarily_unavailable: nothing was created. Retry later, with a new 2FA code if 2FA is on.- Cancel (
POST /api/me/withdrawals/{id}/cancel) now also acceptswithdraw:write.
Return stray USDC (RND-2019)
USDC sent directly to a vault contract address by a plain ERC-20 transfer (not deposited viaPOST /api/deposits/relay) is never credited to trading balances. GET /api/me/vault reports this as strayUsdc on the active vault and inside vaults[].strayUsdc for each deployed vault. strayUsdcReturnEnabled: true means a return would be queued now: it is enabled on the environment, neither the platform nor the withdrawals freeze is on, and the relay can take it. When it is false although returns are enabled, strayUsdcReturnBlockedBy names the code the POST would answer 503 with: platform_frozen, withdrawals_frozen or stray_usdc_unavailable (otherwise null). The freezes are read only when one of your vaults holds stray USDC.
To return stray USDC to your owning wallet without user gas costs:
POST /api/me/vault/stray-usdc/return (withdraw:write or vault:write; optional ?vault=0x... query parameter to target an inactive vault).
The platform relay executes VaultLedger.sweepOffLedgerERC20(USDC):
- Returns
202 Accepted(or200 OKif a return job just finished) with aStrayUsdcReturnRespDtocontainingjobId,status, andenqueued. - Idempotent: while a return job is in flight, repeating the call returns the existing job (
enqueued: false). - Poll status via
GET /api/me/vault/stray-usdc/return/{jobId}untilstatus: "confirmed". - When confirmed,
/api/me/activityshows a correspondingstray_usdc_returntransfer entry. - Returns
409 no_stray_usdcwhen nothing is stray;409 stray_usdc_return_disabledif feature is disabled on the environment;422 stray_usdc_return_revertedif simulation reverts on chain. - Returns
503 platform_frozen/503 withdrawals_frozen(retryAfterSec) while the platform or the withdrawals freeze is on: nothing is queued, so show a paused state rather than an error. A return queued before the freeze stayspending(held, not sent) and goes ahead once the freeze lifts; keep polling itsjobId. If the relay has not sent it in time, it endsexpiredand you ask again. - Returns
503 stray_usdc_unavailable(retryAfterSec) when the chain could not be read or returns are paused on the platform side; nothing is queued, retry later.
Redeem
POST /api/me/positions/redeem (positions:write or vault:write),
then poll GET /api/me/positions/redeem/{jobId} until confirmed.
Redeem a legacy market on the generation-1 vault and a generation-2
market on the generation-2 vault; the payout lands in that vault.
API-key callers sign a RedeemAuthorization (the relay is gasless):
400 redeem_signature_required, 400 redeem_expired,
403 redeem_signer_mismatch. Sign a fresh authorization.
Split / merge / convert
The existing flow withvault added at each step:
POST /api/vault/split-signature(ormerge-signature,convert-signature) with{ marketId, amount, vault }and anIdempotency-Keyheader. For neg-risk markets, supplyoutcomeIndex(uint8, 0..254) to specify the sub-question. The answer carriesvaultAddress, the payload andbackendSig.- Sign the typed data in the domain of
vaultAddress(PredictStreetVault/1). Check it hashes todigest. POST /api/me/positions/op-submitwith{ signedOpId, ownerSig, backendSig, vault }, then pollGET /api/me/positions/op-submit/{jobId}untilconfirmed.
Balances and positions
Legacy markets after the upgrade
- Quote and trade them as before, with the generation-1 vault as maker
and the generation-1 exchange as
verifyingContract, until the generation-1 trading halt (below). - Resting generation-1 orders stay live and cancel as usual. During the
halt they stay on the book but cannot match, since no new order is
accepted on the market. When the platform then retires the market
(status
CLOSED), they are cancelled and their unfilled part is refunded; a partial fill keeps its filled position. - When a legacy market resolves, redeem with
vault= the generation-1 vault, then withdraw or move what is left.
Generation-1 trading halt
At generation-2 activation the platform halts trading on generation-1 markets. From then on, on a generation-1 market:
The halt fields are
generation (1), verdict (halted, or
unknown when the halt could not be confirmed after activation) and
haltedAt (ISO-8601 or null). The refusal is not retryable. A re-send of an order or op
the platform accepted before the halt is answered as it was then, not
refused. 503 vault_generation_unavailable means the market’s
generation could not be read and nothing was done; retry it. The
signature endpoints can answer it at any time, op-submit only while
the halt is in force. Generation-2 markets are never refused, and
cancel, redeem, withdrawal and USDC moves are not affected.
On the market read surface (GET /api/markets, GET /api/markets/{slug}, and markets[] in GET /api/events and /featured):
- Every generation-1 market sets
generationRetired: trueandgenerationHaltedAt(ISO-8601 timestamp). - A generation-1 market in
PRE_MARKET,OPEN,PAUSED,SUSPENDED,POSTPONED, orCLOSEDwithlifecycle.closedAt >= generationHaltedAtreadstradable: false,tradingMode: "closed", andnotTradableReason: "generation_retired"(GET /api/eventslist rows omittradingModeandnotTradableReason, which can be derived with the same rule). A market that had already closed on its schedule before the halt (lifecycle.closedAt < generationHaltedAt) keepsnotTradableReason: "closed_at_cutoff". FromDELAYEDonward, its settlement state (awaiting_data,awaiting_settlement,under_review,resolved,voided) is kept. rfq.availableisfalseon every generation-1 market, andGET /api/markets/{symbol}/executable-pricereportstradable: false.- When the retire walk closes a generation-1 market,
market_lifecyclepushesstatus_changedwithtoStatus: "CLOSED"andaction: "market.retire_gen1"(a normal cutoff close sendsaction: "market.close"), and each cancelled resting order’sorder_cancelledframe carriespublicMessageexplaining that trading ended with the platform upgrade.