Skip to content

Sending Transactions with EIP-7702

The guide below demonstrates how to send EIP-7702 Transactions to invoke Contract functions on an Externally Owned Account.

Overview

Here is an end-to-end overview of how to broadcast an EIP-7702 Transaction to send a batch of Calls. We will break it down into Steps below.

example.ts
import { parseEther } from 'viem'
import { client } from './config'
import { abi, contractAddress } from './contract'
 
const authorization = await client.signAuthorization({
  contractAddress,
})
 
const hash = await client.sendTransaction({
  authorizationList: [authorization],
  data: encodeFunctionData({
    abi,
    functionName: 'execute',
    args: [
      {
        data: '0x',
        to: '0xcb98643b8786950F0461f3B0edf99D88F274574D',
        value: parseEther('0.001'),
      },
      {
        data: '0x',
        to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', 
        value: parseEther('0.002'), 
      },  
    ],
  }),
  to: client.account.address,
})

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. Invoke Contract Function

We can now perform batch calls by sending a Transaction to the Account (account) with the Authorization (authorizationList).

example.ts
import { encodeFunctionData, parseEther } from 'viem'
import { client } from './config'
import { contractAddress } from './contract'
 
const authorization = await client.signAuthorization({
  contractAddress,
})
 
const hash = await client.sendTransaction({ 
  authorizationList: [authorization], 
  data: encodeFunctionData({ 
    abi, 
    functionName: 'execute', 
    args: [ 
      { 
        data: '0x', 
        to: '0xcb98643b8786950F0461f3B0edf99D88F274574D', 
        value: parseEther('0.001'), 
      }, 
      { 
        data: '0x', 
        to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', 
        value: parseEther('0.002'), 
      }, 
    ], 
  }), 
  to: client.account.address, 
}) 

5. 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())