Private DeFi End to End
The anonymizer contract pages cover the Cairo side. This page covers the other half: how a dapp actually reaches that contract through the Starknet Wallet API, without ever touching a viewing key.
Requires starknet@^10.4.0 and a wallet supporting Wallet API 0.10.3.
The two actions
A private DeFi call is one STRK20 transaction carrying two actions:
- A
transferwith the literal amount"OPEN"— this creates an open note, the slot your helper's output gets credited into. Its amount is only known after the helper runs on-chain. - An
invokenaming your helper contract and its calldata.
The wallet resolves two placeholders inside the invoke calldata:
| Placeholder | Resolves to |
|---|---|
${openNoteIds[N]} |
The id of the Nth open note in this transaction |
${poolAddress} |
The privacy pool contract address |
That indirection is what lets you reference a note that does not exist yet at the time you build the calldata.
A private swap
import type { STRK20_ACTION } from "@starknet-io/types-js"
const actions: STRK20_ACTION[] = [
// 1. Open the note the swap output will be credited into.
{ type: "transfer", token: tokenOut, amount: "OPEN", recipient: userAddress },
// 2. Call the helper. ${openNoteIds[0]} is the note opened above.
{
type: "invoke",
contract: swapHelperAddress,
calldata: [tokenIn, tokenOut, amountIn, "${openNoteIds[0]}"],
},
]
const { transaction_hash } = await account.strk20InvokeTransaction(actions)
The pool withdraws amountIn to your helper, calls its privacy_invoke
entry point, and credits the returned OpenNoteDeposit into the open note —
atomically. Observers see pool → helper → AMM → helper. They do not see who
initiated it.
Match the calldata order to your helper's privacy_invoke signature; the pool
deserializes it directly into that function's parameters.
Dry-run first
strk20PrepareInvoke builds and proves the same actions without submitting,
which is the cheapest way to catch a calldata-shape mistake:
const prepared = await account.strk20PrepareInvoke(actions, true) // simulate
Show the shielded balance
Reading private balances is a wallet call too — no viewing key in your app:
const balances = await account.strk20Balances([tokenIn, tokenOut])
// [{ token, balance }, ...]
What stays public
The helper's on-chain action and the amounts it moves are visible; the link back to the user is not. Open-note amounts are plaintext by design — they are measured at execution time. Deposits and withdrawals remain public legs.
Read next
- Anonymizer Contract Anatomy
- Swap Helper
- AVNU Private Swaps - swaps without writing a helper at all