You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The broker's /v1/accept/submit (crates/agentkeys-broker-server/src/handlers/accept.rs) submits the K11-signed accept UserOp by shelling out to cast send EntryPoint.handleOps(...). So the broker IS the bundler — it collapses three ERC-4337 roles (auth/policy, paymaster co-sign, and tx submission) into one service, and drags a foundry/cast runtime dependency into the broker's systemd unit. That already bit us: 502 {"error":"spawn cast: No such file or directory"} — the unit runs ProtectHome=true, so a ~/.foundry/bin cast is unreachable (worked around in 8b3715f with AGENTKEYS_CAST_BIN + copying cast to /usr/local/bin, but that's a band-aid over the coupling).
Canonical ERC-4337 keeps submission behind a dedicated bundler: the broker hands a signed UserOp to the bundler via eth_sendUserOperation, and the bundler batches + submits handleOps, owning the beneficiary EOA, nonce, gas, and resubmission. The broker keeps only the policy it should own — J1 auth + the VerifyingPaymaster co-sign (the Sybil gate).
Proposed architecture
Submit flow:
sequenceDiagram
autonumber
participant C as Client (browser, K11 passkey)
participant B as Broker (auth + paymaster co-sign)
participant U as Bundler (NEW service)
participant E as EntryPoint
participant P as VerifyingPaymaster
Note over C,B: build — sponsorship (unchanged)
C->>B: POST /v1/accept/build (J1_master)
B->>B: assemble executeBatch UserOp + co-sign paymaster getHash
B-->>C: userOpHash + paymasterAndData
Note over C: Touch ID over userOpHash (K11)
Note over C,U: submit — DECOUPLED
C->>B: POST /v1/accept/submit (signed UserOp)
B->>U: eth_sendUserOperation(userOp, EntryPoint)
U->>E: handleOps([userOp], beneficiary)
E->>P: validatePaymasterUserOp (verify broker co-sign)
E->>E: executeBatch([registerAgentDevice, setScope])
U-->>B: userOpHash / receipt
B-->>C: { tx_hash }
Loading
Roles (proposed):
flowchart LR
C[Client] -->|build + submit, J1| B[Broker: auth, cap-mint, paymaster co-sign]
B -->|EIP-191 co-sign| P[VerifyingPaymaster]
B -->|eth_sendUserOperation| U[Bundler: batch, nonce, gas, resubmit]
U -->|handleOps| E[EntryPoint]
E --> P
E --> A[P256Account: validateUserOp + executeBatch]
Loading
The bundler is swappable behind one eth_sendUserOperation RPC: self-hosted eth-infinitism bundler, a thin in-house Rust submitter, or a 3rd-party (Pimlico / Alchemy / Stackup). There's a scripts/erc4337-bundler.sh stub to build on.
Scope
Replace accept_submit's cast send handleOps with an eth_sendUserOperation relay to a configurable bundler URL (AGENTKEYS_BUNDLER_URL); drop the broker's cast/foundry dependency + the AGENTKEYS_CAST_BIN workaround.
Stand up / wire a bundler service (start with self-hosted eth-infinitism or the erc4337-bundler.sh stub) into setup-broker-host.sh (or its own host), incl. the beneficiary EOA + EntryPoint deposit funding.
Keep the broker's build-side paymaster co-sign unchanged (the J1 Sybil gate).
Poll the bundler for the receipt (eth_getUserOperationReceipt) and return tx_hash to the client.
Acceptance
accept_submit no longer spawns cast; the broker unit needs no foundry.
A web accept (one Touch ID) lands executeBatch([registerAgentDevice, setScope]) on-chain via the bundler, sponsored by the VerifyingPaymaster.
The bundler URL is configurable; swapping self-hosted ↔ 3rd-party needs no broker code change.
The diagram above reflects the shipped flow; runbook + arch.md updated.
Effort
~L. The relay + receipt-poll is small; standing up + funding a reliable bundler service (or integrating a 3rd-party) is the bulk. Heima must accept the bundler's handleOps gas model (legacy tx, no prevrandao — see CLAUDE.md "Heima EVM compatibility").
Context
The broker's
/v1/accept/submit(crates/agentkeys-broker-server/src/handlers/accept.rs) submits the K11-signed accept UserOp by shelling out tocast send EntryPoint.handleOps(...). So the broker IS the bundler — it collapses three ERC-4337 roles (auth/policy, paymaster co-sign, and tx submission) into one service, and drags afoundry/castruntime dependency into the broker's systemd unit. That already bit us:502 {"error":"spawn cast: No such file or directory"}— the unit runsProtectHome=true, so a~/.foundry/bincast is unreachable (worked around in8b3715fwithAGENTKEYS_CAST_BIN+ copying cast to/usr/local/bin, but that's a band-aid over the coupling).Canonical ERC-4337 keeps submission behind a dedicated bundler: the broker hands a signed UserOp to the bundler via
eth_sendUserOperation, and the bundler batches + submitshandleOps, owning the beneficiary EOA, nonce, gas, and resubmission. The broker keeps only the policy it should own — J1 auth + the VerifyingPaymaster co-sign (the Sybil gate).Proposed architecture
Submit flow:
sequenceDiagram autonumber participant C as Client (browser, K11 passkey) participant B as Broker (auth + paymaster co-sign) participant U as Bundler (NEW service) participant E as EntryPoint participant P as VerifyingPaymaster Note over C,B: build — sponsorship (unchanged) C->>B: POST /v1/accept/build (J1_master) B->>B: assemble executeBatch UserOp + co-sign paymaster getHash B-->>C: userOpHash + paymasterAndData Note over C: Touch ID over userOpHash (K11) Note over C,U: submit — DECOUPLED C->>B: POST /v1/accept/submit (signed UserOp) B->>U: eth_sendUserOperation(userOp, EntryPoint) U->>E: handleOps([userOp], beneficiary) E->>P: validatePaymasterUserOp (verify broker co-sign) E->>E: executeBatch([registerAgentDevice, setScope]) U-->>B: userOpHash / receipt B-->>C: { tx_hash }Roles (proposed):
The bundler is swappable behind one
eth_sendUserOperationRPC: self-hosted eth-infinitismbundler, a thin in-house Rust submitter, or a 3rd-party (Pimlico / Alchemy / Stackup). There's ascripts/erc4337-bundler.shstub to build on.Scope
accept_submit'scast send handleOpswith aneth_sendUserOperationrelay to a configurable bundler URL (AGENTKEYS_BUNDLER_URL); drop the broker'scast/foundry dependency + theAGENTKEYS_CAST_BINworkaround.erc4337-bundler.shstub) intosetup-broker-host.sh(or its own host), incl. the beneficiary EOA + EntryPoint deposit funding.eth_getUserOperationReceipt) and returntx_hashto the client.Acceptance
accept_submitno longer spawnscast; the broker unit needs no foundry.executeBatch([registerAgentDevice, setScope])on-chain via the bundler, sponsored by the VerifyingPaymaster.Effort
~L. The relay + receipt-poll is small; standing up + funding a reliable bundler service (or integrating a 3rd-party) is the bulk. Heima must accept the bundler's
handleOpsgas model (legacy tx, noprevrandao— see CLAUDE.md "Heima EVM compatibility").