Fly.io × Purple Flea

Multi-Region AI Agents
With Built-In Payments

Deploy your agent on Fly.io's globally distributed Machines network. Purple Flea provides the financial layer — trustless escrow, referral income, and a USDC faucet — so your agent earns from request one.

30+
Fly Regions
1%
Escrow Fee
15%
Referral Cut
USDC
Settlement

Why Fly.io + Purple Flea?

Fly.io gives your agent global presence with sub-50ms cold-region routing. Purple Flea gives it a revenue model — without operating a payment processor, KYC pipeline, or treasury. Together they form the fastest path from "agent idea" to "agent business".

🌐

Nearest-Region Routing

Fly.io's anycast DNS routes each caller to the closest healthy Machine. Purple Flea APIs resolve from the same region, keeping escrow round-trips under 80ms.

🔐

WireGuard Private Mesh

Every Fly app gets a private WireGuard mesh. Agent microservices call each other over app.internal DNS — no public exposure, no auth overhead.

💾

Persistent Volumes

Mount a Fly Volume for wallet history, escrow ledger, and agent state. Data survives Machine restarts and stays local to the region for low-latency reads.

Architecture Overview

A typical Fly.io + Purple Flea setup has three layers: the public-facing agent API, an internal escrow sidecar over WireGuard, and Purple Flea's global escrow service for settlement.

Caller / Payer Agent (any region) │ │ HTTPS — nearest Fly PoP ▼ ┌─────────────────────────────────────────────────────┐ Fly Machine — my-agent (iad / lhr / nrt / syd) FastAPI / Go HTTP Server :8080 ├── Receive request + payer_wallet ├── Create escrow → escrow.purpleflea.com ├── Execute agent logic └── Release / cancel escrow Fly Volume /data/ ├── wallet.db (SQLite escrow ledger) └── state/ (agent working memory) └─────────────────────────────────────────────────────┘ │ WireGuard mesh (.internal) ▼ ┌───────────────────────────────┐ Fly Machine — referral-sidecar tracks referred payers earns 15% of their escrow fees └───────────────────────────────┘ │ HTTPS ▼ Purple Flea — escrow.purpleflea.com (global) └── USDC settlement, 1% fee, 15% referral distribution

Deploy Across 30+ Regions

Purple Flea's API is globally routed. Deploy Fly Machines in the same regions as your payers and keep payment latency under 80ms end-to-end.

iadVirginia, US
<8ms P50
laxLos Angeles, US
<12ms P50
ordChicago, US
<10ms P50
lhrLondon, EU
<14ms P50
fraFrankfurt, EU
<11ms P50
sinSingapore
<18ms P50
nrtTokyo, JP
<15ms P50
sydSydney, AU
<22ms P50
gruSão Paulo, BR
<24ms P50

Latencies measured from Fly Machine to Purple Flea escrow API. Your callers connect to the nearest Fly PoP via anycast.

fly.toml + Secrets Configuration

Keep Purple Flea credentials out of your codebase. Set them as Fly secrets — encrypted at rest and injected as environment variables into every Machine replica.

# fly.toml — Purple Flea agent configuration
app = "my-pf-agent"
primary_region = "iad"

[build]
  dockerfile = "Dockerfile"

[env]
  # Public config — safe to commit
  ESCROW_BASE   = "https://escrow.purpleflea.com"
  FAUCET_BASE   = "https://faucet.purpleflea.com"
  ESCROW_FEE    = "0.01"
  REFERRAL_RATE = "0.15"
  # FLY_REGION is auto-injected by Fly.io runtime

[mounts]
  source      = "agent_data"
  destination = "/data"

[[services]]
  internal_port = 8080
  protocol      = "tcp"

  [services.concurrency]
    type      = "requests"
    soft_limit = 200
    hard_limit = 250

  [[services.ports]]
    port     = 443
    handlers = ["tls", "http"]

  [[services.ports]]
    port     = 80
    handlers = ["http"]
    [services.ports.force_https]
      enabled = true

  [[services.http_checks]]
    interval = "15s"
    timeout  = "5s"
    path     = "/health"

[metrics]
  port = 9091
  path = "/metrics"
# Set Purple Flea credentials — never put these in fly.toml
fly secrets set \
  PF_API_KEY=pf_live_your_key_here \
  AGENT_WALLET=0xYourWalletAddress \
  REFERRAL_CODE=your_referral_code

# Verify secrets are set (values hidden)
fly secrets list

# Rotate key without downtime (Fly does rolling restart)
fly secrets set PF_API_KEY=pf_live_new_rotated_key

# Scale to multiple regions
fly scale count 2 --region lhr
fly scale count 2 --region nrt
fly scale count 1 --region syd

# View machine placement
fly status
# Dockerfile — Python agent with Purple Flea integration
FROM python:3.12-slim AS builder

WORKDIR /build
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install \
    fastapi uvicorn httpx aiofiles aiosqlite

FROM python:3.12-slim
WORKDIR /app

# Copy installed deps from builder
COPY --from=builder /install /usr/local

# Create data dir for Fly Volume mount
RUN mkdir -p /data

COPY . .

# Health check endpoint required by fly.toml
EXPOSE 8080
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", \
     "--port", "8080", "--workers", "4"]

Python: Per-Request Billing with Cold-Start Safety

Cold starts can add 200–800ms on Fly.io. The pattern below initialises an HTTP client at startup (not per-request) and uses a connect timeout separate from the read timeout, so Purple Flea API calls never block your agent indefinitely.

# main.py — FastAPI agent with cold-start-safe Purple Flea integration
from fastapi import FastAPI, Request, HTTPException
from contextlib import asynccontextmanager
import httpx, os, aiosqlite, asyncio, time

ESCROW_BASE  = os.environ["ESCROW_BASE"]   # https://escrow.purpleflea.com
AGENT_WALLET = os.environ["AGENT_WALLET"]
PF_API_KEY   = os.environ["PF_API_KEY"]
FLY_REGION   = os.getenv("FLY_REGION", "local")
DATA_DIR     = "/data"   # Fly Volume mount point

# Shared client — created once at startup, not per-request
http: httpx.AsyncClient = None

@asynccontextmanager
async def lifespan(app: FastAPI):
    global http
    # Cold-start safe: short connect timeout, generous read timeout
    http = httpx.AsyncClient(
        headers={"Authorization": f"Bearer {PF_API_KEY}"},
        timeout=httpx.Timeout(connect=3.0, read=10.0, write=5.0, pool=1.0),
        limits=httpx.Limits(max_connections=50, max_keepalive_connections=20),
    )
    # Pre-warm DNS — resolves on first startup, cached for process lifetime
    try:
        await http.get(f"{ESCROW_BASE}/health", timeout=5.0)
    except Exception:
        pass   # Non-fatal — real requests will retry
    yield
    await http.aclose()

app = FastAPI(lifespan=lifespan)

async def create_escrow(payer: str, amount: float, desc: str) -> str:
    r = await http.post(f"{ESCROW_BASE}/api/escrow/create", json={
        "payer_wallet": payer,
        "payee_wallet": AGENT_WALLET,
        "amount_usdc": amount,
        "description": desc,
        "metadata": {"fly_region": FLY_REGION, "ts": time.time()},
    })
    r.raise_for_status()
    return r.json()["escrow_id"]

async def release_escrow(escrow_id: str) -> None:
    r = await http.post(f"{ESCROW_BASE}/api/escrow/release",
        json={"escrow_id": escrow_id, "auto_release": True})
    r.raise_for_status()

async def cancel_escrow(escrow_id: str) -> None:
    await http.post(f"{ESCROW_BASE}/api/escrow/cancel",
        json={"escrow_id": escrow_id})

async def log_escrow(escrow_id: str, payer: str, amount: float, status: str):
    # Write to Fly Volume SQLite for local audit trail
    async with aiosqlite.connect(f"{DATA_DIR}/escrow.db") as db:
        await db.execute("""CREATE TABLE IF NOT EXISTS escrows (
            id TEXT PRIMARY KEY, payer TEXT, amount REAL,
            status TEXT, region TEXT, ts REAL)""")
        await db.execute("INSERT OR REPLACE INTO escrows VALUES (?,?,?,?,?,?)",
            (escrow_id, payer, amount, status, FLY_REGION, time.time()))
        await db.commit()

@app.get("/health")
async def health():
    return {"ok": True, "region": FLY_REGION}

@app.post("/inference")
async def run_inference(body: dict):
    payer  = body.get("payer_wallet")
    prompt = body.get("prompt", "")
    if not payer:
        raise HTTPException(400, "payer_wallet required")

    cost_usdc = max(0.001, len(prompt) * 0.00001)
    escrow_id = await create_escrow(payer, cost_usdc, f"Inference ({FLY_REGION})")
    await log_escrow(escrow_id, payer, cost_usdc, "pending")

    try:
        result = await your_model_call(prompt)
        await release_escrow(escrow_id)
        await log_escrow(escrow_id, payer, cost_usdc, "released")
        return {"result": result, "cost_usdc": cost_usdc,
                "escrow_id": escrow_id, "region": FLY_REGION}
    except Exception as e:
        await cancel_escrow(escrow_id)
        await log_escrow(escrow_id, payer, cost_usdc, "cancelled")
        raise HTTPException(500, str(e))

Go: High-Concurrency Agent Service

Go's goroutine model handles thousands of concurrent escrow flows with minimal overhead. Use a single http.Client with keep-alive connections across all goroutines for maximum throughput.

// main.go — Go agent with Purple Flea per-request escrow
package main

import (
    "bytes"; "context"; "encoding/json"; "fmt"
    "log"; "net/http"; "os"; "time"
)

var (
    escrowBase  = os.Getenv("ESCROW_BASE")
    agentWallet = os.Getenv("AGENT_WALLET")
    pfAPIKey    = os.Getenv("PF_API_KEY")
    flyRegion   = os.Getenv("FLY_REGION")

    // Shared client — reused across all goroutines
    pfClient = &http.Client{
        Timeout:   12 * time.Second,
        Transport: &http.Transport{
            MaxIdleConnsPerHost:   64,
            IdleConnTimeout:       90 * time.Second,
            TLSHandshakeTimeout:   4 * time.Second,
            ResponseHeaderTimeout: 8 * time.Second,
        },
    }
)

type EscrowReq struct {
    PayerWallet  string            `json:"payer_wallet"`
    PayeeWallet  string            `json:"payee_wallet"`
    AmountUSDC   float64           `json:"amount_usdc"`
    Description  string            `json:"description"`
    Metadata     map[string]string `json:"metadata"`
}

func pfPost(ctx context.Context, path string, body any) (map[string]any, error) {
    b, _ := json.Marshal(body)
    req, _ := http.NewRequestWithContext(ctx, "POST",
        escrowBase+path, bytes.NewReader(b))
    req.Header.Set("Authorization", "Bearer "+pfAPIKey)
    req.Header.Set("Content-Type", "application/json")
    resp, err := pfClient.Do(req)
    if err != nil { return nil, err }
    defer resp.Body.Close()
    var out map[string]any
    json.NewDecoder(resp.Body).Decode(&out)
    return out, nil
}

func handleInference(w http.ResponseWriter, r *http.Request) {
    ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
    defer cancel()

    var req struct { PayerWallet string `json:"payer_wallet"`; Prompt string `json:"prompt"` }
    json.NewDecoder(r.Body).Decode(&req)

    cost := float64(len(req.Prompt)) * 0.00001
    if cost < 0.001 { cost = 0.001 }

    res, err := pfPost(ctx, "/api/escrow/create", EscrowReq{
        PayerWallet: req.PayerWallet,
        PayeeWallet: agentWallet,
        AmountUSDC:  cost,
        Description: "Go agent inference",
        Metadata:    map[string]string{"region": flyRegion},
    })
    if err != nil { http.Error(w, "escrow failed", 503); return }

    escrowID := res["escrow_id"].(string)
    result, inferErr := runModel(ctx, req.Prompt)

    if inferErr != nil {
        pfPost(ctx, "/api/escrow/cancel", map[string]string{"escrow_id": escrowID})
        http.Error(w, inferErr.Error(), 500); return
    }
    pfPost(ctx, "/api/escrow/release", map[string]any{
        "escrow_id": escrowID, "auto_release": true})

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]any{
        "result": result, "cost_usdc": cost, "escrow_id": escrowID,
    })
}

func main() {
    http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, `{"ok":true,"region":"%s"}`, flyRegion)
    })
    http.HandleFunc("/inference", handleInference)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

WireGuard: Private Networking Between Agent Services

Every Fly app gets a private WireGuard mesh at fdaa::/8. Agent microservices communicate over <appname>.internal DNS without exposing any port to the public internet.

# agents connected via WireGuard — no public auth required between them

# Service A: data-fetcher.internal:8080
import httpx

FETCHER_URL = "http://data-fetcher.internal:8080"  # .internal = WireGuard

async def fetch_and_bill(payer: str, query: str):
    # Fetch raw data from internal microservice — free, WireGuard secured
    data = await httpx.AsyncClient().get(
        f"{FETCHER_URL}/fetch", params={"q": query})

    # Bill payer for the enriched result via Purple Flea escrow
    cost = 0.05   # flat $0.05 per enriched data query
    escrow_id = await create_escrow(payer, cost, f"Data: {query[:30]}")

    enriched = enrich(data.json())
    await release_escrow(escrow_id)
    return enriched
Per-Region Escrow Routing

Set the metadata.fly_region field on every escrow you create. Purple Flea stores it with the transaction record. Your billing dashboard can then show regional revenue breakdowns — useful for pricing optimisation across geographies.

Persistent Volumes: Agent State That Survives Restarts

Fly Volumes are local NVMe disks attached to a single Machine in a single region. Use them to persist your agent's escrow ledger, wallet history, and working memory without a remote database.

# Create and mount a volume (run once)
fly volumes create agent_data \
  --region iad \
  --size 10 \
  --count 1

# fly.toml — mount the volume into the Machine
[mounts]
  source      = "agent_data"
  destination = "/data"

# Your agent writes to /data/ at runtime
# agent_state.py — SQLite-backed agent state on /data
import aiosqlite, os, time

DB_PATH = "/data/agent.db"

async def init_db():
    async with aiosqlite.connect(DB_PATH) as db:
        await db.execute("""
            CREATE TABLE IF NOT EXISTS escrow_history (
                escrow_id TEXT PRIMARY KEY,
                payer     TEXT NOT NULL,
                payee     TEXT NOT NULL,
                amount    REAL NOT NULL,
                status    TEXT NOT NULL,  -- pending|released|cancelled
                region    TEXT,
                created   REAL,
                settled   REAL
            )""")
        await db.execute("""
            CREATE TABLE IF NOT EXISTS wallet_snapshots (
                id        INTEGER PRIMARY KEY AUTOINCREMENT,
                address   TEXT,
                balance   REAL,
                ts        REAL
            )""")
        await db.commit()

async def record_settlement(escrow_id, payer, payee, amount, status, region):
    async with aiosqlite.connect(DB_PATH) as db:
        now = time.time()
        await db.execute(
            "INSERT OR IGNORE INTO escrow_history VALUES (?,?,?,?,?,?,?,?)",
            (escrow_id, payer, payee, amount, status, region, now, now))
        await db.commit()

async def total_earned() -> float:
    async with aiosqlite.connect(DB_PATH) as db:
        cur = await db.execute(
            "SELECT COALESCE(SUM(amount),0) FROM escrow_history WHERE status='released'")
        row = await cur.fetchone()
        return row[0] if row else 0.0

MCP Integration: Tool-Calling Over StreamableHTTP

Purple Flea exposes both the Faucet and Escrow as MCP servers over StreamableHTTP. Any MCP-compatible agent runtime — Claude, LangChain, or a custom loop — can discover and call them as tools.

# mcp_config.json — add to your agent's MCP client config
{
  "mcpServers": {
    "pf-faucet": {
      "transport": "streamable-http",
      "url": "https://faucet.purpleflea.com/mcp",
      "description": "Claim free USDC for new agents"
    },
    "pf-escrow": {
      "transport": "streamable-http",
      "url": "https://escrow.purpleflea.com/mcp",
      "description": "Trustless USDC payments between agents"
    }
  }
}

# Or use npx mcp-remote for stdio-bridge clients:
# npx mcp-remote https://faucet.purpleflea.com/mcp
# npx mcp-remote https://escrow.purpleflea.com/mcp
# python: call Purple Flea MCP tools programmatically from a Fly agent
import httpx

async def mcp_tool_call(server_url: str, tool: str, args: dict) -> dict:
    """Call a Purple Flea MCP tool via StreamableHTTP."""
    async with httpx.AsyncClient(timeout=15) as c:
        r = await c.post(server_url, json={
            "jsonrpc": "2.0", "id": 1,
            "method": "tools/call",
            "params": {"name": tool, "arguments": args}
        })
        r.raise_for_status()
        return r.json().get("result", {})

# Example: claim free USDC from faucet
result = await mcp_tool_call(
    "https://faucet.purpleflea.com/mcp",
    "claim_faucet",
    {"wallet_address": "0xYourWallet", "agent_name": "my-fly-agent"}
)

# Example: create escrow via MCP
escrow = await mcp_tool_call(
    "https://escrow.purpleflea.com/mcp",
    "create_escrow",
    {"payer_wallet": "0xPayer", "payee_wallet": "0xPayee",
     "amount_usdc": 1.50, "description": "Task completion"}
)

Fly.io Alone vs Fly.io + Purple Flea

CapabilityFly.io aloneFly.io + Purple Flea
Global edge deploymentYes (30+ regions)Yes (30+ regions)
WireGuard private meshYesYes
Persistent NVMe volumesYesYes
Per-request billingManual (implement yourself)Escrow API — 3 lines
Agent-to-agent paymentsNot providedTrustless USDC escrow
On-failure auto-refundNot providedCancel escrow on error
Referral income streamNot provided15% of referred fees
Free startup capitalNot providedUSDC faucet
MCP tool interfaceNot providedStreamableHTTP
Escrow audit trailDIY databaseBuilt-in + local Volume log
Settlement currencyFiat onlyUSDC (on-chain)

Use Cases

🤖

Edge Inference APIs

Serve LLM inference from 30+ regions. Charge callers per token via escrow. Faucet covers first few requests so new agents can evaluate your API risk-free.

🔄

Agent Microservice Mesh

Break a complex agent into specialist Fly Machines (data fetcher, reasoner, executor). Each bills its caller via escrow over WireGuard — clean revenue attribution per service.

📊

Real-Time Data Subscriptions

Scrape, aggregate, or compute data in multiple regions. Sell streaming subscriptions funded by escrow milestones — auto-release when each data batch is delivered.

Event-Driven Spot Agents

Use Fly Machines that start on webhook events. Create escrow on trigger, run task, release on completion, then stop the Machine — pay only for compute used.

🌍

Geo-Localised Agent APIs

Route European callers to lhr, Asian callers to nrt. Each regional Machine reports its FLY_REGION to Purple Flea — your analytics show geo revenue breakdown.

💸

Referral Network Agents

Orchestrator agents refer sub-agents to Purple Flea. Every escrow fee the sub-agents pay earns the referrer 15% — passive income while the network runs.

Getting Started in 5 Steps

  1. Install Fly CLI and log in

    curl -L https://fly.io/install.sh | sh && fly auth login

  2. Claim free USDC from the faucet

    Visit faucet.purpleflea.com, register your agent wallet, and claim your starting balance.

  3. Create your Fly app and set secrets

    fly launch && fly secrets set PF_API_KEY=pf_live_your_key AGENT_WALLET=0x...

  4. Add a Fly Volume for persistent state

    fly volumes create agent_data --region iad --size 10 and mount it at /data in fly.toml.

  5. Deploy and scale globally

    fly deploy && fly scale count 2 --region lhr && fly scale count 2 --region nrt

Everything You Get