UTAP v0.1 · CFP reference implementation

A payment protocol for AI agents that humans stay accountable for

UTAP lets a human principal delegate bounded spending authority to software agents — with cryptographic delegation chains, hierarchical budgets, purpose-bound tokens, platform-level double-spend prevention, and an audit trail that stays verifiable even if the operator turns adversarial.

View the code Run it locally

What this is

A working reference implementation of the Central Financial Provider (CFP) role defined in UTAP v0.1, built on Cloudflare Workers.

The CFP is the party that mints spend tokens, verifies delegation chains, enforces budgets, prevents double-spends, and keeps the audit ledger. This implementation exists to prove the protocol runs, to give implementers something to test against, and to serve as the technical basis for a production or sovereign-operated CFP.

In scope

Token lifecycle, double-spend prevention, delegation chain verification, budget enforcement, purpose binding, hash-chained audit trail, public verification API.

Out of scope (v0.1)

Real funds, fiat settlement, MiCA-regulated e-money issuance, KYC/AML. The reference settles against a mock ledger behind a clean boundary where a licensed institution's rails can attach.

Design constraints

Single-writer correctness for anything touching value. Append-only, independently verifiable audit. Every value operation traces to a human principal. Edge latency, no regional primary database.

How a payment flows

One token, one purpose, one redemption — every step audited.

  1. DelegateA human principal signs a delegation credential granting an agent bounded authority: maximum amount, allowed purposes, permitted budgets, and how far it may sub-delegate. Chains of credentials extend authority agent-to-agent; the root is always a person.
  2. MintThe agent requests a token — e.g. €40,000 for COMPUTE.INFERENCE. The CFP verifies the whole chain, atomically reserves the amount against the budget hierarchy, signs the token, and appends TOKEN_ISSUED to the audit chain. Above a policy threshold, minting pauses until a human approves with their own key.
  3. ReserveThe merchant presents the token reference plus its own authentication. The token binds to that merchant — this is the anti-replay step. Any other merchant is now rejected.
  4. RedeemPurpose constraints (merchant allow-list, category codes) are checked, the reservation converts to spend at every budget level, and TOKEN_REDEEMED is appended — synchronously. If the audit append fails, the redemption is rolled back: an unauditable transaction is not a valid transaction.
  5. SettleAsynchronous and batched. The reference writes a mock ledger; in production this is where a licensed institution's payment rails attach without touching protocol logic.

The URI is a bearer reference, not the token

https://vendor.example/pay?utap_v=0.1
  &utap_tid=utap_01HQ…        ← token id (ULID)
  &utap_cfp=cfp.example.ie     ← issuing CFP
  &utap_sig=ed25519:…          ← CFP signature

Only the identifier, CFP, version and signature travel in the URI. Full token state is fetched from the CFP under merchant authentication — so URIs stay short, purpose and principal data never leak through referrer headers or logs, and a stolen URI is useless on its own.

Double-spend prevention by construction

One Durable Object per token. Single-threaded. No distributed lock, no consensus protocol, no central database.

Each token lives in its own Durable Object, addressed by token id. The platform serialises all access to it, so concurrent redemption attempts queue up and the state machine simply rejects any transition that isn't legal from the current state. Double-spend prevention is a property of the platform, not something rebuilt on optimistic locking.

issued reserved redeemed settled void expired
A token is redeemed at most once, globally. The integration suite fires five concurrent redeems — exactly one succeeds.

Delegation chains rooted in a human

Authority flows person → agent → agent, and narrows at every hop.

Verification of a chain checks, at every hop:

Human root

The root credential must be issued by a person DID, never an agent. Every value operation traces to a human principal.

Continuity & signatures

Ed25519 over canonical JSON (RFC 8785). Each hop's issuer must be the previous hop's delegate.

Most restrictive wins

The effective scope is the intersection across the chain: minimum amount, purposes that match every hop, budgets allowed by every hop, depth within every hop's limit.

Live revocation

Each principal's Durable Object is the single writer for its revocation list — revocation is immediate and consistent, not eventually propagated.

A delegation credential

{
  "iss": "did:web:acme.example:person:gkavanagh",   // delegator (human root)
  "sub": "did:web:acme.example:agent:procure-01",   // delegate
  "scope": {
    "max_amount": "50000.00", "ccy": "EUR",
    "purposes": ["COMPUTE.*", "DATA.LICENSE"],
    "budget_refs": ["bud_acme_eng_q3"],
    "max_depth": 2                                  // further sub-delegations allowed
  },
  "nbf": 1749000000, "exp": 1756000000,
  "jti": "dlg_01HQ…",
  "sig": "ed25519:…"                                // signed by the delegator's key
}

Hierarchical budgets, atomic enforcement

org → division → team → project, each node an atomic counter.

Reservations walk from the leaf budget to the root, reserving at each level; if any ancestor rejects, everything already reserved in the walk is released. The walk order is always leaf-to-root, giving a consistent lock ordering with no deadlock between concurrent requests on overlapping subtrees. Policy is enforced at every node the walk passes: purpose allow-lists, per-transaction caps, budget periods.

The CFO hook: requires_human_approval_above. A mint that crosses the threshold emits an approval request instead of a token — the token is only minted on an approval credential signed by the human principal's own key. This is a protocol-level control, not a UI convention.

Reservations carry a TTL and are swept by alarms, so a crashed process can never leak a hold forever. A daily reconciliation job recomputes each budget's spend from the audit chain and compares it to the live counter — divergence is a P1: it means either a bug or tampering.

An audit trail you don't have to trust

Hash-chained, Merkle-checkpointed, externally verifiable — designed for an adversarial future operator.

Every value operation appends an entry to a per-organisation hash chain, synchronously, in the critical path. Each entry commits to everything before it:

entry_hash(n) = SHA-256( JCS(entry minus entry_hash) || prev_hash(n) )
prev_hash(n)  = entry_hash(n-1)          // genesis: fixed org-scoped seed

Any mutation or deletion of history breaks every subsequent hash. Hourly checkpoints compute a Merkle root over new entries, archive the segment to immutable storage, and expose public endpoints:

GET /v1/audit/{org}/proof/{seq}      → Merkle inclusion proof + checkpoint root
GET /v1/audit/{org}/checkpoints      → published roots

Given a published root, an external auditor verifies any entry's inclusion without trusting the CFP's word. The threat model is explicit: the CFP is trusted for validation, but must not need to be trusted for honesty about history.

For a sovereign CFP External anchoring of checkpoint roots (a transparency log or notarised feed) should be mandatory, not optional — it is what makes the audit trail credible against the operator itself.

Why Cloudflare

The protocol's hard requirement is strongly consistent, serialised state per token and per budget — reachable globally at edge latency.

Durable Objects give exactly that: a single-threaded, strongly consistent actor addressable by id, with transactional storage. There is deliberately no external primary database — introducing one would reintroduce a regional bottleneck and a consistency boundary the DO model already solves. D1 and R2 hold only derived views, rebuildable from the audit chain.

ConcernPrimitiveRationale
API edge, authn, routingWorkersGlobal, low latency, no origin
Token state, double-spendDurable Object per tokenSerialised single-writer; no race by construction
Budget countersDurable Object per budget nodeAtomic hierarchical decrement
Delegation & revocationDurable Object per principalConsistent revocation state
Audit ledgerDurable Object per orgAppend-only hash chain, single writer
Queryable audit indexD1Derived, rebuildable; reporting and joins
Audit archiveR2Cheap immutable segments per checkpoint
Async fan-outQueuesKeeps indexing/settlement off the hot path
Key & config cacheWorkers KVRead-heavy; eventual consistency is fine here
Checkpoints, reconciliationCron TriggersScheduled integrity jobs

Known limits — documented, with scaling paths

A hot budget node serialises its subtree's reservations (shard into sub-counters with fixed shares when measurement demands it). One audit chain per org is a write bottleneck at very high volume (shard per org per period with a linking hash). Neither is needed at reference scale; both are designed for.

API surface

Every mutating request carries an idempotency key — replays return the original response.

MethodPathPurpose
POST/v1/tokensMint token (agent credential + delegation chain)
GET/v1/tokens/{tid}Token state (authorised parties only)
POST/v1/tokens/{tid}/reserveMerchant hold — binds token to merchant
POST/v1/tokens/{tid}/redeemMerchant redemption
POST/v1/tokens/{tid}/voidCancel unredeemed token
POST/v1/delegationsRegister delegator-signed credential
DELETE/v1/delegations/{jti}Revoke — immediate and consistent
GET/v1/delegations/{jti}/chainResolve and verify chain
PUT/GET/v1/budgets/{ref}Configure / read budget node
POST/v1/budgets/{ref}/policyUpdate policy (human principal only)
POST/v1/approvals/{id}Human approval for above-threshold spend
GET/v1/audit/{org}/entriesAudit query (org credential)
GET/v1/audit/{org}/proof/{seq}Merkle inclusion proof (public)
GET/.well-known/utap-cfp.jsonCFP metadata & supported versions
GET/.well-known/jwks.jsonCFP public keys

Run it yourself

35 unit and integration tests run against the real Workers runtime; the demo drives the whole protocol through the public API.

git clone https://github.com/sirganya/tknl && cd tknl
npm install
npm test                     # unit + integration in workerd

node scripts/genkey.mjs      # generate a CFP signing key
cp .dev.vars.example .dev.vars
npm run dev                  # CFP on :8787

npm run demo                 # separate terminal: full lifecycle,
                             # double-spend rejection, audit verification

The demo plays three parties — a human principal, a procurement agent, and a mock vendor — through keys, budgets, delegation, mint, reserve and redeem, then independently re-verifies the audit hash chain from its genesis seed. The double-spend attempt fails with invalid_transition, every time.

Open questions, held openly

GDPR vs immutability

Pseudonymous DIDs in the chain, erasable identity mapping outside it. Flagged as an open legal question, not a solved one.

Settlement medium

MiCA e-money tokens, commercial bank money, or pure validation-and-audit? The choice determines licensing — and who can operate a CFP.

Multi-CFP interop

Cross-CFP redemption and chain verification is out of scope for v0.1 — and is what turns a product into a standard.

Anchoring target

Checkpoint roots need an external anchor that is credible, durable, and jurisdictionally neutral.