Skip to main content

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:

  1. Layer 0 → 1recursive_2_leaf verifies pairs of Action proofs against the pinned ACTION_VK_HASH, recomputes Poseidon2(values) to bind each, and folds the two SHA-256 per-action digests via sha256_node. An odd Action is paired with itself.
  2. Layer ≥ 1recursive_2_inner verifies pairs of children (each a leaf or inner proof), asserts their pinned VK hashes match and key_hash ∈ {leaf, inner}, and folds. Odd nodes promote unchanged.
  3. Finalizefinalize wraps 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:

fee=max(minBundlerFee,  (baseGas+perActionGasn)×gasPrice×assetRate×margin)\text{fee} = \max\big(\text{minBundlerFee},\; (\text{baseGas} + \text{perActionGas}\cdot n)\times \text{gasPrice}\times \text{assetRate}\times \text{margin}\big)