simulateContract
The simulateContract
function simulates/validates a contract interaction. This is useful for retrieving return data and revert reasons of contract write functions.
This function does not require gas to execute and does not change the state of the blockchain. It is almost identical to readContract
, but also supports contract write functions.
Internally, simulateContract
uses a Public Client to call the call
action with ABI-encoded data
.
Usage
Below is a very basic example of how to simulate a write function on a contract (with no arguments).
The mint
function accepts no arguments, and returns a token ID.
import { account, publicClient } from './config'
import { wagmiAbi } from './abi'
const { result } = await publicClient.simulateContract({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: wagmiAbi,
functionName: 'mint',
account,
})
Passing Arguments
If your function requires argument(s), you can pass them through with the args
attribute.
TypeScript types for args
will be inferred from the function name & ABI, to guard you from inserting the wrong values.
For example, the mint
function name below requires a tokenId argument, and it is typed as [number]
.
import { account, publicClient } from './config'
import { wagmiAbi } from './abi'
const { result } = await publicClient.simulateContract({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: wagmiAbi,
functionName: 'mint',
args: [69420],
account,
})
Pairing with writeContract
The simulateContract
function also pairs well with writeContract
.
In the example below, we are validating if the contract write will be successful via simulateContract
. If no errors are thrown, then we are all good. After that, we perform a contract write to execute the transaction.
import { account, walletClient, publicClient } from './config'
import { wagmiAbi } from './abi'
const { request } = await publicClient.simulateContract({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: wagmiAbi,
functionName: 'mint',
account,
})
const hash = await walletClient.writeContract(request)
Handling Custom Errors
In the example below, we are catching a custom error thrown by the simulateContract
. It is important to include the custom error item in the contract abi
.
You can access the custom error through the data
attribute of the error:
import { BaseError, ContractFunctionRevertedError } from 'viem';
import { account, walletClient, publicClient } from './config'
import { wagmiAbi } from './abi'
try {
await publicClient.simulateContract({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: wagmiAbi,
functionName: 'mint',
account,
})
} catch (err) {
if (err instanceof BaseError) {
const revertError = err.walk(err => err instanceof ContractFunctionRevertedError)
if (revertError instanceof ContractFunctionRevertedError) {
const errorName = revertError.data?.errorName ?? ''
// do something with `errorName`
}
}
}
State Overrides
When using simulateContract
, there sometimes needs to be an initial state change to make the transaction pass. A common use case would be an approval. For that, there are state overrides. In the example below, we are simulating sending a token on behalf of another user. To do this, we need to modify the state of the token contract to have maximum approve from the token owner.
import { account, publicClient } from './config'
import { abi, address } from './contract'
// Allowance slot: A 32 bytes hex string representing the allowance slot of the sender.
const allowanceSlot = '0x....'
// Max allowance: A 32 bytes hex string representing the maximum allowance (2^256 - 1)
const maxAllowance = numberToHex(maxUint256)
const { result } = await publicClient.simulateContract({
abi,
address,
account,
functionName: 'transferFrom',
args: [
'0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
account.address,
69420n
],
stateOverride: [
{
// modifying the state of the token contract
address,
stateDiff: [
{
slot: allowanceSlot,
value: maxAllowance,
},
],
},
],
})
console.log(result)
Output: true
Return Value
The simulation result and write request. Type is inferred.
Parameters
address
- Type:
Address
The contract address.
const { result } = await publicClient.simulateContract({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: wagmiAbi,
functionName: 'mint',
account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'
})
abi
- Type:
Abi
The contract's ABI.
const { result } = await publicClient.simulateContract({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: wagmiAbi,
functionName: 'mint',
account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'
})
functionName
- Type:
string
A function to extract from the ABI.
const { result } = await publicClient.simulateContract({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: wagmiAbi,
functionName: 'mint',
account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'
})
account
- Type:
Account | Address
The Account to simulate the contract method from.
Accepts a JSON-RPC Account or Local Account (Private Key, etc).
const { result } = await publicClient.simulateContract({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: wagmiAbi,
functionName: 'mint',
account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'
})
accessList (optional)
- Type:
AccessList
The access list.
const { result } = await publicClient.simulateContract({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: wagmiAbi,
functionName: 'mint',
args: [69420],
accessList: [{
address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
storageKeys: ['0x1'],
}],
account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'
})
authorizationList (optional)
- Type:
AuthorizationList
Signed EIP-7702 Authorization list.
const authorization = await walletClient.signAuthorization({
contractAddress: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
})
const { result } = await publicClient.simulateContract({
address: account.address,
abi: wagmiAbi,
functionName: 'mint',
args: [69420],
authorizationList: [authorization],
})
args (optional)
- Type: Inferred from ABI.
Arguments to pass to function call.
const { result } = await publicClient.simulateContract({
address: '0x1dfe7ca09e99d10835bf73044a23b73fc20623df',
abi: wagmiAbi,
functionName: 'balanceOf',
args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'],
account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'
})
blockNumber (optional)
- Type:
number
The block number to perform the read against.
const { result } = await publicClient.simulateContract({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: wagmiAbi,
functionName: 'mint',
account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'
blockNumber: 15121123n,
})
blockTag (optional)
- Type:
'latest' | 'earliest' | 'pending' | 'safe' | 'finalized'
- Default:
'latest'
The block tag to perform the read against.
const { result } = await publicClient.simulateContract({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: wagmiAbi,
functionName: 'mint',
account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'
blockTag: 'safe',
})
dataSuffix (optional)
- Type:
Hex
Data to append to the end of the calldata. Useful for adding a "domain" tag.
const { result } = await publicClient.simulateContract({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: wagmiAbi,
functionName: 'mint',
args: [69420],
dataSuffix: '0xdeadbeef'
})
gas (optional)
- Type:
bigint
The gas limit for the transaction.
await walletClient.writeContract({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: wagmiAbi,
functionName: 'mint',
args: [69420],
gas: 69420n,
})
gasPrice (optional)
- Type:
bigint
The price (in wei) to pay per gas. Only applies to Legacy Transactions.
const { result } = await publicClient.simulateContract({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: wagmiAbi,
functionName: 'mint',
args: [69420],
account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'
gasPrice: parseGwei('20'),
})
maxFeePerGas (optional)
- Type:
bigint
Total fee per gas (in wei), inclusive of maxPriorityFeePerGas
. Only applies to EIP-1559 Transactions
const { result } = await publicClient.simulateContract({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: wagmiAbi,
functionName: 'mint',
args: [69420],
account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'
maxFeePerGas: parseGwei('20'),
})
maxPriorityFeePerGas (optional)
- Type:
bigint
Max priority fee per gas (in wei). Only applies to EIP-1559 Transactions
const { result } = await publicClient.simulateContract({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: wagmiAbi,
functionName: 'mint',
args: [69420],
account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'
maxFeePerGas: parseGwei('20'),
maxPriorityFeePerGas: parseGwei('2'),
})
nonce (optional)
- Type:
number
Unique number identifying this transaction.
const { result } = await publicClient.simulateContract({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: wagmiAbi,
functionName: 'mint',
args: [69420],
account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'
nonce: 69
})
stateOverride (optional)
- Type:
StateOverride
The state override set is an optional address-to-state mapping, where each entry specifies some state to be ephemerally overridden prior to executing the call.
Note: By using state overrides, you simulate the contract based on a fake state. Using this is useful for testing purposes, but making a transaction based on the returned
request
object might fail regardless of the simulation result.
const data = await publicClient.simulateContract({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: wagmiAbi,
functionName: 'mint',
account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'
stateOverride: [
{
address: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC',
balance: parseEther('1'),
stateDiff: [
{
slot: '0x3ea2f1d0abf3fc66cf29eebb70cbd4e7fe762ef8a09bcc06c8edf641230afec0',
value: '0x00000000000000000000000000000000000000000000000000000000000001a4',
},
],
}
],
})
value (optional)
- Type:
number
Value in wei sent with this transaction.
const { result } = await publicClient.simulateContract({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: wagmiAbi,
functionName: 'mint',
args: [69420],
account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'
value: parseEther('1')
})
Live Example
Check out the usage of simulateContract
in the live Writing to Contracts Example below.