From ece954123b23e7131e9862e224450d3892056fe5 Mon Sep 17 00:00:00 2001 From: Albert Martinez <58224660+albert-mr@users.noreply.github.com> Date: Mon, 3 Nov 2025 15:57:18 +0700 Subject: [PATCH] fix: default to zero address for read operations when account not provided Previously, readContract and simulateWriteContract would pass undefined as the 'from' parameter when no account was provided, causing RPC errors. This fix defaults to the zero address (0x0000...0000) when no account is specified, following Ethereum standards (eth_call behavior). This allows read-only operations to work without requiring wallet connection. Changes: - src/contracts/actions.ts: Added ?? zeroAddress fallback for both readContract (L74) and simulateWriteContract (L123) - tests/client.test.ts: Added test coverage for zero address default Fixes the issue where users received "Details: 'from'" errors when attempting to read contract state without providing an account. --- src/contracts/actions.ts | 4 ++-- tests/client.test.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/contracts/actions.ts b/src/contracts/actions.ts index a8b7362..8940f70 100644 --- a/src/contracts/actions.ts +++ b/src/contracts/actions.ts @@ -71,7 +71,7 @@ export const contractActions = (client: GenLayerClient, publicCli const encodedData = [calldata.encode(calldata.makeCalldataObject(functionName, callArgs, kwargs)), leaderOnly]; const serializedData = serialize(encodedData); - const senderAddress = account?.address ?? client.account?.address; + const senderAddress = account?.address ?? client.account?.address ?? zeroAddress; const requestParams = { type: "read", @@ -120,7 +120,7 @@ export const contractActions = (client: GenLayerClient, publicCli const encodedData = [calldata.encode(calldata.makeCalldataObject(functionName, callArgs, kwargs)), leaderOnly]; const serializedData = serialize(encodedData); - const senderAddress = account?.address ?? client.account?.address; + const senderAddress = account?.address ?? client.account?.address ?? zeroAddress; const requestParams = { type: "write", diff --git a/tests/client.test.ts b/tests/client.test.ts index f75241e..9b242f7 100644 --- a/tests/client.test.ts +++ b/tests/client.test.ts @@ -5,6 +5,7 @@ import {Address} from "../src/types/accounts"; import {createAccount, generatePrivateKey} from "../src/accounts/account"; import {vi} from "vitest"; import {TransactionHashVariant} from "../src/types/transactions"; +import {zeroAddress} from "viem"; // Setup fetch mock const mockFetch = vi.fn(); @@ -168,4 +169,29 @@ describe("Client Overrides", () => { }, ]); }); + + it("should use zero address when no account is provided anywhere", async () => { + const client = createClient({ + chain: localnet, + // No account provided on client + }); + + const contractAddress = "0x1234567890123456789012345678901234567890"; + await client.readContract({ + // No account override either + address: contractAddress as Address, + functionName: "testFunction", + args: ["arg1", "arg2"], + }); + + expect(lastGenCallParams).toEqual([ + { + type: "read", + to: contractAddress, + from: zeroAddress, // Should default to zero address + data: expect.any(String), + transaction_hash_variant: TransactionHashVariant.LATEST_NONFINAL, + }, + ]); + }); });