Prime Send/Receive Go is a deposit and withdrawal management system with Coinbase Prime API integration, designed to work out of the box with Coinbase Prime's scalable deposit address solution.
This is a sample application; test thoroughly and verify it meets your requirements before using.
This system processes crypto deposits and withdrawals by monitoring Prime API transactions and maintaining user balances in a subledger.
Core Features:
- Deposit and withdrawal detection from Prime API
- Withdrawal confirmation tracking via idempotency keys
- Subledger with O(1) balance lookups
- Complete audit trail and transaction history
- Configurable via environment variables
Architecture Diagrams - System architecture, deposit/withdrawal flows, and database schema
E2E Testing Guide - Step-by-step commands to test all functionality
The subledger mirrors Coinbase Prime's asset-agnostic trading balance model. Balances are tracked per asset symbol only, not per network.
Example:
- User deposits 1 USDC on Base β Balance: 1 USDC
- User deposits 2 USDC on Ethereum β Balance: 3 USDC (aggregated)
- User withdraws 1 USDC on Base β Balance: 2 USDC
This design reflects how Prime manages trading balances internally, where the same asset on different networks contributes to a unified balance per symbol.
Copy and configure environment variables:
cp .env.example .envEdit .env with your Prime API credentials and desired configuration. All settings have sensible defaults except the Prime API credentials which are required.
Required Environment Variables:
# Prime API credentials (required)
PRIME_ACCESS_KEY=your-prime-access-key-here
PRIME_PASSPHRASE=your-prime-passphrase-here
PRIME_SIGNING_KEY=your-prime-signing-key-hereOptional Configuration:
# Database configuration
DATABASE_PATH=addresses.db
DB_MAX_OPEN_CONNS=25
DB_MAX_IDLE_CONNS=5
DB_CONN_MAX_LIFETIME=5m
DB_CONN_MAX_IDLE_TIME=30s
DB_PING_TIMEOUT=5s
CREATE_DUMMY_USERS=false # Set to true to create 3 dummy test users on first run
# Listener configuration
LISTENER_LOOKBACK_WINDOW=6h # How far back to check for missed transactions
LISTENER_POLLING_INTERVAL=30s # How often to poll Prime API
LISTENER_CLEANUP_INTERVAL=15m # How often to clean up processed transaction cache
ASSETS_FILE=assets.yaml # Asset configuration fileAPI Usage Notes:
- The system fetches up to 500 transactions per wallet per polling cycle
- With the default 30-second polling interval, this provides adequate processing time per transaction
- The 6-hour lookback window ensures no transactions are missed between polling cycles
- If you exceed 500 transactions in 30 seconds, consider adjusting the polling interval
The application includes an assets.yaml file with default cryptocurrencies to monitor. You can modify this file to add or remove assets as needed.
Default configuration:
assets:
- symbol: "USDC"
network: "ethereum-mainnet"
- symbol: "USDC"
network: "base-mainnet"
- symbol: "SOL"
network: "solana-mainnet"You must specify the appropriate network for each asset (e.g., ethereum-mainnet, base-mainnet). A full list of supported networks is returned by the List Assets REST API.
To customize: Edit assets.yaml to add or remove assets based on your needs.
By default, the system does not create any users. You have several options for adding users:
Option 1: Use the adduser CLI command (Recommended)
Add users with automatic address generation:
go run cmd/adduser/main.go --name "John Doe" --email "john.doe@example.com"This command will:
- Create the user in the database with a generated UUID
- Validate the email format
- Automatically generate deposit addresses for all assets configured in
assets.yaml - Display a summary of created addresses
Option 2: Enable dummy users for testing
Set CREATE_DUMMY_USERS=true in your .env file to create three test users on first run: Alice Johnson, Bob Smith, and Carol Williams.
Option 3: Insert directly into SQLite database
For advanced use cases, you can insert users directly:
INSERT INTO users (id, name, email) VALUES
('your-uuid-here', 'Your Name', 'your.email@example.com');Then run go run cmd/setup/main.go to generate deposit addresses.
Generate deposit addresses for provided users:
go run cmd/setup/main.goThis will:
- Initialize the database and run migrations (including user creation)
- Generate unique trading balance deposit addresses per user/asset
- Store addresses in the database
# Setup
go run cmd/adduser/main.go [flags] # Add new user with deposit addresses
go run cmd/setup/main.go # Generate deposit addresses for existing users
# Operations
go run cmd/listener/main.go # Start transaction listener
go run cmd/addresses/main.go # View deposit addresses
go run cmd/balances/main.go # View user balances
go run cmd/withdrawal/main.go [flags] # Create withdrawalStart the transaction listener:
go run cmd/listener/main.goThis service:
- Monitors all configured trading balances for new transactions
- Processes deposits automatically when they reach "TRANSACTION_IMPORTED" status
- Processes withdrawals when they reach "TRANSACTION_DONE" status
- Updates user balances
- Handles out-of-order transactions with lookback window
The system provides several CLI commands for managing and querying user balances and addresses.
Create a new user with automatic deposit address generation:
# Add a user
go run cmd/adduser/main.go \
--name "Jane Smith" \
--email "jane.smith@example.com"The command will:
- Validate the email format and name
- Generate a unique UUID for the user
- Create the user in the database
- Automatically generate deposit addresses for all configured assets
- Display the created user details and address summary
Required Flags:
--name: User's full name (minimum 2 characters)--email: User's email address (must be valid format and unique)
Example Output:
USER CREATED
ID: a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8
Name: Jane Smith
Email: jane.smith@example.com
π Generating deposit addresses for 4 assets...
β USDC-ethereum-mainnet: 0x123...
β BTC-bitcoin-mainnet: bc1q...
β ETH-ethereum-mainnet: 0x456...
β SOL-solana-mainnet: 7Np...
β
User and all deposit addresses created successfully!
Display all deposit addresses for users:
# Show addresses for all users
go run cmd/addresses/main.go
# Show addresses for a specific user
go run cmd/addresses/main.go --email alice.johnson@example.comOutput includes:
- User name and email
- Asset-network format (e.g.,
ETH-ethereum-mainnet) - Deposit address
- Account identifier (if different from address)
Query current balances for all users:
# Show balances for all users
go run cmd/balances/main.go
# Show balances for a specific user
go run cmd/balances/main.go --email alice.johnson@example.comOutput includes:
- Current balance per asset
- Version number (for optimistic locking)
- Last transaction ID
- Last updated timestamp
Initiate a withdrawal for a user:
go run cmd/withdrawal/main.go \
--email alice.johnson@example.com \
--asset ETH-ethereum-mainnet \
--amount 0.1 \
--destination 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEbThe withdrawal process:
- Validates user by email
- Checks balance to ensure sufficient funds
- Looks up wallet ID from addresses table
- Creates withdrawal via Prime API with proper idempotency key
- Records transaction (handled automatically by listener)
Required Flags:
--email: User's email address--asset: Asset in formatSYMBOL-network-type(e.g.,ETH-ethereum-mainnet)--amount: Withdrawal amount (as decimal string)--destination: Blockchain address to send funds to
Note: The withdrawal command generates the idempotency key automatically using the format specified below, combining the user's ID prefix with a random UUID suffix.
- Current Balances: Stored in
account_balancestable - Transaction History: Complete audit trail in
transactionstable - Atomic Updates: Balance and transaction record updated together
- Optimistic Locking: Prevents race conditions with version control
-- Fast balance lookups
account_balances: user_id, asset, balance, version
-- Complete transaction history
transactions: user_id, asset, type, amount, balance_before, balance_after, external_transaction_id
-- User and address management
users: id, name, email
addresses: user_id, asset, address, wallet_idThe Coinbase Prime Create Withdrawal API requires a valid UUID when creating a withdrawal. In order to accurately ledger withdrawals within this app, use the following concatenated UUID idempotency key format:
{user_id_first_segment}-{uuid_fragment_without_first_segment}
Generation Steps:
- Extract first segment from user ID (before first hyphen)
- Generate a random UUID
- Replace the UUID's first segment with the user ID's first segment
Example:
# If user ID is: abcd1234-def4-567g-890h-ijklmnop1234
# Generate random UUID: 550e8400-e29b-41d4-a716-446655440000
# Use idempotency key: abcd1234-e29b-41d4-a716-446655440000Implementation:
# Extract user ID prefix
USER_PREFIX=$(echo "$USER_ID" | cut -d'-' -f1)
# Generate random UUID and replace first segment
RANDOM_UUID=$(uuidgen | tr '[:upper:]' '[:lower:]')
WITHDRAWAL_UUID="${USER_PREFIX}-$(echo "$RANDOM_UUID" | cut -d'-' -f2-)"- Create Withdrawal: Submit to Prime API with proper idempotency key
- Transaction Appears: Listener detects new withdrawal transaction
- Status Check: Waits for "TRANSACTION_DONE" status
- User Matching: Matches via idempotency key prefix
- Balance Update: Debits user balance atomically
Use the balances CLI command for a formatted view:
go run cmd/balances/main.goOr query directly via SQL:
SELECT u.name, ab.asset, ab.balance
FROM users u
JOIN account_balances ab ON u.id = ab.user_id
WHERE ab.balance > 0;Use the addresses CLI command:
go run cmd/addresses/main.go --email user@example.comOr query via SQL:
SELECT u.name, a.asset, a.address, a.wallet_id
FROM users u
JOIN addresses a ON u.id = a.user_id
WHERE u.email = 'user@example.com';SELECT u.name, t.transaction_type, t.asset, t.amount, t.created_at
FROM transactions t
JOIN users u ON t.user_id = u.id
ORDER BY t.created_at DESC
LIMIT 10;SELECT
ab.user_id,
ab.asset,
ab.balance as current_balance,
COALESCE(SUM(t.amount), 0) as calculated_balance
FROM account_balances ab
LEFT JOIN transactions t ON ab.user_id = t.user_id AND ab.asset = t.asset
GROUP BY ab.user_id, ab.asset;