Swap
An atomic shielded-to-shielded swap. Leg 1 unshields to a SwapWrapper; leg 2 re-shields the output note. Both legs are bundled through submitter.submitSwap, so the value is never sitting unshielded between two transactions — which is the whole point, and why a swap cannot be assembled from a withdraw followed by a deposit.
A swap needs a Submitter that implements submitSwap. The default HttpRelayerSubmitter does; a custom one that omits it cannot run this path.
import { fetchSwapQuote } from "@lelantos-org/sdk/quoter";
const quote = await fetchSwapQuote(quoterUrl, {
chainId,
tokenIn,
tokenOut,
amountIn,
slippageBps: 50,
});
await wallet.swap({
assetIn: 1n,
assetOut: 2n,
amount: 100n, // gross publicOut in circuit units of `assetIn`
quote, // pins route + minOut
wrapperAddress: "0x0000000000000000000000000000000000000001",
bRecipient: peerBech32, // optional, default own address
});What the swap actually credits
This is the part that is easy to get wrong in a UI.
The re-shielded B-note is not quote.minOut / scaleOut, and it is not a floor either. swap() sizes it with sizeBNote and encodes that exact value as the deposit leg's publicIn — so that is what the wallet receives. The wrapper pulls only what the note needs, and any better-than-quoted fill goes to the treasury as dust.
Show this figure, not expectedOut:
import { sizeBNote } from "@lelantos-org/sdk/wallet";
const feeBps = await chain.fetchFeeBps();
const { scale } = await chain.fetchAsset(asset);
const credited = sizeBNote(quote.minOut, scale, feeBps);
Do not re-derive this
The obvious closed form — minOut * BPS / (scale * (BPS + feeBps)) — is only the lower bound the search starts from. It lands below minOut whenever the division is inexact: wrong on screen, and reverting on chain if used to size a transaction.
Reading the receipt
SwapResult reports leg 1 only — spent, inputSum, sent, and change all describe the unshield into the wrapper. The re-shielded B-note arrives as a deposit, so it surfaces through depositId and does not appear in commitments.
That also means the B-note follows the deposit lifecycle: it is escrowed when the swap is mined, and only spendable once the relayer has flushed it into the tree and the wallet has synced. See Deposit.
Next
- Syncing — the B-note is not spendable until it is synced
- Fees — swaps are quoted on their own endpoint
- Low-level primitives