DEV Community

Cover image for Two-Service Docker Architecture: WAIaaS Daemon + Push-Relay for Production AI Wallets
Wallet Guy
Wallet Guy

Posted on

Two-Service Docker Architecture: WAIaaS Daemon + Push-Relay for Production AI Wallets

Two-Service Docker Architecture: WAIaaS Daemon + Push-Relay for Your AI Agent's Wallet

Would you trust a third party with your AI agent's private keys? If that question gives you pause, you're already thinking about self-hosted wallet infrastructure — and that's exactly what this post is about. WAIaaS is an open-source, self-hosted Wallet-as-a-Service that puts your keys on your server, under your rules, with no third-party custody in the picture.

Why This Actually Matters

There's a philosophical gap between "using a hosted wallet API" and "running your own wallet infrastructure," and it maps pretty cleanly onto the old debate about running your own email server versus using Gmail. The hosted path is convenient. The self-hosted path is sovereign. When AI agents start autonomously spending money — paying for API calls, executing DeFi actions, bridging assets across chains — the question of who controls the signing keys stops being academic. It becomes a custody question, a privacy question, and a rate-limit question all at once. Self-hosting means you answer all three in your favor.

WAIaaS ships as a 15-package monorepo with two Docker images: the main daemon and a push-relay service. Understanding why there are two images — and how they fit together — is the key to running a production-grade setup.

The Two-Image Architecture

WAIaaS provides two Docker images:

  1. waiaas daemon — the core service: wallet management, transaction pipeline, policy engine, DeFi integrations, MCP server, REST API, and everything else.
  2. push-relay — a lightweight relay service for delivering signing notifications to you (the owner) when your AI agent submits a transaction that requires human approval.

Why separate them? The push-relay exists at the boundary between your private infrastructure and the outside world. Your daemon lives on 127.0.0.1:3100 — ideally never directly exposed to the internet. The push-relay handles the outbound notification path so you can approve or reject transactions from your phone without punching holes in your firewall to reach the daemon directly.

The daemon's default port binding is 127.0.0.1:3100:3100 — localhost only, by design. That's not an accident or a placeholder. It's the architecture telling you: this service is internal. You put a reverse proxy or VPN in front of it if you need remote access. You use the push-relay for notification delivery.

The Core docker-compose.yml

The fastest path to a running daemon is three commands:

git clone https://github.com/minhoyoo-iotrust/WAIaaS.git
cd WAIaaS
docker compose up -d
Enter fullscreen mode Exit fullscreen mode

That's it. The included docker-compose.yml handles the rest:

services:
  daemon:
    image: ghcr.io/minhoyoo-iotrust/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
Enter fullscreen mode Exit fullscreen mode

A few things worth calling out here:

  • 127.0.0.1:3100:3100 — The port binding is explicitly localhost-only. Your daemon doesn't accept connections from the network unless you deliberately change this. That's a sensible default for a service that holds private keys.
  • Named volume waiaas-data — Your wallet data persists in a named Docker volume, not a bind mount. This survives docker compose down without losing anything. You'd need docker compose down -v to actually delete data.
  • Healthcheck — The daemon exposes a /health endpoint. Docker will restart the container automatically if it goes unhealthy.
  • restart: unless-stopped — Survives host reboots without you touching anything.

Auto-Provision: First-Boot Without a Password Prompt

One of the friction points with self-hosted services is the initial secret setup. WAIaaS solves this with auto-provision mode, which generates a master password on first start and writes it to a recovery file:

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 auto-generated master password
docker exec waiaas cat /data/recovery.key
Enter fullscreen mode Exit fullscreen mode

This is useful for homelab deployments where you want the service to come up cleanly after a reboot without storing a password in plaintext environment variables. Once you've retrieved the recovery key, you can harden it:

waiaas set-master   # Change the generated password to something you control
Enter fullscreen mode Exit fullscreen mode

Then delete recovery.key. The CLI also supports this same flow:

waiaas init --auto-provision   # Generates random master password → recovery.key
waiaas start                   # No password prompt
waiaas quickset                # Creates wallets + sessions automatically
Enter fullscreen mode Exit fullscreen mode

Production Secrets: Docker Secrets Overlay

For production deployments, environment variables in a .env file are fine for development but not ideal for sensitive values like your master password. WAIaaS supports Docker Secrets via a secrets overlay file:

# 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
Enter fullscreen mode Exit fullscreen mode

The docker-compose.secrets.yml overlay wires the secret files into the container without putting them in environment variables or image layers. This is the pattern you'd use in a swarm deployment or any setup where you want secrets management separate from configuration management.

The Transaction Approval Flow: Where Push-Relay Comes In

Here's the scenario where the two-service architecture earns its keep:

Your AI agent — running as an MCP client in Claude Desktop, or using the TypeScript SDK directly — submits a transaction that exceeds your INSTANT tier threshold. The daemon's policy engine catches it and bumps it to APPROVAL tier. Now what?

The daemon needs to notify you. You're not sitting in front of a terminal watching logs. You need a push notification to your phone, and you need a way to approve or reject the transaction from wherever you are.

That's the push-relay's job. It sits at the edge of your infrastructure, handles the notification delivery path, and relays your approval back to the daemon — all without requiring the daemon itself to be publicly reachable.

WAIaaS has three signing channels available: push-relay-signing-channel, telegram-signing-channel, and wallet-notification-channel. The push-relay service is the backing infrastructure for the first one.

The WalletConnect integration (FEAT-WC) is the other half of this: the owner connects via WalletConnect, and transaction approvals flow through that channel using standard wallet signing (SIWE for EVM, SIWS for Solana).

Setting Up the Policy Engine for Human-in-the-Loop

The approval flow only kicks in when your policies say it should. Here's a realistic SPENDING_LIMIT policy for a trading agent:

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
    }
  }'
Enter fullscreen mode Exit fullscreen mode

What this does:

  • Under $100 → executes immediately, no interruption
  • $100–$500 → executes immediately, sends you a notification
  • $500–$2000 → queued for 15 minutes (900 seconds), then executes unless you cancel
  • Over $2000 → requires your explicit approval before anything happens
  • Daily cap at $5000 → hard limit regardless of individual transaction size

This is the 4-tier security model: INSTANT, NOTIFY, DELAY, APPROVAL. You tune the thresholds to match your risk tolerance and your agent's intended behavior.

The policy engine defaults to deny. If you haven't configured ALLOWED_TOKENS or CONTRACT_WHITELIST, token transfers and contract calls are blocked. This is intentional — a misconfigured agent can't accidentally drain a wallet that has no token whitelist.

Wiring Up Your First AI Agent Session

With the daemon running, here's the minimal path to an agent that can transact:

Step 1: Create a 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"}'
Enter fullscreen mode Exit fullscreen mode

Step 2: Create a session token for the 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>"}'
Enter fullscreen mode Exit fullscreen mode

Step 3: Give the session token to your agent

The agent uses Authorization: Bearer wai_sess_<token> for everything it does — checking balances, sending tokens, executing DeFi actions. It never touches the master password. The master password is yours; the session token is the agent's.

Step 4 (optional): Connect via MCP

waiaas mcp setup --all   # Auto-register all wallets with Claude Desktop
Enter fullscreen mode Exit fullscreen mode

Or manually in claude_desktop_config.json:

{
  "mcpServers": {
    "waiaas": {
      "command": "npx",
      "args": ["-y", "@waiaas/mcp"],
      "env": {
        "WAIAAS_BASE_URL": "http://127.0.0.1:3100",
        "WAIAAS_SESSION_TOKEN": "wai_sess_<your-token>",
        "WAIAAS_DATA_DIR": "~/.waiaas"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The MCP server exposes 45 tools to Claude — everything from get-balance and send-token to get-defi-positions, simulate-transaction, and x402-fetch for automatic HTTP payment handling.

Common Docker Operations

Day-to-day management is straightforward:

docker compose up -d          # Start daemon
docker compose logs -f        # Follow logs
docker compose down           # Stop (data preserved in named volume)
docker compose down -v        # Stop + delete data volume
Enter fullscreen mode Exit fullscreen mode

The daemon auto-updates if you run Watchtower alongside it — the Docker image is ghcr.io/minhoyoo-iotrust/waiaas:latest, so Watchtower will pull new versions automatically. For homelabs that want hands-off maintenance, this is a clean solution.

The Self-Hosting Philosophy in Practice

Running WAIaaS is closer to running a database than running a complex distributed system. You have a single Docker Compose file, a named volume for persistence, a healthcheck endpoint, and a localhost-only port binding. The push-relay handles the one piece that needs to reach the outside world.

Your keys never leave your machine. Your transaction history lives in your volume. Your policies are your business rules, not a third party's rate limit. The REST API has 39 route modules covering every operation your agents might need — all accessible at http://127.0.0.1:3100, all documented at /reference with an interactive Scalar UI.

The 7-stage transaction pipeline (validate → auth → policy → wait → execute → confirm) runs on your hardware. The policy engine enforces your rules. The gas conditional execution feature means transactions only go through when gas prices meet your threshold — another thing you control, not a hosted service's scheduler.

Quick Start Summary

  1. git clone https://github.com/minhoyoo-iotrust/WAIaaS.git && cd WAIaaS
  2. docker compose up -d
  3. Set your master password and create a wallet via the REST API or CLI
  4. Create a session token and wire it into your agent
  5. Configure policies to match your risk tolerance

That's the whole setup. No account registration, no API key from a third party, no usage limits tied to a billing tier.

What's Next

The WAIaaS GitHub repository at https://github.com/minhoyoo-iotrust/WAIaaS has the full source, including the docker-compose.secrets.yml overlay and the entrypoint script that handles auto-provision. The official site at https://waiaas.ai has documentation on the full policy engine, DeFi integrations, and the MCP tool catalog. If you're running a homelab and want AI agents that can actually transact — on Solana, EVM chains, or both — this is the stack worth standing up.

Top comments (0)