Complete AI Agent Lockdown: ALLOWED_TOKENS + CONTRACT_WHITELIST + METHOD_WHITELIST Triple Security
Giving an AI agent a wallet without guardrails is like giving a toddler a credit card — technically possible, practically terrifying. If you're building autonomous agents that interact with crypto wallets, the security model you choose on day one determines whether your system survives contact with mainnet. This post walks through exactly how WAIaaS implements three interlocking default-deny policies — ALLOWED_TOKENS, CONTRACT_WHITELIST, and METHOD_WHITELIST — to create a layered lockdown that keeps your agent's autonomy useful while keeping your funds safe.
Why This Problem Is Harder Than It Looks
Most developers thinking about AI agent security jump straight to authentication. "Give the agent a key, restrict who can call the API, done." But authentication only controls who sends instructions. It says nothing about what those instructions can do.
An authenticated AI agent with a misconfigured wallet can:
- Transfer tokens to any address it derives from context
- Call arbitrary smart contracts passed to it through a prompt
- Approve unlimited token spend to third-party contracts
- Interact with protocols you've never heard of
The threat model here isn't just external attackers. It's prompt injection, model hallucination, compromised API responses, and your own agent misunderstanding ambiguous instructions. Each of these failure modes can result in fund loss even when your authentication is perfect.
This is the problem WAIaaS addresses at the policy layer, not the authentication layer. Authentication tells you the request is coming from your agent. Policy tells you whether the request is something your agent should be allowed to do.
The Default-Deny Foundation
Before diving into the three specific policies, understand the foundational principle: WAIaaS uses default-deny enforcement. Transactions are blocked unless explicitly permitted by policy.
This is the opposite of most systems, which allow everything and let you add restrictions. Default-deny means:
- If you haven't configured ALLOWED_TOKENS, token transfers are blocked
- If you haven't configured CONTRACT_WHITELIST, contract calls are blocked
- If you haven't configured METHOD_WHITELIST, specific function calls can be further restricted even within whitelisted contracts
Your agent starts with zero permissions and you open up exactly what it needs. This is the correct mental model for any autonomous system touching real funds.
WAIaaS supports 21 policy types in total, but these three form the core of what you'd call "transaction lockdown." Let's go through each one.
Layer 1: ALLOWED_TOKENS
ALLOWED_TOKENS is a whitelist of tokens your agent is permitted to transfer. If a token isn't on the list, the transaction is denied before it ever reaches signing or execution.
Here's what the policy configuration looks like:
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": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"symbol": "USDC",
"chain": "solana"
}
]
}
}'
In this example, the agent's wallet can only transfer USDC on Solana. An instruction to send SOL, BONK, or any other token fails at the policy layer — no signing, no broadcast, no transaction fee wasted.
This matters for a specific class of attack: if an adversarial prompt convinces your agent to "send all available tokens to recover funds," ALLOWED_TOKENS means only the explicitly listed tokens are transferable. The agent literally cannot comply with instructions to move tokens outside its permitted set.
For an EVM trading agent, you might allow USDC, WETH, and a specific governance token. For a Solana payments agent, maybe just USDC and USDT. The principle is the same: enumerate exactly what the agent needs, deny everything else.
Layer 2: CONTRACT_WHITELIST
ALLOWED_TOKENS handles transfers, but most interesting DeFi activity involves contract calls — swaps, lending, staking, liquidity provision. CONTRACT_WHITELIST gives you the same default-deny treatment for smart contract interactions.
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"
}
]
}
}'
With this policy active, your agent can interact with Jupiter's contract but nothing else. An agent running an on-chain strategy has no path to accidentally — or maliciously via prompt injection — call an unknown contract address, a newly deployed protocol, or a phishing contract disguised as a legitimate one.
This is particularly important when your agent is consuming external data to determine where to execute trades. If the agent is reading prices from an API, a compromised API response could theoretically return a malicious contract address as the "best venue." CONTRACT_WHITELIST means the agent will attempt the call, the policy engine will check the target address, find it's not on the whitelist, and return a POLICY_DENIED error — never reaching the blockchain.
The error response looks like this:
{
"error": {
"code": "POLICY_DENIED",
"message": "Transaction denied by CONTRACT_WHITELIST policy",
"domain": "POLICY",
"retryable": false
}
}
Your agent gets a clear, structured error it can log and handle. No ambiguity, no silent failure.
Layer 3: METHOD_WHITELIST
The third layer operates at a finer granularity than CONTRACT_WHITELIST. Where CONTRACT_WHITELIST controls which contracts can be called, METHOD_WHITELIST controls which functions within those contracts can be called.
Consider a scenario: you've whitelisted a lending protocol contract. That contract exposes functions for depositing collateral, borrowing, repaying, and withdrawing. Maybe your agent's job is to manage collateral — deposit and withdraw — but you never want it to borrow autonomously. METHOD_WHITELIST lets you express exactly that constraint by specifying allowed function selectors.
This is the difference between "my agent can touch this contract" and "my agent can touch this contract, but only call these specific functions." For risk management, that distinction is significant. An agent that can deposit into Aave is very different from an agent that can also borrow from Aave.
When combined with CONTRACT_WHITELIST, the combination means:
- The target contract must be on the whitelist
- And the function being called must be on the method whitelist
Both conditions must be satisfied. Neither alone is sufficient.
How These Three Layers Stack
The power of this approach is multiplicative, not additive. Each policy operates as an independent gate in the transaction pipeline. WAIaaS processes transactions through a 7-stage pipeline: validation, auth, policy, wait, execute, confirm. Policy evaluation happens at stage 3, before any signing or execution occurs.
For a transaction to succeed, it must pass every applicable policy:
- Is the token in ALLOWED_TOKENS? → If no, denied.
- Is the contract in CONTRACT_WHITELIST? → If no, denied.
- Is the method in METHOD_WHITELIST? → If no, denied.
You can visualize this as concentric rings of restriction. ALLOWED_TOKENS is the outermost ring (what assets can move). CONTRACT_WHITELIST is the middle ring (where can those assets go). METHOD_WHITELIST is the innermost ring (what can be done once there).
An agent trying to do something outside any of these rings hits a wall immediately.
Combining with Spending Limits and Approval Tiers
Token and contract whitelists tell you what is allowed. Spending limits tell you how much is allowed, and in what manner. These policies compose.
Here's a realistic production setup that combines a spending limit with token restrictions:
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
}
}'
WAIaaS uses 4 security tiers:
- INSTANT — Execute immediately, no notification
- NOTIFY — Execute immediately, send notification to owner
- DELAY — Queue for the specified delay period, cancellable before execution
- APPROVAL — Require explicit human approval before executing
Tier assignment is automatic based on transaction amount. In the example above: transactions under $100 execute instantly, $100-$500 execute with notification, $500-$2,000 queue for 15 minutes, and anything over $2,000 requires human approval.
Combined with CONTRACT_WHITELIST and ALLOWED_TOKENS, this means: even for transactions that pass the token and contract checks, large amounts automatically escalate to human review. Your agent can be fully autonomous for small, routine operations and automatically require oversight for anything unusual.
The Dry-Run Safety Net
Before pushing any of this to production, you can validate your transaction logic — including policy evaluation — using the dry-run API. This simulates the full transaction pipeline without submitting anything to the blockchain:
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
}'
The response tells you whether the transaction would succeed or fail, and if it would fail, which policy blocked it. This is invaluable when setting up policies: you can iterate on your configuration and verify the behavior without risking funds or gas.
Use dry-run during development to confirm that:
- Transactions your agent should execute pass all policies
- Transactions your agent should be blocked from attempting are correctly denied
- The tiers assigned to different amounts match your expectations
Setting Up: The Minimal Secure Configuration
Here's a minimal sequence to get a locked-down agent wallet running. This assumes you already have WAIaaS running via Docker or CLI.
Step 1: 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": "trading-wallet", "chain": "solana", "environment": "mainnet"}'
Step 2: Create a session token for your agent
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>"}'
Step 3: Apply your three lockdown policies (ALLOWED_TOKENS, CONTRACT_WHITELIST, METHOD_WHITELIST — using the examples above)
Step 4: Apply a spending limit with approval tier for large transactions (SPENDING_LIMIT — using the example above)
Step 5: Verify with a dry-run before giving the session token to your agent
Your agent receives the session token and can now make calls to the wallet API. Everything it tries will be evaluated against the policy stack before any signing occurs. The agent's code doesn't need to know about policies — it just makes API calls, and the policy engine handles enforcement transparently.
Authentication: Three Roles, Separated Concerns
The policy layer works in conjunction with WAIaaS's three-role authentication model:
# masterAuth — system administrator (wallet creation, session management, policies)
-H "X-Master-Password: my-secret-password"
# sessionAuth — AI agent (transactions, balance queries, DeFi actions)
-H "Authorization: Bearer wai_sess_eyJhbGciOiJIUzI1NiJ9..."
# ownerAuth — fund owner (transaction approval, kill switch recovery)
-H "X-Owner-Signature: <ed25519-or-secp256k1-signature>"
-H "X-Owner-Message: <signed-message>"
Your agent only ever holds a sessionAuth token. It cannot create wallets, modify policies, or approve its own transactions. Policy changes require masterAuth. Transaction approvals — when the APPROVAL tier triggers — require ownerAuth, which is a cryptographic signature from the wallet owner's key.
This means an attacker who compromises your agent's session token has a token that:
- Can only operate on the specific wallet it was issued for
- Is constrained by whatever policies you've applied
- Cannot modify those policies
- Cannot self-approve transactions that require human sign-off
The separation of concerns between these three auth levels is a meaningful security property, not a cosmetic one.
What's Next
The policies covered here — ALLOWED_TOKENS, CONTRACT_WHITELIST, and METHOD_WHITELIST — are the foundation of transaction lockdown, but WAIaaS includes 18 additional policy types covering DeFi-specific constraints (LENDING_LTV_LIMIT, PERP_MAX_LEVERAGE, PERP_MAX_POSITION_USD), network restrictions (ALLOWED_NETWORKS), approval workflow configuration (APPROVE_AMOUNT_LIMIT, APPROVE_TIER_OVERRIDE), and more. Start with the three layers here, verify your configuration with dry-runs, and then layer additional policies as your agent's responsibilities expand.
Explore the full codebase and policy documentation at https://github.com/waiaas/WAIaaS, and find deployment guides, SDK references, and the interactive API explorer at https://waiaas.ai.
Top comments (0)