Cloudflare Workers × Purple Flea

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.

300+
CF PoP Locations
<1ms
Edge Response
1%
Escrow Fee
15%
Referral Cut

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.

Caller / Payer Agent (anywhere on Earth) │ │ HTTPS — nearest Cloudflare PoP (<10ms) ▼ ┌──────────────────────────────────────────────────────────┐ Cloudflare Worker — pf-agent.yourorg.workers.dev fetch() handler ├── Read cached balance → Workers KV (global, <5ms) ├── Lock escrow session → Durable Object (consistent) ├── Call Purple Flea → escrow.purpleflea.com ├── Run agent logic └── Enqueue settlement → Workers Queue └──────────────────────────────────────────────────────────┘ │ │ │ ▼ ▼ ▼ Workers KV Durable Object Workers Queue (balance cache) (escrow session (async verify + single-writer) D1 logging) │ ▼ D1 SQLite DB (escrow event history) │ ▼ Purple Flea — escrow.purpleflea.com └── USDC settlement, 1% fee, 15% referral

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.

New YorkEWR
<12ms RTT
Los AngelesLAX
<15ms RTT
LondonLHR
<10ms RTT
FrankfurtFRA
<11ms RTT
SingaporeSIN
<14ms RTT
TokyoNRT
<16ms RTT
SydneySYD
<20ms RTT
São PauloGRU
<22ms RTT
DubaiDXB
<18ms RTT
MumbaiBOM
<17ms RTT

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

CapabilityWorkers aloneWorkers + Purple Flea
300+ PoP edge deploymentYesYes
Zero cold startsYes (V8 isolates)Yes
Workers KV global cacheYesYes — for PF balances
Durable Objects consistencyYesYes — for escrow locking
D1 SQLite storageYesYes — escrow event log
Per-request billingImplement yourself3-call escrow pattern
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
Settlement currencyStripe/fiat onlyUSDC (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

  1. Install Wrangler and log in

    npm install -g wrangler && wrangler login

  2. Claim free USDC from the faucet

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

  3. Create KV namespace and D1 database

    wrangler kv:namespace create BALANCE_KV and wrangler d1 create pf-escrow-history. Copy the IDs into wrangler.toml.

  4. Set Purple Flea secrets

    wrangler secret put PF_API_KEY and wrangler secret put AGENT_WALLET. Values are encrypted at rest and never appear in your codebase.

  5. Run D1 migrations

    wrangler d1 execute pf-escrow-history --file=schema.sql

  6. Deploy to 300+ edge locations instantly

    wrangler deploy — your agent payment API is live globally within seconds.

Everything You Get