CDP Wallets provide a sendTransaction feature that simplifies sending onchain transactions.
For EVM, it handles gas estimation, nonce management, signing, and broadcasting automatically.
For Solana, it handles signing and broadcasting, and sets the required priority fees for optimal transaction pricing.
EVM
sendTransaction is supported on Base, Ethereum, Avalanche, Polygon, Optimism, and Arbitrum. For other chains, use the sign transaction feature.
React
Node (TypeScript)
Python
After the user logs in, use useSendEvmTransaction from cdp-hooks to send EVM transactions from the user’s wallet.import { useSendEvmTransaction, useEvmAddress } from "@coinbase/cdp-hooks";
function SendTransaction() {
const { sendEvmTransaction } = useSendEvmTransaction();
const { evmAddress } = useEvmAddress();
const handleSend = async () => {
if (!evmAddress) return;
const result = await sendEvmTransaction({
transaction: {
to: evmAddress,
value: 1000000000000n, // 0.000001 ETH in wei
gas: 21000n,
chainId: 84532, // Base Sepolia
type: "eip1559",
},
evmAccount: evmAddress,
network: "base-sepolia",
});
console.log("Transaction hash:", result.transactionHash);
};
return <button onClick={handleSend}>Send Transaction</button>;
}
import { parseEther } from "viem";
const result = await cdp.evm.sendTransaction({
address: account.address,
network: "base-sepolia",
transaction: {
to: "0x4252e0c9A3da5A2700e7d91cb50aEf522D0C6Fe8",
value: parseEther("0.001"),
},
});
console.log("Transaction hash:", result.transactionHash);
from cdp.evm_transaction_types import TransactionRequestEIP1559
from web3 import Web3
tx_hash = await cdp.evm.send_transaction(
address=account.address,
transaction=TransactionRequestEIP1559(
to="0x4252e0c9A3da5A2700e7d91cb50aEf522D0C6Fe8",
value=Web3.to_wei(0.001, "ether"),
),
network="base-sepolia",
)
print(f"Transaction hash: {tx_hash}")
Network-scoped accounts
Use useNetwork() to scope an account to a specific network once, avoiding the need to pass network on every call. When scoped to Base or Base Sepolia, the SDK automatically uses CDP’s managed RPC endpoints.
// Without useNetwork — network required on every call
await cdp.evm.sendTransaction({ address, network: "base", transaction: { ... } });
await cdp.evm.sendTransaction({ address, network: "base", transaction: { ... } });
// With useNetwork — scope once, use everywhere
const baseAccount = await account.useNetwork("base");
await baseAccount.sendTransaction({ transaction: { to: "0x...", value: 0n } });
await baseAccount.waitForTransactionReceipt(result); // uses CDP Node RPC automatically
To use a custom RPC endpoint or a network not natively supported by CDP, pass the RPC URL directly:
const polygonAccount = await account.useNetwork(
"https://polygon-mainnet.rpc-provider.com/YOUR_API_KEY"
);
const result = await polygonAccount.sendTransaction({
transaction: { to: "0x...", value: 0n },
});
const receipt = await polygonAccount.waitForTransactionReceipt(result);
Solana
Solana transactions require building and serializing a transaction before sending. CDP injects the latest blockhash before broadcasting, so use the placeholder blockhash shown below.
React
Node (TypeScript)
Python
Use useSendSolanaTransaction for programmatic control, or SendSolanaTransactionButton for a pre-built UI button with built-in loading and error states.import { useSendSolanaTransaction, useSolanaAddress } from "@coinbase/cdp-hooks";
import {
PublicKey,
SystemProgram,
Transaction,
SYSVAR_RECENT_BLOCKHASHES_PUBKEY,
} from "@solana/web3.js";
import { Buffer } from "buffer";
function SendTransaction() {
const { sendSolanaTransaction } = useSendSolanaTransaction();
const { solanaAddress } = useSolanaAddress();
const handleSend = async () => {
if (!solanaAddress) return;
const tx = new Transaction().add(
SystemProgram.transfer({
fromPubkey: new PublicKey(solanaAddress),
toPubkey: new PublicKey("recipient-address"),
lamports: 1000,
})
);
tx.recentBlockhash = SYSVAR_RECENT_BLOCKHASHES_PUBKEY.toBase58();
tx.feePayer = new PublicKey(solanaAddress);
const serialized = Buffer.from(
tx.serialize({ requireAllSignatures: false })
).toString("base64");
const result = await sendSolanaTransaction({
transaction: serialized,
solanaAccount: solanaAddress,
network: "solana-devnet",
});
console.log("Signature:", result.transactionSignature);
};
return <button onClick={handleSend}>Send</button>;
}
import {
Connection,
PublicKey,
SystemProgram,
Transaction,
SYSVAR_RECENT_BLOCKHASHES_PUBKEY,
} from "@solana/web3.js";
const tx = new Transaction().add(
SystemProgram.transfer({
fromPubkey: new PublicKey(account.address),
toPubkey: new PublicKey("3KzDtddx4i53FBkvCzuDmRbaMozTZoJBb1TToWhz3JfE"),
lamports: 1000,
})
);
tx.recentBlockhash = SYSVAR_RECENT_BLOCKHASHES_PUBKEY.toBase58();
tx.feePayer = new PublicKey(account.address);
const serialized = Buffer.from(
tx.serialize({ requireAllSignatures: false })
).toString("base64");
const result = await cdp.solana.sendTransaction({
network: "solana-devnet",
transaction: serialized,
});
const { signature } = result;
console.log("Signature:", signature);
// Confirm
const connection = new Connection("https://api.devnet.solana.com");
const latestBlockhash = await connection.getLatestBlockhash();
const confirmation = await connection.confirmTransaction({
signature,
blockhash: latestBlockhash.blockhash,
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
});
if (confirmation.value.err) {
throw new Error(`Transaction failed: ${confirmation.value.err}`);
}
console.log(`Explorer: https://explorer.solana.com/tx/${signature}?cluster=devnet`);
import base64
from solders.pubkey import Pubkey as PublicKey
from solders.system_program import TransferParams, transfer
from solders.message import Message
from solders.hash import Hash
from solana.rpc.api import Client as SolanaClient
from solders.signature import Signature
source = PublicKey.from_string(account.address)
dest = PublicKey.from_string("3KzDtddx4i53FBkvCzuDmRbaMozTZoJBb1TToWhz3JfE")
message = Message.new_with_blockhash(
[transfer(TransferParams(from_pubkey=source, to_pubkey=dest, lamports=1000))],
source,
Hash.from_string("SysvarRecentB1ockHashes11111111111111111111"),
)
tx_bytes = bytes([1]) + bytes(64) + bytes(message)
serialized = base64.b64encode(tx_bytes).decode("utf-8")
result = await cdp.solana.send_transaction(
network="solana-devnet",
transaction=serialized,
)
signature = result.transaction_signature
print(f"Signature: {signature}")
# Confirm
connection = SolanaClient("https://api.devnet.solana.com")
confirmation = connection.confirm_transaction(
Signature.from_string(signature), commitment="processed"
)
if hasattr(confirmation, "err") and confirmation.err:
raise ValueError(f"Transaction failed: {confirmation.err}")
print(f"Explorer: https://explorer.solana.com/tx/{signature}?cluster=devnet")
Bring your own node
Use signTransaction to sign with CDP and broadcast via a custom RPC node:
const { blockhash } = await connection.getLatestBlockhash();
tx.recentBlockhash = blockhash;
const signedTx = await cdp.solana.signTransaction({
address: account.address,
transaction: serialized,
});
const decoded = Buffer.from(signedTx.signature, "base64");
const signature = await connection.sendRawTransaction(decoded);
console.log("Signature:", signature);
from solana.rpc.types import TxOpts
response = await cdp.solana.sign_transaction(
address=account.address,
transaction=serialized,
)
decoded = base64.b64decode(response.signed_transaction)
tx_resp = connection.send_raw_transaction(
decoded,
opts=TxOpts(skip_preflight=False, preflight_commitment="processed"),
)
print(f"Signature: {tx_resp.value}")
Priority fees
The priority fee increases the likelihood of your transaction being included in the next block.
Priority fee = Compute unit limit × Compute unit price
CDP automatically adds compute unit limit and price instructions if they are not already present in your transaction.
import { ComputeBudgetProgram, Transaction } from "@solana/web3.js";
const tx = new Transaction().add(
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 5000 }),
ComputeBudgetProgram.setComputeUnitLimit({ units: 300000 }),
// ...your other instructions
);
from solana.transaction import Transaction
from solders.compute_budget import set_compute_unit_price, set_compute_unit_limit
tx = Transaction().add(
set_compute_unit_price(5000),
set_compute_unit_limit(300000),
# ...your other instructions
)
CDP can pay the Solana network fee on behalf of your wallet. Pass useCdpSponsor: true when calling sendTransaction.
Fee sponsorship is in private preview and available to enterprise-scale integrations only. To opt in, apply for access.
React
Node (TypeScript)
Python
import { useSendSolanaTransaction, useSolanaAddress } from "@coinbase/cdp-hooks";
function SendSponsoredTransaction() {
const { sendSolanaTransaction } = useSendSolanaTransaction();
const { solanaAddress } = useSolanaAddress();
const handleSend = async () => {
if (!solanaAddress) return;
const result = await sendSolanaTransaction({
solanaAccount: solanaAddress,
network: "solana-mainnet",
transaction: serializedTx,
useCdpSponsor: true,
});
console.log("Signature:", result.transactionSignature);
};
return <button onClick={handleSend}>Send (sponsored)</button>;
}
const result = await cdp.solana.sendTransaction({
network: "solana-mainnet",
transaction: serializedTx,
useCdpSponsor: true,
});
console.log("Signature:", result.transactionSignature);
response = await cdp.solana.send_transaction(
network="solana-mainnet",
transaction=serialized_tx,
use_cdp_sponsor=True,
)
print(f"Signature: {response.transaction_signature}")
Send USDC
USDC uses 6 decimal places, so 1000000 atomic units equals 1 USDC.
React
Node (TypeScript)
Python
The useSendUsdc hook from @coinbase/cdp-hooks works across all account types (EOA, Smart Account, Solana) and handles contract addresses, decimal conversion, and Associated Token Account creation automatically.import { useSendUsdc } from "@coinbase/cdp-hooks";
function SendUsdcComponent() {
const { sendUsdc, data, error, status } = useSendUsdc();
const handleSend = async () => {
const result = await sendUsdc({
to: "0x1234567890123456789012345678901234567890",
amount: "25.50", // human-readable, not atomic units
network: "base-sepolia",
});
if (result.type === "evm-eoa") {
console.log("EOA Transaction:", result.transactionHash);
} else if (result.type === "evm-smart") {
console.log("Smart Account UserOp:", result.userOpHash);
} else if (result.type === "solana") {
console.log("Solana Transaction:", result.transactionSignature);
}
};
return (
<button onClick={handleSend} disabled={status === "pending"}>
{status === "pending" ? "Sending..." : "Send 25.50 USDC"}
</button>
);
}
For Smart Accounts, pass useCdpPaymaster: true for gasless transactions. For Solana, pass createRecipientAta: true to create the recipient’s token account if it doesn’t exist.See the full parameter reference in the CDP Web SDK docs. Use cdp.evm.sendTransaction with an encoded ERC-20 transfer call. The USDC contract address varies by network — use the correct one for your target network.import { CdpClient } from "@coinbase/cdp-sdk";
import { encodeFunctionData, parseUnits } from "viem";
const cdp = new CdpClient();
// USDC contract addresses
const USDC = {
"base-sepolia": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"base": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
};
const account = await cdp.evm.getOrCreateAccount({ name: "MyAccount" });
const data = encodeFunctionData({
abi: [{
name: "transfer",
type: "function",
inputs: [
{ name: "to", type: "address" },
{ name: "amount", type: "uint256" },
],
outputs: [{ type: "bool" }],
}],
functionName: "transfer",
args: [
"0xRecipientAddress",
parseUnits("25.50", 6), // 25.50 USDC in atomic units
],
});
const { transactionHash } = await cdp.evm.sendTransaction({
address: account.address,
network: "base-sepolia",
transaction: {
to: USDC["base-sepolia"],
data,
},
});
console.log("USDC sent:", transactionHash);
Use cdp.evm.send_transaction with ABI-encoded ERC-20 calldata.from cdp import CdpClient
from cdp.evm_transaction_types import TransactionRequestEIP1559
from web3 import Web3
w3 = Web3()
USDC_ABI = [{
"name": "transfer",
"type": "function",
"inputs": [
{"name": "to", "type": "address"},
{"name": "amount", "type": "uint256"},
],
"outputs": [{"type": "bool"}],
}]
# USDC contract addresses
USDC = {
"base-sepolia": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"base": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
}
async def send_usdc():
async with CdpClient() as cdp:
account = await cdp.evm.get_or_create_account(name="MyAccount")
contract = w3.eth.contract(abi=USDC_ABI)
amount = 25_500_000 # 25.50 USDC (6 decimals)
data = contract.encodeABI(
fn_name="transfer",
args=["0xRecipientAddress", amount],
)
tx_hash = await cdp.evm.send_transaction(
address=account.address,
transaction=TransactionRequestEIP1559(
to=USDC["base-sepolia"],
data=data,
),
network="base-sepolia",
)
print(f"USDC sent: {tx_hash}")
Webhooks
Subscribe to transaction lifecycle events using CDP Wallet Webhooks. The wallet.transaction.* events let you track each transaction from creation through confirmation or failure in real time.