POST /api/execute/plan
Turns a chosen venue quote into a one-call execution plan your wallet runs: typed steps, the price floor computed server-side, an expiry, and an idempotency key. Keyed only.
Request body
| field | required | meaning |
|---|---|---|
from, to, amount | yes | Same semantics as the quote endpoint. |
venueId | yes | The venue you chose from the quote. v1 supports tradecraft. |
takerParty | yes | The user's Canton party (hint::1220 plus 64 hex chars).
Format-checked; no ledger lookup. |
idempotencyKey | yes | Your unique id for this plan request. Retries with the same key return the same plan. |
slippageBps | no | Integer 0 to 5000; default 50. Sets the floor: minReceive = net x (1 - slippageBps/10000), floored. |
feeBps + feeRecipient | no, together | Your fee, same rules and cap as the quote endpoint. |
The plan shape
-
quoteRefpins the quote. Venue, pair, amount,net,minReceive, the fullclientFeesblock, andappliedSchema. The numbers are computed by the same code path as the keyed quote, so what was quoted is what the plan carries. -
stepsare what your wallet performs. Avenue-depositstep (target pool party, instrument, amount, and thememocarrying the price floor) and, when fees are collected, two independentfee-transferlegs: Synfin's flat service fee to the Synfin fee party, and your full integrator fee to your own recipient. - The memo floor is computed server-side. Your integration never hand-builds the venue's memo grammar; a malformed floor is how users lose money, so the server owns it.
-
expiry: a plan is valid for 90 seconds. After that, re-plan; prices have moved and the floor must be recomputed. -
trustWindowstates plainly what the user trusts and for how long during execution.
Example
curl -X POST "https://synfin.xyz/api/execute/plan" \
-H "content-type: application/json" \
-H "x-api-key: sk_live_..." \
-d '{
"from": "CC", "to": "USDCx", "amount": "250",
"venueId": "tradecraft",
"takerParty": "YOUR_USERS_PARTY",
"feeBps": 30, "feeRecipient": "YOUR_PARTY",
"slippageBps": 50,
"idempotencyKey": "your-unique-id"
}' {
"planId": "pl_6833f8c8-9d67-4796-95bb-281c9033915d",
"quoteRef": {
"venueId": "tradecraft",
"pair": {
"give": "Amulet",
"want": "USDCx"
},
"amount": "250",
"asOf": "2026-07-03T16:28:38.523Z",
"net": "34.517390",
"minReceive": "34.344803",
"clientFees": {
"integrator": {
"bps": 10,
"amount": "0.034518",
"recipient": "demo-wallet::1220aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
},
"service": {
"bps": 10,
"amount": "0.034518"
},
"userReceives": "34.448354"
},
"appliedSchema": {
"schemaId": "fs-2026-07-24",
"overrideId": null
}
},
"mode": "managed-deposit",
"steps": [
{
"kind": "venue-deposit",
"target": "tc-swp_CC-USDCx::122096fe076cc065af0cb38f94caa60e8ddfecbe8f0cfe10655ae7aa06fab99c66b7",
"instrument": "Amulet",
"amount": "250",
"memo": "34.344803",
"expiresAt": "2026-07-03T16:30:08.523Z"
},
{
"kind": "fee-transfer",
"target": "synfin-fees::1220...",
"instrument": "USDCx",
"amount": "0.034518",
"beneficiary": "synfin",
"expiresAt": "2026-07-03T16:30:08.523Z"
},
{
"kind": "fee-transfer",
"target": "demo-wallet::1220aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"instrument": "USDCx",
"amount": "0.034518",
"beneficiary": "integrator",
"expiresAt": "2026-07-03T16:30:08.523Z"
}
],
"expiry": "2026-07-03T16:30:08.523Z",
"idempotencyKey": "docs-demo-1",
"trustWindow": "Funds are locked in a CIP-56 transfer proposal that stays in the taker wallet until the venue accepts, and auto-expire back on non-accept.",
"collectsFees": true
} Captured 2026-07-03T16:28Z from this release against live venue quotes.
Note steps[0].memo equals quoteRef.minReceive: the
floor travels to the venue in the deposit memo, and Tradecraft enforces it
at execution (see the
per-venue guarantee table).
Idempotency
The same idempotencyKey from the same key returns the same plan
(same
planId, same numbers, same expiry) for 5 minutes. Safe to retry
on timeouts; a retry never produces a second plan your wallet could
accidentally execute twice.
Errors
| status | code | meaning |
|---|---|---|
| 400 | missing_key | Execution planning requires an API key. |
| 400 | bad_request | Missing or malformed fields (body must be JSON; required fields listed in the error). |
| 400 | unknown_token | Unknown from or to symbol. |
| 400 | bad_party | takerParty is not a Canton party id. |
| 400 | bad_fee_params | Fee params incomplete, over the cap, or a bad recipient. |
| 400 | no_pool | The venue has no pool for this pair. |
| 409 | venue_unavailable | The chosen venue is not quoting this pair right now. Re-quote and choose again. |
| 502 | quote_failed | The upstream quote could not be built. Retry. |
| 500 | internal | Unexpected failure; nothing was created. |
Example bodies and the shared 401/429/503 taxonomy are on the errors page.
The v1 boundary, honestly
- Execution and tracking are client-side in v1. Your wallet performs the steps and observes its own ledger view to a terminal state; the SDK execution client drives both. Synfin holds no funds and no keys at any point.
- A server-side status endpoint is planned. It will report the same lifecycle states the SDK derives today, so an integration written against the SDK states needs no rework.
- Fee collection is flag-gated off. The plan discloses and pins the fee
lines; when
FEE_COLLECTION_ENABLEDflips, it emits the twofee-transferlegs riding one atomic CIP-0112 batch with the deposit. Real on-ledger collection additionally needs a batch-capable wallet (Loop degrades fee-less today). Quotes and the disclosed fee lines are live now. - v1 venue support is Tradecraft, the venue whose swap execution is proven on the venue side by a mainnet canary and end to end on devnet. More venues follow as their capabilities are verified.