feat(realtime): L2 UserOps for bridge-out withdrawals#922
feat(realtime): L2 UserOps for bridge-out withdrawals#922smartprogrammer93 wants to merge 10 commits intosurge-real-time-provingfrom
Conversation
Add the ability for users to submit UserOps that execute on L2, enabling bridge-out functionality. The catalyst now processes both L1→L2 deposits and L2→L1 withdrawals in the same block. Changes: - New `surge_sendL2UserOp` RPC method for submitting L2-targeted UserOps - L2 UserOp execution transactions are constructed and included in L2 blocks - After block execution, existing `find_l1_call()` detects the resulting bridge MessageSent events and relays them to L1 via processMessage - Block building handles mixed deposit + withdrawal transactions - Remove `disable_bridging` gate that prevented bridge handler startup Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tion Instead of separate RPC methods for L1 and L2 UserOps, the single surge_sendUserOp endpoint now auto-detects the target chain by parsing the EIP-712 signature in the executeBatch calldata. The UserOpsSubmitter's EIP-712 domain includes chainId, so the signature is only valid for one chain. We compute the EIP-712 digest for both L1 and L2 chain IDs, ecrecover each, and route accordingly: - L1 signature → L1→L2 deposit flow (simulate on L1, processMessage on L2) - L2 signature → L2 direct execution (UserOp tx in L2 block, L2→L1 relay via find_l1_call) Both types can coexist in the same block. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…etection The surge_sendUserOp RPC now accepts an optional chainId field in the UserOp params. If chainId matches L2, the UserOp is executed directly on L2. Otherwise defaults to L1 (backwards compatible). Removes the EIP-712 signature parsing logic which was unreliable (ecrecover always returns a non-zero address). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
processMessage and L2 UserOp transactions need more gas for operations that deploy contracts (e.g. CREATE2 smart wallet deployment via bridge relay). 1M gas was insufficient — the bridge's post-call gas check was failing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The anchor tx has a required gas limit enforced by the L2 engine. Revert anchor to 1M, keep processMessage and UserOp txs at 3M. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
L2 direct UserOps now get ProvingBlock and Executed/Rejected status updates, same as L1 UserOps. Added l2_user_op_ids to Proposal struct and included them in the async submitter's status tracking. Also adds cleanup: status entries are removed from sled after 60s to prevent unbounded disk growth. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@claude review |
|
Claude finished @smartprogrammer93's task in 2m 11s —— View job Code Review: feat(realtime): L2 UserOps for bridge-out withdrawalsTasks:
Overall the PR is well-structured and the feature is coherent. A few correctness and clarity issues are worth addressing before merge. Issues1. Misleading status sequence for L2Direct UserOps —
|
- Fix misleading Processing(zero_hash) status: remove premature status update for L2Direct UserOps; status remains Pending until async_submitter sets ProvingBlock - Propagate add_l2_user_op_id error instead of silently ignoring with let _, which could leave status entries orphaned forever - Remove redundant target_chain variable, simplify to direct chain_id comparison - Fix doc comment: routing is based on chainId field, not EIP-712 signature - Add SAFETY comment for Recovered::new_unchecked explaining why it's correct Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@claude review |
|
Claude finished @smartprogrammer93's task in 2m 12s —— View job Code Review: feat(realtime): L2 UserOps for bridge-out withdrawalsTasks:
The previous review issues have all been addressed (no misleading Issues1. L2 UserOp IDs not rejected on L1 multicall failure —
|
- Mark L2 UserOp IDs as Rejected on L1 multicall failure (were stuck at ProvingBlock) - Track L2 UserOp ID before inserting tx into block (prevents executed-but-Rejected state) - Reject UserOps with unknown chainId instead of silently treating as L1 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@claude review |
|
Claude finished @smartprogrammer93's task in 2m 50s —— View job Code Review: feat(realtime): L2 UserOps for bridge-out withdrawalsTasks:
All issues from the previous two rounds have been addressed. The code is in good shape. A few remaining items worth considering before merge: Issues1. Misleading
|
The "No pending UserOps" log was misleading when an L2Direct op was processed, since add_pending_user_ops_to_draft_block returns None for both "nothing queued" and "L2Direct handled". Updated to distinguish. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
Adds L2 UserOp support to the catalyst sequencer with automatic chain routing and full status tracking.
Changes
How it works
Related PRs
🤖 Generated with Claude Code