The Protocol · v2026.1

Two components.
One invariant.

Paystack is a custodial escrow protocol. It is defined by a single invariant: no money leaves the buffer without a matching hold, a valid receipt, and an elapsed audit window. The Ledger records events. The Verifier enforces the invariant.

§1 · The Ledger

An append-only record.

Every operation — hold, release, reverse, refund — writes a new entry. Nothing is edited. Nothing is deleted. Each entry carries the hash of its parent, forming a chain that the Verifier can walk end-to-end.

  • UUIDv7 ids give you causal order without a central clock.
  • Per-tenant partitions; cross-tenant access is architecturally impossible.
  • Weekly root hashes anchored to a public notary (IPFS + timestamping authority).
  • Reasoning envelopes travel with the entry — audit reads the same record operators do.
ledger.ts
type LedgerEntry = {
  id:          UUIDv7              // monotonically sortable
  parent_id:   UUIDv7 | null       // forms a hash chain
  op:          'hold' | 'release' | 'reverse' | 'refund'
  amount_cents: i64
  currency:    'USD'
  counter_party: string            // stable vendor handle
  vfp:         string               // public receipt id
  state:       'pending' | 'cleared' | 'void'
  reasoning:   ReasoningEnvelope    // see /docs
  hash:        sha256               // H(parent.hash || body)
  signed_at:   iso8601
}
§2 · The Verifier

An adversarial reader.

The Verifier runs out-of-process and has read-only credentials. It cannot alter a single cent. Its job is to look for breaks in the chain, releases without holds, and releases before the audit window has elapsed.

If it finds one, it pages the on-call engineer, freezes downstream releases, and emits a public incident on the Trust Center. You find out before your treasury does.

See incident process →
verifier.ts
// Verifier runs out-of-band. It never mutates the ledger.
// It proves that every release has (a) an earlier hold,
// (b) a valid receipt, and (c) an elapsed audit window.

for entry in ledger.walk(from: checkpoint) {
  if entry.op == 'release':
    hold      = ledger.find(vfp: entry.vfp, op: 'hold')
    receipt   = receipts.find(vfp: entry.vfp)

    assert hold                                 // no release without a hold
    assert receipt and receipt.valid            // vendor confirmed work
    assert now - hold.signed_at >= audit_window // buffer elapsed
    assert entry.hash == sha256(parent || body) // chain intact
}

checkpoint.advance(entry.id)
anchor.publish(checkpoint.root)  // notarized to a public anchor weekly
§3 · States

Every dollar is in exactly one.

An escrow transitions through a small number of states. The Verifier enforces that transitions only go one way, with the one exception allowed before release: reversal.

StateMeaningExits toWho triggers
heldFunds are in the FBO buffer against a VFP.→ released · → reversed · → refundedAgent (hold) · Operator (reverse)
releasedFunds have cleared to the counter-party.terminal — dispute-only after thisProtocol (auto, T+buffer)
reversedHold was cancelled inside the audit window.terminal — funds return to senderOperator · SafetySwitch · Verifier
refundedVendor-initiated return after release.terminalVendor
§4 · Guarantees

What the protocol promises.

No orphan releases

Every release must reference a prior hold on the same VFP. Verifier enforced.

No early releases

Audit window elapses before funds move. Enforced by protocol, not policy.

Reversible by default

Any held escrow can be pulled back pre-release. One API call. No vendor involvement.

Single source of truth

Compliance, finance, and engineering read the same ledger. No reconciliation.

Public anchors

Weekly root hash is notarized publicly. Tampering is externally detectable.

Scoped by tenant

Cross-tenant reads and writes are architecturally impossible, not merely forbidden.