Skip to content
LogoLogo

Tevm actions & JSON-RPC

An ethers provider can only do what a remote node exposes. TevmProvider also gives you direct access to the EVM behind it through provider.tevm, and through the tevm_* JSON-RPC methods on provider.send.

provider.tevm

provider.tevm is the Tevm node extended with tevmActions() and tevmSend():

ActionWhat it does
callExecute a message call with fine-grained control over state and tracing.
contractExecute a call encoded from an ABI and decode the result.
deployDeploy bytecode, optionally adding the tx to a block.
dealGive an account ETH or an ERC20 balance by writing storage.
mineMine one or more blocks.
getAccountRead balance, nonce, code, and storage root of an account.
setAccountOverwrite balance, nonce, and deployed bytecode of an account.
dumpStateSerialize the whole EVM state.
loadStateRestore a previously dumped state.
send / sendBulkRaw JSON-RPC against the node.

Reading and writing accounts

import {  } from '@tevm/ethers'
 
const  = await .createMemoryProvider({})
 
await .tevm.setAccount({
	: `0x${'69'.(20)}`,
	: 420n,
})
 
const  = await .tevm.getAccount({ : `0x${'69'.(20)}` })
 
.(.balance) // 420n
.(.isContract) // false

Executing a contract call without deploying

setAccount can install bytecode at any address, so you can execute a contract that was never deployed by a transaction.

import {  } from '@tevm/ethers'
import {  } from '@tevm/contract'
 
const  = await .createMemoryProvider({})
 
const  = ({
	: 'AddContract',
	: ['function add(uint256 a, uint256 b) public pure returns (uint256)'],
	: `0x${'42'.(20)}`,
	:
		'0x608060405234801561000f575f80fd5b5060043610610029575f3560e01c8063771602f71461002d575b5f80fd5b610047600480360381019061004291906100a9565b61005d565b60405161005491906100f6565b60405180910390f35b5f818361006a919061013c565b905092915050565b5f80fd5b5f819050919050565b61008881610076565b8114610092575f80fd5b50565b5f813590506100a38161007f565b92915050565b5f80604083850312156100bf576100be610072565b5b5f6100cc85828601610095565b92505060206100dd85828601610095565b9150509250929050565b6100f081610076565b82525050565b5f6020820190506101095f8301846100e7565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61014682610076565b915061015183610076565b92508282019050808211156101695761016861010f565b5b9291505056fea2646970667358221220a8f4b7187c62760aefc097c1827799c61a6df322acc9d7575862a525f9aa59a364736f6c63430008170033',
})
 
await .tevm.setAccount({
	: .,
	: .,
})
 
const  = await .tevm.contract(..add(390n, 30n))
 
.(.data) // 420n
.(.executionGasUsed) // gas used by the call

Mining

import {  } from '@tevm/ethers'
 
const  = await .createMemoryProvider({})
 
await .tevm.mine({ : 3 })
 
.(await .getBlockNumber()) // 3

tevm_* JSON-RPC methods

Everything above is also reachable over JSON-RPC, which matters when the provider is the only object you can pass around (for example, inside a library that accepts an EIP-1193 provider).

import {  } from '@tevm/ethers'
import {  } from '@tevm/utils'
 
const  = await .createMemoryProvider({})
 
await .send('tevm_setAccount', [
	{
		: `0x${'69'.(20)}`,
		: (1n),
		: (420n),
	},
])
 
const  = await .send('tevm_getAccount', [{ : `0x${'69'.(20)}` }])
 
.(.balance) // '0x1a4'
.(.nonce) // '0x1'

Available methods: tevm_call, tevm_getAccount, tevm_setAccount, tevm_mine, tevm_dumpState, tevm_loadState. Standard eth_* and anvil_*/debug_* methods supported by the node work too.

Persisting and restoring state

import {  } from '@tevm/ethers'
 
const  = await .createMemoryProvider({})
 
await .tevm.setAccount({ : `0x${'42'.(20)}`, : 100n })
 
const  = await .tevm.dumpState()
 
const  = await .createMemoryProvider({})
await .tevm.loadState({ : .state })
 
.(await .getBalance(`0x${'42'.(20)}`)) // 100n

Because the dump is plain JSON you can write it to disk and reuse it as a test fixture — see Forking a network.