Skip to main content

Overview

Transactions are the fundamental way to interact with blockchain networks. The CDP SDK supports both traditional EVM transactions and Account Abstraction user operations, with built-in support for signing, sending, and tracking transactions.

Transaction Types

EVM Transactions (EIP-1559)

Standard Ethereum transactions used by Server Accounts:
import { CdpClient } from "@coinbase/cdp-sdk";

const cdp = CdpClient.configureFromJson({
  filePath: "~/Downloads/cdp_api_key.json",
});

const account = await cdp.evm.createAccount();
const baseAccount = await account.useNetwork("base");

// Send EIP-1559 transaction
const result = await baseAccount.sendTransaction({
  to: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
  value: "1000000000000000", // 0.001 ETH in wei
  data: "0x", // Optional contract call data
});

console.log(`Transaction hash: ${result.transactionHash}`);
Transaction Fields:
  • to - Recipient address (required)
  • value - Amount of native token to send in wei (optional)
  • data - Contract call data (optional)
  • gas - Gas limit (optional, auto-estimated if not provided)
  • maxFeePerGas - Maximum fee per gas unit (optional, auto-estimated)
  • maxPriorityFeePerGas - Priority fee for miners (optional, auto-estimated)
  • nonce - Transaction nonce (optional, managed by CDP if not provided)

User Operations (ERC-4337)

Account Abstraction transactions used by Smart Accounts:
import { CdpClient } from "@coinbase/cdp-sdk";

const cdp = CdpClient.configureFromJson({
  filePath: "~/Downloads/cdp_api_key.json",
});

const owner = await cdp.evm.createAccount();
const smartAccount = await cdp.evm.createSmartAccount({ owner });
const baseSmart = await smartAccount.useNetwork("base");

// Send user operation with multiple calls
const userOp = await baseSmart.sendUserOperation({
  calls: [
    {
      to: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
      value: "1000000000000000",
      data: "0x",
    },
    {
      to: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      data: "0xa9059cbb...", // ERC-20 transfer
    },
  ],
  paymasterUrl: "https://paymaster.base.org", // Optional gas sponsorship
});

console.log(`User operation hash: ${userOp.userOpHash}`);
User Operation Benefits:
  • Batch multiple operations into one transaction
  • Gas sponsorship via paymasters (gasless transactions)
  • More flexible execution and validation logic
  • Better UX for end users

Solana Transactions

Solana uses a different transaction model based on instructions:
import { CdpClient } from "@coinbase/cdp-sdk";

const cdp = CdpClient.configureFromJson({
  filePath: "~/Downloads/cdp_api_key.json",
});

const account = await cdp.solana.createAccount();

// Sign and send Solana transaction
const signature = await account.transfer({
  to: "7EcDhSYGxXyscszYEp35KHN8vvw3svAuLKTzXwCFLtV",
  amount: 1000000, // 0.001 SOL in lamports
  token: "sol",
  network: "solana-devnet",
});

console.log(`Transaction signature: ${signature}`);

Transaction Lifecycle

1. Construction

Build the transaction with all required parameters:
import { parseUnits } from "viem";

// For EVM transactions
const transaction = {
  to: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
  value: parseUnits("0.01", 18).toString(), // 0.01 ETH
  data: "0x",
};

// For contract interactions
const contractCall = {
  to: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
  data: encodeFunctionData({
    abi: erc20Abi,
    functionName: "transfer",
    args: ["0x...", parseUnits("10", 6)], // 10 USDC
  }),
};

2. Signing

For server accounts, signing happens automatically when sending. You can also sign separately:
// Sign without sending
const signedTx = await account.signTransaction(transaction);
console.log(`Signed transaction: ${signedTx.rawTransaction}`);
console.log(`Transaction hash: ${signedTx.hash}`);

// Sign typed data (EIP-712)
const signature = await account.signTypedData({
  domain: {
    name: "MyApp",
    version: "1",
    chainId: 8453,
    verifyingContract: "0x...",
  },
  types: {
    Message: [
      { name: "content", type: "string" },
    ],
  },
  primaryType: "Message",
  message: {
    content: "Hello, World!",
  },
});

3. Sending

Transmit the signed transaction to the network:
// Send transaction
const result = await baseAccount.sendTransaction(transaction);

// Result includes transaction hash
console.log(`Transaction hash: ${result.transactionHash}`);

4. Confirmation

Wait for the transaction to be mined and confirmed:
// Wait for transaction receipt
const receipt = await baseAccount.waitForTransactionReceipt({
  hash: result.transactionHash,
});

console.log(`Status: ${receipt.status}`);
console.log(`Block number: ${receipt.blockNumber}`);
console.log(`Gas used: ${receipt.gasUsed}`);

// For user operations
const userOpReceipt = await baseSmart.waitForUserOperation({
  userOpHash: userOp.userOpHash,
});

console.log(`User op status: ${userOpReceipt.status}`);
console.log(`Transaction hash: ${userOpReceipt.transactionHash}`);

Gas Management

Automatic Gas Estimation

The CDP SDK automatically estimates gas parameters if not provided:
// Gas is estimated automatically
const result = await baseAccount.sendTransaction({
  to: "0x...",
  value: "1000000000000000",
});

// You can also specify gas parameters manually
const resultWithGas = await baseAccount.sendTransaction({
  to: "0x...",
  value: "1000000000000000",
  gas: 21000n,
  maxFeePerGas: parseGwei("2"),
  maxPriorityFeePerGas: parseGwei("1"),
});

Gas Sponsorship (Paymasters)

Smart accounts can use paymasters to sponsor gas:
// User doesn't pay gas - paymaster sponsors it
const userOp = await baseSmart.sendUserOperation({
  calls: [{ to: "0x...", value: "1000000000000000", data: "0x" }],
  paymasterUrl: "https://paymaster.base.org",
});
Paymasters enable gasless transactions, improving UX by removing the need for users to hold native tokens for gas.

Transaction Status

Transactions can be in various states:
StatusDescriptionNext Steps
PendingSubmitted to mempool, awaiting inclusionWait for confirmation
ConfirmedIncluded in a blockTransaction successful
FailedReverted or rejectedCheck error and retry
DroppedRemoved from mempoolResubmit with higher gas

Idempotency

Use idempotency keys to prevent duplicate transactions:
const idempotencyKey = crypto.randomUUID();

const result = await baseAccount.sendTransaction(
  {
    to: "0x...",
    value: "1000000000000000",
  },
  { idempotencyKey }
);

// Retry with same key - won't create duplicate
const retry = await baseAccount.sendTransaction(
  {
    to: "0x...",
    value: "1000000000000000",
  },
  { idempotencyKey } // Same key
);
Idempotency keys are valid for 24 hours. After that, the same key can create a new transaction.

Error Handling

Common transaction errors and how to handle them:
try {
  await baseAccount.sendTransaction({ to: "0x...", value: "1000000" });
} catch (error) {
  if (error.message.includes("insufficient funds")) {
    console.error("Not enough balance to send transaction");
    // Prompt user to add funds or reduce amount
  }
}
try {
  await baseAccount.sendTransaction({
    to: "0x...",
    value: "1000000",
    gas: 21000n,
  });
} catch (error) {
  if (error.message.includes("gas too low")) {
    // Retry with higher gas limit
    await baseAccount.sendTransaction({
      to: "0x...",
      value: "1000000",
      // Let SDK estimate gas
    });
  }
}
try {
  await baseAccount.sendTransaction({ to: "0x...", value: "1000000" });
} catch (error) {
  if (error.message.includes("nonce too low")) {
    console.error("Transaction already submitted or nonce conflict");
    // Let CDP manage nonce automatically
  }
}
const receipt = await baseAccount.waitForTransactionReceipt(result);

if (receipt.status === "reverted") {
  console.error("Transaction reverted");
  console.error(`Revert reason: ${receipt.revertReason}`);
  // Handle specific revert reasons
}

Best Practices

  • Always validate recipient addresses before sending
  • Use idempotency keys for critical transactions
  • Wait for sufficient confirmations on mainnet (6+ blocks)
  • Implement retry logic with exponential backoff
  • Monitor transaction status until confirmed
  • Let CDP estimate gas automatically for best results
  • Use Layer 2 networks (Base, Optimism) for lower fees
  • Batch operations with smart accounts to save gas
  • Consider using paymasters for user-friendly gas payment
  • Monitor gas prices and adjust timing for large transactions
  • Implement proper error handling for all transaction calls
  • Log transaction hashes for debugging and support
  • Provide clear error messages to users
  • Build retry mechanisms for transient failures
  • Test error scenarios on testnet before mainnet

Next Steps

Policies

Control transaction behavior with policies

Accounts

Learn about different account types

Sending Transactions Guide

Detailed guide with examples

Token Transfers

Transfer tokens between accounts

Build docs developers (and LLMs) love