Skip to main content
Swaps let you offer token exchange to end-users who can only make a plain fund transfer — their wallet cannot sign a Canton DvP transaction. You create a swap, hand the user a Canton party to fund, and OneSwap does the rest custodially and atomically, returning the output token to the party that funded the swap.

Lifecycle

1

Create the swap

Call swaps.createSwap with your stable userRef, the input token and amount, and the output token. OneSwap resolves the { inSymbol, outSymbol } pair to a visible pool and returns a Swap in status awaiting_deposit with a per-user depositParty and a deadline.
2

The user funds the deposit party

Tell your end-user to send amountIn of inSymbol to swap.depositParty before swap.deadline. This is a normal token transfer — the user’s wallet never signs a swap.
3

OneSwap detects and swaps

Once the deposit lands on-ledger, the swap moves to deposit_detected, then swapping while the atomic swap executes.
4

Output is returned to sender

On success the swap reaches returned: amountOut of outSymbol is sent back to senderParty — the exact party that funded the deposit.

Status reference

Terminal statuses are returned, refunded, expired, failed, and cancelled.

Deposit-amount tolerance

The deposit must be within 1% of amountIn. If the amount that arrives is outside that band, the swap is refunded — OneSwap never guesses intent from a wrong amount. Show your users the exact amountIn and inSymbol and make it easy to send precisely that.

Deposit deadline

Every swap has a 60-minute deposit window from creation, exposed as deadline (an ISO timestamp). If no deposit arrives before then, the swap becomes expired and nothing happens — the user was never charged. Surface the countdown to your user and create a fresh swap if they miss the window.

Slippage and minimum output

By default OneSwap applies 2% (200 bps) slippage tolerance. You can override it per swap:
  • slippageBps — set a different tolerance in basis points (e.g. 100 for 1%).
  • minOut — set a hard floor on the output amount. If the swap would return less than minOut of outSymbol, it is refunded instead.
Use quotes.get to price the swap first. Quotes made with your SDK key already include the input-token network fee (feeModel: 'deposit'), so their amountOut matches settlement — use it directly as the basis for minOut, minus your slippage margin.

Return-to-sender semantics

The output is always paid back to the party that funded the deposit, reported as senderParty on the returned swap. You do not supply a destination address — there isn’t one to get wrong. This is what makes these swaps safe for fund-transfer-only wallets: the user gets their output at the same party they sent from.

One open swap per user

At most one open (non-terminal) swap per userRef may exist at a time. If you call createSwap for a userRef that already has an open swap, the call throws OpenSwapExistsError:
this user already has an open swap; finish or let it expire first
You do not have to wait out the deadline. swaps.getOpenSwap(userRef) returns that open swap — use it to recover the id when you no longer have it (after a restart, or when the createSwap response was lost) — and swaps.cancel(id) closes it if no deposit has arrived yet:
Once a deposit has arrived the swap holds funds and must run to completion — cancel throws ConflictError (409) in that case, so wait it out instead. See Error handling for how to detect and recover from this.

One pool per swap

Every swap settles against exactly one pool. The { inSymbol, outSymbol } pair must have a live pool that trades it directly; if it doesn’t, createSwap errors (and quotes.get throws NoDirectPoolError). There is no multi-hop routing — to move between two tokens with no shared pool, run two swaps through an intermediate asset (usually CC), each with its own settlement and fees. SDK 1.3.0 and later accept poolId in createSwap. Pass the poolId returned by quotes.get to guarantee that settlement uses the pool you quoted.

Network fee comes from the input

A per-swap network fee is taken from the input token to cover the on-chain settlement cost. It is priced live for every swap — the measured cost of settling the swap on Canton (the swap’s traffic size × the network’s current price per MB, plus a small buffer), converted into the input token — so it tracks the real cost rather than a fixed amount (typically around $1.5–2 at recent network prices, and it moves with the network). Your end-user’s wallet needs no CC or gas of any kind — they only ever send the input token. Account for this when you quote amounts to your users: the output reflects the input minus this network fee and the pool’s 0.30% swap fee.

Atomic settlement

Swaps are atomic DvP. The swap either completes and returns the output token, or the input is refunded — funds are never stranded by a partial swap. That means every swap you create ends in a clean terminal state you can act on.

Reconciling on your side

Use your own userRef as the reconciliation key:
  • Store the returned swap.id (an esc_... id) against your userRef when you create a swap.
  • One open swap per userRef means there is never ambiguity about which swap a user’s deposit belongs to.
  • On terminal status, record the outcome: returned (with amountOut), refunded, expired, cancelled, or failed (with error).

Polling for status

Poll getSwap on your own cadence:

Awaiting completion

waitForSwap wraps the polling loop for you and resolves on the first terminal status (or on timeout):
Swaps are live on OneSwap devnet and mainnet — see API keys for getting your sk_live_... credential.