@@ -111,6 +111,8 @@ pub const JsonRpcServer = struct {
111111 // Handle method
112112 if (std .mem .eql (u8 , request .method , "eth_sendRawTransaction" )) {
113113 return try self .handleSendRawTransaction (& request );
114+ } else if (std .mem .eql (u8 , request .method , "eth_sendRawTransactionConditional" )) {
115+ return try self .handleSendRawTransactionConditional (& request );
114116 } else if (std .mem .eql (u8 , request .method , "eth_getTransactionReceipt" )) {
115117 return try self .handleGetTransactionReceipt (& request );
116118 } else if (std .mem .eql (u8 , request .method , "eth_blockNumber" )) {
@@ -267,6 +269,136 @@ pub const JsonRpcServer = struct {
267269 }
268270 }
269271
272+ fn handleSendRawTransactionConditional (self : * JsonRpcServer , request : * const jsonrpc.JsonRpcRequest ) ! []u8 {
273+ self .metrics .incrementTransactionsReceived ();
274+
275+ // Parse params
276+ const params = request .params orelse {
277+ return try jsonrpc .JsonRpcResponse .errorResponse (self .allocator , request .id , jsonrpc .ErrorCode .InvalidParams , "Missing params" );
278+ };
279+
280+ const params_array = switch (params ) {
281+ .array = > | arr | arr ,
282+ else = > {
283+ return try jsonrpc .JsonRpcResponse .errorResponse (self .allocator , request .id , jsonrpc .ErrorCode .InvalidParams , "Invalid params - expected array" );
284+ },
285+ };
286+
287+ if (params_array .items .len < 2 ) {
288+ return try jsonrpc .JsonRpcResponse .errorResponse (self .allocator , request .id , jsonrpc .ErrorCode .InvalidParams , "Missing transaction data or options" );
289+ }
290+
291+ // Parse transaction hex
292+ const first_param = params_array .items [0 ];
293+ const tx_hex = switch (first_param ) {
294+ .string = > | s | s ,
295+ else = > {
296+ return try jsonrpc .JsonRpcResponse .errorResponse (self .allocator , request .id , jsonrpc .ErrorCode .InvalidParams , "Invalid transaction format" );
297+ },
298+ };
299+
300+ // Parse conditional options
301+ const second_param = params_array .items [1 ];
302+ const options_json = switch (second_param ) {
303+ .object = > | obj | std.json.Value { .object = obj },
304+ else = > {
305+ return try jsonrpc .JsonRpcResponse .errorResponse (self .allocator , request .id , jsonrpc .ErrorCode .InvalidParams , "Invalid options format - expected object" );
306+ },
307+ };
308+
309+ // Parse conditional options
310+ const conditional_options = core .conditional_tx .ConditionalOptions .fromJson (self .allocator , options_json ) catch {
311+ return try jsonrpc .JsonRpcResponse .errorResponse (self .allocator , request .id , jsonrpc .ErrorCode .InvalidParams , "Failed to parse conditional options" );
312+ };
313+
314+ // Decode hex string (remove 0x prefix if present)
315+ const hex_start : usize = if (std .mem .startsWith (u8 , tx_hex , "0x" )) 2 else 0 ;
316+ const hex_data = tx_hex [hex_start .. ];
317+
318+ var tx_bytes = std .ArrayList (u8 ).init (self .allocator );
319+ defer tx_bytes .deinit ();
320+
321+ var i : usize = 0 ;
322+ while (i < hex_data .len ) : (i += 2 ) {
323+ if (i + 1 >= hex_data .len ) break ;
324+ const byte = try std .fmt .parseInt (u8 , hex_data [i .. i + 2 ], 16 );
325+ try tx_bytes .append (byte );
326+ }
327+
328+ const tx_bytes_slice = try tx_bytes .toOwnedSlice ();
329+ defer self .allocator .free (tx_bytes_slice );
330+
331+ // Check transaction type (EIP-2718)
332+ if (tx_bytes_slice .len > 0 and tx_bytes_slice [0 ] == core .transaction .ExecuteTxType ) {
333+ // ExecuteTx transactions don't support conditional submission
334+ return try jsonrpc .JsonRpcResponse .errorResponse (self .allocator , request .id , jsonrpc .ErrorCode .InvalidParams , "ExecuteTx transactions do not support conditional submission" );
335+ }
336+
337+ // Parse legacy transaction
338+ const tx = core .transaction .Transaction .fromRaw (self .allocator , tx_bytes_slice ) catch {
339+ return try jsonrpc .JsonRpcResponse .errorResponse (self .allocator , request .id , jsonrpc .ErrorCode .InvalidParams , "Invalid transaction encoding" );
340+ };
341+ defer self .allocator .free (tx .data );
342+
343+ // Validate transaction first
344+ const result = self .ingress_handler .acceptTransaction (tx ) catch {
345+ self .metrics .incrementTransactionsRejected ();
346+ return try jsonrpc .JsonRpcResponse .errorResponse (self .allocator , request .id , jsonrpc .ErrorCode .ServerError , "Transaction processing failed" );
347+ };
348+
349+ if (result != .valid ) {
350+ self .metrics .incrementTransactionsRejected ();
351+ return try jsonrpc .JsonRpcResponse .errorResponse (self .allocator , request .id , jsonrpc .ErrorCode .ServerError , "Transaction validation failed" );
352+ }
353+
354+ // Clone transaction data since acceptTransaction may have consumed it
355+ const tx_data_clone = try self .allocator .dupe (u8 , tx .data );
356+ const tx_clone = core.transaction.Transaction {
357+ .nonce = tx .nonce ,
358+ .gas_price = tx .gas_price ,
359+ .gas_limit = tx .gas_limit ,
360+ .to = tx .to ,
361+ .value = tx .value ,
362+ .data = tx_data_clone ,
363+ .v = tx .v ,
364+ .r = tx .r ,
365+ .s = tx .s ,
366+ };
367+
368+ // Insert transaction with conditional options into mempool
369+ const inserted = self .ingress_handler .mempool .insertWithConditions (tx_clone , conditional_options ) catch {
370+ self .allocator .free (tx_data_clone );
371+ self .metrics .incrementTransactionsRejected ();
372+ return try jsonrpc .JsonRpcResponse .errorResponse (self .allocator , request .id , jsonrpc .ErrorCode .ServerError , "Failed to insert conditional transaction" );
373+ };
374+
375+ if (! inserted ) {
376+ self .allocator .free (tx_data_clone );
377+ self .metrics .incrementTransactionsRejected ();
378+ return try jsonrpc .JsonRpcResponse .errorResponse (self .allocator , request .id , jsonrpc .ErrorCode .ServerError , "Transaction already in mempool" );
379+ }
380+
381+ self .metrics .incrementTransactionsAccepted ();
382+
383+ // Return transaction hash
384+ const tx_hash = try tx .hash (self .allocator );
385+ const hash_bytes = core .types .hashToBytes (tx_hash );
386+ var hex_buf : [66 ]u8 = undefined ; // 0x + 64 hex chars
387+ hex_buf [0 ] = '0' ;
388+ hex_buf [1 ] = 'x' ;
389+ var j : usize = 0 ;
390+ while (j < 32 ) : (j += 1 ) {
391+ const hex_digits = "0123456789abcdef" ;
392+ hex_buf [2 + j * 2 ] = hex_digits [hash_bytes [j ] >> 4 ];
393+ hex_buf [2 + j * 2 + 1 ] = hex_digits [hash_bytes [j ] & 0xf ];
394+ }
395+ const hash_hex = try std .fmt .allocPrint (self .allocator , "{s}" , .{& hex_buf });
396+ defer self .allocator .free (hash_hex );
397+
398+ const result_value = std.json.Value { .string = hash_hex };
399+ return try jsonrpc .JsonRpcResponse .success (self .allocator , request .id , result_value );
400+ }
401+
270402 fn handleGetTransactionReceipt (self : * JsonRpcServer , request : * const jsonrpc.JsonRpcRequest ) ! []u8 {
271403 // In production, fetch receipt from state manager
272404 const result_value = std.json.Value { .null = {} };
@@ -343,8 +475,10 @@ pub const JsonRpcServer = struct {
343475 var witness_builder = core .witness_builder .WitnessBuilder .init (self .allocator );
344476 defer witness_builder .deinit ();
345477
346- // Create execution engine with witness builder
347- var exec_engine = sequencer .execution_engine ;
478+ // Create execution engine with witness builder for witness generation
479+ // Note: In op-node architecture, execution is delegated to L2 geth,
480+ // but we still need local execution for witness generation (debug endpoint)
481+ var exec_engine = @import ("../sequencer/execution.zig" ).ExecutionEngine .init (self .allocator , sequencer .state_manager );
348482 exec_engine .witness_builder = & witness_builder ;
349483
350484 // Execute transaction (this will track state access)
@@ -445,8 +579,14 @@ pub const JsonRpcServer = struct {
445579 var witness_builder = core .witness_builder .WitnessBuilder .init (self .allocator );
446580 defer witness_builder .deinit ();
447581
582+ // Create execution engine for witness generation
583+ // Note: In op-node architecture, execution is delegated to L2 geth,
584+ // but we still need local execution for witness generation (debug endpoint)
585+ var exec_engine = @import ("../sequencer/execution.zig" ).ExecutionEngine .init (self .allocator , sequencer .state_manager );
586+ exec_engine .witness_builder = & witness_builder ;
587+
448588 // Generate witness for the block
449- try witness_builder .generateBlockWitness (& block , & sequencer . execution_engine );
589+ try witness_builder .generateBlockWitness (& block , & exec_engine );
450590
451591 // Build witness
452592 _ = try witness_builder .buildWitness (sequencer .state_manager , null );
0 commit comments