Skip to content

Sending User Operations

The guide below demonstrates how to send User Operations with a Smart Account.

Overview

Here is an end-to-end overview of how to broadcast a User Operation with a Smart Account. We will break it down into Steps below.

example.ts
import { parseEther } from 'viem'
import { bundlerClient } from './config.js'
 
const hash = await bundlerClient.sendUserOperation({ 
  account, 
  calls: [{ 
    to: '0xcb98643b8786950F0461f3B0edf99D88F274574D', 
    value: parseEther('0.001') 
  }] 
}) 
 
const receipt = await bundlerClient.waitForUserOperationReceipt({ hash }) 

Steps

1. Set up a Client

A Smart Account needs access to the Network to query for information about its state (e.g. nonce, address, etc). Let's set up a Client that we can use for the Smart Account:

import { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'
 
const client = createPublicClient({
  chain: mainnet,
  transport: http(),
})

See createPublicClient Docs

2. Set up a Bundler Client

Next, we will need to set up a Bundler Client. A Bundler is required to submit User Operations to the Blockchain for the Smart Account.

import { createPublicClient, http } from 'viem'
import { createBundlerClient } from 'viem/account-abstraction' // [!code ++]
import { mainnet } from 'viem/chains'
 
const client = createPublicClient({
  chain: mainnet,
  transport: http(),
})
 
const bundlerClient = createBundlerClient({ // [!code ++]
  client, // [!code ++]
  transport: http('https://public.pimlico.io/v2/1/rpc'), // [!code ++]
}) // [!code ++]

See createBundlerClient Docs

3. Set up an Owner

We will also need to set up an Owner for the Smart Account which will be used to sign User Operations (transactions) for the Smart Account.

import { createPublicClient, http } from 'viem'
import { createBundlerClient } from 'viem/account-abstraction'
import { mainnet } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts' // [!code ++]
 
const client = createPublicClient({
  chain: mainnet,
  transport: http(),
})
 
const bundlerClient = createBundlerClient({
  client,
  transport: http('https://public.pimlico.io/v2/1/rpc'),
})
 
const owner = privateKeyToAccount('0x...') // [!code ++]

See privateKeyToAccount Docs

4. Create a Smart Account

Next, we will instantiate a Smart Account. For this example, we will use toCoinbaseSmartAccount (Coinbase Smart Wallet).

import { createPublicClient, http } from 'viem'
import { // [!code ++]
  createBundlerClient, // [!code ++]
  toCoinbaseSmartAccount // [!code ++]
} from 'viem/account-abstraction' // [!code ++]
import { mainnet } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'
 
const client = createPublicClient({
  chain: mainnet,
  transport: http(),
})
 
const bundlerClient = createBundlerClient({
  client,
  transport: http('https://public.pimlico.io/v2/1/rpc'),
})
 
const owner = privateKeyToAccount('0x...')
 
const account = await toCoinbaseSmartAccount({ // [!code ++]
  client, // [!code ++]
  owners: [owner] // [!code ++]
}) // [!code ++]

See toCoinbaseSmartAccount Docs

5. Send User Operation

Next, we will send a User Operation to the Bundler. For the example below, we will send 0.001 ETH to a random address.

import { createPublicClient, http, parseEther } from 'viem'
import { 
  createBundlerClient, 
  toCoinbaseSmartAccount 
} from 'viem/account-abstraction'
import { mainnet } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts' 
 
const client = createPublicClient({
  chain: mainnet,
  transport: http(),
})
 
const bundlerClient = createBundlerClient({
  client,
  transport: http('https://public.pimlico.io/v2/1/rpc'),
})
 
const owner = privateKeyToAccount('0x...')
 
const account = await toCoinbaseSmartAccount({ 
  client, 
  owners: [owner]
}) 
 
const hash = await bundlerClient.sendUserOperation({ // [!code ++]
  account, // [!code ++]
  calls: [{ // [!code ++]
    to: '0xcb98643b8786950F0461f3B0edf99D88F274574D', // [!code ++]
    value: parseEther('0.001') // [!code ++]
  }] // [!code ++]
}) // [!code ++]
 
const receipt = await bundlerClient.waitForUserOperationReceipt({ hash }) // [!code ++]

See sendUserOperation Docs

6. Optional: Hoist the Account

If you do not wish to pass an account around to every Action that requires an account, you can also hoist the account onto a Wallet Client.

import { createPublicClient, http, parseEther } from 'viem'
import { createBundlerClient, toCoinbaseSmartAccount } from 'viem/account-abstraction'
import { mainnet } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts' 
 
const client = createPublicClient({
  chain: mainnet,
  transport: http(),
})
 
const owner = privateKeyToAccount('0x...')
 
const account = await toCoinbaseSmartAccount({ 
  client, 
  owners: [owner]
}) 
 
const bundlerClient = createBundlerClient({
  account, 
  client,
  transport: http('https://public.pimlico.io/v2/1/rpc'),
})
 
const hash = await bundlerClient.sendUserOperation({
  account, 
  calls: [{
    to: '0xcb98643b8786950F0461f3B0edf99D88F274574D',
    value: parseEther('0.001')
  }]
})

7. Optional: Sponsor User Operation

By using a Paymaster, we can add sponsorship of User Operation fees.

Viem exposes a paymaster property on both the Bundler Client ("on Client" tab) and User Operation Action ("on Action" tab) to add User Operation sponsorship capabilities.

The paymaster property accepts a Paymaster Client (among others), which is used to fetch the necessary data for User Operation sponsorship.

example.ts (on Client)
import { http } from 'viem'
import { 
  createBundlerClient, 
  createPaymasterClient,
} from 'viem/account-abstraction'
import { account, client } from './config.ts'
 
const paymasterClient = createPaymasterClient({ 
  transport: http('https://public.pimlico.io/v2/11155111/rpc'), 
}) 
 
const bundlerClient = createBundlerClient({
  account,
  client,
  paymaster: paymasterClient, 
  transport: http('https://public.pimlico.io/v2/1/rpc'),
})
 
const hash = await bundlerClient.sendUserOperation({
  calls: [{
    to: '0xcb98643b8786950F0461f3B0edf99D88F274574D',
    value: parseEther('0.001')
  }]
})