English
Wallet Tutorials
Build a custodial customer wallet on Ethereum Sepolia with USDT. This tutorial covers the complete customer-facing lifecycle from account provisioning to deposit detection and external withdrawal completion.
Run this example on a testnet. Keep production mainnets and test assets separate; see Supported Networks for network availability.
Customer→USER account→Deposit address→Balance & ledger→External withdrawal
What you will build
text
Connect and sign Wallet API requests
→ discover the asset and network
→ create or reuse a customer USER account
→ issue a deposit address
→ detect and confirm an external deposit
→ read balances and finalized ledger entries
→ validate, quote, and request an external withdrawal
→ track the on-chain result to a final stateBefore you start
Prepare Node.js, curl, openssl, and jq, along with the Base URL, API Key, and HMAC key issued by BPMG.
bash
export WALLET_BASE_URL='https://{environment-api-host}'
export WALLET_API_KEY='{issued-api-key}'
export WALLET_HMAC_KEY='{issued-hmac-key}'Keep credentials in a secret manager and synchronize the calling server clock with NTP. The request timestamp must be within the server tolerance.
wallet_request
wallet_request is a shell function that adds HMAC authentication headers and calls the Wallet API with curl. Its call format is wallet_request <HTTP method> <path and query without the Base URL> [JSON body]. Register the entire code block below in the shell where you configured the environment variables. Run later steps in that same shell, or configure the variables and function again in a new shell.
bash
wallet_request() {
local method="$1"
local request_path="$2"
local body="${3-}"
local timestamp nonce signature
timestamp="$(node -e 'process.stdout.write(String(Date.now()))')"
nonce="$(node -e 'process.stdout.write(require("crypto").randomUUID())')"
signature="$(printf '%s\n\n%s\n\n%s\n\n%s\n\n%s' \
"$method" "$request_path" "$timestamp" "$nonce" "$body" \
| openssl dgst -sha256 -hmac "$WALLET_HMAC_KEY" -binary \
| openssl base64 -A)"
if [ -n "$body" ]; then
curl -sS -X "$method" "$WALLET_BASE_URL$request_path" \
-H "X-API-KEY: $WALLET_API_KEY" \
-H "X-TIMESTAMP: $timestamp" \
-H "X-NONCE: $nonce" \
-H "X-SIGNATURE: $signature" \
-H 'Content-Type: application/json' \
--data-raw "$body"
else
curl -sS -X "$method" "$WALLET_BASE_URL$request_path" \
-H "X-API-KEY: $WALLET_API_KEY" \
-H "X-TIMESTAMP: $timestamp" \
-H "X-NONCE: $nonce" \
-H "X-SIGNATURE: $signature"
fi
}The helper is for the tutorial. A production client must add response validation, timeouts, and safe bounded retries. See HMAC Request Signing for the signing contract.
Running examples and checking responses
Each page uses variables saved in earlier steps.
| Variable | Preparation step |
|---|---|
CUSTOMER_UID, ACCOUNT_ID | Create a user and wallet account |
ASSET_TYPE_ID, NETWORK_ID, ASSET_DECIMALS, DEPOSIT_ADDRESS | Issue a deposit address |
DEPOSIT_ID, LEDGER_ID | Select a record from the deposit or ledger list |
TO_ADDRESS, WITHDRAW_AMOUNT, ASSET_ID, WITHDRAWAL_REQUEST_ID, WITHDRAWAL_ID | Request an external withdrawal |
curl -sS can print JSON even for HTTP errors; | jq only formats the response. Check every request's HTTP status and success before continuing. Never use null extracted from an error or empty list as an ID. Also verify the returned asset, network, and account match your intent.
Shell variables are temporary storage for following the tutorial. In production, persist customer/account mappings, business request IDs, and transaction IDs in a database, then recover from those stored values after a restart.
Tutorial sequence
- Create a user and wallet account
- Issue a deposit address
- Check a deposit
- Check balances and ledger
- Request an external withdrawal
- Track withdrawal status
- Build the complete wallet flow
IMPORTANT
IDs, addresses, and amounts in this tutorial are examples. Always use values returned by the target environment and a valid address for the selected network.