One invoke per transaction. So weave the plan inside it.
Jalin is a programmable execution router for the STRK20 shielded pool. It takes a plan — any number of steps, any contract, any calldata — and runs the whole thing inside the single privacy_invoke the protocol allows.
The constraint everything downstream is shaped by
Two protocol rules. At most one external call per pool transaction, and every token's balance must end at exactly zero.
Together they mean a private swap needs a swap helper, private lending needs another, and swap-then-lend needs a third. Every new interaction is a new Cairo contract to write, deploy and audit — 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.
A plan, not a parameter list
fn privacy_invoke(
ref self: TContractState,
pool_address: ContractAddress,
steps: Array<Step>,
outputs: Array<OpenNoteDeposit>,
)
struct Step {
target: ContractAddress, // any contract
selector: felt252, // any entrypoint
calldata: Array<felt252>, // any arguments
approvals: Array<Approval>,
}Each step names a target, a selector, calldata and the approvals it needs. Nothing is whitelisted. A bridge call, a DEX route and a lending deposit are the same object.
Integration depth: what it actually reaches
Two third-party mainnet protocols are reachable today with no adapter written for either, because they have an ABI and that is the only requirement:
- Endur — a real ERC-4626 deposit; xSTRK shares land straight in a shielded note
- AVNU — multi_route_swap with a live quote, beside an Endur stake, in one invoke
The router holds nothing between transactions and has no admin key. Every parameter it reads belongs to a governor that moves only by a vote which has cleared a timelock, and governance ballots themselves arrive through the pool — the weight of a vote is public and the voter is not.
Safety without a whitelist
Free calldata sounds dangerous. It is not, because Jalin is non-custodial and holds nothing between transactions, so a hostile plan can only harm the notes of whoever wrote it. Six invariants carry the rest, each with a test:
- The pool is the only callercloses: anyone calling the router directly
- No step may target the poolcloses: reentrancy into the pool
- Every approval is reset after its stepcloses: a stale allowance draining the router
- Zero residual: touched tokens end at zerocloses: sweeping another plan’s leftovers
- Each output claims its floorcloses: slippage and hostile routes
- Steps and calldata are boundedcloses: griefing the proof budget
Working on mainnet, and checkable
4 transactions, each an invoke through a contract of ours, each of which succeeded and touched the pool. The same rule the sprint applies is applied by /verify and by scripts/verify-transactions.mjs, from the SDK.
- T10x060a25127edcca8a5f310fa711c1566dd39c688c8b30406d7482388d715ed311
- T20x023f7828c9be1a04c54ab0d2b95e48506a807906c76fd6646f2c6cedc77cdf70
- T30x07edfb70871f834236ff00f330ae20f214c0bfefa11f219a69d681d5627f9cab
- T40x694f9d76480b957a0badeb1ea72a637dba903bf33c833716c5ff956f96a96bc
pool 0x040337…812a · router 0x008498…3a7e · governor 0x05bd98…6984
What the pool actually hides, said plainly
The headcount 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, most cells hold exactly one person.
Jalin cannot conjure other people. What it changes is how many public legs you need: three transactions at three separate moments is three chances to be the only one there, and a plan is one. The composer names the size of the cell you would land in before you sign.
What it does not do
- Unaudited. Six invariants and 44 Cairo tests are the whole of the safety argument, and nobody outside this project has checked them.
- The governor counts ballot weight it never measures. The router reads balance_of and trusts nothing; the governor takes the weight off calldata. Same repository, same week, and the discipline is only in one of them. No ballot has ever been cast and the escrow is empty, so nothing is at risk today — but it is a defect, it is written up in the threat model, and the fix is not deployed.
- It does not beat a venue's own anonymizer at that venue's own job. A single swap is better done on AVNU or Ekubo directly. Jalin is for the plan that crosses venues.
- Self-hosting the prover needs hardware we do not have. The image is public and we ran it: it exits 132 on a CPU without AVX-512. A hosted mainnet prover does answer — this deck said for a week that none was published, which was wrong and excused a shallower integration.
Where to check every claim on these slides
- github.com/PugarHuda/jalin — MIT, the contracts, the SDK, the app and the tests
- docs/threat-model.md — what each invariant closes, and what it does not
- docs/what-mainnet-says.md — every mainnet finding with the query that produced it
- jalin-sdk — the plan encoder, published
- the demo, 2:49 — a plan built and signed on mainnet