Contract Writes with EIP-7702
The guide below demonstrates how to perform Contract Writes with EIP-7702 to invoke Contract functions on an Externally Owned Account.
Overview
Here is an end-to-end overview of how to perform a Contract Write to send a batch of Calls. We will break it down into Steps below.
import { getContract, parseEther } from 'viem'
import { client } from './config'
import { abi, contractAddress } from './contract'
const authorization = await client.signAuthorization({
contractAddress,
})
const batchCallInvoker = getContract({
abi,
address: client.account.address,
client,
})
const hash = await batchCallInvoker.write.execute([{
data: '0x',
to: '0xcb98643b8786950F0461f3B0edf99D88F274574D',
value: parseEther('0.001'),
}, {
data: '0x',
to: '0xd2135CfB216b74109775236E36d4b433F1DF507B',
value: parseEther('0.002'),
}], {
authorizationList: [authorization],
})
Steps
1. Set up Smart Contract
We will need to set up a Smart Contract to interact with. For the purposes of this guide, we will create and deploy a BatchCallInvoker.sol
contract, however, you can use any existing deployed contract.
Firstly, deploy a Contract to the Network with the following source:
pragma solidity ^0.8.20;
contract BatchCallInvoker {
struct Call {
bytes data;
address to;
uint256 value;
}
function execute(Call[] calldata calls) external payable {
for (uint256 i = 0; i < calls.length; i++) {
Call memory call = calls[i];
(bool success, ) = call.to.call{value: call.value}(call.data);
require(success, "call reverted");
}
}
}
2. Set up Client & Account
Next, we will need to set up a Client and Externally Owned Account to sign EIP-7702 Authorizations.
This code snippet uses the Extending Client guide.
import { createWalletClient, http } from 'viem'
import { mainnet } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'
import { eip7702Actions } from 'viem/experimental'
export const account = privateKeyToAccount('0x...')
export const client = createWalletClient({
account,
chain: mainnet,
transport: http(),
}).extend(eip7702Actions())
3. Authorize Contract Bytecode Injection
We will need to sign an Authorization to authorize the injection of the Contract's bytecode onto the Account.
In the example below, we are:
- using the
account
attached to theclient
to sign the Authorization – this will be the Account that the Contract's bytecode will be injected into. - creating a
contract.ts
file to store our deployed Contract artifacts (ABI and deployed Address).
import { client } from './config'
import { contractAddress } from './contract'
const authorization = await client.signAuthorization({
contractAddress,
})
4. Instantiate a Contract Instance
We will instantiate a Contract Instance for our BatchCallInvoker
contract.
import { getContract } from 'viem'
import { client } from './config'
import { contractAddress } from './contract'
const authorization = await client.signAuthorization({
contractAddress,
})
const batchCallInvoker = getContract({
abi,
address: client.account.address,
client,
})
5. Invoke Contract Function
Using our Contract Instance, we can now call the execute
function on it to perform batch calls.
import { getContract, parseEther } from 'viem'
import { client } from './config'
import { contractAddress } from './contract'
const authorization = await client.signAuthorization({
contractAddress,
})
const batchCallInvoker = getContract({
abi,
address: client.account.address,
client,
})
const hash = await batchCallInvoker.write.execute([{
data: '0x',
to: '0xcb98643b8786950F0461f3B0edf99D88F274574D',
value: parseEther('0.001'),
}, {
data: '0x',
to: '0xd2135CfB216b74109775236E36d4b433F1DF507B',
value: parseEther('0.002'),
}], {
authorizationList: [authorization],
})
6. Optional: Use an Invoker
We can also utilize an Invoker Account to execute a call on behalf of the authorizing Account. This is useful for cases where we want to "sponsor" the Transaction for the user (i.e. pay for their gas fees).
import { createWalletClient, http } from 'viem'
import { mainnet } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'
import { eip7702Actions } from 'viem/experimental'
export const account = privateKeyToAccount('0x...')
export const invoker = privateKeyToAccount('0x...')
export const client = createWalletClient({
account,
account: invoker,
chain: mainnet,
transport: http(),
}).extend(eip7702Actions())