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:
- All-in-one pattern: Create and execute a swap in one call (recommended)
- 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}`);
from cdp import CdpClient
from cdp.actions.evm.swap import AccountSwapOptions
async with CdpClient() as cdp:
account = await cdp.evm.get_or_create_account(
name="SwapAccount"
)
# Swap 0.1 WETH for USDC
result = await account.swap(
AccountSwapOptions(
network="base",
from_token="0x4200000000000000000000000000000000000006", # WETH
to_token="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", # USDC
from_amount="100000000000000000", # 0.1 WETH (18 decimals)
slippage_bps=100 # 1% slippage tolerance
)
)
print(f"Swap transaction: {result.transaction_hash}")
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}`);
from cdp import parse_units
from cdp.actions.evm.swap import AccountSwapOptions
# Step 1: Get a swap quote
quote = await account.quote_swap(
from_token="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", # USDC
to_token="0x4200000000000000000000000000000000000006", # WETH
from_amount="100000000", # 100 USDC (6 decimals)
network="base",
slippage_bps=100,
idempotency_key="unique-swap-key-123"
)
# Step 2: Check liquidity and details
if not quote.liquidity_available:
print("Insufficient liquidity for this swap")
return
print(f"Will receive ~{quote.to_amount} smallest units of WETH")
print(f"Minimum receive: {quote.min_to_amount} smallest units")
if quote.fees and quote.fees.gas_fee:
print(f"Estimated gas: {quote.fees.gas_fee.amount}")
# Step 3: Execute the swap
result = await account.swap(
AccountSwapOptions(swap_quote=quote)
)
print(f"Swap executed: {result.transaction_hash}")
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}`);
from cdp.actions.evm.swap.types import SmartAccountSwapOptions
owner = await cdp.evm.create_account()
smart_account = await cdp.evm.create_smart_account(owner=owner)
# Get a quote with paymaster support
quote = await smart_account.quote_swap(
from_token="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", # USDC
to_token="0x4200000000000000000000000000000000000006", # WETH
from_amount="100000000",
network="base",
paymaster_url="https://paymaster.example.com" # Optional
)
# Execute via user operation
result = await smart_account.swap(
SmartAccountSwapOptions(
swap_quote=quote,
idempotency_key="smart-swap-456"
)
)
print(f"User operation hash: {result.user_op_hash}")
# Wait for confirmation
confirmed = await smart_account.wait_for_user_operation(
user_op_hash=result.user_op_hash
)
print(f"Swap confirmed: {confirmed.transaction_hash}")
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
});
from web3 import Web3
from cdp import parse_units
from cdp.actions.evm.swap import AccountSwapOptions
PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3"
w3 = Web3(Web3.HTTPProvider("https://mainnet.base.org"))
# Check current allowance
token_address = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" # USDC
token_contract = w3.eth.contract(
address=Web3.to_checksum_address(token_address),
abi=[...erc20_abi...]
)
allowance = token_contract.functions.allowance(
account.address,
PERMIT2_ADDRESS
).call()
required_amount = parse_units("100", 6) # 100 USDC
# Approve if needed
if allowance < required_amount:
print("Setting token allowance...")
data = token_contract.encode_abi(
fn_name="approve",
args=[PERMIT2_ADDRESS, required_amount]
)
tx_hash = await cdp.evm.send_transaction(
address=account.address,
network="base",
transaction={
"to": token_address,
"data": data,
"value": 0
}
)
w3.eth.wait_for_transaction_receipt(tx_hash)
print("Allowance approved!")
# Now execute the swap
result = await account.swap(
AccountSwapOptions(
network="base",
from_token=token_address,
to_token="0x4200000000000000000000000000000000000006",
from_amount=required_amount,
slippage_bps=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");
}
price = await cdp.evm.get_swap_price(
from_token="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", # USDC
to_token="0x4200000000000000000000000000000000000006", # WETH
from_amount="100000000",
network="base",
taker=account.address
)
if price.liquidity_available:
print(f"Price: {price.to_amount} smallest units of WETH for 100 USDC")
else:
print("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