diff --git a/examples/02_query_blockchain.zig b/examples/02_query_blockchain.zig index dbf4c17..f4f8365 100644 --- a/examples/02_query_blockchain.zig +++ b/examples/02_query_blockchain.zig @@ -55,7 +55,7 @@ pub fn main() !void { defer allocator.free(addr_hex); std.debug.print("✅ Address: {s}\n", .{addr_hex}); - std.debug.print(" Balance: {} wei\n", .{balance}); + std.debug.print(" Balance: {d} wei\n", .{balance}); // Convert to ether const ether = try zigeth.utils.units.weiToEther(balance); @@ -68,7 +68,7 @@ pub fn main() !void { { const gas_price = try provider.getGasPrice(); - std.debug.print("✅ Gas price: {} wei\n", .{gas_price}); + std.debug.print("✅ Gas price: {d} wei\n", .{gas_price}); // Convert to gwei const gas_u64: u64 = @intCast(gas_price / 1_000_000_000); @@ -94,7 +94,7 @@ pub fn main() !void { std.debug.print(" Transactions: {d}\n", .{block.transactions.len}); if (block.header.base_fee_per_gas) |base_fee| { - std.debug.print(" Base fee: {} wei\n", .{base_fee}); + std.debug.print(" Base fee: {d} wei\n", .{base_fee}); } std.debug.print("\n", .{}); } diff --git a/examples/03_send_transaction.zig b/examples/03_send_transaction.zig index f7af033..b5a1a06 100644 --- a/examples/03_send_transaction.zig +++ b/examples/03_send_transaction.zig @@ -61,9 +61,9 @@ pub fn main() !void { std.debug.print("✅ Created legacy transaction\n", .{}); std.debug.print(" To: {any}\n", .{tx.to.?}); - std.debug.print(" Value: {} wei\n", .{tx.value}); + std.debug.print(" Value: {d} wei\n", .{tx.value}); std.debug.print(" Gas limit: {d}\n", .{tx.gas_limit}); - std.debug.print(" Gas price: {?} wei\n\n", .{tx.gas_price}); + std.debug.print(" Gas price: {d} wei\n\n", .{tx.gas_price.?}); } // Example 2: Create EIP-1559 transaction @@ -90,8 +90,8 @@ pub fn main() !void { std.debug.print("✅ Created EIP-1559 transaction\n", .{}); std.debug.print(" Type: EIP-1559\n", .{}); - std.debug.print(" Max fee: {?} wei\n", .{tx.max_fee_per_gas}); - std.debug.print(" Priority fee: {?} wei\n\n", .{tx.max_priority_fee_per_gas}); + std.debug.print(" Max fee: {d} wei\n", .{tx.max_fee_per_gas.?}); + std.debug.print(" Priority fee: {d} wei\n\n", .{tx.max_priority_fee_per_gas.?}); } // Example 3: Using Middleware for Automatic Gas & Nonce @@ -139,7 +139,7 @@ pub fn main() !void { std.debug.print("✅ Transaction configured with middleware\n", .{}); std.debug.print(" Nonce: {d} (auto-managed)\n", .{tx.nonce}); std.debug.print(" Gas limit: {d}\n", .{tx.gas_limit}); - std.debug.print(" Max fee: {?}\n", .{tx.max_fee_per_gas}); + std.debug.print(" Max fee: {any}\n", .{tx.max_fee_per_gas}); // Check if we have sufficient balance const has_balance = try gas_middleware.checkSufficientBalance( diff --git a/examples/05_transaction_receipts.zig b/examples/05_transaction_receipts.zig index 59f62ab..854676a 100644 --- a/examples/05_transaction_receipts.zig +++ b/examples/05_transaction_receipts.zig @@ -80,7 +80,7 @@ pub fn main() !void { std.debug.print(" Gas used: {d}\n", .{gas_used}); std.debug.print(" Gas price: 50 gwei\n", .{}); - std.debug.print(" Total fee: {} wei\n", .{fee}); + std.debug.print(" Total fee: {d} wei\n", .{fee}); // Convert to ETH (simple cast since we know it's a small value) const fee_u64: u64 = @intCast(fee); // Safe cast for this example diff --git a/examples/07_complete_workflow.zig b/examples/07_complete_workflow.zig index b2ae4b0..4a9455e 100644 --- a/examples/07_complete_workflow.zig +++ b/examples/07_complete_workflow.zig @@ -233,11 +233,11 @@ pub fn main() !void { std.debug.print(" if (receipt.isSuccess()) {{\n", .{}); std.debug.print(" std.debug.print(\"✅ Transaction successful!\\n\", .{{}});\n", .{}); std.debug.print(" std.debug.print(\" Block: {{}}\\n\", .{{receipt.block_number}});\n", .{}); - std.debug.print(" std.debug.print(\" Gas used: {{}}\\n\", .{{receipt.gas_used}});\n\n", .{}); + std.debug.print(" std.debug.print(\" Gas used: {{d}}\\n\", .{{receipt.gas_used}});\n\n", .{}); std.debug.print(" // Calculate fee\n", .{}); std.debug.print(" const fee = receipt.calculateFee();\n", .{}); - std.debug.print(" std.debug.print(\" Fee: {{}} wei\\n\", .{{fee}});\n\n", .{}); + std.debug.print(" std.debug.print(\" Fee: {{d}} wei\\n\", .{{fee}});\n\n", .{}); std.debug.print(" // Remove from pending\n", .{}); std.debug.print(" nonce.removePendingTx(tx.from, tx.nonce);\n", .{}); diff --git a/src/abi/types.zig b/src/abi/types.zig index 61ed1c0..7c14de8 100644 --- a/src/abi/types.zig +++ b/src/abi/types.zig @@ -93,42 +93,51 @@ pub const AbiType = union(enum) { } /// Convert AbiType to string representation for selector generation - pub fn toString(self: AbiType, allocator: std.mem.Allocator) ![]u8 { + /// For simple types, returns const string literals (no allocation) + /// For complex types (arrays), allocates and caller must free + pub fn toStringAlloc(self: AbiType, allocator: std.mem.Allocator) ![]const u8 { return switch (self) { - .uint8 => try allocator.dupe(u8, "uint8"), - .uint16 => try allocator.dupe(u8, "uint16"), - .uint32 => try allocator.dupe(u8, "uint32"), - .uint64 => try allocator.dupe(u8, "uint64"), - .uint128 => try allocator.dupe(u8, "uint128"), - .uint256 => try allocator.dupe(u8, "uint256"), - .int8 => try allocator.dupe(u8, "int8"), - .int16 => try allocator.dupe(u8, "int16"), - .int32 => try allocator.dupe(u8, "int32"), - .int64 => try allocator.dupe(u8, "int64"), - .int128 => try allocator.dupe(u8, "int128"), - .int256 => try allocator.dupe(u8, "int256"), - .address => try allocator.dupe(u8, "address"), - .bool_type => try allocator.dupe(u8, "bool"), - .bytes1 => try allocator.dupe(u8, "bytes1"), - .bytes2 => try allocator.dupe(u8, "bytes2"), - .bytes4 => try allocator.dupe(u8, "bytes4"), - .bytes8 => try allocator.dupe(u8, "bytes8"), - .bytes16 => try allocator.dupe(u8, "bytes16"), - .bytes32 => try allocator.dupe(u8, "bytes32"), - .string => try allocator.dupe(u8, "string"), - .bytes => try allocator.dupe(u8, "bytes"), + // Return const string literals - NO allocation needed! + .uint8 => "uint8", + .uint16 => "uint16", + .uint32 => "uint32", + .uint64 => "uint64", + .uint128 => "uint128", + .uint256 => "uint256", + .int8 => "int8", + .int16 => "int16", + .int32 => "int32", + .int64 => "int64", + .int128 => "int128", + .int256 => "int256", + .address => "address", + .bool_type => "bool", + .bytes1 => "bytes1", + .bytes2 => "bytes2", + .bytes4 => "bytes4", + .bytes8 => "bytes8", + .bytes16 => "bytes16", + .bytes32 => "bytes32", + .string => "string", + .bytes => "bytes", + // Complex types need allocation .array => |arr| { - const elem_str = try arr.element_type.toString(allocator); - defer allocator.free(elem_str); + const elem_str = try arr.element_type.toStringAlloc(allocator); + // Only free if it was actually allocated (for nested arrays) if (arr.length) |len| { return try std.fmt.allocPrint(allocator, "{s}[{d}]", .{ elem_str, len }); } else { return try std.fmt.allocPrint(allocator, "{s}[]", .{elem_str}); } }, - .tuple => try allocator.dupe(u8, "tuple"), + .tuple => "tuple", }; } + + /// Legacy name for backwards compatibility + pub fn toString(self: AbiType, allocator: std.mem.Allocator) ![]const u8 { + return self.toStringAlloc(allocator); + } }; /// ABI encoded value diff --git a/src/middleware/gas.zig b/src/middleware/gas.zig index b18eb5b..0466d0a 100644 --- a/src/middleware/gas.zig +++ b/src/middleware/gas.zig @@ -167,7 +167,8 @@ pub const GasMiddleware = struct { _ = base_fee; // Reserved for future use // Try to get max priority fee from provider - const suggested_priority = self.provider.eth.maxPriorityFeePerGas() catch { + var eth = self.provider.getEth(); + const suggested_priority = eth.maxPriorityFeePerGas() catch { // Fallback: 2.5 gwei for standard, adjust for strategy return @as(u256, 2_500_000_000); }; diff --git a/src/providers/http.zig b/src/providers/http.zig index e2f3471..27fe1f9 100644 --- a/src/providers/http.zig +++ b/src/providers/http.zig @@ -12,7 +12,7 @@ pub const HttpProvider = struct { } /// Free allocated memory - pub fn deinit(self: HttpProvider) void { + pub fn deinit(self: *HttpProvider) void { self.provider.deinit(); } @@ -22,39 +22,39 @@ pub const HttpProvider = struct { } /// Common provider methods (convenience wrappers) - pub fn getBlockNumber(self: HttpProvider) !u64 { + pub fn getBlockNumber(self: *HttpProvider) !u64 { return try self.provider.getBlockNumber(); } - pub fn getBalance(self: HttpProvider, address: @import("../primitives/address.zig").Address) !u256 { + pub fn getBalance(self: *HttpProvider, address: @import("../primitives/address.zig").Address) !u256 { return try self.provider.getBalance(address); } - pub fn getChainId(self: HttpProvider) !u64 { + pub fn getChainId(self: *HttpProvider) !u64 { return try self.provider.getChainId(); } - pub fn getTransactionCount(self: HttpProvider, address: @import("../primitives/address.zig").Address) !u64 { + pub fn getTransactionCount(self: *HttpProvider, address: @import("../primitives/address.zig").Address) !u64 { return try self.provider.getTransactionCount(address); } - pub fn getGasPrice(self: HttpProvider) !u256 { + pub fn getGasPrice(self: *HttpProvider) !u256 { return try self.provider.getGasPrice(); } - pub fn getLatestBlock(self: HttpProvider) !@import("../types/block.zig").Block { + pub fn getLatestBlock(self: *HttpProvider) !@import("../types/block.zig").Block { return try self.provider.getLatestBlock(); } - pub fn getTransaction(self: HttpProvider, hash: @import("../primitives/hash.zig").Hash) !@import("../types/transaction.zig").Transaction { + pub fn getTransaction(self: *HttpProvider, hash: @import("../primitives/hash.zig").Hash) !@import("../types/transaction.zig").Transaction { return try self.provider.getTransaction(hash); } - pub fn getTransactionReceipt(self: HttpProvider, hash: @import("../primitives/hash.zig").Hash) !@import("../types/receipt.zig").Receipt { + pub fn getTransactionReceipt(self: *HttpProvider, hash: @import("../primitives/hash.zig").Hash) !@import("../types/receipt.zig").Receipt { return try self.provider.getTransactionReceipt(hash); } - pub fn sendTransaction(self: HttpProvider, signed_tx: []const u8) !@import("../primitives/hash.zig").Hash { + pub fn sendTransaction(self: *HttpProvider, signed_tx: []const u8) !@import("../primitives/hash.zig").Hash { return try self.provider.sendTransaction(signed_tx); } @@ -66,7 +66,7 @@ pub const HttpProvider = struct { return try self.provider.waitForTransaction(tx_hash, timeout_ms, 1000); } - pub fn isContract(self: HttpProvider, address: @import("../primitives/address.zig").Address) !bool { + pub fn isContract(self: *HttpProvider, address: @import("../primitives/address.zig").Address) !bool { return try self.provider.isContract(address); } }; diff --git a/src/providers/provider.zig b/src/providers/provider.zig index bff6f02..e5418c9 100644 --- a/src/providers/provider.zig +++ b/src/providers/provider.zig @@ -14,31 +14,35 @@ const Receipt = @import("../types/receipt.zig").Receipt; /// Provider interface for Ethereum network access pub const Provider = struct { rpc_client: RpcClient, - eth: EthNamespace, - net: NetNamespace, - web3: Web3Namespace, - debug: DebugNamespace, allocator: std.mem.Allocator, /// Initialize a new provider pub fn init(allocator: std.mem.Allocator, endpoint: []const u8) !Provider { const rpc_client = try RpcClient.init(allocator, endpoint); - var provider = Provider{ + return Provider{ .rpc_client = rpc_client, - .eth = undefined, - .net = undefined, - .web3 = undefined, - .debug = undefined, .allocator = allocator, }; + } + + /// Get EthNamespace (creates on-demand to avoid dangling pointers) + pub fn getEth(self: *Provider) EthNamespace { + return EthNamespace.init(&self.rpc_client); + } - // Initialize namespaces with pointer to rpc_client - provider.eth = EthNamespace.init(&provider.rpc_client); - provider.net = NetNamespace.init(&provider.rpc_client); - provider.web3 = Web3Namespace.init(&provider.rpc_client); - provider.debug = DebugNamespace.init(&provider.rpc_client); + /// Get NetNamespace (creates on-demand) + pub fn getNet(self: *Provider) NetNamespace { + return NetNamespace.init(&self.rpc_client); + } + + /// Get Web3Namespace (creates on-demand) + pub fn getWeb3(self: *Provider) Web3Namespace { + return Web3Namespace.init(&self.rpc_client); + } - return provider; + /// Get DebugNamespace (creates on-demand) + pub fn getDebug(self: *Provider) DebugNamespace { + return DebugNamespace.init(&self.rpc_client); } /// Free allocated memory @@ -47,83 +51,97 @@ pub const Provider = struct { } /// Get the endpoint URL - pub fn getEndpoint(self: Provider) []const u8 { + pub fn getEndpoint(self: *Provider) []const u8 { return self.rpc_client.endpoint; } /// Get current block number - pub fn getBlockNumber(self: Provider) !u64 { - return try self.eth.blockNumber(); + pub fn getBlockNumber(self: *Provider) !u64 { + var eth = self.getEth(); + return try eth.blockNumber(); } /// Get account balance - pub fn getBalance(self: Provider, address: Address) !u256 { - return try self.eth.getBalance(address, .{ .tag = .latest }); + pub fn getBalance(self: *Provider, address: Address) !u256 { + var eth = self.getEth(); + return try eth.getBalance(address, .{ .tag = .latest }); } /// Get transaction count (nonce) - pub fn getTransactionCount(self: Provider, address: Address) !u64 { - return try self.eth.getTransactionCount(address, .{ .tag = .latest }); + pub fn getTransactionCount(self: *Provider, address: Address) !u64 { + var eth = self.getEth(); + return try eth.getTransactionCount(address, .{ .tag = .latest }); } /// Get contract code - pub fn getCode(self: Provider, address: Address) ![]u8 { - return try self.eth.getCode(address, .{ .tag = .latest }); + pub fn getCode(self: *Provider, address: Address) ![]u8 { + var eth = self.getEth(); + return try eth.getCode(address, .{ .tag = .latest }); } /// Get chain ID - pub fn getChainId(self: Provider) !u64 { - return try self.eth.chainId(); + pub fn getChainId(self: *Provider) !u64 { + var eth = self.getEth(); + return try eth.chainId(); } /// Get network ID - pub fn getNetworkId(self: Provider) !u64 { - return try self.net.version(); + pub fn getNetworkId(self: *Provider) !u64 { + var net = self.getNet(); + return try net.version(); } /// Get gas price - pub fn getGasPrice(self: Provider) !u256 { - return try self.eth.gasPrice(); + pub fn getGasPrice(self: *Provider) !u256 { + var eth = self.getEth(); + return try eth.gasPrice(); } /// Get latest block - pub fn getLatestBlock(self: Provider) !Block { - return try self.eth.getBlockByNumber(.{ .tag = .latest }, false); + pub fn getLatestBlock(self: *Provider) !Block { + var eth = self.getEth(); + return try eth.getBlockByNumber(.{ .tag = .latest }, false); } /// Get block by number - pub fn getBlock(self: Provider, block_number: u64, full_tx: bool) !Block { - return try self.eth.getBlockByNumber(.{ .number = block_number }, full_tx); + pub fn getBlock(self: *Provider, block_number: u64, full_tx: bool) !Block { + var eth = self.getEth(); + return try eth.getBlockByNumber(.{ .number = block_number }, full_tx); } /// Get block by hash - pub fn getBlockByHash(self: Provider, hash: Hash, full_tx: bool) !Block { - return try self.eth.getBlockByHash(hash, full_tx); + pub fn getBlockByHash(self: *Provider, hash: Hash, full_tx: bool) !Block { + var eth = self.getEth(); + return try eth.getBlockByHash(hash, full_tx); } /// Get transaction by hash - pub fn getTransaction(self: Provider, hash: Hash) !Transaction { - return try self.eth.getTransactionByHash(hash); + pub fn getTransaction(self: *Provider, hash: Hash) !Transaction { + var eth = self.getEth(); + return try eth.getTransactionByHash(hash); } /// Get transaction receipt - pub fn getTransactionReceipt(self: Provider, hash: Hash) !Receipt { - return try self.eth.getTransactionReceipt(hash); + pub fn getTransactionReceipt(self: *Provider, hash: Hash) !Receipt { + var eth = self.getEth(); + return try eth.getTransactionReceipt(hash); } /// Send signed transaction - pub fn sendTransaction(self: Provider, signed_tx: []const u8) !Hash { - return try self.eth.sendRawTransaction(signed_tx); + pub fn sendTransaction(self: *Provider, signed_tx: []const u8) !Hash { + var eth = self.getEth(); + return try eth.sendRawTransaction(signed_tx); } /// Estimate gas for transaction - pub fn estimateGas(self: Provider, params: @import("../rpc/types.zig").CallParams) !u64 { - return try self.eth.estimateGas(params); + pub fn estimateGas(self: *Provider, params: @import("../rpc/types.zig").CallParams) !u64 { + var eth = self.getEth(); + return try eth.estimateGas(params); } /// Wait for transaction to be mined pub fn waitForTransaction( - self: Provider, + self: *Provider, tx_hash: Hash, timeout_ms: u64, poll_interval_ms: u64, @@ -132,7 +150,8 @@ pub const Provider = struct { while (true) { // Try to get receipt - const receipt = self.eth.getTransactionReceipt(tx_hash) catch |err| { + var eth = self.getEth(); + const receipt = eth.getTransactionReceipt(tx_hash) catch |err| { if (err == error.ReceiptNotFound) { // Check timeout const elapsed = std.time.milliTimestamp() - start_time; @@ -152,7 +171,7 @@ pub const Provider = struct { } /// Check if address is a contract - pub fn isContract(self: Provider, address: Address) !bool { + pub fn isContract(self: *Provider, address: Address) !bool { const code = try self.getCode(address); defer self.allocator.free(code); return code.len > 0; diff --git a/src/rpc/client.zig b/src/rpc/client.zig index 51394a7..cac4e4e 100644 --- a/src/rpc/client.zig +++ b/src/rpc/client.zig @@ -95,7 +95,9 @@ pub const RpcClient = struct { /// Make a JSON-RPC call with no parameters pub fn callNoParams(self: *RpcClient, method: []const u8) !std.json.Value { - const params = std.json.Value{ .array = std.json.Array.init(self.allocator) }; + // Create empty array using fromOwnedSlice with empty slice + const empty_slice: []std.json.Value = &[_]std.json.Value{}; + const params = std.json.Value{ .array = std.json.Array.fromOwnedSlice(self.allocator, @constCast(empty_slice)) }; return try self.call(method, params); } }; diff --git a/src/rpc/eth.zig b/src/rpc/eth.zig index 72f0fb2..407d008 100644 --- a/src/rpc/eth.zig +++ b/src/rpc/eth.zig @@ -22,6 +22,7 @@ pub const EthNamespace = struct { /// eth_blockNumber - Returns the current block number pub fn blockNumber(self: EthNamespace) !u64 { const result = try self.client.callNoParams("eth_blockNumber"); + defer @import("./free_json.zig").freeJsonValue(self.client.allocator, result); // Parse hex string to u64 if (result != .string) { @@ -391,6 +392,7 @@ pub const EthNamespace = struct { /// eth_chainId - Returns the chain ID pub fn chainId(self: EthNamespace) !u64 { const result = try self.client.callNoParams("eth_chainId"); + defer @import("./free_json.zig").freeJsonValue(self.client.allocator, result); if (result != .string) { return error.InvalidResponse; diff --git a/src/rpc/free_json.zig b/src/rpc/free_json.zig new file mode 100644 index 0000000..e46dbe0 --- /dev/null +++ b/src/rpc/free_json.zig @@ -0,0 +1,25 @@ +const std = @import("std"); + +/// Free a copied JSON value (recursive cleanup) +/// Use this to free JSON values returned by copyJsonValue in client.zig +pub fn freeJsonValue(allocator: std.mem.Allocator, value: std.json.Value) void { + switch (value) { + .null, .bool, .integer, .float => {}, + .number_string => |ns| allocator.free(ns), + .string => |s| allocator.free(s), + .array => |arr| { + for (arr.items) |item| { + freeJsonValue(allocator, item); + } + @constCast(&arr).deinit(); + }, + .object => |obj| { + var it = obj.iterator(); + while (it.next()) |entry| { + allocator.free(entry.key_ptr.*); + freeJsonValue(allocator, entry.value_ptr.*); + } + @constCast(&obj).deinit(); + }, + } +} diff --git a/src/rpc/types.zig b/src/rpc/types.zig index 0ad1539..2468af8 100644 --- a/src/rpc/types.zig +++ b/src/rpc/types.zig @@ -1,7 +1,6 @@ const std = @import("std"); const Address = @import("../primitives/address.zig").Address; const Hash = @import("../primitives/hash.zig").Hash; -const U256 = @import("../primitives/uint.zig").U256; /// JSON-RPC request pub const JsonRpcRequest = struct { @@ -77,8 +76,8 @@ pub const CallParams = struct { from: ?Address = null, to: ?Address, gas: ?u64 = null, - gas_price: ?U256 = null, - value: ?U256 = null, + gas_price: ?u256 = null, + value: ?u256 = null, data: ?[]const u8 = null, }; @@ -87,15 +86,15 @@ pub const TransactionParams = struct { from: Address, to: ?Address, gas: ?u64 = null, - gas_price: ?U256 = null, - value: ?U256 = null, + gas_price: ?u256 = null, + value: ?u256 = null, data: ?[]const u8 = null, nonce: ?u64 = null, chain_id: ?u64 = null, // EIP-1559 fields - max_fee_per_gas: ?U256 = null, - max_priority_fee_per_gas: ?U256 = null, + max_fee_per_gas: ?u256 = null, + max_priority_fee_per_gas: ?u256 = null, }; /// Filter options for eth_getLogs @@ -118,9 +117,9 @@ pub const SubscriptionParams = union(enum) { /// Fee history result pub const FeeHistory = struct { oldest_block: u64, - base_fee_per_gas: []U256, + base_fee_per_gas: []u256, gas_used_ratio: []f64, - reward: ?[][]U256 = null, + reward: ?[][]u256 = null, allocator: std.mem.Allocator, pub fn deinit(self: FeeHistory) void { @@ -172,7 +171,7 @@ pub const PeerInfo = struct { pub const EthProtocol = struct { version: u64, - difficulty: U256, + difficulty: u256, head: Hash, };