Secure API Key Management for Multi-Protocol Trading Bots
Secure API Key Management for Multi-Protocol Trading Bots
Trading bots live and die by their infrastructure — and nowhere is that more true than in how they manage wallet access, API keys, and on-chain execution. If your arb bot spotted a Jupiter-to-Drift spread three seconds ago and is still waiting on a signing round-trip, the opportunity is gone. Worse, if your session token is stored in plaintext and your bot gets compromised, the damage compounds fast. This post covers how to structure wallet infrastructure for automated trading bots using WAIaaS, with a focus on security, multi-protocol access, and execution reliability.
The Real Problem with Bot Wallet Management
Most developers building trading bots start with the same setup: a private key in a .env file, a few provider clients stitched together, and some ad-hoc rate limiting logic. It works until it doesn't. The failure modes are well-known — leaked keys, no spending controls, a runaway bot draining funds, or a bug that sends the wrong asset to the wrong address.
The deeper issue is that your bot needs two different kinds of access. It needs enough permission to execute trades autonomously at low latency. But you, as the fund owner, need to retain hard limits on what it can do — and a way to intervene when something goes wrong. Embedding a raw private key gives the bot everything with no guardrails whatsoever.
What you actually want is scoped, auditable, time-limited access to a wallet — with a policy layer that enforces risk controls independently of the bot's own code.
How WAIaaS Structures Access for Bots
WAIaaS separates wallet access into three distinct authentication roles, each with a different trust level:
- masterAuth (Argon2id password) — System administrator. Creates wallets, manages sessions, sets policies. Your bot should never hold this.
- sessionAuth (JWT HS256) — AI agent / bot. Executes transactions, queries balances, calls DeFi actions. This is what your bot uses at runtime.
- ownerAuth (SIWS/SIWE signature) — Fund owner. Approves high-value transactions, can kill sessions. Lives on your hardware wallet or mobile device.
This maps directly to how you'd want a trading operation structured: your bot gets a scoped session token, not root access.
Setting Up a Trading Bot Wallet
Start with the CLI to get the daemon running:
npm install -g @waiaas/cli
waiaas init
waiaas start
Then create a dedicated wallet for your bot and issue it a session token:
# Create the wallet
curl -X POST http://127.0.0.1:3100/v1/wallets \
-H "Content-Type: application/json" \
-H "X-Master-Password: my-secret-password" \
-d '{"name": "arb-bot-wallet", "chain": "solana", "environment": "mainnet"}'
# Issue a session token for the bot
curl -X POST http://127.0.0.1:3100/v1/sessions \
-H "Content-Type: application/json" \
-H "X-Master-Password: my-secret-password" \
-d '{"walletId": "<wallet-uuid>"}'
The session token (wai_sess_...) is what your bot stores and uses. It has per-session TTL, maxRenewals, and absoluteLifetime controls — so you can issue a token that expires after 24 hours and requires renewal, rather than a credential that's valid forever.
Your bot's environment now looks like this:
WAIAAS_SESSION_TOKEN=wai_sess_eyJhbGciOiJIUzI1NiJ9...
WAIAAS_BASE_URL=http://127.0.0.1:3100
No private key. No mnemonic. If this token is compromised, you revoke it from the master and issue a new one. The wallet itself is never exposed.
Policy Engine: Risk Controls That Live Outside Your Bot
Here's where things get interesting for trading bot builders. WAIaaS has a policy engine with 21 policy types and 4 security tiers (INSTANT, NOTIFY, DELAY, APPROVAL). These are enforced by the daemon's transaction pipeline — not by your bot's code. That means even a bug in your trading logic can't override them.
The four tiers work like this:
- INSTANT — Execute immediately, no notification
- NOTIFY — Execute immediately, notify you
- DELAY — Queue for N seconds before execution (cancellable)
- APPROVAL — Require explicit human approval
For a trading bot, a reasonable starting policy looks like this:
curl -X POST http://127.0.0.1:3100/v1/policies \
-H "Content-Type: application/json" \
-H "X-Master-Password: my-secret-password" \
-d '{
"walletId": "<wallet-uuid>",
"type": "SPENDING_LIMIT",
"rules": {
"instant_max_usd": 100,
"notify_max_usd": 500,
"delay_max_usd": 2000,
"delay_seconds": 900,
"daily_limit_usd": 5000
}
}'
This means your bot can execute trades under $100 instantly. Trades between $100-$500 execute immediately but you get notified. Trades between $500-$2,000 are queued for 15 minutes — enough time to cancel if something looks wrong. Anything above $2,000 requires your explicit approval.
Beyond spending limits, you can lock down exactly which tokens and contracts your bot is allowed to touch:
# Only allow USDC transfers — everything else is blocked by default
curl -X POST http://127.0.0.1:3100/v1/policies \
-H "Content-Type: application/json" \
-H "X-Master-Password: my-secret-password" \
-d '{
"walletId": "<wallet-uuid>",
"type": "ALLOWED_TOKENS",
"rules": {
"tokens": [{"address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "symbol": "USDC", "chain": "solana"}]
}
}'
The ALLOWED_TOKENS and CONTRACT_WHITELIST policies are default-deny: if the token or contract isn't on the list, the transaction is blocked. This is important for bots — it prevents your bot from being tricked into sending an unexpected asset, even if your logic has a bug.
For perpetual futures trading, there are dedicated policy types: PERP_MAX_LEVERAGE to cap leverage, PERP_MAX_POSITION_USD to cap position size, and PERP_ALLOWED_MARKETS to restrict which markets your bot can trade.
Multi-Protocol Execution: Swap, Hedge, Bridge in One Stack
WAIaaS integrates 15 DeFi protocol providers. For a Solana-based arb bot, the relevant ones are Jupiter (swap), Drift (perpetuals), Jito (staking/MEV), and Across or LI.FI (cross-chain bridging). For EVM bots, you have Aave v3, Uniswap (via 0x), Lido, Pendle, Hyperliquid, and more.
Executing a Jupiter swap from your bot looks like this:
import { WAIaaSClient } from '@waiaas/sdk';
const client = new WAIaaSClient({
baseUrl: process.env['WAIAAS_BASE_URL'] ?? 'http://localhost:3100',
sessionToken: process.env['WAIAAS_SESSION_TOKEN'],
});
// Check balance before trading
const balance = await client.getBalance();
console.log(`Balance: ${balance.balance} ${balance.symbol} (${balance.chain}/${balance.network})`);
For the swap itself, hit the actions endpoint directly:
curl -X POST http://127.0.0.1:3100/v1/actions/jupiter-swap/swap \
-H "Content-Type: application/json" \
-H "Authorization: Bearer wai_sess_<token>" \
-d '{
"inputMint": "So11111111111111111111111111111111111111112",
"outputMint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"amount": "1000000000"
}'
The same session token works for all 15 protocol integrations — you don't need a separate SDK, separate keys, or separate auth flow for each protocol. The daemon handles the provider routing.
Gas Conditional Execution
One feature that's particularly relevant for EVM trading bots: gas conditional execution. The daemon can hold a transaction in the pipeline until gas price meets a configured threshold. Your bot submits the transaction, and it only executes when the gas condition is satisfied — no polling loop in your own code, no wasted gas from submitting at peak prices.
This runs as part of the 7-stage transaction pipeline (validate → auth → policy → wait → execute → confirm), which means gas conditions are checked before the transaction is ever broadcast.
Simulate Before You Execute
For strategies where you want to validate execution before committing — especially for larger trades or less liquid markets — the dry-run API lets you simulate without broadcasting:
curl -X POST http://127.0.0.1:3100/v1/transactions/send \
-H "Content-Type: application/json" \
-H "Authorization: Bearer wai_sess_<token>" \
-d '{
"type": "TRANSFER",
"to": "recipient-address",
"amount": "0.1",
"dryRun": true
}'
This goes through the full pipeline including policy checks, so you'll catch a policy violation before the transaction hits the chain. Useful for pre-flight checks in your bot's decision loop.
Error Handling That Makes Sense for Bots
WAIaaS returns structured errors with machine-readable codes:
{
"error": {
"code": "POLICY_DENIED",
"message": "Transaction denied by SPENDING_LIMIT policy",
"domain": "POLICY",
"retryable": false
}
}
The retryable field is directly useful for your bot's retry logic. A POLICY_DENIED error is not retryable — don't loop. An INSUFFICIENT_BALANCE error might be retryable after a rebalance. Your bot can branch on error.code without parsing human-readable strings.
In the TypeScript SDK:
import { WAIaaSClient, WAIaaSError } from '@waiaas/sdk';
try {
const tx = await client.sendToken({ to: '...', amount: '1.0' });
} catch (error) {
if (error instanceof WAIaaSError) {
console.error(`API Error: [${error.code}] ${error.message}`);
// error.code examples: INSUFFICIENT_BALANCE, POLICY_DENIED, TOKEN_EXPIRED
}
}
Deploying in Production
For a production trading bot deployment, run the daemon in Docker with a named volume for persistence and secrets management:
docker run -d \
--name waiaas \
-p 127.0.0.1:3100:3100 \
-v waiaas-data:/data \
-e WAIAAS_AUTO_PROVISION=true \
ghcr.io/minhoyoo-iotrust/waiaas:latest
# Retrieve the auto-generated master password
docker exec waiaas cat /data/recovery.key
For secrets in a more hardened setup, use the Docker Secrets overlay:
mkdir -p secrets
echo "your-secure-password" > secrets/master_password.txt
chmod 600 secrets/master_password.txt
docker compose -f docker-compose.yml -f docker-compose.secrets.yml up -d
The daemon runs as a non-root user (UID 1001), includes a healthcheck, and exposes the port bound to 127.0.0.1 by default — so it's not reachable from outside the host without explicit configuration.
Set your RPC endpoints as environment variables to use your own nodes:
WAIAAS_RPC_SOLANA_MAINNET=<your-rpc-url>
WAIAAS_RPC_EVM_ETHEREUM_MAINNET=<your-rpc-url>
Quick Start Checklist for Bot Builders
Here's the minimal path to get a bot running with proper wallet infrastructure:
- Install and start:
npm install -g @waiaas/cli && waiaas init && waiaas start - Create a bot wallet: POST to
/v1/walletswith masterAuth, get awalletId - Issue a scoped session: POST to
/v1/sessionswith masterAuth, store thewai_sess_...token in your bot's environment — not the master password - Set spending policies: At minimum, configure
SPENDING_LIMIT,ALLOWED_TOKENS, andCONTRACT_WHITELISTbefore running live - Test with dry-run: Set
"dryRun": truein your first few transaction calls to verify policy behavior before going live
The OpenAPI spec is available at http://127.0.0.1:3100/doc and the interactive reference UI at http://127.0.0.1:3100/reference — useful for exploring all 39 API route modules without digging through source.
What's Next
If your bot needs to interact with AI agent frameworks like Claude or other LLM-driven systems, WAIaaS provides 45 MCP tools covering wallet, transaction, DeFi, NFT, and x402 operations — so the same wallet infrastructure works for both your automated bot and any human-in-the-loop AI workflows. The policy engine applies equally regardless of which interface is making the call.
The codebase is open source and self-hosted, meaning your keys and trading data never leave your infrastructure. Start at GitHub for the source and deployment docs, or waiaas.ai for the full product overview.