Two-Container AI Wallet Setup: WAIaaS + Push-Relay Production Architecture
Would you trust a third party with your AI agent's private keys? If you're building autonomous agents that move real money — swapping tokens, paying for API calls, managing DeFi positions — that question deserves a serious answer. WAIaaS is an open-source, self-hosted Wallet-as-a-Service that puts the answer firmly in your hands: your keys, your server, your rules.
Why Self-Hosting Your Agent's Wallet Actually Matters
There's a philosophical divide in crypto infrastructure that mirrors the one in email: you can use Gmail, or you can run your own mail server. Most people choose convenience. A smaller group chooses control — and for good reason.
When an AI agent has signing authority over a wallet, the custody question becomes acutely important. A hosted wallet service means a third party holds the keys, enforces their own rate limits, can inspect your transaction patterns, and can cut off your agent's access at any time. For hobbyists building toy projects, that's probably fine. For anyone running agents with meaningful funds, or anyone who simply values sovereignty over their infrastructure, self-hosting is the natural conclusion.
The good news is that WAIaaS makes this genuinely practical. Unlike running your own email server — which is famously painful — a production WAIaaS deployment is two Docker containers and a docker compose up -d.
The Two-Container Architecture
WAIaaS ships two Docker images (fact DOCKER-05): the main daemon and a push-relay. Understanding why both exist is the key to understanding the production architecture.
The daemon (waiaas/daemon:latest) is the core of the system. It exposes the REST API on port 3100, manages wallets and sessions, runs the 7-stage transaction pipeline, enforces policies, and handles all signing operations. This is the container your AI agents talk to.
The push-relay is a lightweight relay service that handles real-time notifications — specifically, it's what allows your phone to receive push notifications when an AI agent submits a transaction that requires your approval. Without it, you'd have to poll for pending transactions manually. With it, your phone buzzes the moment your agent tries to do something that needs a human decision.
Together they give you:
- Full local custody of private keys (the daemon never phones home)
- Real-time approval workflows via push notifications
- A clean separation of concerns between signing logic and notification delivery
What's Actually Running in Each Container
The daemon is the heavy lifter. It's a 15-package monorepo (PKG-01) condensed into a single container. It provides:
-
39 REST API route modules (
API-01) covering wallets, sessions, transactions, DeFi actions, NFTs, and more -
45 MCP tools (
MCP-01) for direct Claude Desktop or other AI framework integration -
15 DeFi protocol integrations (
DEFI-01) including Jupiter, Aave v3, Hyperliquid, Lido, Jito, and others (DEFI-02) -
A policy engine with 21 policy types (
POLICY-01) and 4 security tiers (POLICY-02) -
2 chain types across 18 networks (
NET-01) -
3 signing channels (
SIGN-01): push-relay, Telegram, and wallet notification
The default port binding is 127.0.0.1:3100:3100 (DOCKER-02), which means by default the daemon only listens on localhost. That's intentional — you don't want your signing API accidentally exposed to the internet.
The push-relay handles the notification side of things. When the daemon needs to send you a push notification (say, an AI agent just tried to move $2,000 and your SPENDING_LIMIT policy requires your approval), the push-relay is the bridge that gets that alert to your device without requiring the daemon itself to be reachable from the public internet.
Setting Up the Production Stack
Let's walk through a real production deployment. The full docker-compose.yml looks like this:
services:
daemon:
image: ghcr.io/waiaas/waiaas:latest
container_name: waiaas-daemon
ports:
- "127.0.0.1:3100:3100"
volumes:
- waiaas-data:/data
environment:
- WAIAAS_DATA_DIR=/data
- WAIAAS_DAEMON_HOSTNAME=0.0.0.0
env_file:
- path: .env
required: false
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3100/health"]
interval: 30s
timeout: 5s
start_period: 10s
retries: 3
volumes:
waiaas-data:
driver: local
Notice a few things about this config that matter for production:
-
Port binding to
127.0.0.1— The daemon isn't reachable from outside the host. If you need external access, put a reverse proxy (nginx, Caddy) in front of it. -
Named volume
waiaas-data— Your wallet data and keys persist in a named volume. Runningdocker compose downwon't delete them; you'd needdocker compose down -vto wipe the data. - Healthcheck — Docker will restart the container if the health endpoint stops responding.
Handling Production Secrets Properly
The default setup is fine for local development, but production deserves better than environment variables in a .env file. WAIaaS ships a Docker Secrets overlay (DOCKER-04) specifically for this.
# Create secret files
mkdir -p secrets
echo "your-secure-password" > secrets/master_password.txt
chmod 600 secrets/master_password.txt
# Deploy with secrets overlay
docker compose -f docker-compose.yml -f docker-compose.secrets.yml up -d
The docker-compose.secrets.yml overlay wires Docker Secrets into the container without the secret values ever appearing in environment variables or docker inspect output. For a self-hoster who's serious about security, this is the right way to do it.
Auto-Provision: First Boot Without a Password Prompt
If you're deploying to a headless server and don't want to manually set a master password on first boot, the WAIAAS_AUTO_PROVISION flag handles it:
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 generates a random master password on first start, writes it to /data/recovery.key, and proceeds without waiting for human input (DOCKER-03). This is useful for automated deployments. After you've retrieved the recovery key and stored it somewhere safe, you can harden the password later with the CLI's set-master command.
The Three Authentication Layers
Once the stack is running, understanding the three auth layers helps you understand who can do what:
# 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>"
This three-layer model (SEC-02) maps cleanly to a production self-hosted setup:
- You hold the master password (Argon2id hashed). It lives only on your server.
- Your AI agents get session tokens (JWT HS256) with scoped permissions and TTLs.
- Your phone or hardware wallet provides owner signatures for high-value approvals.
No third party is in this chain at any point.
Putting Policies Between Your Agent and Your Funds
The policy engine is what makes autonomous agents safe to run. With 21 policy types (POLICY-01) enforcing a default-deny model (SEC-03), you can be very precise about what your agent is allowed to do.
Here's a realistic policy for a trading agent — a spending limit with tiered security:
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 policy, transactions under $100 execute immediately. $100-$500 execute immediately but you get a push notification. $500-$2,000 are queued for 15 minutes — cancellable if you catch them. Over $2,000 requires your explicit approval (POLICY-02). The push-relay container is what makes that approval flow work in real-time.
If your agent tries to interact with a token you haven't whitelisted, it gets blocked entirely — that's the default-deny enforcement of ALLOWED_TOKENS at work (SEC-03).
Quick Start: Five Steps to a Running Stack
Step 1: Clone the repo
git clone https://github.com/waiaas/WAIaaS.git
cd WAIaaS
Step 2: Start the stack
docker compose up -d
Step 3: Initialize via CLI
npm install -g @waiaas/cli
waiaas init
waiaas start
Step 4: Create wallets and sessions in one command
waiaas quickset --mode mainnet
Step 5: Connect Claude Desktop (or your agent framework)
waiaas mcp setup --all
After step 5, paste the printed MCP config JSON into your Claude Desktop configuration. Claude immediately gains access to 45 MCP tools (MCP-01) — balance checks, token transfers, DeFi actions, NFT operations, and more — all routed through your local daemon, all subject to your local policies.
Verifying the Stack is Healthy
A few commands to confirm everything is running correctly:
docker compose logs -f # Follow logs from both containers
docker compose ps # Check container status
And from outside Docker, hit the health endpoint directly:
curl http://127.0.0.1:3100/health
The daemon also exposes an OpenAPI 3.0 spec and interactive docs (FEAT-OPENAPI):
# Download the full spec
curl http://127.0.0.1:3100/doc -o openapi.json
# Open interactive API reference in your browser
open http://127.0.0.1:3100/reference
The interactive reference at /reference is useful for exploring all 39 API route modules (API-01) without reading raw JSON.
The Philosophy: Practical Sovereignty
Running your own email server is notoriously painful — SPF records, DKIM, deliverability issues, spam filtering. The comparison to self-hosting crypto infrastructure gets made often, and it's usually meant to scare people off. WAIaaS is designed to make that comparison unfair.
The Docker deployment handles auto-provisioning, Docker Secrets, health checks, runs as a non-root user (UID 1001), and supports watchtower auto-updates (FEAT-DOCKER). The CLI covers backup and restore (CLI-01), so your wallet data isn't one bad hard drive away from disappearing. The 684+ test files (TEST-01) across the codebase mean you're not beta-testing someone's weekend project.
The two-container architecture means you can update either container independently. The push-relay is lightweight and rarely changes. The daemon gets the DeFi protocol updates and security patches. You keep full control of the upgrade schedule.
For homelab enthusiasts and self-hosters who already run Nextcloud, Vaultwarden, or Immich — this fits naturally into that stack. It's one more service you control, one fewer third party with access to something important.
What's Next
To go deeper on the policy engine — particularly DeFi-specific policies like LENDING_LTV_LIMIT, PERP_MAX_LEVERAGE, and VENUE_WHITELIST — the policy configuration documentation in the repo covers every rule format for all 21 types. For connecting your AI agent framework of choice, the MCP package (@waiaas/mcp) and the TypeScript and Python SDKs (FEAT-SDK) give you clean programmatic access without writing raw HTTP calls.
The full source, Dockerfiles, and compose files are at https://github.com/waiaas/WAIaaS. If you want to understand the project before diving into the code, start at https://waiaas.ai. Star the repo if this is useful — it helps others find it.
Top comments (0)