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:

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:

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:

  1. Install and start: npm install -g @waiaas/cli && waiaas init && waiaas start
  2. Create a bot wallet: POST to /v1/wallets with masterAuth, get a walletId
  3. Issue a scoped session: POST to /v1/sessions with masterAuth, store the wai_sess_... token in your bot's environment — not the master password
  4. Set spending policies: At minimum, configure SPENDING_LIMIT, ALLOWED_TOKENS, and CONTRACT_WHITELIST before running live
  5. Test with dry-run: Set "dryRun": true in 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.