Lock Down Your DeFi Bot: METHOD_WHITELIST and Smart Contract Security with WAIaaS
Your DeFi trading bot has a problem: it holds private keys, executes transactions autonomously, and interacts with smart contracts — and if something goes wrong, there's no undo button. Whether you're running an arb bot, a liquidity manager, or an automated hedging strategy, the attack surface is real. A compromised session token, a buggy strategy loop, or a malicious contract call can drain your wallet in a single block. METHOD_WHITELIST is one of the tools that closes that gap, and in this post we'll look at how WAIaaS builds it into a broader security architecture designed specifically for autonomous agents.
Why Contract-Level Restrictions Actually Matter
Most wallet security discussions stop at "protect your private key." That's necessary but not sufficient when you have a bot running 24/7 with signing authority. The real risk surface for an automated trading system looks like this:
- Your bot's session token is stolen or leaked
- A bug in your strategy logic calls the wrong function on the wrong contract
- A dependency in your pipeline gets compromised and injects a malicious payload
- A smart contract you've whitelisted gets upgraded to a honeypot
In each of these cases, you want a layer of defense that sits between the session token and the blockchain — something that says "even if this token is fully authenticated, it can only call these specific functions on these specific contracts." That's exactly what METHOD_WHITELIST and CONTRACT_WHITELIST do in WAIaaS.
WAIaaS is an open-source, self-hosted Wallet-as-a-Service built for AI agents and automated systems. It runs as a local daemon (or Docker container), your bot talks to it over HTTP, and it handles signing, policy enforcement, and transaction execution. The policy engine sits in the middle of a 7-stage transaction pipeline — validate, auth, policy, wait, execute, confirm — so every transaction is checked before it touches a private key.
The Policy Engine: 21 Types, 4 Tiers, Default-Deny
Before diving into METHOD_WHITELIST specifically, it's worth understanding the full policy architecture, because METHOD_WHITELIST is most effective as part of a layered configuration.
WAIaaS has 21 policy types across 4 security tiers:
INSTANT — Execute immediately, no notification
NOTIFY — Execute immediately, send notification
DELAY — Queue for delay_seconds, then execute (cancellable)
APPROVAL — Require human approval via WalletConnect/Telegram/Push
The critical detail is default-deny: if you haven't configured ALLOWED_TOKENS, transactions involving that token are blocked. If you haven't configured CONTRACT_WHITELIST, contract calls are blocked. Your bot doesn't accidentally call something it shouldn't — it's blocked by default.
For a trading bot, a minimal hardened configuration typically combines:
- CONTRACT_WHITELIST — only the specific contracts your strategy touches
- METHOD_WHITELIST — only the function selectors those contracts expose
- SPENDING_LIMIT — 4-tier amount-based controls
- ALLOWED_TOKENS — only the tokens your strategy trades
- ALLOWED_NETWORKS — restrict to the chains you actually use
Let's set these up.
Step 1: Deploy WAIaaS and Create a Trading Wallet
If you're running locally, the quickest path is Docker:
docker run -d \
--name waiaas \
-p 127.0.0.1:3100:3100 \
-v waiaas-data:/data \
-e WAIAAS_AUTO_PROVISION=true \
ghcr.io/waiaas/waiaas:latest
# Retrieve auto-generated master password
docker exec waiaas cat /data/recovery.key
The daemon binds to 127.0.0.1:3100 by default — not publicly exposed. Now create a wallet for your trading bot:
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"}'
Then create a session token — this is what your bot will use at runtime:
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_...) goes into your bot's environment. The master password stays locked away — your bot never touches it.
Step 2: Configure CONTRACT_WHITELIST
Before METHOD_WHITELIST, you need to whitelist the contracts themselves. For a Jupiter swap bot on Solana:
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": "CONTRACT_WHITELIST",
"rules": {
"contracts": [
{
"address": "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4",
"name": "Jupiter",
"chain": "solana"
}
]
}
}'
Any call to a contract not on this list is rejected before signing. Your bot physically cannot interact with a contract you haven't explicitly approved — even if a compromised dependency tries to construct a malicious payload.
Step 3: Add METHOD_WHITELIST for Function-Level Control
CONTRACT_WHITELIST tells WAIaaS which contracts are allowed. METHOD_WHITELIST goes one level deeper: it restricts which function selectors can be called on those contracts. On EVM chains, function selectors are the 4-byte prefixes of the keccak256 hash of the function signature — transfer(address,uint256) maps to 0xa9059cbb, for example.
This matters because smart contracts often expose administrative or dangerous functions alongside their public trading interface. Even if your strategy only needs swap(), a compromised session could theoretically call emergencyWithdraw() or an admin function if you haven't locked it down. METHOD_WHITELIST prevents that:
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": "METHOD_WHITELIST",
"rules": {
"selectors": ["0xa9059cbb", "0x23b872dd"]
}
}'
The exact structure of METHOD_WHITELIST rules follows the pattern of the WAIaaS policy engine. The principle: your bot signs only the function calls you explicitly enumerated. Nothing else.
Step 4: Lock Down Tokens and Spending
For a USDC/SOL arb bot, you don't want the session token to be able to move arbitrary tokens. Add ALLOWED_TOKENS:
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"
}
]
}
}'
And a SPENDING_LIMIT to cap damage from any single runaway transaction:
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
}
}'
With this configuration, a single transaction under $100 executes immediately. $100–$500 triggers a notification. $500–$2,000 is delayed 15 minutes (giving you a window to cancel). Above $2,000 requires your explicit approval.
Step 5: Always Dry-Run Before You Execute
Before your bot goes live, simulate transactions through the full policy pipeline:
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 dryRun: true flag runs the full 7-stage pipeline — validation, auth, all policy checks — without submitting anything to the chain. If your METHOD_WHITELIST or CONTRACT_WHITELIST is misconfigured, you'll see a POLICY_DENIED error response in test, not on mainnet:
{
"error": {
"code": "POLICY_DENIED",
"message": "Transaction denied by SPENDING_LIMIT policy",
"domain": "POLICY",
"retryable": false
}
}
This is how you verify your policy configuration before deploying capital.
Your Bot's Runtime Loop
At runtime, your bot authenticates with the session token only. Here's what a basic trading loop looks like using the TypeScript SDK:
import { WAIaaSClient, WAIaaSError } 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})`);
// Submit a swap through the policy pipeline
try {
const sendResult = await client.sendToken({
type: 'TRANSFER',
to: 'recipient-address',
amount: '0.001',
});
console.log(`Transaction submitted: ${sendResult.id} (status: ${sendResult.status})`);
// Poll for confirmation
const POLL_TIMEOUT_MS = 60_000;
const startTime = Date.now();
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
const tx = await client.getTransaction(sendResult.id);
if (tx.status === 'COMPLETED') {
console.log(`Confirmed! Hash: ${tx.txHash}`);
break;
}
if (tx.status === 'FAILED') {
console.error(`Failed: ${tx.error}`);
break;
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
} catch (error) {
if (error instanceof WAIaaSError) {
console.error(`API Error: [${error.code}] ${error.message}`);
// error.code: INSUFFICIENT_BALANCE, POLICY_DENIED, TOKEN_EXPIRED
}
}
The session token in WAIAAS_SESSION_TOKEN has its entire behavior constrained by the policies you configured above. Even if this token is stolen, the attacker can only call the functions you've whitelisted, on the contracts you've whitelisted, up to the spending limits you've set.
Gas Conditional Execution
WAIaaS also supports gas conditional execution — transactions execute only when the gas price meets a threshold you define. For bots where gas costs directly affect profitability, this means you can submit a transaction and let WAIaaS wait for favorable conditions rather than polling and resubmitting yourself.
This is built into the transaction pipeline at the stage level, not bolted on after the fact.
Multi-Protocol Access
The 15 integrated DeFi protocol providers — including Jupiter swap, Drift (perpetual futures), LI.FI (cross-chain bridging), Aave V3, Hyperliquid, and Across — are accessible through the same session-authenticated API. Your bot can:
# Swap on Jupiter (Solana)
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"
}'
All of these go through the same policy pipeline. Your CONTRACT_WHITELIST, METHOD_WHITELIST, and SPENDING_LIMIT apply to DeFi action calls just as they apply to raw contract calls.
Quick Start Summary
Here's the minimal path to a hardened trading bot setup:
-
Start the daemon —
docker compose up -dorwaiaas start -
Create a wallet —
POST /v1/walletswith masterAuth -
Issue a session token —
POST /v1/sessionswith masterAuth - Configure policies — CONTRACT_WHITELIST, METHOD_WHITELIST, ALLOWED_TOKENS, SPENDING_LIMIT, ALLOWED_NETWORKS
-
Dry-run your first transaction — verify policy configuration with
dryRun: true - Deploy your bot — session token in environment, master password locked away
The OpenAPI spec is available at http://127.0.0.1:3100/doc and the interactive API reference at http://127.0.0.1:3100/reference — useful for exploring all 39 API route modules before writing your integration.
What's Next
The policy system has 21 types covering scenarios from DeFi-specific limits (PERP_MAX_LEVERAGE, LENDING_LTV_LIMIT) to reputation thresholds (REPUTATION_THRESHOLD for ERC-8004 agents) — worth reviewing the full list as your strategy grows more complex. If you're building on EVM, the ERC-4337 Account Abstraction support with gasless transactions and UserOp build/sign API opens up additional execution patterns. Start with the codebase and documentation:
- GitHub: https://github.com/waiaas/WAIaaS
- Official site: https://waiaas.ai
Top comments (0)