<100 subscribers
Today, let’s talk about a common pain point in the blockchain ecosystem.
If you want your users to enjoy a web2-like experience in an onchain application, you have to hide all the complexities, such as managing wallets, enforcing compliance rules, and sometimes even sponsoring gas. Doing all this securely and at scale is tough.
That’s where Server Wallets from Coinbase Developer Platform (CDP) come in.
They let you create and manage wallets programmatically, send transactions, enforce policies, and sponsor gas all through secure APIs. You can focus on your app logic while CDP handles the cryptography and infrastructure.
In our previous video, we covered how CDP secures private keys and how the wallet infrastructure works behind the scenes.
This time, we’re going hands-on with Python SDK (also available in TypeScript).
By the end of this walkthrough, you’ll be able to:
⚙ Set up the CDP SDK and securely manage environment variables
🔑 Create new Ethereum accounts and import existing ones
💸 Send transactions and fetch balances
🧩 Enforce policies with both failing and successful examples
⛽ Enable gas sponsorship for seamless, gasless UX
(Watch the Full Video here)
Create a requirements.txtfile and add these inside it:
cdp-sdk>=0.0.1
python-dotenv>=1.0.0
web3>=6.0.0
cryptography>=41.0.0
eth-account>=0.13.0Then run this below command in your terminal:
pip install -r requirements.txtThen we’ll create a reusable singleton instance of the CDP client inside utils.py
# Singleton CDP client instance
_cdp_instance = None
async def get_cdp_client() -> CdpClient:
"""
Get or create a singleton CDP client instance
"""
global _cdp_instance
if _cdp_instance is None:
# Verify environment variables
api_key_id = os.getenv('CDP_API_KEY_ID')
api_key_secret = os.getenv('CDP_API_KEY_SECRET')
wallet_secret = os.getenv('CDP_WALLET_SECRET')
if not all([api_key_id, api_key_secret, wallet_secret]):
raise ValueError("Missing required environment variables. Please check your .env file.")
_cdp_instance = CdpClient()
return _cdp_instanceCreating, fetching, or importing accounts is very straightforward with server wallets
You can create an account using
cdp = await get_cdp_client()
# Create Alice — a new account
timestamp = datetime.now().strftime(“%Y%m%d%H%M%S”)
alice = await cdp.evm.create_or_get_account(name=f”demo-account-new-20251001002758")
print(f”Alice: {alice.address}”)To import an account using an existing private key
# Import Bob — from private key
bob_key = BOB_PRIVATE_KEY[2:] if BOB_PRIVATE_KEY.startswith(‘0x’) else BOB_PRIVATE_KEY
# Calculate the expected address from the private key
eth_account = Account.from_key(BOB_PRIVATE_KEY)
expected_address = eth_account.address
bob = await cdp.evm.import_account(
private_key=bob_key,
name=f”bob-{timestamp}”
)
print(f”Bob: {bob.address}”)You can use evm.send_transactionfrom the cdp client to initialise a transaction
tx_response = await cdp.evm.send_transaction(
address=alice.address,
transaction=TransactionRequestEIP1559(
to=bob.address,
value=w3.to_wei(amount_eth, “ether”),
),
network=NETWORK,
)And in the same way to fetch the balance of the user we use list_token_balancesas below
balances = await cdp.evm.list_token_balances(
address=address,
network=NETWORK
)This will provide the entire list of tokens the user is holding, and can be parsed as follow
if balances:
balance_list = balances.balances if hasattr(balances, ‘balances’) else balances
print(f”{address}:”)
for balance in balance_list:
symbol = balance.token.symbol if hasattr(balance, ‘token’) else ‘ETH’
if hasattr(balance, ‘amount’) and hasattr(balance.amount, ‘amount’):
amount = balance.amount.amount
decimals = balance.amount.decimals if hasattr(balance.amount, ‘decimals’) else 18
readable_amount = amount / (10 ** decimals)
print(f” {symbol}: {readable_amount}”)
return balancesCDP Server Wallets allow you to attach policies that define allowed actions or spending limits.
For example, a policy might restrict transfers above a threshold or to certain addresses.
You can enable this from your CDP dashboard by editing an existing template or writing your own custom rules.

Once a policy is created, try to test with a transaction and your transaction will fail with an API error if in case it violates the policy.
This can play a key role in compliance and operational safety.
To deliver a true web2-like experience, CDP lets you sponsor gas fees for your users by abstracting all the complexities of managing a paymaster.
You can enable sponsorship through your CDP console under onchain tool > paymaster or click here
Once you enable it, you’ll get the Paymaster URL, which should be passed as a parameter for gasless txn in the code.
Before we jump into the gasless txn, there’s an important to thing to note, that is gasless txn works only with smart accounts so it’s important to create one, and CDP let’s you do that with a single line of code using their get_or_create_smart_accountfunction. But these smart accounts needs an owner account which is a EOA.
Here’s how we create smart accounts
async def _get_or_create_smart_account(cdp: Any, name: str, display_name: str) -> Any:
try:
owner = await cdp.evm.get_or_create_account(name=name)
account = await cdp.evm.get_or_create_smart_account(name=name, owner=owner)
print(f”{display_name}: {account.address}”)
return account
except Exception as e:
print(f”x Failed to create: {str(e)}”)
raise
async def create_smart_accounts() -> Tuple[Any, Any]:
cdp = await get_cdp_client()
alice = await _get_or_create_smart_account(cdp, “alice”, “Alice”)
bob = await _get_or_create_smart_account(cdp, “bob”, “Bob”)
return alice, bobThis makes transactions feel instant and free from the user’s perspective, while you control limits via policy.
You can call all these function in main function such as this
async def main():
“””
Main orchestration function
“””
try:
# Create accounts
alice, bob = await create_accounts()
# Initial balances
print(“\nInitial Balances:”)
await fetch_balance(alice.address)
await fetch_balance(bob.address)
# Send transaction
print(“\nSending Transaction:”)
await send_tokens(alice, bob, amount_eth=0.001)
# Final balances
print(“\nFinal Balances:”)
await fetch_balance(alice.address)
await fetch_balance(bob.address)
except Exception as e:
print(f”Error: {str(e)}”)
import traceback
traceback.print_exc()
finally:
await close_cdp_client()When working with Server Wallets, keep these best practices in mind:
🔒 Always load secrets from environment variables — never hardcode keys
🔁 Rotate wallet secrets regularly
🧾 Redact API keys and private key material in logs
Keep access scoped and audit permissions frequently
Beyond what we built today, Server Wallets V2 also support:
🧠 Smart accounts for account abstraction and batching
🔄 Token swaps for direct ERC-20 swaps
🧰 Spend permissions for granular, scoped access
🏢 Managed mode for team-scale infrastructure
These are optional but powerful tools as your app grows.
You’ve just built and tested Server Wallets V2 with Python — from setup to transactions, policy enforcement, and gas sponsorship.
You now have a secure, programmable wallet infrastructure ready for production.
For deeper docs and SDK examples, visit the official Coinbase Developer Platform documentation.
And follow HeimLabs for more technical walkthroughs, tutorials, and hands-on guides for Web3 developers.
CDP Docs
Python SDK Reference
HeimLabs
Clap it up if this saved you time!
Follow HeimLabs for unapologetically practical Web3 dev content.
Twitter, LinkedIn.
Today, let’s talk about a common pain point in the blockchain ecosystem.
If you want your users to enjoy a web2-like experience in an onchain application, you have to hide all the complexities, such as managing wallets, enforcing compliance rules, and sometimes even sponsoring gas. Doing all this securely and at scale is tough.
That’s where Server Wallets from Coinbase Developer Platform (CDP) come in.
They let you create and manage wallets programmatically, send transactions, enforce policies, and sponsor gas all through secure APIs. You can focus on your app logic while CDP handles the cryptography and infrastructure.
In our previous video, we covered how CDP secures private keys and how the wallet infrastructure works behind the scenes.
This time, we’re going hands-on with Python SDK (also available in TypeScript).
By the end of this walkthrough, you’ll be able to:
⚙ Set up the CDP SDK and securely manage environment variables
🔑 Create new Ethereum accounts and import existing ones
💸 Send transactions and fetch balances
🧩 Enforce policies with both failing and successful examples
⛽ Enable gas sponsorship for seamless, gasless UX
(Watch the Full Video here)
Create a requirements.txtfile and add these inside it:
cdp-sdk>=0.0.1
python-dotenv>=1.0.0
web3>=6.0.0
cryptography>=41.0.0
eth-account>=0.13.0Then run this below command in your terminal:
pip install -r requirements.txtThen we’ll create a reusable singleton instance of the CDP client inside utils.py
# Singleton CDP client instance
_cdp_instance = None
async def get_cdp_client() -> CdpClient:
"""
Get or create a singleton CDP client instance
"""
global _cdp_instance
if _cdp_instance is None:
# Verify environment variables
api_key_id = os.getenv('CDP_API_KEY_ID')
api_key_secret = os.getenv('CDP_API_KEY_SECRET')
wallet_secret = os.getenv('CDP_WALLET_SECRET')
if not all([api_key_id, api_key_secret, wallet_secret]):
raise ValueError("Missing required environment variables. Please check your .env file.")
_cdp_instance = CdpClient()
return _cdp_instanceCreating, fetching, or importing accounts is very straightforward with server wallets
You can create an account using
cdp = await get_cdp_client()
# Create Alice — a new account
timestamp = datetime.now().strftime(“%Y%m%d%H%M%S”)
alice = await cdp.evm.create_or_get_account(name=f”demo-account-new-20251001002758")
print(f”Alice: {alice.address}”)To import an account using an existing private key
# Import Bob — from private key
bob_key = BOB_PRIVATE_KEY[2:] if BOB_PRIVATE_KEY.startswith(‘0x’) else BOB_PRIVATE_KEY
# Calculate the expected address from the private key
eth_account = Account.from_key(BOB_PRIVATE_KEY)
expected_address = eth_account.address
bob = await cdp.evm.import_account(
private_key=bob_key,
name=f”bob-{timestamp}”
)
print(f”Bob: {bob.address}”)You can use evm.send_transactionfrom the cdp client to initialise a transaction
tx_response = await cdp.evm.send_transaction(
address=alice.address,
transaction=TransactionRequestEIP1559(
to=bob.address,
value=w3.to_wei(amount_eth, “ether”),
),
network=NETWORK,
)And in the same way to fetch the balance of the user we use list_token_balancesas below
balances = await cdp.evm.list_token_balances(
address=address,
network=NETWORK
)This will provide the entire list of tokens the user is holding, and can be parsed as follow
if balances:
balance_list = balances.balances if hasattr(balances, ‘balances’) else balances
print(f”{address}:”)
for balance in balance_list:
symbol = balance.token.symbol if hasattr(balance, ‘token’) else ‘ETH’
if hasattr(balance, ‘amount’) and hasattr(balance.amount, ‘amount’):
amount = balance.amount.amount
decimals = balance.amount.decimals if hasattr(balance.amount, ‘decimals’) else 18
readable_amount = amount / (10 ** decimals)
print(f” {symbol}: {readable_amount}”)
return balancesCDP Server Wallets allow you to attach policies that define allowed actions or spending limits.
For example, a policy might restrict transfers above a threshold or to certain addresses.
You can enable this from your CDP dashboard by editing an existing template or writing your own custom rules.

Once a policy is created, try to test with a transaction and your transaction will fail with an API error if in case it violates the policy.
This can play a key role in compliance and operational safety.
To deliver a true web2-like experience, CDP lets you sponsor gas fees for your users by abstracting all the complexities of managing a paymaster.
You can enable sponsorship through your CDP console under onchain tool > paymaster or click here
Once you enable it, you’ll get the Paymaster URL, which should be passed as a parameter for gasless txn in the code.
Before we jump into the gasless txn, there’s an important to thing to note, that is gasless txn works only with smart accounts so it’s important to create one, and CDP let’s you do that with a single line of code using their get_or_create_smart_accountfunction. But these smart accounts needs an owner account which is a EOA.
Here’s how we create smart accounts
async def _get_or_create_smart_account(cdp: Any, name: str, display_name: str) -> Any:
try:
owner = await cdp.evm.get_or_create_account(name=name)
account = await cdp.evm.get_or_create_smart_account(name=name, owner=owner)
print(f”{display_name}: {account.address}”)
return account
except Exception as e:
print(f”x Failed to create: {str(e)}”)
raise
async def create_smart_accounts() -> Tuple[Any, Any]:
cdp = await get_cdp_client()
alice = await _get_or_create_smart_account(cdp, “alice”, “Alice”)
bob = await _get_or_create_smart_account(cdp, “bob”, “Bob”)
return alice, bobThis makes transactions feel instant and free from the user’s perspective, while you control limits via policy.
You can call all these function in main function such as this
async def main():
“””
Main orchestration function
“””
try:
# Create accounts
alice, bob = await create_accounts()
# Initial balances
print(“\nInitial Balances:”)
await fetch_balance(alice.address)
await fetch_balance(bob.address)
# Send transaction
print(“\nSending Transaction:”)
await send_tokens(alice, bob, amount_eth=0.001)
# Final balances
print(“\nFinal Balances:”)
await fetch_balance(alice.address)
await fetch_balance(bob.address)
except Exception as e:
print(f”Error: {str(e)}”)
import traceback
traceback.print_exc()
finally:
await close_cdp_client()When working with Server Wallets, keep these best practices in mind:
🔒 Always load secrets from environment variables — never hardcode keys
🔁 Rotate wallet secrets regularly
🧾 Redact API keys and private key material in logs
Keep access scoped and audit permissions frequently
Beyond what we built today, Server Wallets V2 also support:
🧠 Smart accounts for account abstraction and batching
🔄 Token swaps for direct ERC-20 swaps
🧰 Spend permissions for granular, scoped access
🏢 Managed mode for team-scale infrastructure
These are optional but powerful tools as your app grows.
You’ve just built and tested Server Wallets V2 with Python — from setup to transactions, policy enforcement, and gas sponsorship.
You now have a secure, programmable wallet infrastructure ready for production.
For deeper docs and SDK examples, visit the official Coinbase Developer Platform documentation.
And follow HeimLabs for more technical walkthroughs, tutorials, and hands-on guides for Web3 developers.
CDP Docs
Python SDK Reference
HeimLabs
Clap it up if this saved you time!
Follow HeimLabs for unapologetically practical Web3 dev content.
Twitter, LinkedIn.


Share Dialog
Share Dialog
HeimLabs
HeimLabs
No comments yet