Bundler
The bundler is the off-chain coordinator. It accepts proven operations from wallets, validates them, aggregates their per-Action proofs into one recursive proof, packs multiple users into one batch, and calls Orchestrator.transact.
It is a convenience relayer. A user authorizes a specific bundler address inside their signed digest (bundlerAddress), enforced on-chain, so a third party cannot lift a signed op from the mempool. A bundler that is offline or hostile can only delay or refuse — it cannot forge a proof, move funds, or alter what a user signed. Setting bundlerAddress = 0 opts into permissionless submission.
Batching and submission
The aggregation pipeline
buildBinaryRecursionTree builds the proof that transact verifies:
- Layer 0 → 1 —
recursive_2_leafverifies pairs of Action proofs against the pinnedACTION_VK_HASH, recomputesPoseidon2(values)to bind each, and folds the two SHA-256 per-action digests viasha256_node. An odd Action is paired with itself. - Layer ≥ 1 —
recursive_2_innerverifies pairs of children (each a leaf or inner proof), asserts their pinned VK hashes match andkey_hash ∈ {leaf, inner}, and folds. Odd nodes promote unchanged. - Finalize —
finalizewraps the root into an EVM-format proof, emitting[publics_digest, action_vk_hash, leaf_vk_hash, inner_vk_hash].
How the bundler is paid
The bundler is reimbursed in the bundle's asset via bundlerFee, which is folded into netPublicValue, bound into the signed opDigest, and paid to msg.sender during settlement (validatePayloadConservationRange enforces Σ payloadAmount + bundlerFee == netPublicValue). The required fee is computed identically by /quote (wallet pre-flight) and ingress: