Skip to main content
The CDP SDK provides seamless token swap functionality powered by decentralized exchange aggregators. Swap between any supported token pairs with automatic routing, slippage protection, and Permit2 signature handling.

Swap Methods

There are two approaches to executing swaps:
  1. All-in-one pattern: Create and execute a swap in one call (recommended)
  2. Quote-then-execute pattern: Create a quote first, inspect details, then execute

Basic Token Swap (All-in-One)

The simplest way to swap tokens:
import { CdpClient } from "@coinbase/cdp-sdk";
import { parseUnits } from "viem";

const cdp = new CdpClient();
const account = await cdp.evm.getOrCreateAccount({ 
  name: "SwapAccount" 
});

// Swap 0.1 WETH for USDC
const result = await account.swap({
  network: "base",
  fromToken: "0x4200000000000000000000000000000000000006", // WETH
  toToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC
  fromAmount: parseUnits("0.1", 18), // 0.1 WETH
  slippageBps: 100 // 1% slippage tolerance
});

console.log(`Swap transaction: ${result.transactionHash}`);

Quote-Then-Execute Pattern

For more control, create a quote first to inspect swap details:
// Step 1: Get a swap quote
const quote = await account.quoteSwap({
  network: "base",
  fromToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC
  toToken: "0x4200000000000000000000000000000000000006", // WETH
  fromAmount: parseUnits("100", 6), // 100 USDC
  slippageBps: 100,
  idempotencyKey: "unique-swap-key-123"
});

// Step 2: Check liquidity and details
if (!quote.liquidityAvailable) {
  console.log("Insufficient liquidity for this swap");
  return;
}

console.log(`Will receive ~${formatUnits(quote.toAmount, 18)} WETH`);
console.log(`Minimum receive: ${formatUnits(quote.minToAmount, 18)} WETH`);

if (quote.fees?.gasFee) {
  console.log(
    `Estimated gas: ${formatEther(quote.fees.gasFee.amount)} ETH`
  );
}

// Step 3: Execute the swap
const result = await account.swap({
  swapQuote: quote
});

console.log(`Swap executed: ${result.transactionHash}`);

Smart Account Swaps

Execute swaps via smart accounts with optional gas sponsorship:
const owner = await cdp.evm.createAccount();
const smartAccount = await cdp.evm.createSmartAccount({ owner });

// Get a quote with paymaster support
const quote = await smartAccount.quoteSwap({
  fromToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC
  toToken: "0x4200000000000000000000000000000000000006", // WETH
  fromAmount: parseUnits("100", 6),
  network: "base",
  paymasterUrl: "https://paymaster.example.com" // Optional
});

// Execute via user operation
const result = await smartAccount.swap({
  swapQuote: quote,
  idempotencyKey: "smart-swap-456"
});

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

// Wait for confirmation
const confirmed = await smartAccount.waitForUserOperation(
  result.userOpHash
);
console.log(`Swap confirmed: ${confirmed.transactionHash}`);

Token Allowances and Permit2

When swapping ERC-20 tokens (not native ETH), the Permit2 contract needs approval to move tokens:
import { 
  createPublicClient, 
  http, 
  erc20Abi, 
  encodeFunctionData,
  formatEther 
} from "viem";
import { base } from "viem/chains";

const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";

const publicClient = createPublicClient({
  chain: base,
  transport: http()
});

// Check current allowance
const tokenAddress = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; // USDC
const allowance = await publicClient.readContract({
  address: tokenAddress,
  abi: erc20Abi,
  functionName: "allowance",
  args: [account.address, PERMIT2_ADDRESS]
});

const requiredAmount = parseUnits("100", 6); // 100 USDC

// Approve if needed
if (allowance < requiredAmount) {
  console.log("Setting token allowance...");
  
  const data = encodeFunctionData({
    abi: erc20Abi,
    functionName: "approve",
    args: [PERMIT2_ADDRESS, requiredAmount]
  });
  
  const txHash = await cdp.evm.sendTransaction({
    address: account.address,
    network: "base",
    transaction: {
      to: tokenAddress,
      data: data,
      value: 0n
    }
  });
  
  await publicClient.waitForTransactionReceipt({ hash: txHash });
  console.log("Allowance approved!");
}

// Now execute the swap
const result = await account.swap({
  network: "base",
  fromToken: tokenAddress,
  toToken: "0x4200000000000000000000000000000000000006",
  fromAmount: requiredAmount,
  slippageBps: 100
});
The Permit2 contract address (0x000000000022D473030F116dDEE9F6B43aC78BA3) is the same across all EVM networks. The CDP SDK automatically handles Permit2 signatures for swaps.

Getting Swap Prices

Get a price estimate without committing to a swap:
const price = await cdp.evm.getSwapPrice({
  fromToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC
  toToken: "0x4200000000000000000000000000000000000006", // WETH
  fromAmount: parseUnits("100", 6),
  network: "base",
  taker: account.address
});

if (price.liquidityAvailable) {
  console.log(`Price: ${formatUnits(price.toAmount, 18)} WETH for 100 USDC`);
} else {
  console.log("No liquidity available");
}

Slippage Protection

Slippage is specified in basis points (100 = 1%):
// 0.5% slippage (tight)
slippageBps: 50

// 1% slippage (default, recommended)
slippageBps: 100

// 3% slippage (loose, for volatile pairs)
slippageBps: 300
The swap will revert if the final exchange rate differs by more than the specified slippage percentage.

Supported Networks

Swaps are currently supported on:
  • Ethereum: ethereum, ethereum-sepolia
  • Base: base, base-sepolia
  • Optimism: optimism, optimism-sepolia
  • Arbitrum: arbitrum, arbitrum-sepolia
  • Polygon: polygon, polygon-amoy

Common Token Addresses

Base Mainnet

const TOKENS = {
  WETH: "0x4200000000000000000000000000000000000006",
  USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  USDbC: "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA",
  DAI: "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb"
};

Base Sepolia Testnet

const TOKENS = {
  WETH: "0x4200000000000000000000000000000000000006",
  USDC: "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
};

Complete Swap Example

import { CdpClient } from "@coinbase/cdp-sdk";
import { 
  parseUnits, 
  formatUnits,
  createPublicClient, 
  http,
  erc20Abi,
  encodeFunctionData
} from "viem";
import { base } from "viem/chains";

const NETWORK = "base";
const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
const WETH = "0x4200000000000000000000000000000000000006";
const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";

const cdp = new CdpClient();
const publicClient = createPublicClient({
  chain: base,
  transport: http()
});

const account = await cdp.evm.getOrCreateAccount({ 
  name: "SwapDemo" 
});

// Check and set allowance
const fromAmount = parseUnits("0.1", 18); // 0.1 WETH
const allowance = await publicClient.readContract({
  address: WETH,
  abi: erc20Abi,
  functionName: "allowance",
  args: [account.address, PERMIT2_ADDRESS]
});

if (allowance < fromAmount) {
  console.log("Approving WETH...");
  const approveTx = await cdp.evm.sendTransaction({
    address: account.address,
    network: NETWORK,
    transaction: {
      to: WETH,
      data: encodeFunctionData({
        abi: erc20Abi,
        functionName: "approve",
        args: [PERMIT2_ADDRESS, fromAmount]
      }),
      value: 0n
    }
  });
  await publicClient.waitForTransactionReceipt({ 
    hash: approveTx.transactionHash 
  });
  console.log("Approval confirmed!");
}

// Get a swap quote
console.log("Getting swap quote...");
const quote = await account.quoteSwap({
  fromToken: WETH,
  toToken: USDC,
  fromAmount: fromAmount,
  network: NETWORK,
  slippageBps: 100
});

if (!quote.liquidityAvailable) {
  throw new Error("Insufficient liquidity");
}

console.log(
  `Will receive ~${formatUnits(quote.toAmount, 6)} USDC for 0.1 WETH`
);

// Execute the swap
console.log("Executing swap...");
const result = await account.swap({ swapQuote: quote });

console.log(`Swap submitted: ${result.transactionHash}`);

// Wait for confirmation
const receipt = await publicClient.waitForTransactionReceipt({
  hash: result.transactionHash
});

console.log(`Swap confirmed! Status: ${receipt.status}`);
console.log(
  `Explorer: https://basescan.org/tx/${receipt.transactionHash}`
);

Troubleshooting

Insufficient Liquidity

If liquidityAvailable is false:
  • Try a smaller swap amount
  • Use a different token pair
  • Check if the tokens are available on the selected network

Slippage Exceeded

If the swap reverts due to slippage:
  • Increase slippageBps (e.g., from 100 to 300)
  • Split large swaps into smaller chunks
  • Wait for less volatile market conditions

Allowance Not Set

Before swapping ERC-20 tokens, approve the Permit2 contract:
// Always approve Permit2, not the swap contract
const PERMIT2 = "0x000000000022D473030F116dDEE9F6B43aC78BA3";

Transaction Failed

Common reasons for swap failures:
  • Insufficient token balance
  • Insufficient ETH for gas
  • Expired quote (quotes are time-sensitive)
  • Price moved beyond slippage tolerance

Next Steps

Build docs developers (and LLMs) love