Skip to content

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.

ts
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:

ts
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 onlyspent, 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