Skip to main content
Smart accounts (also known as Account Abstraction or ERC-4337 accounts) are smart contract-based accounts that enable advanced features like gasless transactions, batch operations, and social recovery. Unlike traditional EOA accounts, smart accounts are controlled by code and can sponsor gas fees via paymasters.

Key Benefits

  • Gasless Transactions: Use paymasters to sponsor gas fees for users
  • Batch Operations: Execute multiple calls in a single user operation
  • Flexible Ownership: Support for multi-signature and social recovery
  • Policy Controls: Apply transaction policies at the smart account level

Creating a Smart Account

1

Create an owner account

Smart accounts require an owner account (EOA) to sign operations:
import { CdpClient } from "@coinbase/cdp-sdk";

const cdp = new CdpClient();
const owner = await cdp.evm.createAccount({ name: "Owner" });
2

Create the smart account

Create a smart account with the owner:
const smartAccount = await cdp.evm.createSmartAccount({
  owner: owner,
  name: "MySmartAccount"
});

console.log(`Smart account: ${smartAccount.address}`);
console.log(`Owner: ${owner.address}`);
3

Get or create pattern

Use idempotent creation for production:
const smartAccount = await cdp.evm.getOrCreateSmartAccount({
  name: "MySmartAccount",
  owner: owner
});

Sending User Operations

User operations are the smart account equivalent of transactions:
import { encodeFunctionData } from "viem";

// Simple ETH transfer
const userOp = await smartAccount.sendUserOperation({
  calls: [
    {
      to: "0x1234...",
      value: 1000000000000000n, // 0.001 ETH
      data: "0x"
    }
  ],
  network: "base-sepolia"
});

console.log(`User operation hash: ${userOp.userOpHash}`);

Batch Operations

Execute multiple calls atomically:
const userOp = await smartAccount.sendUserOperation({
  calls: [
    {
      to: "0xTokenContract...",
      value: 0n,
      data: encodeFunctionData({
        abi: erc20Abi,
        functionName: "approve",
        args: ["0xSpender...", 1000000n]
      })
    },
    {
      to: "0xDEXContract...",
      value: 0n,
      data: encodeFunctionData({
        abi: dexAbi,
        functionName: "swap",
        args: [/* swap params */]
      })
    }
  ],
  network: "base-sepolia"
});

Gasless Transactions with Paymasters

Use a paymaster to sponsor gas fees:
const userOp = await smartAccount.sendUserOperation({
  calls: [{
    to: "0x1234...",
    value: 1000000000000000n,
    data: "0x"
  }],
  network: "base-sepolia",
  paymasterUrl: "https://paymaster.example.com"
});
Paymasters allow you to sponsor transactions for your users, enabling truly gasless experiences. Contact your paymaster provider for the correct URL.

Waiting for User Operations

Wait for a user operation to be confirmed:
const confirmedOp = await smartAccount.waitForUserOperation(
  userOp.userOpHash,
  {
    timeoutSeconds: 30,
    intervalSeconds: 0.5
  }
);

console.log(`Status: ${confirmedOp.status}`);
console.log(`Transaction hash: ${confirmedOp.transactionHash}`);

Token Transfers

Transfer tokens using the simplified transfer method:
const result = await smartAccount.transfer({
  to: "0x1234...",
  amount: 10000n, // 0.01 USDC (6 decimals)
  token: "usdc",
  network: "base-sepolia",
  paymasterUrl: "https://paymaster.example.com" // Optional
});

console.log(`Transfer user op: ${result.userOpHash}`);

Token Swaps

Execute token swaps via user operations:
// Get a swap quote first
const quote = await smartAccount.quoteSwap({
  fromToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC
  toToken: "0x4200000000000000000000000000000000000006", // WETH
  fromAmount: 100000000n, // 100 USDC
  network: "base-sepolia",
  paymasterUrl: "https://paymaster.example.com"
});

// Execute the swap
const result = await smartAccount.swap({
  swapQuote: quote,
  idempotencyKey: "unique-key-123"
});

console.log(`Swap user op: ${result.userOpHash}`);

Checking Balances

const balances = await smartAccount.listTokenBalances({
  network: "base-sepolia"
});

for (const balance of balances.tokenBalances) {
  console.log(`${balance.symbol}: ${balance.amount}`);
}

Retrieving User Operations

Get details about a specific user operation:
const userOp = await smartAccount.getUserOperation(
  "0xUserOpHash..."
);

console.log(`Status: ${userOp.status}`);
console.log(`Gas used: ${userOp.actualGasUsed}`);

Listing Smart Accounts

const response = await cdp.evm.listSmartAccounts({
  pageSize: 50
});

for (const account of response.accounts) {
  console.log(`${account.name}: ${account.address}`);
}

Signing Typed Data

Sign EIP-712 typed data with a smart account:
const signature = await smartAccount.signTypedData({
  domain: {
    name: "MyApp",
    version: "1",
    chainId: 84532,
    verifyingContract: "0x..."
  },
  types: {
    Mail: [
      { name: "from", type: "address" },
      { name: "to", type: "address" },
      { name: "contents", type: "string" }
    ]
  },
  primaryType: "Mail",
  message: {
    from: "0x...",
    to: "0x...",
    contents: "Hello!"
  },
  network: "base-sepolia"
});

Troubleshooting

User Operation Failed

Check the user operation status for detailed error information:
const userOp = await smartAccount.getUserOperation(userOpHash);
console.log(`Status: ${userOp.status}`);
console.log(`Revert reason: ${userOp.revertReason}`);

Insufficient Funds

Smart accounts need ETH for gas unless using a paymaster:
// Request ETH from faucet
await cdp.evm.requestFaucet({
  address: smartAccount.address,
  network: "base-sepolia",
  token: "eth"
});

Paymaster Rejection

If a paymaster rejects your operation:
  • Verify the paymaster URL is correct
  • Check if the operation meets paymaster policies
  • Ensure the smart account is eligible for sponsorship

Next Steps

Build docs developers (and LLMs) love