Skip to main content
A private swap trades one token for another entirely from your shielded balance. The sell token is withdrawn from the privacy pool to avnu’s executor, the swap runs through avnu’s solver-optimized routing, and the bought token lands back in your private balance as a new note.
Preview. Requires @avnu/avnu-sdk >= 4.2.0-next.2 and a proving backend: the Starknet privacy SDK or a STRK20-capable wallet (starknet.js >= 10.4). The sell token must already be in your private balance — deposit into the pool first. Questions? Reach out on Telegram.

Setup

The snippets below assume a few objects are already wired up:
IdentifierSource
getQuotes, executePrivateSwap, buildPrivateSwapFee, submitPrivateSwap, PRIVACY_POOL_ADDRESS@avnu/avnu-sdk
accountyour Starknet account (e.g. starknet.js Account)
transfers, Open, provingBlockIdthe Starknet privacy SDK
The paymaster client is built into the SDK: it targets starknet.paymaster.avnu.fi by default (override with AvnuOptions.paymasterBaseUrl, e.g. sepolia.paymaster.avnu.fi for testing).

How it works

A private swap is an apply_action transaction relayed by avnu’s paymaster. No user signature is needed, since everything settles on-chain straight from the proof. executePrivateSwap orchestrates the four steps:
  1. Pool fee. The paymaster returns the pool fee to withdraw (token, recipient, amount).
  2. Private calls. quoteToCalls({ private: true }): the backend sets takerAddress = executor and returns the inner swap calls plus the executorAddress.
  3. Proof. Your injected PrivateSwapProver builds and proves the private transaction: withdraw the sell amount to the executor, withdraw the pool fee, open a note for the bought token, and invoke the executor with the serialized swap calls.
  4. Submit. The paymaster relays the proof on-chain. The relayer pays gas; the pool fee reimburses it.
The SDK never touches keys, notes, or proofs — that’s the prover’s job. Both proving backends produce the same { call, proof } artifact.

Execute a private swap

import { getQuotes, executePrivateSwap, PRIVACY_POOL_ADDRESS } from '@avnu/avnu-sdk';

const STRK = '0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d';
const ETH  = '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7';

const [quote] = await getQuotes({
  sellTokenAddress: STRK,
  buyTokenAddress: ETH,
  sellAmount,
  takerAddress: account.address,
  size: 1,
});

const { transactionHash } = await executePrivateSwap({
  quote,
  slippage: 0.05, // 5%
  takerAddress: account.address,
  poolAddress: PRIVACY_POOL_ADDRESS, // SEPOLIA_PRIVACY_POOL_ADDRESS on testnet
  feeMode: { poolFeeToken: STRK, tip: 'normal' },
  prover,
  paymasterApiKey: PAYMASTER_API_KEY, // Portal key, same as gasfree
});

Implement the prover

The prover is where the cryptography lives. It receives a PrivateSwapPlan (sell/buy tokens, amounts, executorAddress, inner calls, pool fee) and returns the proven call. With the privacy SDK’s transfers builder:
import { transaction } from 'starknet';
import type { PrivateSwapProver } from '@avnu/avnu-sdk';

const prover: PrivateSwapProver = {
  buildAndProve: (plan) =>
    transfers
      .build()
      .with(plan.sellTokenAddress, (t) => {
        t.withdraw({ recipient: plan.executorAddress, amount: plan.sellAmount });
        t.surplusTo(plan.takerAddress);
      })
      .with(plan.fee.token, (t) =>
        t.withdraw({ recipient: plan.fee.recipient, amount: plan.fee.amount }),
      )
      .with(plan.buyTokenAddress, (t) =>
        t.transfer({ recipient: plan.takerAddress, amount: Open }),
      )
      .invoke(({ openNotes }) => ({
        contractAddress: plan.executorAddress,
        calldata: [
          plan.buyTokenAddress,
          ...transaction.fromCallsToExecuteCalldata_cairo1(plan.executorCalls),
          openNotes[0].noteId,
        ],
      }))
      .execute({ provingBlockId }),
};
Open is a privacy SDK sentinel that opens a note for the swap output, whose amount is only known after execution. The inner calls are serialized with starknet.js’s transaction.fromCallsToExecuteCalldata_cairo1; the executor expects [buyToken, ...serializedCalls, openNoteId]. Alternatively, a STRK20-capable wallet (e.g. Ready or Xverse) can produce the same artifact via WalletAccountV6.strk20PrepareInvoke: describe the same four actions and map the returned call and proof to PrivateSwapCallAndProof. The wallet keeps the keys; the dapp only describes actions.

Manual control

executePrivateSwap wraps three functions you can also call yourself:
import { buildPrivateSwapFee, quoteToCalls, submitPrivateSwap, PRIVACY_POOL_ADDRESS } from '@avnu/avnu-sdk';

// 1. Pool fee from the paymaster
const fee = await buildPrivateSwapFee({
  poolAddress: PRIVACY_POOL_ADDRESS,
  feeMode: { poolFeeToken: STRK, tip: 'normal' },
  paymasterApiKey: PAYMASTER_API_KEY,
});
// { token, recipient, amount } — withdraw this inside the private transaction

// 2. Private swap calls (backend sets takerAddress = executor)
const { calls, executorAddress } = await quoteToCalls({
  quoteId: quote.quoteId,
  slippage: 0.05,
  private: true,
});

// 3. Prove with your backend (see above), then submit through the paymaster
const callAndProof = await prover.buildAndProve({ /* plan */ });
const { transactionHash } = await submitPrivateSwap({
  callAndProof,
  feeMode: { poolFeeToken: STRK, tip: 'normal' },
  paymasterApiKey: PAYMASTER_API_KEY,
});

Key parameters

The SDK’s private swap functions always run in the sponsored_private fee mode: the relayer pays gas, the user pays the pool fee from their private balance.
feeMode.poolFeeToken
string
required
Token used to pay the pool fee (e.g. STRK, ETH, USDC). The paymaster converts the base STRK amount to this token via its price oracle.
feeMode.tip
'slow' | 'normal' | 'fast'
default:"normal"
Relayer priority tip.
poolAddress
string
required
The privacy pool contract address. The SDK exports the pools whitelisted by the paymaster: PRIVACY_POOL_ADDRESS (mainnet) and SEPOLIA_PRIVACY_POOL_ADDRESS.
prover
PrivateSwapProver
required
Your proving backend: buildAndProve(plan) returns the proven { call, proof }. The SDK never handles private keys, notes, or proof generation.
chainId
string
Optional fail-fast check: when provided, it is compared to quote.chainId before any paymaster or proving round-trip.
paymasterApiKey
string
required
Portal API key, the same one used for gasfree — the paymaster requires it for the sponsored modes. Server-side only: do not ship it in client code. Browser dapps should split the flow: buildPrivateSwapFee and submitPrivateSwap behind server endpoints, proving (prover) client-side with the user’s wallet.

Privacy Overview

Privacy pool, fees, and transaction types

Get Quotes

Fetch solver-optimized swap quotes