Plan. apply ships it. Between those two words sits
the execution pipeline: a deterministic gate that builds calldata, runs a
simulation, enforces a policy allowlist, and hands the transaction to
Turnkey for signing. The user never sees a wallet popup because there
isn’t one to show. The CLI never holds a signing key.
This page traces what happens between the moment the user types
apply and the moment the transaction lands on chain.
The user-facing flow
What happens on apply
Concretely, applyPlan (in packages/runtime/src/tools/plan/index.ts)
runs five gates in order. Any gate failing aborts the apply before
Turnkey sees the request.
1. Approval state
approve it. This
is a state flag on the session; no signing happens at approval time.
2. Deterministic transaction build
buildExecutionTransaction (in
packages/execution/src/plans/transaction.ts) builds the calldata from
the plan’s tick math, not from anything the LLM produced:
(widthMultiplier, centerOffsetTicks). The execution
layer turns those into bytes.
3. On-chain simulation
simulatePlan runs an eth_call-style preflight against the live chain
state. Catches reverts before any signing happens. Estimates gas,
slippage, and the post-trade range.
4. Policy gate
checkExecutionPolicy (in packages/execution/src/plans/policy.ts)
enforces a hard allowlist before the transaction is allowed to leave
the runtime:
If any of these fail, the apply returns
POLICY_REJECTED with the
reasons. Nothing reaches Turnkey.
5. Turnkey sign + submit
Only if every gate passes does the runtime call:signAndSubmit (in packages/chain/src/wallet/turnkey/client.ts)
constructs a user-scoped Turnkey client using the session’s
ephemeral keypair, then calls Turnkey’s ethSendTransaction. Turnkey
verifies the request, signs with the user’s sub-organization wallet,
and submits to the chain. The runtime gets back transactionHash and
turnkeyActivityId (Turnkey’s audit trail id).
Sign-in and session lifecycle
The signing setup happens once per hour, not per transaction.- User types
login <email>. CLI calls TurnkeyinitOtp(directly for self-hosted; viaapps/proxyfor the npm-published binary). - Email arrives. User enters the OTP.
- CLI generates a fresh P256 ephemeral keypair locally
(
generateP256KeyPair), hands the public key to Turnkey viaotpLogin. Turnkey returns scoped session credentials (apiPublicKey,apiPrivateKey) bound to that ephemeral keypair. - First-time sign-in provisions a Turnkey sub-organization with its own wallet address. The wallet’s signing key lives inside Turnkey’s secure enclave; the parent organization has read-only access by Turnkey’s architecture.
- Session is persisted to
~/.zuno/session.jsonwith mode0600and a 1-hour TTL.
Security properties at a glance
What ships back to the user
apply returns a structured result the CLI renders as a confirmation
panel: plan id, position id, pair, fee tier, old and new range, gas
estimate, transaction hash, and the Turnkey activity id (so the user
can audit the request in their Turnkey dashboard).
Where the code lives
packages/runtime/src/tools/plan/index.ts-applyPlantoolpackages/execution/src/plans/apply.ts-prepareApplypipelinepackages/execution/src/plans/transaction.ts- calldata buildpackages/execution/src/plans/policy.ts- policy allowlistpackages/execution/src/plans/simulation.ts- on-chain preflightpackages/chain/src/wallet/turnkey/client.ts-signAndSubmitpackages/chain/src/wallet/turnkey/auth.ts- OTP, session keys