Smart accounts
Create and manage smart accounts for your users with just a few lines of code, using Embedded Wallets smart accounts. Smart accounts offer enhanced control and programmability, enabling powerful features that traditional wallets can't provide. Key features include:
- Gas abstraction: Cover transaction fees for users, or allow users to pay for their own transactions using ERC-20 tokens.
- Batch transactions: Perform multiple transactions in a single call.
- Automated transactions: Allow users to automate actions, like swapping ETH to USDT when ETH hits a specific price.
- Custom spending limits: Allow users to set tailored spending limits.
Embedded Wallets smart accounts are powered by the MetaMask Smart Accounts kit, so you can create and manage ERC-4337 compatible smart accounts using your preferred libraries like viem, ethers.js, and Wagmi.
For more about ERC-4337 and its components, read this blog post: An ultimate guide to Web3 Wallets.
This is a paid feature and the minimum pricing plan to use this SDK in a production environment is the Growth Plan. You can use this feature on Sapphire Devnet for free.
Configure smart accounts
Configure in the dashboard
Go to the Smart Accounts section in the Embedded Wallets dashboard, and Set up Smart accounts. Embedded Wallets supports MetaMask Smart Accounts as a smart account provider.

Configure wallets
Once smart accounts are enabled, you can configure the wallets:
- All supported wallets (default): Allows users to create smart accounts using both embedded and external wallets.
- Embedded wallets only: Restricts smart account creation to embedded wallets.
Configure bundler and paymaster
A bundler aggregates user operations and submits them onchain via the global entry point contract. To configure the bundler:
- In the Smart accounts section, go to the Bundler & Paymaster tab.
- Add the bundler URL for each supported chain.
- Add the paymaster URL for each supported chain.

You can only configure the sponsored paymaster in the dashboard. For an ERC-20 paymaster, see the following section.
(Optional) Configure with the SDK
For more flexibility, configure smart accounts using the SDK.
Basic configuration
To enable smart accounts in your dapp, add the accountAbstractionConfig to your Web3Auth options:
import { WEB3AUTH_NETWORK, Web3AuthOptions } from '@web3auth/modal'
const web3AuthOptions: Web3AuthOptions = {
clientId: 'YOUR_WEB3AUTH_CLIENT_ID', // Pass your Web3Auth Client ID, ideally using an environment variable
web3AuthNetwork: WEB3AUTH_NETWORK.SAPPHIRE_DEVNET,
accountAbstractionConfig: {
smartAccountType: 'metamask',
chains: [
{
chainId: '0x1',
bundlerConfig: {
url: 'YOUR_BUNDLER_URL',
},
},
],
},
}
Advanced configuration: override paymaster context
You can override the paymaster context defined in the dashboard for specific chains. This is useful when your paymaster requires custom settings or parameters.
import { WEB3AUTH_NETWORK, Web3AuthOptions } from '@web3auth/modal'
const web3AuthOptions: Web3AuthOptions = {
clientId: 'YOUR_WEB3AUTH_CLIENT_ID', // Pass your Web3Auth Client ID, ideally using an environment variable
web3AuthNetwork: WEB3AUTH_NETWORK.SAPPHIRE_DEVNET,
accountAbstractionConfig: {
smartAccountType: 'metamask',
chains: [
{
chainId: '0x1',
bundlerConfig: {
url: 'YOUR_BUNDLER_URL',
// This is just an example of how you can configure the paymaster context.
// Please refer to the documentation of the paymaster you are using
// to understand the required parameters.
paymasterContext: {
token: 'SUPPORTED_TOKEN_CONTRACT_ADDRESS',
sponsorshipPolicyId: 'sp_my_policy_id',
},
},
},
],
},
}
You must configure the paymaster context for each supported chain.
Use smart accounts
Configure the signer
Embedded Wallets smart accounts are compatible with popular signer SDKs, including Wagmi, ethers.js,
and viem. Choose your preferred package to configure the signer.
Retreive the provider to configure the signer from the web3auth instance.
Wagmi does not require any special configuration to use the signer with smart accounts.
Once you have set up your Embedded Wallets provider and connected your wallet, Wagmi's hooks (such as useSigner or
useAccount) will automatically use the smart account as the signer.
You can interact with smart accounts using Wagmi just like you would with an externally owned account (EOA) signer, with no additional setup.
- viem
- ethers.js
import { createWalletClient } from 'viem'
// Use your Web3Auth instance to retreive the provider.
const provider = web3auth.provider
const walletClient = createWalletClient({
transport: custom(provider),
})
import { ethers } from 'ethers'
// Use your Web3Auth instance to retreive the provider.
const provider = web3auth.provider
const ethersProvider = new ethers.providers.Web3Provider(provider)
const signer = await ethersProvider.getSigner()
Get the smart account address
Once the signers or Wagmi configuration is set up, you can use it to retrieve the user's smart account address.
- viem
- ethers.js
- Wagmi
// Use walletClient instance from previous step
const addresses = await walletClient.getAddresses()
const smartAccountAddress = addresses[0]
const eoaAddress = addresses[1]
// User signer from previous step
const smartAccountAddress = signer.getAddress()
import { useAccount } from 'wagmi'
const { address } = useAccount()
const smartAccountAddress = address
Send a transaction
You can use your preferred signer or Wagmi hooks to initiate onchain transactions, while
Embedded Wallets manages the creation and submission of the user operation.
You only need to provide the to, data, and value fields.
Any additional parameters will be ignored and automatically `overridden.
To ensure reliable execution, the bundler client sets maxFeePerGas and maxPriorityFeePerGas.
If custom values are required, use Viem's Bundler Client to
manually construct and send the user operation.
Since smart accounts are deployed smart contracts, the user's first transaction also triggers the onchain deployment of their wallet.
- viem
- ethers.js
- Wagmi
import { useSendTransaction } from 'wagmi'
const { data: hash, sendTransaction } = useSendTransaction()
// Convert 1 ether to WEI format
const value = web3.utils.toWei(1)
sendTransaction({ to: 'DESTINATION_ADDRESS', value, data: '0x' })
const {
data: receipt,
isLoading: isConfirming,
isSuccess: isConfirmed,
} = useWaitForTransactionReceipt({
hash,
})
// Convert 1 ether to WEI format
const amount = ethers.parseEther('1.0')
// Submits a user operation to the blockchain
const transaction = await signer.sendTransaction({
to: 'DESTINATION_ADDRESS',
value: amount,
// This will perform the transfer of ETH
data: '0x',
})
// Wait for the transaction to be mined
const receipt = await transaction.wait()
// Convert 1 ether to WEI format
const amount = parseEther('1')
// Submits a user operation to the blockchain
const hash = await walletClient.sendTransaction({
to: 'DESTINATION_ADDRESS',
value: amount,
// This will perform the transfer of ETH
data: '0x',
})
// Wait for the transaction to be mined
const receipt = await publicClient.waitForTransactionReceipt({ hash })
Sign a transaction
You can sign a transaction without immediately sending it. For the user's first transaction, the user operation calldata will automatically include the deployment data needed to create the smart account onchain.
Wagmi doesn't have hooks for signing transactions yet.
- viem
- ethers.js
// Convert 1 ether to WEI format
const amount = parseEther('1')
const signature = await signer.signTransaction({
to: 'DESTINATION_ADDRESS',
value: amount,
})
// Convert 1 ether to WEI format
const amount = parseEther('1')
const addresses = await walletClient.getAddresses()
const request = await walletClient.prepareTransactionRequest({
account: addresses[0],
to: 'DESTINATION_ADDRESS',
value: amount,
})
const signature = await walletClient.signTransaction(request as any)
Sign a message
Smart accounts support message signing following the EIP-1271 standard for signature verification, using the isValidSignature method defined in the smart contract wallet instead of the ecrecover function used by EOAs.
- viem
- ethers.js
- Wagmi
import { useSignMessage } from 'wagmi'
const { data: signature, signMessage } = useSignMessage()
const message = 'YOUR_MESSAGE'
signMessage({ message })
// Use signature as needed
const originalMessage = 'YOUR_MESSAGE'
// Sign the message
const signedMessage = await signer.signMessage(originalMessage)
const originalMessage = 'YOUR_MESSAGE'
const addresses = await walletClient.getAddresses()
const signedMessage = await walletClient.signMessage({
account: address[0],
message: originalMessage,
})
Send batch transactions
One of the key advantages of smart accounts is the ability to execute multiple operations in a single transaction. For example, instead of requiring separate transactions for token approval and then swapping, both operations can be combined into a single user operation.
To perform batch transactions, use the BundlerClient provided by the web3auth instance:
// Use your Web3Auth instance
const accountAbstractionProvider = web3auth.accountAbstractionProvider
// Use the same accountAbstractionProvider we created earlier.
const bundlerClient = accountAbstractionProvider.bundlerClient!
const smartAccount = accountAbstractionProvider.smartAccount!
// 0.00001 ETH in WEI format
const amount = 10000000000000n
const userOpHash = await bundlerClient.sendUserOperation({
account: smartAccount,
calls: [
{
to: 'DESTINATION_ADDRESS',
value: amount,
data: '0x',
},
{
to: 'DESTINATION_ADDRESS',
value: amount,
data: '0x',
},
],
})
// Retrieve user operation receipt
const receipt = await bundlerClient.waitForUserOperationReceipt({
hash: userOpHash,
})
const transactionHash = receipt.receipt.transactionHash
When calling sendUserOperation, it returns a user operation hash, not the transaction hash.
To get the final transaction details, use waitForUserOperationReceipt.
Send a transaction using ERC-20 paymaster
You can use ERC-20 tokens to pay for transaction fees instead of the native token (for example, ETH). This requires approving the token for use by the paymaster:
// Use your Web3Auth instance
const accountAbstractionProvider = web3auth.accountAbstractionProvider
// Use the same accountAbstractionProvider we created earlier.
const bundlerClient = accountAbstractionProvider.bundlerClient!
const smartAccount = accountAbstractionProvider.smartAccount!
// Pimlico's ERC-20 paymaster address
const pimlicoPaymasterAddress = '0x0000000000000039cd5e8aE05257CE51C473ddd1'
// USDC address on Ethereum Sepolia
const usdcAddress = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238'
// 0.00001 ETH in WEI format
const amount = 10000000000000n
// 10 USDC in WEI format. Since USDC has 6 decimals, 10 * 10^6
const approvalAmount = 10000000n
const userOpHash = await bundlerClient.sendUserOperation({
account: smartAccount,
calls: [
// Approve USDC on Sepolia chain for Pimlico's ERC 20 Paymaster
{
to: usdcAddress,
abi: parseAbi(['function approve(address,uint)']),
functionName: 'approve',
args: [pimlicoPaymasterAddress, approvalAmount],
},
{
to: 'DESTINATION_ADDRESS',
value: amount,
data: '0x',
},
{
to: 'DESTINATION_ADDRESS',
value: amount,
data: '0x',
},
],
})
// Retrieve user operation receipt
const receipt = await bundlerClient.waitForUserOperationReceipt({
hash: userOpHash,
})
const transactionHash = receipt.receipt.transactionHash
Next steps
See the following end-to-end tutorials: