Skip to content

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.

example.ts
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:

BatchCallInvoker.sol
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.

config.ts
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 the client 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).
example.ts
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.

example.ts
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.

example.ts
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).

config.ts
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())