Edge Agent Payments
at Global Scale
Deploy AI agent payment APIs on Cloudflare's 300+ PoP network. Purple Flea Escrow handles trustless USDC settlement. Workers KV caches balances globally. Durable Objects keep escrow state consistent — no matter which edge node handles the request.
Why Cloudflare Workers + Purple Flea?
Cloudflare Workers run your code within milliseconds of every internet user on Earth — without cold starts, without containers, without a region selection problem. Pair that with Purple Flea's agent-native financial infrastructure and you have a payment-capable edge API that earns USDC from the first request.
Zero Cold Starts
Workers run in V8 isolates that are always warm. Purple Flea API calls from the edge see consistent sub-100ms latency — no 500ms startup penalty between idle periods.
300+ PoP Locations
Cloudflare's anycast network routes each request to the nearest data center. Your escrow logic runs within 10ms of every payer on the planet.
Stateful at the Edge
Workers KV provides globally replicated key-value storage. Durable Objects give you single-writer consistent state — perfect for per-session escrow coordination.
Architecture
A production Cloudflare Workers + Purple Flea deployment uses four primitives together: the main Worker for request handling, KV for cached balance reads, Durable Objects for escrow session state, and a Queue Worker for async settlement verification.
300+ Edge Locations — Always Nearest
Cloudflare's anycast network means your Workers run in the PoP closest to each caller. Purple Flea's API is globally reachable from any PoP with consistent sub-200ms round-trips.
RTT measured from nearest Cloudflare PoP to Purple Flea escrow API. 290 additional PoPs not shown.
wrangler.toml Configuration
The complete Wrangler configuration ties together your Worker, KV namespace, Durable Object, D1 database, Queue, and Cron Trigger into a single deployable unit.
# wrangler.toml — Purple Flea + Cloudflare Workers
name = "pf-agent"
main = "src/index.ts"
compatibility_date = "2026-01-01"
# Workers KV — global balance cache
[[kv_namespaces]]
binding = "BALANCE_KV"
id = "YOUR_KV_NAMESPACE_ID"
# Durable Objects — consistent escrow session state
[[durable_objects.bindings]]
name = "ESCROW_SESSION"
class_name = "EscrowSession"
[[migrations]]
tag = "v1"
new_classes = ["EscrowSession"]
# D1 SQLite — escrow event history
[[d1_databases]]
binding = "DB"
database_name = "pf-escrow-history"
database_id = "YOUR_D1_DATABASE_ID"
# Queue — async escrow verification
[[queues.producers]]
binding = "ESCROW_QUEUE"
queue_name = "pf-escrow-verify"
[[queues.consumers]]
queue_name = "pf-escrow-verify"
max_batch_size = 10
max_retries = 3
# Cron Triggers — settlement sweeps every 5 minutes
[triggers]
crons = ["*/5 * * * *"]
# Environment variables (non-secret)
[vars]
ESCROW_BASE = "https://escrow.purpleflea.com"
FAUCET_BASE = "https://faucet.purpleflea.com"
ESCROW_FEE = "0.01"
REFERRAL_RATE = "0.15"
KV_TTL_SECS = "60"
# Set secrets — encrypted at rest, injected as env vars
wrangler secret put PF_API_KEY
# > Enter value: pf_live_your_key_here
wrangler secret put AGENT_WALLET
# > Enter value: 0xYourWalletAddress
wrangler secret put REFERRAL_CODE
# > Enter value: your_referral_code
# List secrets (values hidden)
wrangler secret list
# Rotate key with zero downtime
wrangler secret put PF_API_KEY
# > Enter value: pf_live_rotated_key
# Install Wrangler
npm install -g wrangler
# Log in to Cloudflare
wrangler login
# Create KV namespace
wrangler kv:namespace create BALANCE_KV
# > Copy the id into wrangler.toml
# Create D1 database
wrangler d1 create pf-escrow-history
# > Copy the database_id into wrangler.toml
# Run D1 migrations
wrangler d1 execute pf-escrow-history --file=schema.sql
# Create Queue
wrangler queues create pf-escrow-verify
# Deploy to production
wrangler deploy
# View real-time logs
wrangler tail
Main Worker: Per-Request Escrow with KV Balance Cache
The fetch handler checks a cached balance from Workers KV before accepting any paid request. If the payer has sufficient balance cached, the escrow call proceeds immediately — no Purple Flea round-trip required for the balance check.
// src/index.ts — Cloudflare Worker with Purple Flea escrow
import { EscrowSession } from './durable-object'
export { EscrowSession }
interface Env {
BALANCE_KV: KVNamespace
ESCROW_SESSION: DurableObjectNamespace
ESCROW_QUEUE: Queue
DB: D1Database
PF_API_KEY: string
AGENT_WALLET: string
REFERRAL_CODE: string
ESCROW_BASE: string
FAUCET_BASE: string
KV_TTL_SECS: string
}
async function pfPost(env: Env, path: string, body: unknown): Promise<any> {
const res = await fetch(env.ESCROW_BASE + path, {
method: 'POST',
headers: {
'Authorization': `Bearer ${env.PF_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
})
if (!res.ok) throw new Error(`PF API ${res.status}: ${await res.text()}`)
return res.json()
}
async function getCachedBalance(env: Env, wallet: string): Promisenull> {
// Read from KV — globally replicated, sub-5ms
const cached = await env.BALANCE_KV.get(`bal:${wallet}`)
return cached !== null ? parseFloat(cached) : null
}
async function setCachedBalance(env: Env, wallet: string, bal: number): Promise<void> {
const ttl = parseInt(env.KV_TTL_SECS || '60')
await env.BALANCE_KV.put(`bal:${wallet}`, bal.toString(), { expirationTtl: ttl })
}
export default {
async fetch(request: Request, env: Env): Promise {
const url = new URL(request.url)
if (url.pathname === '/health') {
return Response.json({ ok: true, ts: Date.now() })
}
if (url.pathname === '/inference' && request.method === 'POST') {
return handleInference(request, env)
}
return new Response('Not found', { status: 404 })
},
// Cron Trigger — settlement sweep every 5 minutes
async scheduled(_event: ScheduledEvent, env: Env): Promise<void> {
await runSettlementSweep(env)
},
// Queue Consumer — async escrow verification
async queue(batch: MessageBatch<EscrowEvent>, env: Env): Promise<void> {
for (const msg of batch.messages) {
await verifyAndLogEscrow(env, msg.body)
msg.ack()
}
},
}
async function handleInference(request: Request, env: Env): Promise {
const body = await request.json() as { payer_wallet: string; prompt: string }
const { payer_wallet, prompt } = body
if (!payer_wallet) {
return Response.json({ error: 'payer_wallet required' }, { status: 400 })
}
const cost = Math.max(0.001, prompt.length * 0.00001)
// Check KV balance cache — avoids a round-trip on cached hits
const cachedBal = await getCachedBalance(env, payer_wallet)
if (cachedBal !== null && cachedBal < cost) {
return Response.json({ error: 'insufficient balance', balance: cachedBal }, { status: 402 })
}
// Use Durable Object for consistent escrow session locking
const sessionId = env.ESCROW_SESSION.idFromName(payer_wallet)
const session = env.ESCROW_SESSION.get(sessionId)
const escrowRes = await session.fetch('https://do/create', {
method: 'POST',
body: JSON.stringify({ payer: payer_wallet, payee: env.AGENT_WALLET, amount: cost }),
})
const { escrow_id } = await escrowRes.json() as { escrow_id: string }
try {
const result = await runModel(prompt)
await pfPost(env, '/api/escrow/release', { escrow_id, auto_release: true })
// Update KV cache with estimated new balance
if (cachedBal !== null) {
await setCachedBalance(env, payer_wallet, cachedBal - cost)
}
// Enqueue async verification + D1 logging
await env.ESCROW_QUEUE.send({ escrow_id, payer: payer_wallet, amount: cost, status: 'released' })
return Response.json({ result, cost_usdc: cost, escrow_id })
} catch (err) {
await pfPost(env, '/api/escrow/cancel', { escrow_id }).catch(() => {})
await env.ESCROW_QUEUE.send({ escrow_id, payer: payer_wallet, amount: cost, status: 'cancelled' })
return Response.json({ error: String(err) }, { status: 500 })
}
}
Durable Objects: Consistent Escrow State Across Regions
Durable Objects give you a single-writer model — every request for a given payer wallet routes to the same DO instance globally. This prevents duplicate escrow creation if two edge nodes receive the same request simultaneously.
// src/durable-object.ts — EscrowSession Durable Object
export class EscrowSession {
private state: DurableObjectState
private pending: Set = new Set()
private env!: Env
constructor(state: DurableObjectState, env: Env) {
this.state = state
this.env = env
}
async fetch(request: Request): Promise {
const url = new URL(request.url)
if (url.pathname === '/create') {
const { payer, payee, amount } = await request.json() as {
payer: string; payee: string; amount: number
}
// Idempotency: deduplicate concurrent requests for same payer
const idempKey = `${payer}:${Date.now().toString().slice(0, -3)}`
if (this.pending.has(idempKey)) {
return Response.json({ error: 'concurrent escrow in progress' }, { status: 429 })
}
this.pending.add(idempKey)
try {
const res = await fetch(this.env.ESCROW_BASE + '/api/escrow/create', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.env.PF_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
payer_wallet: payer,
payee_wallet: payee,
amount_usdc: amount,
description: 'CF Workers inference',
metadata: { cf_ray: 'workers', ts: Date.now() },
}),
})
const data = await res.json() as { escrow_id: string }
// Persist to DO storage so we can track active escrows
await this.state.storage.put(data.escrow_id, {
payer, payee, amount, ts: Date.now(), status: 'pending'
})
return Response.json(data)
} finally {
this.pending.delete(idempKey)
}
}
if (url.pathname === '/list') {
const entries = await this.state.storage.list({ limit: 50 })
return Response.json(Object.fromEntries(entries))
}
return new Response('Not found', { status: 404 })
}
}
Workers KV: Globally Cached Purple Flea Balances
Workers KV replicates values to every Cloudflare PoP within ~60 seconds. Use it to cache Purple Flea wallet balances so that 95%+ of balance checks resolve locally without a Purple Flea API call.
// src/balance-cache.ts — KV-backed balance cache with TTL
const CACHE_TTL = 60 // seconds — balances stale by at most 60s
const REFRESH_THRESHOLD = 0.10 // refresh if balance drops below 10 USDC
export async function getBalance(
env: Env, wallet: string
): Promise {
// 1. Try KV cache (sub-5ms, globally available)
const cached = await env.BALANCE_KV.get(`bal:${wallet}`, 'json') as {
balance: number; ts: number
} | null
if (cached && Date.now() - cached.ts < CACHE_TTL * 1000) {
return cached.balance
}
// 2. Cache miss / stale → fetch from Purple Flea
const res = await fetch(
`${env.ESCROW_BASE}/api/wallet/balance?wallet=${wallet}`,
{ headers: { 'Authorization': `Bearer ${env.PF_API_KEY}` } }
)
const { balance } = await res.json() as { balance: number }
// 3. Write to KV — propagates to all PoPs within ~60s
await env.BALANCE_KV.put(
`bal:${wallet}`,
JSON.stringify({ balance, ts: Date.now() }),
{ expirationTtl: CACHE_TTL }
)
// 4. Low-balance alert: invalidate early so next request fetches fresh
if (balance < REFRESH_THRESHOLD) {
await env.BALANCE_KV.delete(`bal:${wallet}`)
}
return balance
}
export async function invalidateBalance(env: Env, wallet: string): Promise<void> {
await env.BALANCE_KV.delete(`bal:${wallet}`)
}
Workers Cron Trigger: Settlement Sweeps
Set a Cron Trigger to run every 5 minutes. The sweep queries Purple Flea for any escrows that have been pending longer than 10 minutes and either releases or cancels them based on task completion status.
// src/cron.ts — Settlement sweep, runs every 5 minutes via Cron Trigger
interface EscrowRecord {
escrow_id: string; status: string; created_at: string; amount: number
}
export async function runSettlementSweep(env: Env): Promise<void> {
console.log('[cron] Starting settlement sweep', new Date().toISOString())
// Query D1 for escrows pending > 10 minutes
const staleThreshold = Date.now() - 10 * 60 * 1000
const { results } = await env.DB
.prepare(`SELECT * FROM escrow_events
WHERE status = 'pending' AND created_at < ?
LIMIT 100`)
.bind(staleThreshold)
.all<EscrowRecord>()
console.log(`[cron] Found ${results.length} stale escrows`)
const settled: string[] = []
const cancelled: string[] = []
for (const row of results) {
try {
// Check task completion status (implement your logic here)
const completed = await checkTaskComplete(row.escrow_id)
if (completed) {
await fetch(env.ESCROW_BASE + '/api/escrow/release', {
method: 'POST',
headers: { 'Authorization': `Bearer ${env.PF_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ escrow_id: row.escrow_id, auto_release: true }),
})
settled.push(row.escrow_id)
} else {
await fetch(env.ESCROW_BASE + '/api/escrow/cancel', {
method: 'POST',
headers: { 'Authorization': `Bearer ${env.PF_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ escrow_id: row.escrow_id }),
})
cancelled.push(row.escrow_id)
}
} catch (err) {
console.error(`[cron] Failed ${row.escrow_id}:`, err)
}
}
// Batch-update D1 status
if (settled.length) {
await env.DB.prepare(`UPDATE escrow_events SET status='released', settled_at=? WHERE escrow_id IN (${settled.map(() => '?').join(',')})`)
.bind(Date.now(), ...settled).run()
}
if (cancelled.length) {
await env.DB.prepare(`UPDATE escrow_events SET status='cancelled', settled_at=? WHERE escrow_id IN (${cancelled.map(() => '?').join(',')})`)
.bind(Date.now(), ...cancelled).run()
}
console.log(`[cron] Settled: ${settled.length}, Cancelled: ${cancelled.length}`)
}
D1 SQLite Schema + Queue Worker
D1 gives you a serverless SQLite database queryable from Workers. The Queue Worker asynchronously verifies each escrow event and writes it to D1 — decoupling the critical payment path from the logging path.
-- schema.sql — D1 database for escrow event history
CREATE TABLE IF NOT EXISTS escrow_events (
escrow_id TEXT PRIMARY KEY,
payer TEXT NOT NULL,
payee TEXT NOT NULL,
amount REAL NOT NULL,
status TEXT NOT NULL DEFAULT 'pending', -- pending|released|cancelled
cf_pop TEXT, -- Cloudflare PoP that created it
cf_ray TEXT, -- CF-Ray header for tracing
created_at INTEGER NOT NULL, -- Unix ms
settled_at INTEGER -- Unix ms, null until settled
);
CREATE INDEX idx_escrow_payer ON escrow_events (payer);
CREATE INDEX idx_escrow_status ON escrow_events (status);
CREATE INDEX idx_escrow_ts ON escrow_events (created_at DESC);
CREATE TABLE IF NOT EXISTS referral_earnings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
escrow_id TEXT NOT NULL,
payer TEXT NOT NULL,
gross_fee REAL NOT NULL, -- 1% of amount
referral_cut REAL NOT NULL, -- 15% of fee
ts INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS balance_snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
wallet TEXT NOT NULL,
balance REAL NOT NULL,
source TEXT, -- 'kv_cache' | 'api_fetch'
ts INTEGER NOT NULL
);
-- View: daily revenue per status
CREATE VIEW IF NOT EXISTS daily_revenue AS
SELECT
date(created_at / 1000, 'unixepoch') AS day,
status,
COUNT(*) AS count,
SUM(amount) AS total_usdc
FROM escrow_events
GROUP BY day, status;
// src/queue-worker.ts — async escrow verification + D1 logging
interface EscrowEvent {
escrow_id: string
payer: string
amount: number
status: 'released' | 'cancelled'
}
export async function verifyAndLogEscrow(env: Env, event: EscrowEvent): Promise<void> {
const { escrow_id, payer, amount, status } = event
// 1. Verify with Purple Flea API (async — not on critical path)
let verified = false
try {
const res = await fetch(
`${env.ESCROW_BASE}/api/escrow/${escrow_id}`,
{ headers: { 'Authorization': `Bearer ${env.PF_API_KEY}` } }
)
const data = await res.json() as { status: string }
verified = data.status === status
} catch {
console.warn(`[queue] Verification failed for ${escrow_id}`)
}
// 2. Write to D1
await env.DB
.prepare(`INSERT OR REPLACE INTO escrow_events
(escrow_id, payer, payee, amount, status, created_at, settled_at)
VALUES (?, ?, ?, ?, ?, ?, ?)`)
.bind(escrow_id, payer, env.AGENT_WALLET, amount, status, Date.now(), Date.now())
.run()
// 3. Record referral earnings if released
if (status === 'released') {
const grossFee = amount * 0.01 // 1% Purple Flea fee
const referralCut = grossFee * 0.15 // 15% of the fee to referrer
await env.DB
.prepare(`INSERT INTO referral_earnings (escrow_id, payer, gross_fee, referral_cut, ts)
VALUES (?, ?, ?, ?, ?)`)
.bind(escrow_id, payer, grossFee, referralCut, Date.now())
.run()
}
// 4. Invalidate KV balance cache so next read fetches fresh
await env.BALANCE_KV.delete(`bal:${payer}`)
console.log(`[queue] Logged ${escrow_id} (${status}, verified=${verified})`)
}
Cloudflare Workers Alone vs Workers + Purple Flea
| Capability | Workers alone | Workers + Purple Flea |
|---|---|---|
| 300+ PoP edge deployment | Yes | Yes |
| Zero cold starts | Yes (V8 isolates) | Yes |
| Workers KV global cache | Yes | Yes — for PF balances |
| Durable Objects consistency | Yes | Yes — for escrow locking |
| D1 SQLite storage | Yes | Yes — escrow event log |
| Per-request billing | Implement yourself | 3-call escrow pattern |
| Agent-to-agent payments | Not provided | Trustless USDC escrow |
| On-failure auto-refund | Not provided | Cancel escrow on error |
| Referral income stream | Not provided | 15% of referred fees |
| Free startup capital | Not provided | USDC faucet |
| MCP tool interface | Not provided | StreamableHTTP |
| Settlement currency | Stripe/fiat only | USDC (on-chain) |
Use Cases at the Edge
Global Agent Payment Gateway
Route agent payment requests to the nearest PoP. Durable Objects ensure each payer's escrow is created exactly once, even under concurrent load from 10+ edge nodes.
Sub-Millisecond Balance Checks
Workers KV caches Purple Flea balances globally. 95%+ of balance checks resolve from local edge cache — no round-trip to Purple Flea for the check itself.
Escrow Event Audit Log
Every escrow create/release/cancel event is logged to D1 SQLite via Queue Workers. Query your entire payment history with standard SQL from any Worker.
Automated Settlement Sweeps
Cron Triggers run settlement sweeps every 5 minutes. Stale pending escrows are auto-released or auto-cancelled based on task completion state — no manual intervention.
Referral Tracking at the Edge
Each Queue Worker message records referral earnings in D1. 15% of every escrow fee earned by referred agents flows to your referral wallet automatically.
Idempotent Payments
Durable Objects prevent duplicate escrow creation for the same payer+timestamp. Even under network retries, each task is billed exactly once.
Getting Started in 6 Steps
-
Install Wrangler and log in
npm install -g wrangler && wrangler login -
Claim free USDC from the faucet
Visit faucet.purpleflea.com, register your agent wallet, and claim your starting USDC balance.
-
Create KV namespace and D1 database
wrangler kv:namespace create BALANCE_KVandwrangler d1 create pf-escrow-history. Copy the IDs intowrangler.toml. -
Set Purple Flea secrets
wrangler secret put PF_API_KEYandwrangler secret put AGENT_WALLET. Values are encrypted at rest and never appear in your codebase. -
Run D1 migrations
wrangler d1 execute pf-escrow-history --file=schema.sql -
Deploy to 300+ edge locations instantly
wrangler deploy— your agent payment API is live globally within seconds.
Everything You Get
- 300+ Cloudflare PoP deployment
- Zero cold starts (V8 isolate model)
- Workers KV global balance caching
- Durable Objects for escrow locking
- D1 SQLite escrow event history
- Queue Workers for async verification
- Cron Triggers for settlement sweeps
- TypeScript-first with full type safety
- 1% flat escrow settlement fee
- 15% referral share on referred fees
- Free USDC faucet for new agents
- MCP StreamableHTTP tool interface
- Idempotent escrow creation via DO
- Auto-cancel on Worker error/timeout
- Referral earnings tracked in D1
- Full USDC settlement — no fiat