Execution Safety Design¶
The execution arm of the platform is the on-chain proof manager: the contract that executes batched call sequences over pool funds. It holds no standing balances and has no discretionary authority — it can only execute calls that are simultaneously:
- Pre-approved by policy — the call must hash to a leaf in the Merkle tree installed for the executing strategist.
- Correctly shaped — an on-chain decoder contract extracts the address parameters from the calldata; if the extracted bytes do not match the leaf's address constraints, the proof fails and execution reverts.
- Escrow-bracketed — SDK-built flows always wrap token movement in escrow withdraw/deposit pairs, so funds are only ever in the execution manager transiently.
The DeFi SDK is the layer that produces both the calldata and the policy leaves, and its design stacks several protections around that authority:
| Layer | Mechanism | What it prevents |
|---|---|---|
| Leaf hash | Every authorized call is a (decoder, target, valueNonZero, selector, addresses) tuple hashed into the Merkle tree |
Calling any unapproved function on any unapproved contract |
| Address packing | The SDK packs the leaf's address constraints byte-for-byte as the on-chain decoder extracts them | Substituting tokens, receivers, markets, or routers inside an approved function |
| Manager binding | Most leaves pack the executing proof manager's address into their constraints | Redirecting outputs to any address other than the authorized manager |
| Escrow flows | Fixed call ordering: withdraw → scoped approve → action → sweep approve → deposit | Funds being stranded in, or extracted from, the execution manager |
| Input validation | Address checksumming, slippage floors, selector allowlists, router pinning, fail-closed resolution | Malformed calldata, MEV extraction, tampered API responses |
| Trust boundary | The SDK never imports backend or database code; all runtime data arrives through an explicit context object | Backend state leaking into, or being blindly trusted by, policy generation |
The rest of this page elaborates these layers.
The policy and Merkle leaf system¶
The leaf hash invariant¶
Every leaf commits to exactly one authorized call shape:
leaf = keccak256(abi.encodePacked(
decoder, # deployed decoder-and-sanitizer contract address
target, # contract being called (e.g. Aave Pool, DEX router)
valueNonZero, # whether native value may be sent with the call
selector, # 4-byte function selector
addresses, # packed address constraints — the decoder's output
))
This hash must reproduce identically on-chain when the proof manager verifies a call, or the call reverts. The determinism and byte layout of the encoding are pinned by dedicated tests, and the same hash structure is enforced by the on-chain proof managers and decoders. Amounts are deliberately not part of the leaf: policies constrain where funds can go, not how much moves — amount safety comes from the escrow discipline and slippage validation described below.
Address packing follows one fixed convention per protocol family: standard protocols pack 20-byte-aligned EVM addresses; protocols with custom decoders (CCTP, for example) pack raw mixed-type bytes matching their decoder's extraction exactly.
The SDK ↔ backend contract¶
Two types form the entire interface between the SDK and the backend —
LeafContext (runtime context the backend assembles and passes in:
chain, deployed contract addresses, the decoder address, the proof manager
address, and bridge destinations where relevant) and LeafSpec (one policy
leaf the SDK produces and returns). Neither type carries anything
database-specific, and the SDK never imports backend code. This keeps the
trust boundary clean in both directions: policy generation cannot silently
depend on backend state, and the backend cannot inject anything into a leaf
except through the explicit, typed context.
One consequence of that boundary: when the SDK declares what a strategist may configure, token fields carry symbols only, never addresses — the backend resolves symbols to addresses through its own asset records, so the SDK stays entirely database-free.
The leaf lifecycle¶
Every action participates in a four-step lifecycle, from strategist intent to on-chain enforcement and back:
1 declare each action declares its configurable surface
(token symbols, markets, vaults) — this feeds the
Intent Builder UI where a strategist configures a
policy
│
▼
2 select the strategist's selection is captured; the backend
resolves symbols to concrete addresses
│
▼
3 expand the SDK expands the selection into concrete policy
leaves — exact target, selector, and packed address
constraints matching the on-chain decoder
│
▼
install the backend hashes the leaves into a Merkle tree;
the root is installed on the proof manager through
the timelock-backed, hub-side governance channel
│
▼
enforce at execution time, every call in a batch must carry
a valid Merkle proof — the on-chain decoder extracts
the address parameters and the leaf is recomputed
and verified on-chain
│
▼
4 reverse leaves map back to the original selection, so an
installed tree can be reconstructed, reviewed, and
edited on the rebalance page
Steps 1–4 are SDK responsibilities; installation and enforcement are the backend's and the contracts' — see policy gating in the execution pipeline for the off-chain pre-flight and Arkonix Extensions for the on-chain half. The expansion step is fail-closed: if a required contract address is missing from the context, the action emits no leaves at all rather than a leaf with a guessed or defaulted address.
The escrow-flow discipline¶
Flows compose a single action's transaction into an ordered multi-call sequence executed atomically by the proof manager. Each emitted call carries a canonical function signature that must correspond to a policy leaf — including the escrow steps themselves — so the entire sequence, not just the DeFi action, is policy-verified.
The canonical five-call sequence¶
The core safety primitive is a fixed sequence of exactly five calls, in this order:
1 escrow withdraw BalanceSheet.withdraw — pull exactly the input
amount from pool escrow to the proof manager;
funds exist in the manager only from this point
2 scoped approve ERC-20 approve of the input token, scoped to the
action's target contract and the exact amount
3 the action the DeFi call itself — swap, supply, bridge, ...
4 sweep approve approve the escrow contract to pull the output
token
5 escrow deposit BalanceSheet.deposit — sweep the FULL output
balance (including any positive slippage) back
into pool escrow; nothing is ever stranded in
the proof manager
This is why a compromised execution credential has a bounded blast radius: the manager holds funds only between steps 1 and 5 of an atomically executed, policy-verified batch; approvals are scoped to one target and one amount; and the terminal sweep leaves no residual balance or approval an attacker could later exploit. The exact call count and ordering are pinned by tests (see verified properties).
Flow shapes¶
A unified flow builder derives one of four shapes from an action's declared escrow behavior:
| Withdraw leg | Deposit leg | Shape | Used for |
|---|---|---|---|
| yes | yes | full five-call sequence | swaps, PT trades |
| yes | no | withdraw → approve → action | bridge-out and other exits where funds leave the chain or escrow |
| no | yes | action → approve → deposit | bridge-in (claimed funds swept straight into escrow) |
| no | no | the action alone | direct actions with no token movement (e.g. Hyperliquid orders via precompile) |
Two safety nuances are worth noting:
- Where the expected output is known, the deposit step uses the real minimum-output amount rather than a sweep-everything sentinel, because the escrow deposit performs an exact transfer.
- Flows may insert bracketing calls around the action for protocols that need a standing authorization — the Morpho pattern is grant → act → revoke within a single transaction, so an operator authorization exists only for the duration of that transaction. Every inserted call must independently pass the Merkle policy: defense in depth, not a bypass.
Bridging follows the same discipline split across chains: the outbound leg is withdraw → approve → burn (no deposit-back; the funds leave the source chain), and the inbound leg is mint → approve → deposit, sweeping the bridged funds directly into escrow on arrival. Destination claiming is permissionless; an asynchronous attestation service watches the burn, polls the issuer's attestation API, and fails with a timeout rather than waiting forever.
Untrusted quote services¶
Aggregator integrations (Odos, OKX DEX, 1inch, Pendle, Enso) pair the on-chain action with an off-chain quoting service. API responses are treated as untrusted input and pass a validation gate before anything is signed:
strategist amount ──▶ quote service ──▶ aggregator API
│
quote (route, minimum out, price impact)
▼
validation gate: router allowlist, selector
whitelist, slippage floor, checksum pass
▼
transaction request → flow builder → proof manager
Concretely: Enso rejects any returned bundle whose selector is not on an accepted-selector whitelist and exact-matches returned router and factory addresses against pinned canonical values (non-canonical targets are accepted only where a Merkle leaf independently pins them); Pendle verifies the API-returned router against the canonical router address; Odos re-derives the minimum output locally and pads slippage so the aggregator's executor cannot pocket positive slippage; OKX enforces a hard price-impact cap. And even if a compromised aggregator API defeated all of these, it still could not push the executor outside its Merkle policy — the on-chain leaf check is the final gate.
Safety pattern catalogue¶
Ten patterns recur across every protocol module:
- Mandatory address checksumming. Every address input is EIP-55 validated before it enters calldata or leaf packing.
- Pinned selectors with a self-check. Action metadata stores literal 4-byte selectors, and calldata encoders recompute the selector from the canonical signature at build time — signature/selector drift fails fast. Enso additionally rejects API calldata with unrecognized selectors.
- Slippage validation against MEV. PancakeSwap V3 actions hard-reject a zero or negative minimum output (an explicit anti-sandwich guard); Odos pads slippage against executor capture; OKX caps price impact; Pendle's guard is enforced inside the router calldata itself.
- Leaf packing mirrors the on-chain decoder byte-for-byte. The core invariant: the leaf hash must reproduce on-chain or execution reverts.
- Manager binding scopes leaves. Most leaves pack the proof manager's address into their constraints, so authorization is scoped to a single manager and outputs cannot be redirected.
- Fail-closed on unresolved context. Leaf expansion emits nothing when a required contract is absent; constructors raise descriptive errors when required addresses (vault, market, pool, chain) are unresolved — never a silently malformed call.
- Router-address allowlisting. API-returned targets are verified against pinned canonical routers; exceptions exist only where a Merkle leaf independently pins the target.
- The SDK is database-agnostic. All runtime resolution flows through the explicit leaf context; the SDK declares which deployment-manifest keys the backend must resolve and never reaches into backend state.
- Approval hygiene. Token approvals are emitted only when needed and tightly scoped (asset, spender, amount); native tokens skip approvals; escrow flows scope the action approval to the exact router and amount; standing authorizations are granted and revoked within one transaction.
- No key custody, no gas guessing. The SDK never handles private keys and never estimates gas itself — gas is estimated at execution time by the platform that signs, and signing lives in the KMS/HSM layer.
Protocol modules add their own guards on top: Aave E-Mode rejects out-of-range category values, Morpho refuses to act without an explicitly resolved market and packs the full market tuple (loan, collateral, oracle, interest-rate model, manager) into its leaves, PancakeSwap actions never attach native value, and Hyperliquid actions validate coins and order parameters against known asset maps.
Continuously verified properties¶
The SDK ships a test suite of roughly 450 tests covering every protocol module, all flow builders, all type models, and both base-class contracts. It runs on every change, alongside lint and strict static type checking — so the type contracts described on these pages are machine-enforced, not aspirational. The safety-critical properties pinned by dedicated tests:
| Property |
|---|
| The escrow flow is exactly five calls, in the fixed canonical order |
| Emitted calldata begins with the correct function selector |
| The leaf hash is deterministic and byte-exact, including the address packing |
| Policy inputs distinguish FREE (unconstrained) from fixed-set constraints correctly |
| New protocol modules auto-register with the action registry |
| Registry policy, deployment-manifest, and configuration resolution behave as specified |
| The Morpho grant → act → revoke authorization bracket is produced correctly |
| Aggregator swaps survive escrow wrapping, including native-token paths |
Worked example: Aave V3 supply¶
The Aave V3 module shows the complete pattern every protocol follows. Its actions cover supply, withdraw, borrow, repay, collateral toggles, and E-Mode — each mapping 1:1 to one Pool function, with the Pool address resolved per chain from the deployment manifest.
Calldata derivation. Constructing a supply action checksums every address and coerces numeric inputs; encoding recomputes the 4-byte selector from the canonical Solidity signature and ABI-encodes the parameters behind it. Supply and repay declare a token approval scoped to the Pool and the exact amount; withdraw and borrow do not. The receiving address defaults to the proof manager, so interest-bearing tokens accrue to — and withdrawals return to — the manager the policy authorizes, inside an escrow-bracketed flow.
Leaf derivation. The supply action declares its configurable surface as a list of supply-token symbols (borrow declares borrow-token symbols). Expansion resolves the Pool address from the context — returning no leaves at all if it is missing (fail closed) — and emits one leaf per selected token, whose packed constraints bind both the asset and the proof manager, exactly matching what the on-chain decoder extracts from supply calldata. Actions with different shapes pack accordingly: the collateral-toggle leaf binds only the asset, and the E-Mode leaf carries no address constraints at all (its calldata has no address parameters). The reverse mapping slices token addresses back out of the leaves so an installed policy can be reconstructed for review.
The result: a strategist who selected USDC and WETH for supply gets a policy under which the execution arm can supply exactly those two assets to exactly that Pool with proceeds bound to exactly that manager — and nothing else.
Related pages¶
- DeFi SDK overview — what the SDK is and the protocol inventory.
- Execution & Strategy Layer — the policy-gated pipeline (pre-flight verification, simulation, signing) that consumes these flows.
- Arkonix Extensions — the proof managers and decoder contracts that enforce these policies on-chain.
- Custody & Key Management — where signing authority actually lives.