- CDP SDK
- Documentation
- Installation
- API Keys
- Usage
- Account Actions
- Policy Management
- End-user Management
- Authentication Tools
- Error Reporting
- Usage Tracking
- License
- Support
- Security
Tip
If you're looking to contribute to the SDK, please see the Contributing Guide.
This module contains the Python CDP SDK, which is a library that provides a client for interacting with the Coinbase Developer Platform (CDP). It includes a CDP Client for interacting with EVM and Solana APIs to create accounts and send transactions, policy APIs to govern transaction permissions, as well as authentication tools for interacting directly with the CDP APIs.
CDP SDK has auto-generated docs for the Python SDK.
Further documentation is also available on the CDP docs website:
pip install cdp-sdkTo start, create a CDP API Key. Save the API Key ID and API Key Secret for use in the SDK. You will also need to create a wallet secret in the Portal to sign transactions.
One option is to export your CDP API Key and Wallet Secret as environment variables:
export CDP_API_KEY_ID="YOUR_API_KEY_ID"
export CDP_API_KEY_SECRET="YOUR_API_KEY_SECRET"
export CDP_WALLET_SECRET="YOUR_WALLET_SECRET"Then, initialize the client:
from cdp import CdpClient
import asyncio
async def main():
async with CdpClient() as cdp:
pass
asyncio.run(main())Another option is to save your CDP API Key and Wallet Secret in a .env file:
touch .env
echo "CDP_API_KEY_ID=YOUR_API_KEY_ID" >> .env
echo "CDP_API_KEY_SECRET=YOUR_API_KEY_SECRET" >> .env
echo "CDP_WALLET_SECRET=YOUR_WALLET_SECRET" >> .envThen, load the client config from the .env file:
from cdp import CdpClient
from dotenv import load_dotenv
import asyncio
load_dotenv()
async def main():
async with CdpClient() as cdp:
pass
asyncio.run(main())Another option is to directly pass the API Key and Wallet Secret to the client:
import asyncio
from cdp import CdpClient
async def main():
async with CdpClient(
api_key_id="YOUR_API_KEY_ID",
api_key_secret="YOUR_API_KEY_SECRET",
wallet_secret="YOUR_WALLET_SECRET",
) as cdp:
pass
asyncio.run(main())The CDP client wraps an HTTP client (aiohttp) and should be created once and reused throughout your application's lifecycle. The underlying HTTP client handles connection pooling automatically, so there's no need to recreate the client per request—doing so would be less efficient.
- Long-lived services: Create a single client instance at startup using
async with CdpClient() as cdp: - Serverless/request-based runtimes: Create once per cold start, or use a module-level singleton
- Concurrency: The client is safe to use across concurrent async operations
import asyncio
from cdp import CdpClient
async def main():
async with CdpClient() as cdp:
account = await cdp.evm.create_account()
asyncio.run(main())import asyncio
from cdp import CdpClient
async def main():
async with CdpClient() as cdp:
account = await cdp.evm.import_account(
private_key="0x12345",
name="MyAccount",
)
asyncio.run(main())import asyncio
from cdp import CdpClient
async def main():
async with CdpClient() as cdp:
account = await cdp.solana.create_account()
asyncio.run(main())import asyncio
from cdp import CdpClient
async def main():
async with CdpClient() as cdp:
account = await cdp.solana.import_account(
private_key="3MLZ...Uko8zz",
name="MyAccount",
)
asyncio.run(main())# by name
private_key = await cdp.evm.export_account(
name="MyAccount",
)
# by address
private_key = await cdp.evm.export_account(
address="0x123",
)# by name
private_key = await cdp.solana.export_account(
name="MyAccount",
)
# by address
private_key = await cdp.solana.export_account(
address="Abc",
)import asyncio
from cdp import CdpClient
async def main():
async with CdpClient() as cdp:
account = await cdp.evm.get_or_create_account()
asyncio.run(main())import asyncio
from cdp import CdpClient
async def main():
async with CdpClient() as cdp:
account = await cdp.solana.get_or_create_account()
asyncio.run(main())import asyncio
from cdp import CdpClient
async def main():
async with CdpClient() as cdp:
owner = await cdp.evm.create_account()
account = await cdp.evm.get_or_create_smart_account(name="Account1", owner=owner)
asyncio.run(main())account = await cdp.evm.create_account(
name="AccountWithPolicy",
account_policy="abcdef12-3456-7890-1234-567890123456",
)account = await cdp.solana.create_account(
name="AccountWithPolicy",
account_policy="abcdef12-3456-7890-1234-567890123456",
)account = await cdp.evm.update_account(
address=account.address,
update=UpdateAccountOptions(
name="Updated name",
account_policy="1622d4b7-9d60-44a2-9a6a-e9bbb167e412",
),
)account = await cdp.solana.update_account(
address=account.address,
update=UpdateAccountOptions(
name="Updated name",
account_policy="1622d4b7-9d60-44a2-9a6a-e9bbb167e412",
),
)You can use the faucet function to request testnet ETH or SOL from the CDP.
import asyncio
from cdp import CdpClient
async def main():
async with CdpClient() as cdp:
await cdp.evm.request_faucet(
address=evm_account.address, network="base-sepolia", token="eth"
)
asyncio.run(main())import asyncio
from cdp import CdpClient
async def main():
async with CdpClient() as cdp:
await cdp.solana.request_faucet(
address=address, token="sol"
)
asyncio.run(main())You can use CDP SDK to send transactions on EVM networks. By default, Coinbase will manage the nonce and gas for you.
import asyncio
from dotenv import load_dotenv
from web3 import Web3
from cdp import CdpClient
from cdp.evm_transaction_types import TransactionRequestEIP1559
w3 = Web3(Web3.HTTPProvider("https://sepolia.base.org"))
async def main():
load_dotenv()
async with CdpClient() as cdp:
evm_account = await cdp.evm.create_account()
faucet_hash = await cdp.evm.request_faucet(
address=evm_account.address, network="base-sepolia", token="eth"
)
w3.eth.wait_for_transaction_receipt(faucet_hash)
zero_address = "0x0000000000000000000000000000000000000000"
amount_to_send = w3.to_wei(0.000001, "ether")
tx_hash = await cdp.evm.send_transaction(
address=evm_account.address,
transaction=TransactionRequestEIP1559(
to=zero_address,
value=amount_to_send,
),
network="base-sepolia",
)
print(f"Transaction sent! Hash: {tx_hash}")
print("Waiting for transaction confirmation...")
tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Transaction confirmed in block {tx_receipt.blockNumber}")
print(f"Transaction status: {'Success' if tx_receipt.status == 1 else 'Failed'}")
asyncio.run(main())If you'd like to manage the nonce and gas yourself, you can do so as follows:
import asyncio
from dotenv import load_dotenv
from web3 import Web3
from cdp import CdpClient
from cdp.evm_transaction_types import TransactionRequestEIP1559
w3 = Web3(Web3.HTTPProvider("https://sepolia.base.org"))
async def main():
load_dotenv()
async with CdpClient() as cdp:
evm_account = await cdp.evm.create_account()
faucet_hash = await cdp.evm.request_faucet(
address=evm_account.address, network="base-sepolia", token="eth"
)
w3.eth.wait_for_transaction_receipt(faucet_hash)
zero_address = "0x0000000000000000000000000000000000000000"
amount_to_send = w3.to_wei(0.000001, "ether")
nonce = w3.eth.get_transaction_count(evm_account.address)
gas_estimate = w3.eth.estimate_gas(
{"to": zero_address, "from": evm_account.address, "value": amount_to_send}
)
max_priority_fee = w3.eth.max_priority_fee
max_fee = w3.eth.gas_price + max_priority_fee
tx_hash = await cdp.evm.send_transaction(
address=evm_account.address,
transaction=TransactionRequestEIP1559(
to=zero_address,
value=amount_to_send,
gas=gas_estimate,
maxFeePerGas=max_fee,
maxPriorityFeePerGas=max_priority_fee,
nonce=nonce,
),
network="base-sepolia",
)
print(f"Transaction sent! Hash: {tx_hash}")
print("Waiting for transaction confirmation...")
tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Transaction confirmed in block {tx_receipt.blockNumber}")
print(f"Transaction status: {'Success' if tx_receipt.status == 1 else 'Failed'}")
asyncio.run(main())You can also use DynamicFeeTransaction from eth-account:
import asyncio
from dotenv import load_dotenv
from web3 import Web3
from cdp import CdpClient
from eth_account.typed_transactions import DynamicFeeTransaction
w3 = Web3(Web3.HTTPProvider("https://sepolia.base.org"))
async def main():
load_dotenv()
async with CdpClient() as cdp:
evm_account = await cdp.evm.create_account()
faucet_hash = await cdp.evm.request_faucet(
address=evm_account.address, network="base-sepolia", token="eth"
)
w3.eth.wait_for_transaction_receipt(faucet_hash)
zero_address = "0x0000000000000000000000000000000000000000"
amount_to_send = w3.to_wei(0.000001, "ether")
nonce = w3.eth.get_transaction_count(evm_account.address)
gas_estimate = w3.eth.estimate_gas(
{"to": zero_address, "from": evm_account.address, "value": amount_to_send}
)
max_priority_fee = w3.eth.max_priority_fee
max_fee = w3.eth.gas_price + max_priority_fee
tx_hash = await cdp.evm.send_transaction(
address=evm_account.address,
transaction=DynamicFeeTransaction.from_dict(
{
"to": zero_address,
"value": amount_to_send,
"chainId": 84532,
"gas": gas_estimate,
"maxFeePerGas": max_fee,
"maxPriorityFeePerGas": max_priority_fee,
"nonce": nonce,
"type": "0x2",
}
),
network="base-sepolia",
)
print(f"Transaction sent! Hash: {tx_hash}")
print("Waiting for transaction confirmation...")
tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Transaction confirmed in block {tx_receipt.blockNumber}")
print(f"Transaction status: {'Success' if tx_receipt.status == 1 else 'Failed'}")
asyncio.run(main())CDP SDK provides two account types for different use cases:
-
EvmServerAccount- Async-first API for CDP server-managed accounts. All signing operations are asynchronous and involve network calls to the CDP API. This is the recommended type for most CDP SDK usage. -
EvmLocalAccount- Synchronous wrapper aroundEvmServerAccountthat provides compatibility witheth-account'sBaseAccountinterface. Use this when you need to integrate with libraries that expect synchronouseth-accountLocalAccount objects (e.g., creating smart accounts with a server account as owner).
When to use EvmLocalAccount:
- Creating smart accounts where the owner must be a
BaseAccount - Integrating with
web3.pyor other libraries expectingeth-accountLocalAccount - Any synchronous context where you cannot use async/await
With EvmLocalAccount, you may sign a hash, message, typed data, or a transaction synchronously.
import asyncio
from cdp import CdpClient
from dotenv import load_dotenv
from cdp.evm_local_account import EvmLocalAccount
from web3 import Web3
load_dotenv()
async def main():
async with CdpClient() as cdp:
account = await cdp.evm.get_or_create_account(name="MyServerAccount")
evm_local_account = EvmLocalAccount(account)
# Sign a transaction.
w3 = Web3(Web3.HTTPProvider("https://sepolia.base.org"))
nonce = w3.eth.get_transaction_count(evm_local_account.address)
transaction = evm_local_account.sign_transaction(
transaction_dict={
"to": "0x000000000000000000000000000000000000dEaD",
"value": 10000000000,
"chainId": 84532,
"gas": 21000,
"maxFeePerGas": 1000000000,
"maxPriorityFeePerGas": 1000000000,
"nonce": nonce,
"type": "0x2",
}
)
faucet_hash = await cdp.evm.request_faucet(
address=evm_local_account.address, network="base-sepolia", token="eth"
)
w3.eth.wait_for_transaction_receipt(faucet_hash)
# Use Web3 to send the transaction.
tx_hash = w3.eth.send_raw_transaction(transaction.raw_transaction)
tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Transaction receipt: {tx_receipt}")
asyncio.run(main())You can use CDP SDK to send transactions on Solana.
For complete examples, check out send_transaction.py, send_batch_transaction.py, and send_many_batch_transactions.py.
import asyncio
import base64
from cdp import CdpClient
from solana.rpc.api import Client as SolanaClient
from solders.message import Message
from solders.pubkey import Pubkey as PublicKey
from solders.system_program import TransferParams, transfer
from dotenv import load_dotenv
load_dotenv()
async def main():
async with CdpClient() as cdp:
sender = await cdp.solana.create_account()
await cdp.solana.request_faucet(address=sender.address, token="sol")
connection = SolanaClient("https://api.devnet.solana.com")
source_pubkey = PublicKey.from_string(sender.address)
dest_pubkey = PublicKey.from_string("3KzDtddx4i53FBkvCzuDmRbaMozTZoJBb1TToWhz3JfE")
blockhash = connection.get_latest_blockhash().value.blockhash
transfer_instruction = transfer(TransferParams(
from_pubkey=source_pubkey,
to_pubkey=dest_pubkey,
lamports=1000
))
message = Message.new_with_blockhash([transfer_instruction], source_pubkey, blockhash)
tx_bytes = bytes([1]) + bytes([0] * 64) + bytes(message)
serialized_tx = base64.b64encode(tx_bytes).decode("utf-8")
response = await cdp.solana.send_transaction(
network="solana-devnet",
transaction=serialized_tx
)
print(f"Transaction sent: {response.transaction_signature}")
print(f"Explorer: https://explorer.solana.com/tx/{response.transaction_signature}?cluster=devnet")
if __name__ == "__main__":
asyncio.run(main())For complete examples, check out account.transfer.py and smart_account.transfer.py.
You can transfer tokens between accounts using the transfer function:
sender = await cdp.evm.create_account(name="Sender")
tx_hash = await sender.transfer(
to="0x9F663335Cd6Ad02a37B633602E98866CF944124d",
amount=w3.to_wei("0.001", "ether"),
token="eth",
network="base-sepolia",
)
w3.eth.wait_for_transaction_receipt(tx_hash)To send USDC, the SDK exports a helper function to convert a whole number to a bigint:
from cdp import parse_units
# returns atomic representation of 0.01 USDC, which uses 6 decimal places
amount = parse_units("0.01", 6)
tx_hash = await sender.transfer(
to="0x9F663335Cd6Ad02a37B633602E98866CF944124d",
amount=amount,
token="usdc",
network="base-sepolia",
)Smart Accounts also have a transfer function:
from cdp import parse_units
sender = await cdp.evm.create_smart_account(
owner=privateKeyToAccount(generatePrivateKey()),
);
print("Created smart account", sender);
transfer_result = await sender.transfer(
to="0x9F663335Cd6Ad02a37B633602E98866CF944124d",
amount=parse_units("0.01", 6),
token="usdc",
network="base-sepolia",
)
user_op_result = await sender.wait_for_user_operation(user_op_hash=transfer_result.user_op_hash)Using Smart Accounts, you can also specify a paymaster URL:
transfer_result = await sender.transfer(
to="0x9F663335Cd6Ad02a37B633602E98866CF944124d",
amount="0.01",
token="usdc",
network="base-sepolia",
paymaster_url="https://some-paymaster-url.com",
)You can pass usdc or eth as the token to transfer, or you can pass a contract address directly:
transfer_result = await sender.transfer(
to="0x9F663335Cd6Ad02a37B633602E98866CF944124d",
amount=w3.to_wei("0.000001", "ether"),
token="0x4200000000000000000000000000000000000006", # WETH on Base Sepolia
network="base-sepolia",
)You can pass another account as the to parameter:
from cdp import parse_units
sender = await cdp.evm.create_account(name="Sender")
receiver = await cdp.evm.create_account(name="Receiver")
transfer_result = await sender.transfer(
to=receiver,
amount=parse_units("0.01", 6),
token="usdc",
network="base-sepolia",
)For complete examples, check out solana/account.transfer.py.
You can transfer tokens between accounts using the transfer function, and wait for the transaction to be confirmed using the confirmTransaction function from solana:
import asyncio
from cdp import CdpClient
from solana.rpc.api import Client as SolanaClient
async def main():
async with CdpClient() as cdp:
sender = await cdp.solana.create_account()
connection = SolanaClient("https://api.devnet.solana.com")
signature = await sender.transfer({
to="3KzDtddx4i53FBkvCzuDmRbaMozTZoJBb1TToWhz3JfE",
amount=0.01 * LAMPORTS_PER_SOL,
token="sol",
network=connection,
});
blockhash, lastValidBlockHeight = await connection.get_latest_blockhash()
confirmation = await connection.confirm_transaction(
{
signature,
blockhash,
lastValidBlockHeight,
},
)
if confirmation.value.err:
print(f"Something went wrong! Error: {confirmation.value.err.toString()}")
else:
print(
f"Transaction confirmed: Link: https://explorer.solana.com/tx/${signature}?cluster=devnet",
)To send USDC, the SDK exports a helper function to convert a whole number to a bigint:
from cdp import parse_units
# returns atomic representation of 0.01 USDC, which uses 6 decimal places
amount = parse_units("0.01", 6)
tx_hash = await sender.transfer(
to="0x9F663335Cd6Ad02a37B633602E98866CF944124d",
amount=amount,
token="usdc",
network="devet",
)You can use the CDP SDK to swap tokens on EVM networks using both regular accounts (EOAs) and smart accounts.
The SDK provides three approaches for performing token swaps:
The simplest approach for performing swaps. Creates and executes the swap in a single line of code:
Regular Account (EOA):
from cdp import CdpClient
from cdp.actions.evm.swap import AccountSwapOptions
async with CdpClient() as cdp:
# Retrieve an existing EVM account with funds already in it
account = await cdp.evm.get_or_create_account(name="MyExistingFundedAccount")
# Execute a swap directly on an EVM account in one line
result = await account.swap(
AccountSwapOptions(
network="base",
from_token="0x4200000000000000000000000000000000000006", # WETH on Base
to_token="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", # USDC on Base
from_amount="1000000000000000000", # 1 WETH (18 decimals)
slippage_bps=100 # 1% slippage tolerance
)
)
print(f"Transaction hash: {result.transaction_hash}")Smart Account:
from cdp import CdpClient
from cdp.actions.evm.swap import SmartAccountSwapOptions
async with CdpClient() as cdp:
# Create or retrieve a smart account with funds already in it
owner = await cdp.evm.get_or_create_account(name="MyOwnerAccount")
smart_account = await cdp.evm.get_or_create_smart_account(name="MyExistingFundedSmartAccount", owner=owner)
# Execute a swap directly on a smart account in one line
result = await smart_account.swap(
SmartAccountSwapOptions(
network="base",
from_token="0x4200000000000000000000000000000000000006", # WETH on Base
to_token="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", # USDC on Base
from_amount="1000000000000000000", # 1 WETH (18 decimals)
slippage_bps=100 # 1% slippage tolerance
# Optional: paymaster_url="https://paymaster.example.com" # For gas sponsorship
)
)
print(f"User operation hash: {result.user_op_hash}")
# Wait for the user operation to complete
receipt = await smart_account.wait_for_user_operation(user_op_hash=result.user_op_hash)
print(f"Status: {receipt.status}")Use get_swap_price for quick price estimates and display purposes. This is ideal for showing exchange rates without committing to a swap:
# Get price for swapping 1 WETH to USDC
price = await cdp.evm.get_swap_price(
from_token="0x4200000000000000000000000000000000000006", # WETH
to_token="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", # USDC
from_amount="1000000000000000000", # 1 WETH (18 decimals)
network="base",
taker="0x1234567890123456789012345678901234567890"
)
if price.liquidity_available:
print(f"You'll receive: {price.to_amount} USDC")
print(f"Minimum after slippage: {price.min_to_amount} USDC")Note: get_swap_price does not reserve funds or signal commitment to swap, making it suitable for more frequent price updates with less strict rate limiting - although the data may be slightly less precise.
Use account.quote_swap() / smart_account.quote_swap() when you need full control over the swap process. This returns complete transaction data for execution:
Important: quote_swap() signals a soft commitment to swap and may reserve funds on-chain. It is rate-limited more strictly than get_swap_price to prevent abuse.
Regular Account (EOA):
# Retrieve an existing EVM account with funds already in it
account = await cdp.evm.get_or_create_account(name="MyExistingFundedAccount")
# Step 1: Create a swap quote with full transaction details
swap_quote = await account.quote_swap(
from_token="0x4200000000000000000000000000000000000006", # WETH
to_token="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", # USDC
from_amount="1000000000000000000", # 1 WETH (18 decimals)
network="base",
slippage_bps=100 # 1% slippage tolerance
)
# Step 2: Check if liquidity is available, and/or perform other analysis on the swap quote
if not swap_quote.liquidity_available:
print("Insufficient liquidity for swap")
else:
# Step 3: Execute using the quote
result = await swap_quote.execute()
print(f"Transaction hash: {result.transaction_hash}")Smart Account:
# Create or retrieve a smart account with funds already in it
owner = await cdp.evm.get_or_create_account(name="MyOwnerAccount")
smart_account = await cdp.evm.get_or_create_smart_account(name="MyExistingFundedSmartAccount", owner=owner)
# Step 1: Create a swap quote with full transaction details for smart account
swap_quote = await smart_account.quote_swap(
from_token="0x4200000000000000000000000000000000000006", # WETH
to_token="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", # USDC
from_amount="1000000000000000000", # 1 WETH (18 decimals)
network="base",
slippage_bps=100, # 1% slippage tolerance
# Optional: paymaster_url="https://paymaster.example.com" # For gas sponsorship
)
# Step 2: Check if liquidity is available, and/or perform other analysis on the swap quote
if not swap_quote.liquidity_available:
print("Insufficient liquidity for swap")
else:
# Step 3: Execute using the quote
result = await swap_quote.execute()
print(f"User operation hash: {result.user_op_hash}")
# Wait for the user operation to complete
receipt = await smart_account.wait_for_user_operation(user_op_hash=result.user_op_hash)
print(f"Status: {receipt.status}")- All-in-one (
account.swap()/smart_account.swap()): Best for most use cases. Simple, handles everything automatically. - Price only (
get_swap_price): For displaying exchange rates, building price calculators, or checking liquidity without executing. Suitable when frequent price updates are needed - although the data may be slightly less precise. - Create then execute (
account.quote_swap()/smart_account.quote_swap()): When you need to inspect swap details, implement custom logic, or handle complex scenarios before execution. Note: May reserve funds on-chain and is more strictly rate-limited.
- Regular accounts (EOAs) return
transaction_hashand execute immediately on-chain - Smart accounts return
user_op_hashand execute via user operations with optional gas sponsorship through paymasters - Smart accounts require an owner account for signing operations
- Smart accounts support batch operations and advanced account abstraction features
All approaches handle Permit2 signatures automatically for ERC20 token swaps. Make sure tokens have proper allowances set for the Permit2 contract before swapping.
To help you get started with token swaps in your application, we provide the following fully-working examples demonstrating different scenarios:
Regular account (EOA) swap examples:
- Execute a swap transaction using account (RECOMMENDED) - All-in-one regular account swap execution
- Quote swap using account convenience method - Account convenience method for creating quotes
- Two-step quote and execute process - Detailed two-step approach with analysis
Smart account swap examples:
- Execute a swap transaction using smart account (RECOMMENDED) - All-in-one smart account swap execution with user operations and optional paymaster support
- Quote swap using smart account convenience method - Smart account convenience method for creating quotes
- Two-step quote and execute process - Detailed two-step approach with analysis
You can create an EIP-7702 delegation for an existing EOA, upgrading it with smart account capabilities on supported networks. The delegated EOA can then use batched transactions and gas sponsorship via paymaster.
import asyncio
from cdp import CdpClient
async def main():
async with CdpClient() as cdp:
account = await cdp.evm.get_or_create_account(name="MyAccount")
delegation_operation_id = await cdp.evm.create_evm_eip7702_delegation(
address=account.address,
network="base-sepolia",
enable_spend_permissions=False, # optional, defaults to False
)
# Wait for the delegation operation to complete
delegation_operation = await cdp.evm.wait_for_evm_eip7702_delegation_operation_status(
delegation_operation_id=delegation_operation_id,
)
print(f"Delegation confirmed (status: {delegation_operation.status})")
asyncio.run(main())For a runnable example that includes faucet and receipt waiting, see examples/python/evm/eip7702/create_eip7702_delegation.py.
For EVM, we support Smart Accounts which are account-abstraction (ERC-4337) accounts. Currently there is only support for Base Sepolia and Base Mainnet for Smart Accounts.
import asyncio
from cdp import CdpClient
async def main():
async with CdpClient() as cdp:
evm_account = await cdp.evm.create_account()
smart_account = await cdp.evm.create_smart_account(
owner=evm_account
)
asyncio.run(main())import asyncio
from cdp.evm_call_types import EncodedCall
from cdp import CdpClient
async def main():
async with CdpClient() as cdp:
user_operation = await cdp.evm.send_user_operation(
smart_account=smart_account,
network="base-sepolia",
calls=[
EncodedCall(
to="0x0000000000000000000000000000000000000000",
value=0,
data="0x"
)
]
)
asyncio.run(main())In Base Sepolia, all user operations are gasless by default. If you'd like to specify a different paymaster, you can do so as follows:
import asyncio
from cdp.evm_call_types import EncodedCall
from cdp import CdpClient
async def main():
async with CdpClient() as cdp:
user_operation = await cdp.evm.send_user_operation(
smart_account=smart_account,
network="base-sepolia",
calls=[],
paymaster_url="https://some-paymaster-url.com"
)
asyncio.run(main())Account objects have actions that can be used to interact with the account. These can be used in place of the cdp client.
For example, instead of:
token_balances = await cdp.evm.list_token_balances(
address=account.address,
network="base-sepolia"
)You can use the list_token_balances action:
balances = await account.list_token_balances(
network="base-sepolia",
)EvmAccount supports the following actions:
list_token_balancesrequest_faucetsign_transactionsend_transactiontransferswapquote_swap
EvmSmartAccount supports the following actions:
list_token_balancesrequest_faucetsend_user_operationwait_for_user_operationget_user_operationtransferswapquote_swap
SolanaAccount supports the following actions:
sign_messagesign_transactionrequest_faucet
You can use the policies SDK to manage sets of rules that govern the behavior of accounts and projects, such as enforce allowlists and denylists.
This policy will accept any account sending less than a specific amount of ETH to a specific address.
policy = await cdp.policies.create_policy(
policy=CreatePolicyOptions(
scope="project",
description="Project-wide Allowlist Example",
rules=[
SignEvmTransactionRule(
action="accept",
criteria=[
EthValueCriterion(
ethValue="1000000000000000000",
operator="<=",
),
EvmAddressCriterion(
addresses=["0x000000000000000000000000000000000000dEaD"],
operator="in",
),
],
),
],
)
)This policy will accept any transaction with a value less than or equal to 1 ETH to a specific address.
policy = await cdp.policies.create_policy(
policy=CreatePolicyOptions(
scope="account",
description="Account Allowlist Example",
rules=[
SignEvmTransactionRule(
action="accept",
criteria=[
EthValueCriterion(
ethValue="1000000000000000000",
operator="<=",
),
EvmAddressCriterion(
addresses=["0x000000000000000000000000000000000000dEaD"],
operator="in",
),
],
),
],
)
)policy = await cdp.policies.create_policy(
policy=CreatePolicyOptions(
scope="account",
description="Account Allowlist Example",
rules=[
SignSolanaTransactionRule(
action="accept",
criteria=[
SolanaAddressCriterion(
addresses=["123456789abcdef123456789abcdef12"],
operator="in",
),
],
)
],
)
)You can filter by account:
policies = await cdp.policies.list_policies(scope="account")You can also filter by project:
policies = await cdp.policies.list_policies(scope="project")Or you can list all of them without any filter:
policies = await cdp.policies.list_policies()policies = await cdp.policies.get_policy_by_id(id="__POLICY_ID__")This policy will update an existing policy to accept transactions to any address except one.
policy = await cdp.policies.update_policy(
id="__POLICY_ID__",
policy=UpdatePolicyOptions(
description="Updated Denylist Policy",
rules=[
SignEvmTransactionRule(
action="accept",
criteria=[
EvmAddressCriterion(
addresses=["0x000000000000000000000000000000000000dEaD"],
operator="not in",
),
],
)
],
)
)[!WARNING] Attempting to delete an account-level policy in-use by at least one account will fail.
policy = await cdp.policies.delete_policy(id="__POLICY_ID__")We currently support the following policy rules:
Server wallet rules:
- SignEvmTransactionRule
- SendEvmTransactionRule
- SignEvmMessageRule
- SignEvmTypedDataRule
- SignSolanaTransactionRule
- SendSolanaTransactionRule
- SignEvmHashRule
- PrepareUserOperationRule
- SendUserOperationRule
End user embedded wallet rules:
SignEndUserEvmTransactionRule— operation:signEndUserEvmTransaction(criteria:ethValue,evmAddress,evmData,netUSDChange)SendEndUserEvmTransactionRule— operation:sendEndUserEvmTransaction(criteria:ethValue,evmAddress,evmNetwork,evmData,netUSDChange)SignEndUserEvmMessageRule— operation:signEndUserEvmMessage(criteria:evmMessage)SignEndUserEvmTypedDataRule— operation:signEndUserEvmTypedData(criteria:evmTypedDataField,evmTypedDataVerifyingContract)SignEndUserSolTransactionRule— operation:signEndUserSolTransaction(criteria:solAddress,solValue,splAddress,splValue,mintAddress,solData,programId)SendEndUserSolTransactionRule— operation:sendEndUserSolTransaction(criteria:solAddress,solValue,splAddress,splValue,mintAddress,solData,programId,solNetwork)SignEndUserSolMessageRule— operation:signEndUserSolMessage(criteria:solMessage)
End user rules use the same criteria types as their server wallet counterparts. For example, SignEndUserEvmTransactionRule supports the same EthValueCriterion, EvmAddressCriterion, and EvmDataCriterion criteria as SignEvmTransactionRule.
You can create policies that govern end-user operations using the same criteria types available for server wallet policies. The only difference is the rule type, which targets end-user-specific actions.
This policy restricts end-user EVM transaction signing to a max value and allowlisted recipients — the same criteria used in SignEvmTransactionRule:
from cdp.policies.types import (
CreatePolicyOptions,
SignEndUserEvmTransactionRule,
EthValueCriterion,
EvmAddressCriterion,
)
policy = await cdp.policies.create_policy(
policy=CreatePolicyOptions(
scope="project",
description="End User EVM Policy",
rules=[
SignEndUserEvmTransactionRule(
action="accept",
criteria=[
EthValueCriterion(
ethValue="1000000000000000000", # 1 ETH in wei
operator="<=",
),
EvmAddressCriterion(
addresses=["0x000000000000000000000000000000000000dEaD"],
operator="in",
),
],
),
],
)
)This policy restricts end-user Solana transaction signing to allowlisted recipients under a SOL value threshold — the same criteria used in SignSolanaTransactionRule:
from cdp.policies.types import (
CreatePolicyOptions,
SignEndUserSolTransactionRule,
SolAddressCriterion,
SolValueCriterion,
)
policy = await cdp.policies.create_policy(
policy=CreatePolicyOptions(
scope="project",
description="End User Solana Policy",
rules=[
SignEndUserSolTransactionRule(
action="accept",
criteria=[
SolAddressCriterion(
addresses=["11111111111111111111111111111111"],
operator="in",
),
SolValueCriterion(
solValue="1000000000", # 1 SOL in lamports
operator="<=",
),
],
),
],
)
)For a comprehensive example demonstrating all 7 end-user operations, see create_end_user_policy.py.
You can use the End User SDK to manage the users of your applications.
Create an end user with authentication methods and optional accounts:
import asyncio
from cdp import CdpClient
from cdp.openapi_client.models.authentication_method import AuthenticationMethod
from cdp.openapi_client.models.email_authentication import EmailAuthentication
from cdp.openapi_client.models.create_end_user_request_evm_account import CreateEndUserRequestEvmAccount
from cdp.openapi_client.models.create_end_user_request_solana_account import CreateEndUserRequestSolanaAccount
async def main():
async with CdpClient() as cdp:
# Create an end user with email authentication and accounts
end_user = await cdp.end_user.create_end_user(
authentication_methods=[
AuthenticationMethod(EmailAuthentication(type="email", email="user@example.com"))
],
evm_account=CreateEndUserRequestEvmAccount(create_smart_account=True),
solana_account=CreateEndUserRequestSolanaAccount(create_smart_account=False),
)
print(f"Created end user: {end_user}")
asyncio.run(main())Import an existing private key for an end user:
import asyncio
from cdp import CdpClient
from cdp.openapi_client.models.authentication_method import AuthenticationMethod
from cdp.openapi_client.models.email_authentication import EmailAuthentication
async def main():
async with CdpClient() as cdp:
# Import an end user with an EVM private key
end_user = await cdp.end_user.import_end_user(
authentication_methods=[
AuthenticationMethod(EmailAuthentication(type="email", email="user@example.com"))
],
private_key="0x...", # EVM private key (hex string)
key_type="evm",
)
print(f"Imported end user: {end_user}")
asyncio.run(main())You can also import a Solana private key:
end_user = await cdp.end_user.import_end_user(
authentication_methods=[
AuthenticationMethod(EmailAuthentication(type="email", email="user@example.com"))
],
private_key="3Kzj...", # base58 encoded
key_type="solana",
)Add an additional EVM EOA (Externally Owned Account) to an existing end user. You can call the method directly on the EndUserAccount object:
result = await end_user.add_evm_account()
print(f"Added EVM account: {result.evm_account.address}")Or use the client method with a user ID:
result = await cdp.end_user.add_end_user_evm_account(user_id=end_user.user_id)
print(f"Added EVM account: {result.evm_account.address}")Add an EVM smart account to an existing end user. You can call the method directly on the EndUserAccount object:
result = await end_user.add_evm_smart_account(enable_spend_permissions=True)
print(f"Added EVM smart account: {result.evm_smart_account.address}")Or use the client method with a user ID:
result = await cdp.end_user.add_end_user_evm_smart_account(
user_id=end_user.user_id,
enable_spend_permissions=True,
)
print(f"Added EVM smart account: {result.evm_smart_account.address}")Add an additional Solana account to an existing end user. You can call the method directly on the EndUserAccount object:
result = await end_user.add_solana_account()
print(f"Added Solana account: {result.solana_account.address}")Or use the client method with a user ID:
result = await cdp.end_user.add_end_user_solana_account(user_id=end_user.user_id)
print(f"Added Solana account: {result.solana_account.address}")When your end user has signed in with an Embedded Wallet, you can check whether the access token they were granted is valid, and which of your user's it is associated with.
try:
end_user = await cdp.end_user.validate_access_token(
access_token=access_token,
)
# Access token is valid
except Exception as e:
# Access token is invalid or expiredThis SDK also contains simple tools for authenticating REST API requests to the Coinbase Developer Platform (CDP). See the Auth README for more details.
This SDK contains error reporting functionality that sends error events to CDP. If you would like to disable this behavior, you can set the DISABLE_CDP_ERROR_REPORTING environment variable to true.
DISABLE_CDP_ERROR_REPORTING=trueThis SDK contains usage tracking functionality that sends usage events to CDP. If you would like to disable this behavior, you can set the DISABLE_CDP_USAGE_TRACKING environment variable to true.
DISABLE_CDP_USAGE_TRACKING=trueThis project is licensed under the MIT License - see the LICENSE file for details.
For feature requests, feedback, or questions, please reach out to us in the #cdp-sdk channel of the Coinbase Developer Platform Discord.
If you discover a security vulnerability within this SDK, please see our Security Policy for disclosure information.