Documentation Index Fetch the complete documentation index at: https://docs.cloud.coinbase.com/llms.txt
Use this file to discover all available pages before exploring further.
Smart accounts (ERC-4337 ) are programmable wallets that enable advanced features like batching multiple transactions together and sponsoring gas fees for your users.
Key benefits of CDP Smart Accounts include:
Batch transactions Execute multiple calls in a single user operation
Gas sponsorship Optional paymasters for gasless UX
Multi-chain support Deploy on 8 mainnets and 2 testnets across EVM chains
For keeping the same address, EIP-7702 upgrades an existing EOA with the same smart account capabilities without creating a new contract address.
Create a smart account
React
Node (TypeScript)
Python
Configure createOnLogin: "smart" in your provider so new users get a smart account automatically on sign-in. import { CDPHooksProvider , useCurrentUser } from "@coinbase/cdp-hooks" ;
function App () {
return (
< CDPHooksProvider
config = { {
projectId: "your-project-id" ,
ethereum: { createOnLogin: "smart" },
} }
>
< YourApp />
</ CDPHooksProvider >
);
}
function SmartAccountInfo () {
const { currentUser } = useCurrentUser ();
const smartAccount = currentUser ?. evmSmartAccounts ?.[ 0 ];
return < p > Smart Account: { smartAccount } </ p > ;
}
A smart account requires an EOA as its owner. The contract is not deployed until the first user operation is submitted. const owner = await cdp . evm . createAccount ();
const smartAccount = await cdp . evm . createSmartAccount ({ owner });
console . log ( "Smart Account:" , smartAccount . address );
A smart account requires an EOA as its owner. The contract is not deployed until the first user operation is submitted. owner = await cdp.evm.create_account()
smart_account = await cdp.evm.create_smart_account(owner)
print ( f "Smart Account: { smart_account.address } " )
Send a user operation
On Base Sepolia, user operations are subsidized and the smart account does not need to be funded with ETH. On Base mainnet, fund the smart account with ETH before submitting.
React
Node (TypeScript)
Python
useSendUserOperation tracks status, data, and error through on-chain confirmation.import { useSendUserOperation , useCurrentUser } from "@coinbase/cdp-hooks" ;
function SendUserOperation () {
const { sendUserOperation , status , data , error } = useSendUserOperation ();
const { currentUser } = useCurrentUser ();
const handleSend = async () => {
const smartAccount = currentUser ?. evmSmartAccounts ?.[ 0 ];
if ( ! smartAccount ) return ;
await sendUserOperation ({
evmSmartAccount: smartAccount ,
network: "base-sepolia" ,
calls: [{ to: "0x000...000" , value: 0 n , data: "0x" }],
});
};
return (
< div >
< button onClick = { handleSend } disabled = { status === "pending" } >
{ status === "pending" ? "Sending..." : "Send" }
</ button >
{ status === "success" && data && < p > Tx: { data . transactionHash } </ p > }
{ error && < p > Error: { error . message } </ p > }
</ div >
);
}
import { parseEther } from "viem" ;
const result = await cdp . evm . sendUserOperation ({
smartAccount ,
network: "base-sepolia" ,
calls: [{ to: "0x000...000" , value: parseEther ( "0" ), data: "0x" }],
});
const userOperation = await cdp . evm . waitForUserOperation ({
smartAccountAddress: smartAccount . address ,
userOpHash: result . userOpHash ,
});
if ( userOperation . status === "complete" ) {
console . log ( "Tx:" , userOperation . transactionHash );
}
from cdp.evm_call_types import EncodedCall
user_operation = await cdp.evm.send_user_operation(
smart_account = smart_account,
calls = [EncodedCall( to = "0x000...000" , data = "0x" , value = 0 )],
network = "base-sepolia" ,
)
user_operation = await cdp.evm.wait_for_user_operation(
smart_account_address = smart_account.address,
user_op_hash = user_operation.user_op_hash,
)
if user_operation.status == "complete" :
print ( f "Tx: { user_operation.transaction_hash } " )
Batch calls
Pass multiple entries in calls[] to execute them atomically in a single user operation. Calls run in order and revert together on failure.
React
Node (TypeScript)
Python
await sendUserOperation ({
evmSmartAccount: smartAccount ,
network: "base-sepolia" ,
calls: [
{ to: addr1 , value: parseEther ( "0.001" ), data: "0x" },
{ to: addr2 , value: parseEther ( "0.001" ), data: "0x" },
{ to: addr3 , value: parseEther ( "0.001" ), data: "0x" },
],
});
import { parseEther } from "viem" ;
const destinations = [
"0xba5f3764f0A714EfaEDC00a5297715Fd75A416B7" ,
"0xD84523e4F239190E9553ea59D7e109461752EC3E" ,
"0xf1F7Bf05A81dBd5ACBc701c04ce79FbC82fEAD8b" ,
];
const { userOpHash } = await smartAccount . sendUserOperation ({
network: "base-sepolia" ,
calls: destinations . map (( to ) => ({
to ,
value: parseEther ( "0.000001" ),
data: "0x" ,
})),
});
const result = await smartAccount . waitForUserOperation ({ userOpHash });
if ( result . status === "complete" ) {
console . log ( "Tx:" , result . transactionHash );
}
from cdp.evm_call_types import EncodedCall
destinations = [
"0xba5f3764f0A714EfaEDC00a5297715Fd75A416B7" ,
"0xD84523e4F239190E9553ea59D7e109461752EC3E" ,
"0xf1F7Bf05A81dBd5ACBc701c04ce79FbC82fEAD8b" ,
]
user_operation = await cdp.evm.send_user_operation(
smart_account = smart_account,
calls = [EncodedCall( to = dest, data = "0x" , value = 1000 ) for dest in destinations],
network = "base-sepolia" ,
)
user_operation = await cdp.evm.wait_for_user_operation(
smart_account_address = smart_account.address,
user_op_hash = user_operation.user_op_hash,
)
if user_operation.status == "complete" :
print ( f "Tx: { user_operation.transaction_hash } " )
Encode contract calls
To interact with contracts, pass data using an ABI-encoded payload. This example encodes an ERC-20 transfer using viem:
import { encodeFunctionData } from "viem" ;
const erc20Abi = [
{
name: "transfer" ,
type: "function" ,
stateMutability: "nonpayable" ,
inputs: [
{ name: "to" , type: "address" },
{ name: "value" , type: "uint256" },
],
outputs: [{ name: "" , type: "bool" }],
},
];
const data = encodeFunctionData ({
abi: erc20Abi as const ,
functionName: "transfer" ,
args: [ recipient , 1_000_000 n ], // 1 USDC assuming 6 decimals
});
await sendUserOperation ({
evmSmartAccount: smartAccount ,
network: "base-sepolia" ,
calls: [
{
to: usdcAddress ,
data ,
value: 0 n ,
},
],
});
Pass a paymasterUrl to cover gas fees for your users with any ERC-7677 -compatible paymaster.
React
Node (TypeScript)
Python
React also supports useCdpPaymaster: true to use the CDP Paymaster on Base without providing a URL. useCdpPaymaster is only supported on Base. You cannot specify both useCdpPaymaster and paymasterUrl.
await sendUserOperation ({
evmSmartAccount: smartAccount ,
network: "base-sepolia" ,
calls: [{ to: recipient , value: 10 n ** 15 n , data: "0x" }],
useCdpPaymaster: true ,
// or: paymasterUrl: "https://your-paymaster.example.com"
});
import { parseEther } from "viem" ;
const userOperation = await cdp . evm . sendUserOperation ({
smartAccount ,
network: "base-sepolia" ,
calls: [
{
to: "0x0000000000000000000000000000000000000000" ,
value: parseEther ( "0" ),
data: "0x" ,
},
],
paymasterUrl: "https://your-paymaster.example.com" ,
});
const confirmed = await cdp . evm . waitForUserOperation ({
smartAccountAddress: smartAccount . address ,
userOpHash: userOperation . userOpHash ,
});
if ( confirmed . status === "complete" ) {
console . log ( "Tx:" , confirmed . transactionHash );
}
from cdp.evm_call_types import EncodedCall
from decimal import Decimal
from web3 import Web3
user_operation = await cdp.evm.send_user_operation(
smart_account = smart_account,
calls = [
EncodedCall(
to = "0x0000000000000000000000000000000000000000" ,
data = "0x" ,
value = Web3.to_wei(Decimal( "0" ), "ether" ),
)
],
network = "base-sepolia" ,
paymaster_url = "https://your-paymaster.example.com" ,
)
confirmed = await cdp.evm.wait_for_user_operation(
smart_account_address = smart_account.address,
user_op_hash = user_operation.user_op_hash,
)
if confirmed.status == "complete" :
print ( f "Tx: { confirmed.transaction_hash } " )
Builder Codes
Base Builder Codes attribute onchain activity back to your app for rewards and analytics. Pass dataSuffix on any user operation — no contract changes needed.
First, register at base.dev and generate your suffix:
import { Attribution } from "ox/erc8021" ;
const DATA_SUFFIX = Attribution . toDataSuffix ({
codes: [ "YOUR-BUILDER-CODE" ],
});
Then pass it to sendUserOperation:
React
Node (TypeScript)
Python
import { useSendUserOperation , useCurrentUser } from "@coinbase/cdp-hooks" ;
await sendUserOperation ({
evmSmartAccount: smartAccount ,
network: "base" ,
calls: [{ to: "0xYourContract" , value: 0 n , data: "0x" }],
dataSuffix: DATA_SUFFIX ,
});
await cdp . evm . sendUserOperation ({
smartAccount ,
network: "base" ,
calls: [{ to: "0xYourContract" , value: parseEther ( "0" ), data: "0x" }],
dataSuffix: DATA_SUFFIX ,
});
await smart_account.send_user_operation(
calls = [EncodedCall( to = "0xYourContract" , value = 0 , data = "0x" )],
network = "base" ,
data_suffix = DATA_SUFFIX ,
)
Supported networks
Network Mainnet Testnet Base ✓ ✓ (Base Sepolia) Arbitrum ✓ Optimism ✓ Zora ✓ Polygon ✓ BNB Chain ✓ Avalanche ✓ Ethereum ✓ ✓ (Sepolia)
Debugging
When a user operation reverts, the revert reason is included in its receipts if it can be decoded:
const userOp = await cdp . evm . getUserOperation ({
userOpHash: result . userOpHash ,
smartAccount ,
});
console . log ( userOp . receipts );
user_op = await cdp.evm.get_user_operation(
smart_account.address,
user_operation.user_op_hash,
)
print (user_op.receipts)