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: 21000 n ,
maxFeePerGas: parseGwei ( "2" ),
maxPriorityFeePerGas: parseGwei ( "1" ),
});
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:
Status Description Next Steps Pending Submitted to mempool, awaiting inclusion Wait for confirmation Confirmed Included in a block Transaction successful Failed Reverted or rejected Check error and retry Dropped Removed from mempool Resubmit 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: 21000 n ,
});
} 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