One invoke per transaction.
So weave the plan inside it.
The STRK20 pool allows a single external call per private transaction. That makes a private DeFi action only as expressive as the one helper contract it reaches. Jalin takes a plan instead — any number of steps, any contract, any calldata — and runs it inside that single invoke.
Qualifying transactions on mainnet
- T10x060a25127edcca8a5f310fa711c1566dd39c688c8b30406d7482388d715ed311
- T20x023f7828c9be1a04c54ab0d2b95e48506a807906c76fd6646f2c6cedc77cdf70
- T30x07edfb70871f834236ff00f330ae20f214c0bfefa11f219a69d681d5627f9cab
- T40x694f9d76480b957a0badeb1ea72a637dba903bf33c833716c5ff956f96a96bc
Each succeeded, touched the pool, and ran through a contract of ours — check them against the sprint's own rules. Or watch one being built: the demo, 2:49 — linked rather than embedded, because a 20 MB autoplay is not what a first viewport is for.
A note is what the pool gives back: an encrypted record of a balance, readable only by the viewing key that owns it. Steps enter as ordinary calls and their outputs land as notes, so the plan is public and the position is not.
The constraint
Two protocol rules shape everything downstream. Together they mean a private swap needs a swap helper, lending needs another, and swap-then-lend needs a third. Every new interaction is a new Cairo contract — which is why almost everything built on STRK20 so far is a payment app. Payments are the only thing you can ship without writing Cairo.
One invoke per transaction. At most one external call per pool transaction.
Every token's balance must end at exactly zero. No value created or destroyed.
A plan, not a parameter list
fn privacy_invoke(
ref self: ContractState,
pool_address: ContractAddress,
steps: Array<Step>,
outputs: Array<Output>,
) -> Span<OpenNoteDeposit>Each step names a target, a selector, calldata, and the approvals it needs. Nothing is whitelisted: any Starknet contract is a valid target and calldata is free-form, which is what makes a bridge call, a DEX route and a lending deposit the same object.
Why free calldata is safe here
Jalin is non-custodial and holds nothing between transactions, so a hostile plan can only harm the notes of whoever wrote it. Safety comes from invariants enforced in Cairo, not from a gatekeeper's list. Each has a test.
- I1The pool is the only callercloses: Anyone calling the router directly
- I2No step may target the pool or the routercloses: Reentrancy into the sandwich
- I3Every approval is reset after its stepcloses: A stale allowance draining the next user
- I4Zero residue: touched tokens end at zerocloses: Sweeping another user's leftovers
- I5Each output clears its floorcloses: Slippage and hostile routes
- I6Steps and calldata are boundedcloses: Griefing the proof budget
129 SDK tests, 348 Playwright tests across six projects on three engines, and 44 Cairo tests, four of which fork mainnet and run the router against Endur's vault and AVNU's exchange. The reasoning behind these six rules is in docs/threat-model.md. None of it has been audited, and that word belongs here rather than only in the footer: these six rules are the whole of the safety argument, and nobody outside this project has checked them.
When not to use Jalin
Private swaps are already live on AVNU through its own anonymizer, and Ekubo is next. For a single swap, use those — they are purpose-built and they will price better than a generic router calling the same pool.
One invoke per transaction means a venue's anonymizer and this router compete for the same slot rather than composing. That every venue has to write its own, and that none of them compose, is the argument for Jalin existing — not a reason to use it for one step.
Deployed
That first number is the pool's headcount, and it is not what hides you. An observer of the public deposit leg sees the asset, the order of magnitude and roughly when — so two deposits only cover each other if they agree on all three. Grouped that way, 58% of the resulting cells hold exactly one person, and the biggest crowd anywhere in the pool is 14.2. The figure is a perplexity rather than a headcount, because a cell where one address carries most of the volume is not the crowd its headcount claims.
Jalin does not fix that — nothing a helper contract does can conjure other people. What it changes is how many transactions you need: three public legs at three separate moments is three chances to be the only one there, and a plan is one. The composer tells you the size of the cell you are about to land in, before you sign. Every finding on this page, with the query that produced it, is written up in docs/what-mainnet-says.md.