Overview
The CoW Protocol Python SDK provides a high-level swap_tokens function that enables you to execute token swaps with built-in MEV protection through batch auctions. This guide covers the complete swap workflow including token approval and order execution.
Prerequisites
Before swapping tokens, ensure you have:
An Ethereum account with a private key
Sufficient balance of the token you want to sell
Token approval for the CoW Protocol Vault Relayer
Token Approval Flow
Before calling swap_tokens, you must approve the CowContractAddress.VAULT_RELAYER to spend your sell token. The swap will fail without proper approval.
Import Required Modules
from web3 import Web3, Account
from web3.types import Wei
from cowdao_cowpy.cow.swap import swap_tokens
from cowdao_cowpy.common.chains import Chain
from cowdao_cowpy.common.constants import CowContractAddress
Approve Token Spending
Grant the Vault Relayer permission to spend your tokens: from eth_typing import ChecksumAddress
# Initialize Web3 and account
w3 = Web3(Web3.HTTPProvider( 'YOUR_RPC_URL' ))
account = Account.from_key( 'YOUR_PRIVATE_KEY' )
# Token addresses
sell_token: ChecksumAddress = Web3.to_checksum_address(
"0xbe72E441BF55620febc26715db68d3494213D8Cb" # USDC
)
# ERC20 ABI for approve function
erc20_abi = [
{
"constant" : False ,
"inputs" : [
{ "name" : "spender" , "type" : "address" },
{ "name" : "amount" , "type" : "uint256" }
],
"name" : "approve" ,
"outputs" : [{ "name" : "" , "type" : "bool" }],
"type" : "function"
}
]
# Create contract instance
token_contract = w3.eth.contract( address = sell_token, abi = erc20_abi)
# Approve Vault Relayer
vault_relayer = CowContractAddress. VAULT_RELAYER .value
approve_amount = Wei( 2 ** 256 - 1 ) # Max approval
tx = token_contract.functions.approve(
vault_relayer,
approve_amount
).build_transaction({
'from' : account.address,
'nonce' : w3.eth.get_transaction_count(account.address),
})
signed_tx = account.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction)
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
Execute the Swap
Once approval is complete, you can execute the swap.
Basic Token Swap
Here’s a complete example of swapping tokens:
import os
import asyncio
from dotenv import load_dotenv
from web3 import Account, Web3
from web3.types import Wei
from cowdao_cowpy.cow.swap import swap_tokens
from cowdao_cowpy.common.chains import Chain
# Token addresses (Sepolia testnet)
BUY_TOKEN = Web3.to_checksum_address(
"0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14" # WETH
)
SELL_TOKEN = Web3.to_checksum_address(
"0xbe72E441BF55620febc26715db68d3494213D8Cb" # USDC
)
# Amount to sell (50 USDC with 18 decimals)
SELL_AMOUNT = Wei( 50_000_000_000_000_000_000 )
load_dotenv()
PRIVATE_KEY = os.getenv( "PRIVATE_KEY" )
ACCOUNT = Account.from_key( PRIVATE_KEY )
async def main ():
completed_order = await swap_tokens(
amount = SELL_AMOUNT ,
account = ACCOUNT ,
chain = Chain. SEPOLIA ,
sell_token = SELL_TOKEN ,
buy_token = BUY_TOKEN ,
)
print ( f "Order UID: { completed_order.uid } " )
print ( f "Order URL: { completed_order.url } " )
if __name__ == "__main__" :
asyncio.run(main())
Advanced Configuration
The swap_tokens function accepts several optional parameters for advanced use cases:
Slippage Tolerance
Custom Validity Period
Partially Fillable Orders
Safe/Multisig Orders
Custom App Data
# Default slippage is 0.5% (0.005)
# Increase slippage tolerance to 1%
completed_order = await swap_tokens(
amount = SELL_AMOUNT ,
account = ACCOUNT ,
chain = Chain. MAINNET ,
sell_token = SELL_TOKEN ,
buy_token = BUY_TOKEN ,
slippage_tolerance = 0.01 , # 1%
)
Function Parameters
The swap_tokens function accepts the following parameters:
Parameter Type Required Default Description amountWeiYes - Amount of sell token to swap accountLocalAccountYes - Ethereum account for signing chainChainYes - Target blockchain network sell_tokenChecksumAddressYes - Address of token to sell buy_tokenChecksumAddressYes - Address of token to buy safe_addressChecksumAddress | NoneNo NoneSafe/multisig address (if applicable) app_datastrNo Default hash Custom app data hash valid_toint | NoneNo Quote validity Order expiration (Unix timestamp) envEnvsNo "prod"Environment ("prod", "staging") slippage_tolerancefloatNo 0.005Maximum slippage (0.005 = 0.5%) partially_fillableboolNo FalseAllow partial order fills
Return Value
The function returns a CompletedOrder object with:
@dataclass
class CompletedOrder :
uid: UID # Unique order identifier
url: str # CoW Explorer URL for tracking
How It Works
Quote Request
The function requests a quote from the OrderBook API with your sell amount and tokens. Source: swap.py:64-75
Order Construction
Creates an Order object with:
Sell/buy amounts (with slippage protection)
Validity period
Token balances configuration
App data metadata
Source: swap.py:83-100
Order Signing
Signs the order using EIP-712 typed data:
For regular accounts: ECDSA signature
For Safe addresses: PreSign signature
Source: swap.py:102-109
Order Submission
Posts the signed order to the OrderBook API for solvers to execute. Source: swap.py:110-113
Supported Chains
The SDK supports swapping on the following networks:
Ethereum Mainnet - Chain.MAINNET
Gnosis Chain - Chain.GNOSIS
Arbitrum One - Chain.ARBITRUM_ONE
Base - Chain.BASE
Sepolia Testnet - Chain.SEPOLIA
Use Chain.SEPOLIA for testing without spending real funds. Get testnet tokens from Sepolia faucets.
Error Handling
import asyncio
from cowdao_cowpy.common.api.errors import UnexpectedResponseError
async def safe_swap ():
try :
completed_order = await swap_tokens(
amount = SELL_AMOUNT ,
account = ACCOUNT ,
chain = Chain. MAINNET ,
sell_token = SELL_TOKEN ,
buy_token = BUY_TOKEN ,
)
print ( f "Swap successful: { completed_order.url } " )
except UnexpectedResponseError as e:
print ( f "API error: { e } " )
except ValueError as e:
print ( f "Invalid parameter: { e } " )
except Exception as e:
print ( f "Unexpected error: { e } " )
asyncio.run(safe_swap())
Common Issues
Insufficient Allowance : If you get an error during swap execution, verify that the Vault Relayer has sufficient token allowance.
Fee Handling : CoW Protocol charges zero protocol fees. The fee_amount is set to "0" in the order. You only pay network fees when the order settles.
Slippage vs. Price : The buy_amount in the order is calculated as quote.buyAmount * (1 - slippage_tolerance). This ensures you receive at least your expected minimum.
Next Steps