Policy Stack Architecture: Layering 21 Security Controls for High-Frequency DeFi Bots
DeFi trading bots operate in an adversarial environment where a single misconfigured permission can drain a wallet in seconds — so before your bot executes a single swap, you need a policy stack that's as carefully engineered as the trading logic itself. Most teams bolt on risk controls as an afterthought, wrapping transactions in ad-hoc checks scattered across their codebase. WAIaaS takes the opposite approach: a structured 21-policy engine with four security tiers baked directly into the transaction pipeline, so your bot inherits production-grade risk management without you writing a line of custom guard logic.
The Problem With DIY Risk Controls
When you're building an arb bot or a market-making system, the trading logic gets all the attention — spreads, slippage tolerances, execution latency. The risk layer usually ends up as a handful of if statements: check the recipient, check the amount, maybe check the gas price. This works until it doesn't.
The failure modes are well-documented. A compromised session token gives an attacker unlimited spend. A DeFi integration bug routes funds to an unintended contract. A runaway bot loop drains its own wallet executing bad trades at 2am. None of these are exotic attacks — they're routine incidents that teams only think about after the fact.
What you actually need is a policy engine that sits between your bot's intent and execution: one that enforces spending caps, whitelists contracts, restricts token approvals, sets leverage limits, and requires human sign-off above a threshold — all before the transaction ever hits the chain.
The WAIaaS Policy Architecture
WAIaaS runs every transaction through a 7-stage pipeline. Stage 3 is the policy engine, and it enforces 21 policy types organized into four security tiers:
- INSTANT — Execute immediately, no notification
- NOTIFY — Execute immediately, send a notification to the owner
- DELAY — Queue the transaction for a configurable delay period (cancellable)
- APPROVAL — Block until the fund owner explicitly approves via WalletConnect, Telegram, or push notification
The engine is default-deny: if you haven't configured ALLOWED_TOKENS or CONTRACT_WHITELIST, the transaction is blocked. This is the right default for a bot. You explicitly enumerate what the bot is allowed to do, and everything else fails closed.
Building a Policy Stack for a Solana Trading Bot
Let's walk through a realistic configuration for a Solana arbitrage bot that swaps on Jupiter and holds positions on Drift.
Layer 1: Spending Limits With Tiered Security
The foundation of any bot policy stack is a spending limit that maps transaction sizes to security tiers. Small routine trades execute instantly. Larger positions trigger notifications. Anything above your comfort threshold requires your explicit approval.
curl -X POST http://localhost:3100/v1/policies \
-H 'Content-Type: application/json' \
-H 'X-Master-Password: <password>' \
-d '{
"walletId": "<wallet-uuid>",
"type": "SPENDING_LIMIT",
"rules": {
"instant_max_usd": 100,
"notify_max_usd": 1000,
"delay_max_usd": 5000,
"delay_seconds": 300,
"daily_limit_usd": 20000,
"monthly_limit_usd": 200000
}
}'
This single policy means your arb bot can execute $100 swaps all day without friction. A $500 position gets a push notification to your phone. A $3000 trade queues for five minutes before executing — giving you a window to cancel if something looks wrong. Anything above $5000 hard-blocks until you approve it.
The tier assignment logic is straightforward: amount <= instant_max → INSTANT, <= notify_max → NOTIFY, <= delay_max → DELAY, > delay_max → APPROVAL. You can also set per-token limits within the same policy using token_limits, so your SOL exposure and your USDC exposure have separate thresholds.
Layer 2: Token Whitelist (Default-Deny)
Without an ALLOWED_TOKENS policy, the engine blocks all token transfers. Add one to define exactly which tokens your bot is permitted to touch:
curl -X POST http://localhost:3100/v1/policies \
-H 'Content-Type: application/json' \
-H 'X-Master-Password: <password>' \
-d '{
"walletId": "<wallet-uuid>",
"type": "ALLOWED_TOKENS",
"rules": {
"tokens": [
{"address": "So11111111111111111111111111111111111111112", "symbol": "SOL", "chain": "solana"},
{"address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "symbol": "USDC", "chain": "solana"}
]
}
}'
If a bug in your Jupiter integration accidentally constructs a swap to an unfamiliar mint address, the engine rejects it before it ever touches the signing stage. Your bot cannot move tokens that aren't on this list.
Layer 3: Contract Whitelist
For a Solana bot interacting with Jupiter and Drift, you want to explicitly enumerate the program addresses the bot is allowed to call:
curl -X POST http://localhost:3100/v1/policies \
-H 'Content-Type: application/json' \
-H 'X-Master-Password: <password>' \
-d '{
"walletId": "<wallet-uuid>",
"type": "CONTRACT_WHITELIST",
"rules": {
"contracts": [
{"address": "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4", "name": "Jupiter", "chain": "solana"},
{"address": "dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH", "name": "Drift", "chain": "solana"}
]
}
}'
This is your blast radius limiter. Even if your bot's session token is leaked, an attacker can't route funds to an arbitrary program. The whitelist is enforced at the policy layer — before signing, before broadcast.
Layer 4: Approval Controls
Perpetual futures bots need a different class of policy. The PERP_MAX_LEVERAGE, PERP_MAX_POSITION_USD, and PERP_ALLOWED_MARKETS types give you guardrails at the DeFi semantic level rather than just the transaction value level:
# Cap leverage at 5x
curl -X POST http://localhost:3100/v1/policies \
-H 'Content-Type: application/json' \
-H 'X-Master-Password: <password>' \
-d '{
"walletId": "<wallet-uuid>",
"type": "PERP_MAX_LEVERAGE",
"rules": {"maxLeverage": 5}
}'
# Cap position size
curl -X POST http://localhost:3100/v1/policies \
-H 'Content-Type: application/json' \
-H 'X-Master-Password: <password>' \
-d '{
"walletId": "<wallet-uuid>",
"type": "PERP_MAX_POSITION_USD",
"rules": {"maxPositionUsd": 10000}
}'
A runaway position-sizing bug can't lever your wallet into oblivion when PERP_MAX_LEVERAGE is enforced at the policy layer.
Layer 5: Rate Limiting and Time Restrictions
For bots that should only trade during specific market windows:
# Maximum 50 transactions per hour
curl -X POST http://localhost:3100/v1/policies \
-H 'Content-Type: application/json' \
-H 'X-Master-Password: <password>' \
-d '{
"walletId": "<wallet-uuid>",
"type": "RATE_LIMIT",
"rules": {"maxTransactions": 50, "period": "hourly"}
}'
A RATE_LIMIT policy is your circuit breaker for feedback loops. If a bug causes your bot to hammer the same trade repeatedly, it hits the rate cap before the damage compounds.
Gas Conditional Execution
One of the more operationally useful features for high-frequency bots is gas conditional execution — transactions execute only when the gas price meets a configured threshold. Your bot submits the transaction to the pipeline; the pipeline holds it in the wait stage until gas conditions are met. This is particularly relevant for EVM bots where gas spikes can make an otherwise profitable arb unprofitable.
This behavior is handled by the pipeline's wait stage, which sits before execution. Combined with the DELAY tier from your spending limit policy, you get a transaction queue that's both security-gated and gas-aware.
Simulating Before You Execute
Before your bot sends a real transaction, use the dry-run API to validate that the transaction would pass policy checks and estimate the outcome:
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
}'
For a trading bot this is valuable during development and in production before entering new market conditions. A dry run traverses the full pipeline — validation, policy engine, simulation — without touching the chain. If your policy stack would block the transaction, you find out here rather than at execution time.
Executing DeFi Actions
Once your policy stack is configured, your bot interacts with the 15 integrated DeFi protocols through a consistent action API. Here's a Jupiter swap:
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 session token used here is scoped — it can only operate within the policy constraints you configured for that wallet. The bot never touches the master password. If the session is compromised, the attacker operates within a policy-constrained environment: token whitelist enforced, contract whitelist enforced, spending limits applied, rate limits active.
The Three Auth Layers
Understanding the auth separation matters for bot architecture:
- masterAuth (Argon2id) — Your system administrator identity. Creates wallets, manages sessions, sets policies. Your bot never holds this credential.
- sessionAuth (JWT HS256) — Your bot's operating credential. Scoped to a specific wallet, subject to all configured policies. This is what goes into your bot's environment variables.
- ownerAuth (SIWS/SIWE) — The fund owner's identity for approving high-value transactions. Used by you personally via WalletConnect or a signing tool.
# Bot uses sessionAuth for all trading operations
curl -X POST http://127.0.0.1:3100/v1/transactions/send \
-H "Authorization: Bearer wai_sess_<token>" \
-H "Content-Type: application/json" \
-d '{"type": "TRANSFER", "to": "recipient-address", "amount": "0.1"}'
# Owner approves a transaction that hit the APPROVAL tier
curl -X POST http://127.0.0.1:3100/v1/transactions/<tx-id>/approve \
-H "X-Owner-Signature: <ed25519-or-secp256k1-signature>" \
-H "X-Owner-Message: <signed-message>"
Quick Start: Standing Up a Policy-Gated Trading Wallet
Here's the minimum viable setup for a bot with sensible defaults:
1. Start the daemon:
git clone https://github.com/waiaas/WAIaaS.git
cd WAIaaS
docker compose up -d
2. Create a wallet and session:
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": "trading-wallet", "chain": "solana", "environment": "mainnet"}'
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>"}'
3. Apply your policy stack — spending limit, token whitelist, contract whitelist, rate limit as shown above.
4. Put the session token in your bot's environment and start trading:
export WAIAAS_SESSION_TOKEN=wai_sess_<token>
Your bot now operates within a policy-gated environment. All 21 policy types are available to layer in as your strategy grows more complex.
What's Next
The full list of 21 policy types — including LENDING_LTV_LIMIT, APPROVE_AMOUNT_LIMIT, VENUE_WHITELIST, and ERC8128_ALLOWED_DOMAINS — is documented in the interactive API reference at http://127.0.0.1:3100/reference once your daemon is running. The OpenAPI 3.0 spec is available at /doc for integration with your tooling.
If you're building cross-chain strategies that also need bridge operations via LI.FI or Across, or want to connect to Claude or another LLM via the 45-tool MCP server for a hybrid agent/bot architecture, the same policy stack governs all of it — one configuration, consistent enforcement across every protocol and chain.
Explore the codebase and self-host your own instance: github.com/waiaas/WAIaaS
Full documentation and hosted options: waiaas.ai
Top comments (0)