@@ -130,14 +130,35 @@ pub const Client = struct {
130130 const result = try self .callRpc ("eth_getTransactionCount" , std.json.Value { .array = params });
131131 defer self .allocator .free (result );
132132
133- // Parse result
133+ // Parse full JSON-RPC response
134134 const parsed = try std .json .parseFromSliceLeaky (
135- struct { result : []const u8 },
135+ struct {
136+ jsonrpc : []const u8 ,
137+ id : std.json.Value ,
138+ result : []const u8 ,
139+ @"error" : ? struct {
140+ code : i32 ,
141+ message : []const u8 ,
142+ data : ? std.json.Value = null ,
143+ } = null ,
144+ },
136145 self .allocator ,
137146 result ,
138- .{},
147+ .{ . ignore_unknown_fields = true },
139148 );
140149
150+ // Check for error response
151+ if (parsed .@"error" ) | err | {
152+ std .log .err ("[L1Client] eth_getTransactionCount error: code={d}, message={s}" , .{ err .code , err .message });
153+ return error .JsonRpcError ;
154+ }
155+
156+ // Validate jsonrpc version
157+ if (! std .mem .eql (u8 , parsed .jsonrpc , "2.0" )) {
158+ std .log .err ("[L1Client] Invalid jsonrpc version: {s}" , .{parsed .jsonrpc });
159+ return error .InvalidJsonRpcVersion ;
160+ }
161+
141162 // Convert hex string to u64
142163 const hex_str = parsed .result ;
143164 const hex_start : usize = if (std .mem .startsWith (u8 , hex_str , "0x" )) 2 else 0 ;
@@ -204,14 +225,35 @@ pub const Client = struct {
204225 const result = try self .callRpc ("eth_sendRawTransaction" , std.json.Value { .array = params });
205226 defer self .allocator .free (result );
206227
207- // Parse result - should be transaction hash
228+ // Parse full JSON-RPC response
208229 const parsed = try std .json .parseFromSliceLeaky (
209- struct { result : []const u8 },
230+ struct {
231+ jsonrpc : []const u8 ,
232+ id : std.json.Value ,
233+ result : []const u8 ,
234+ @"error" : ? struct {
235+ code : i32 ,
236+ message : []const u8 ,
237+ data : ? std.json.Value = null ,
238+ } = null ,
239+ },
210240 self .allocator ,
211241 result ,
212- .{},
242+ .{ . ignore_unknown_fields = true },
213243 );
214244
245+ // Check for error response
246+ if (parsed .@"error" ) | err | {
247+ std .log .err ("[L1Client] eth_sendRawTransaction error: code={d}, message={s}" , .{ err .code , err .message });
248+ return error .JsonRpcError ;
249+ }
250+
251+ // Validate jsonrpc version
252+ if (! std .mem .eql (u8 , parsed .jsonrpc , "2.0" )) {
253+ std .log .err ("[L1Client] Invalid jsonrpc version: {s}" , .{parsed .jsonrpc });
254+ return error .InvalidJsonRpcVersion ;
255+ }
256+
215257 // Convert hex string to Hash
216258 const hash_str = parsed .result ;
217259 const hash_bytes = try self .hexToBytes (hash_str );
@@ -256,14 +298,35 @@ pub const Client = struct {
256298 const result = try self .callRpc ("eth_sendRawTransactionConditional" , std.json.Value { .array = params });
257299 defer self .allocator .free (result );
258300
259- // Parse result - should be transaction hash
301+ // Parse full JSON-RPC response
260302 const parsed = try std .json .parseFromSliceLeaky (
261- struct { result : []const u8 },
303+ struct {
304+ jsonrpc : []const u8 ,
305+ id : std.json.Value ,
306+ result : []const u8 ,
307+ @"error" : ? struct {
308+ code : i32 ,
309+ message : []const u8 ,
310+ data : ? std.json.Value = null ,
311+ } = null ,
312+ },
262313 self .allocator ,
263314 result ,
264- .{},
315+ .{ . ignore_unknown_fields = true },
265316 );
266317
318+ // Check for error response
319+ if (parsed .@"error" ) | err | {
320+ std .log .err ("[L1Client] eth_sendRawTransactionConditional error: code={d}, message={s}" , .{ err .code , err .message });
321+ return error .JsonRpcError ;
322+ }
323+
324+ // Validate jsonrpc version
325+ if (! std .mem .eql (u8 , parsed .jsonrpc , "2.0" )) {
326+ std .log .err ("[L1Client] Invalid jsonrpc version: {s}" , .{parsed .jsonrpc });
327+ return error .InvalidJsonRpcVersion ;
328+ }
329+
267330 // Convert hex string to Hash
268331 const hash_str = parsed .result ;
269332 const hash_bytes = try self .hexToBytes (hash_str );
@@ -316,17 +379,38 @@ pub const Client = struct {
316379 return error .EmptyResponse ;
317380 }
318381
319- // Parse result - should be hex string
382+ // Parse full JSON-RPC response
320383 const parsed = std .json .parseFromSliceLeaky (
321- struct { result : []const u8 },
384+ struct {
385+ jsonrpc : []const u8 ,
386+ id : std.json.Value ,
387+ result : []const u8 ,
388+ @"error" : ? struct {
389+ code : i32 ,
390+ message : []const u8 ,
391+ data : ? std.json.Value = null ,
392+ } = null ,
393+ },
322394 self .allocator ,
323395 result ,
324- .{},
396+ .{ . ignore_unknown_fields = true },
325397 ) catch | err | {
326398 std .log .err ("[L1Client] Failed to parse eth_blockNumber response: {any}, response: {s}" , .{ err , result });
327399 return error .UnexpectedToken ;
328400 };
329401
402+ // Check for error response
403+ if (parsed .@"error" ) | err | {
404+ std .log .err ("[L1Client] eth_blockNumber error: code={d}, message={s}" , .{ err .code , err .message });
405+ return error .JsonRpcError ;
406+ }
407+
408+ // Validate jsonrpc version
409+ if (! std .mem .eql (u8 , parsed .jsonrpc , "2.0" )) {
410+ std .log .err ("[L1Client] Invalid jsonrpc version: {s}" , .{parsed .jsonrpc });
411+ return error .InvalidJsonRpcVersion ;
412+ }
413+
330414 // Convert hex string to u64
331415 const hex_str = parsed .result ;
332416 const hex_start : usize = if (std .mem .startsWith (u8 , hex_str , "0x" )) 2 else 0 ;
@@ -362,22 +446,41 @@ pub const Client = struct {
362446 const result = try self .callRpc ("eth_getBlockByNumber" , std.json.Value { .array = params });
363447 defer self .allocator .free (result );
364448
365- // Parse result
449+ // Parse full JSON-RPC response
366450 const parsed = try std .json .parseFromSliceLeaky (
367451 struct {
452+ jsonrpc : []const u8 ,
453+ id : std.json.Value ,
368454 result : struct {
369455 number : []const u8 ,
370456 hash : []const u8 ,
371457 parentHash : []const u8 ,
372458 timestamp : []const u8 ,
373459 transactions : []struct { to : ? []const u8 , input : []const u8 },
374460 },
461+ @"error" : ? struct {
462+ code : i32 ,
463+ message : []const u8 ,
464+ data : ? std.json.Value = null ,
465+ } = null ,
375466 },
376467 self .allocator ,
377468 result ,
378- .{},
469+ .{ . ignore_unknown_fields = true },
379470 );
380471
472+ // Check for error response
473+ if (parsed .@"error" ) | err | {
474+ std .log .err ("[L1Client] eth_getBlockByNumber error: code={d}, message={s}" , .{ err .code , err .message });
475+ return error .JsonRpcError ;
476+ }
477+
478+ // Validate jsonrpc version
479+ if (! std .mem .eql (u8 , parsed .jsonrpc , "2.0" )) {
480+ std .log .err ("[L1Client] Invalid jsonrpc version: {s}" , .{parsed .jsonrpc });
481+ return error .InvalidJsonRpcVersion ;
482+ }
483+
381484 const hex_start : usize = if (std .mem .startsWith (u8 , parsed .result .number , "0x" )) 2 else 0 ;
382485 const number = try std .fmt .parseInt (u64 , parsed .result .number [hex_start .. ], 16 );
383486
@@ -439,14 +542,35 @@ pub const Client = struct {
439542 const result = try self .callRpc ("eth_getTransactionReceipt" , std.json.Value { .array = params });
440543 defer self .allocator .free (result );
441544
442- // Parse result
545+ // Parse full JSON-RPC response
443546 const parsed = try std .json .parseFromSliceLeaky (
444- struct { result : ? struct { blockNumber : []const u8 } },
547+ struct {
548+ jsonrpc : []const u8 ,
549+ id : std.json.Value ,
550+ result : ? struct { blockNumber : []const u8 },
551+ @"error" : ? struct {
552+ code : i32 ,
553+ message : []const u8 ,
554+ data : ? std.json.Value = null ,
555+ } = null ,
556+ },
445557 self .allocator ,
446558 result ,
447- .{},
559+ .{ . ignore_unknown_fields = true },
448560 );
449561
562+ // Check for error response
563+ if (parsed .@"error" ) | err | {
564+ std .log .err ("[L1Client] eth_getTransactionReceipt error: code={d}, message={s}" , .{ err .code , err .message });
565+ return error .JsonRpcError ;
566+ }
567+
568+ // Validate jsonrpc version
569+ if (! std .mem .eql (u8 , parsed .jsonrpc , "2.0" )) {
570+ std .log .err ("[L1Client] Invalid jsonrpc version: {s}" , .{parsed .jsonrpc });
571+ return error .InvalidJsonRpcVersion ;
572+ }
573+
450574 if (parsed .result ) | rec | {
451575 const hex_start : usize = if (std .mem .startsWith (u8 , rec .blockNumber , "0x" )) 2 else 0 ;
452576 const block_num = try std .fmt .parseInt (u64 , rec .blockNumber [hex_start .. ], 16 );
0 commit comments