Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Viem: use simulateContract to get values return from a smart contract write function

In the viem docs, it mentions that writeContract only returns the transaction hash and to use simulateContract to get the values returned from a writeContract call. https://viem.sh/docs/contract/writeContract.html#return-value

However, the only thing I get returned from the simulateContract call is the request that I pass into the writeContract function after it's verified, but I don't see anything resembling the return values in this request and there is nothing else returned.

So how do you get the values returned from a write function when using viem? Is it not possible? Also, won't the simulation results sometimes differ from the result that would actually be returned from a write based on what happens during that block?

like image 262
Kyle B. Avatar asked Jan 22 '26 22:01

Kyle B.


1 Answers

However, the only thing I get returned from the simulateContract call is the request that I pass into the writeContract function ...

You are right. You have to use use simulateContract instead. The page you linked states that:

If you would like to retrieve the return data of a write function, you can use the simulateContract action ...

See more about it at https://viem.sh/docs/contract/simulateContract

The following is an example custom writeContract method that works. Notice how result is gotten from simulateContract. And hash as you rightly stated comes from walletClient.writeContract.

Viem uses the publicClient instead to wait for transaction receipts (that's wait for the call to be confirmed [and possibly indexed]). This is necessary when you are chaining contract calls. Like requesting approval from ERC20 token before actually spending it.

The toastError function is custom-defined. You can implement your choice error handler.

 const writeContract = async (address, abi, functionName, args) => {
   try {
      const walletClient = createWalletClient({
        chain: sepolia,
        transport: custom((window as any).ethereum),
      });
      const [account] = await walletClient.getAddresses();
      const { result, request } = await publicClient.simulateContract({
        address,
        abi,
        functionName,
        args,
        account,
      });
      const hash = await walletClient.writeContract(request);
      await publicClient.waitForTransactionReceipt({ hash });
      return { hash, result };
    } catch (e: any) {
      if (!`${e}`.toLowerCase().includes('user rejected')) {
        toastError(
          `${e}`.toLowerCase().includes('failed to fetch')
            ? 'Network Error'
            : (e['shortMessage'] ?? e['details'] ?? e['message'] ?? `${e}`)
        );
      }
      return null;
    }
}

Extra

You asked mainly about simulateContract and I suppose the above can serve. But I also wanted to add that sometimes, the contract call could emit events. And the result you are looking for could also be in the event logs.

You get the logs from the transaction receipt and then you parse it for the event of interest and get the value you are looking for.

This can be useful when the result from simulateContract will certainly be different from the actual result of execution. Such difference can happen if some dynamic variables like block.timestamp are part of what determine the result from the contract call.

const receipt = await publicClient.waitForTransactionReceipt({ hash });

// receipt.logs contains raw data about the emitted events. 
// parse these logs with parseEventLogs imported from viem 
// for event of choice and use the args (event data) as you need. 

const events = parseEventLogs({ logs: receipt.logs, abi, eventName: ['YOUR-EVENT'] });
const result = events[0].args['ARG-NAME'];
like image 144
Obum Avatar answered Jan 24 '26 17:01

Obum



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!