Skip to content
Dan Connolly edited this page Jul 19, 2026 · 1 revision

ERTP — Electronic Rights Transfer Protocol

Overview

ERTP is Agoric's token standard for digital assets in JavaScript. It uses object-capability discipline for access control: if your program holds a reference to an object, it can call methods on it; if not, it can't.

From makeCounter to makeMint

ERTP is not a separate security layer — it emerges directly from Hardened JavaScript. The same harden() + closure encapsulation pattern that gives us makeCounter scales to makeMint in ~30 lines:

makeCounter → makeMint (mint + purse + issuer) → ERTP (brands, payments, deposit facets)

A mint creates purses; purses hold balances via a WeakMap ledger; deposit/withdraw move value between purses; the issuer is a facet for creating empty purses. All the core ERTP invariants — no double-spending, payments consumed on deposit, unforgeable transfers — come from the ocap discipline (harden(), encapsulation, reference-based authority), not from a blockchain or consensus mechanism.

See The Mint Pattern (8 min, starting at 25:25) for the live walkthrough.

Core Concepts

Concept Role
Brand An asset type (e.g., "QUATLOOS", "BUCKS"). Answer to "what is it?"
Amount A description: { brand, value }. Not an asset — just a description.
Mint The unique creator of new assets of a given brand.
Issuer Source of truth for purse and payment balances; validates payments.
Purse Holds assets of one brand. Supports deposit(), withdraw(), getCurrentAmount(), getDepositFacet().
Payment A transferable object containing assets. Consumed on deposit.
DepositFacet A attenuated facet of a purse: can only deposit, never withdraw or check balance.

Asset Kinds

  • AssetKind.NAT — fungible (natural number value, e.g., 400n QUATLOOS)
  • AssetKind.SET — non-fungible (array of records, e.g., tickets by seat)
  • AssetKind.COPY_SET / AssetKind.COPY_BAG — semi-fungible (set/bag semantics)

Lifecycle

makeIssuerKit("BUCKS")
  → mint.mintPayment(amount)
    → purse.deposit(payment)
      → purse.withdraw(amount) → payment
        → depositFacet.receive(payment)

Each mint creates an issuer+brand trio. Assets flow from mint → payment → purse → payment → deposit facet. Payments are consumed (deleted from issuer records) on deposit.

ERTP in Governance (vattr97)

The vattr97 repo (commit 67a451b) implements ERTP on a GnuCash SQLite ledger — the @vattr/ertp-ledgerguise package.

Mapping

ERTP Concept GnuCash Mapping
Brand Commodity (namespace + mnemonic, e.g., COMMODITY:BUCKS)
Purse Account (with GUID, name, parent)
Payment Transaction + Splits (with reconcile_state='n' until deposited)
Amount Brand + bigint value
Mint Recovery account transfers
Issuer Account tree + split query logic

Key Source Files

  • src/index.ts — createIssuerKit(), openIssuerKit() entry points
  • src/ertp-types.ts — TypeScript types for Brand, Issuer, Mint, Purse, Payment, Amount
  • src/purse.ts — makePurseFactory() — ERTP purse backed by GnuCash account
  • src/escrow-ertp.ts — makeErtpEscrow() — AMIX-style atomic swap
  • src/chart.ts — makeChartFacet() — names purses in the GnuCash chart of accounts
  • src/sealer.ts — sealer/unsealer for capability attenuation
  • src/db-helpers.ts — commodity, account, and transfer database operations

Escrow

makeErtpEscrow() implements an AMIX (American Information Exchange) escrow exchange:

Agreement → [Party A Funds] → [Party B Funds] → Settlement
                 ↓                  ↓
            (cancellation → refunds)

Each party funds the escrow with a Promise<Payment>. Settlement only happens when both have funded. The escrow uses the sealer/unsealer pattern to expose account GUIDs for ledger queries without leaking withdrawal authority.

See docs-dev/escrow-accounting.md for the double-entry accounting model.

OCap Discipline in vattr97

From docs-dev/ocap-discipline.md:

  1. Inject, don't import — DB, clock, and GUID generation are injected as capabilities, never ambient.
  2. Freeze API surfaces — all returned objects are harden()-ed or freeze()-ed.
  3. Zone pattern — zone.exo() creates hardened objects with method-only interfaces.
  4. Sealer/Unsealer — attenuate authority before sharing (e.g., purse identity without withdrawal authority).
  5. Closely held vs widely shared — purses are private; deposit facets are public.
// Capability injection
const kit = createIssuerKit({
  db,               // database capability
  commodity,
  makeGuid,         // GUID generation
  nowMs: () => Date.now(),  // clock
  zone,
});

Why ERTP for Governance

ERTP gives the governance system a standard, proven vocabulary for value:

  • Contributions as Payments — a contributor submits work as a payment of value.
  • Approvals as deposits into domain purses — a steward receives contributions into domain-branded purses.
  • Escrow for conditional exchanges — e.g., payment contingent on review approval.
  • Attenuated authority via DepositFacet — anyone can send value to a domain; only stewards withdraw.

Issue #3 proposes re-implementing the RChain bounty system (trust metric + roles) on ERTP, replacing raw database rows with Purses and Mints.

References

  • ERTP docs (Agoric)
  • ERTP API reference
  • vattr97/packages/ertp-ledgerguise/test/design-doc.test.ts — executable design doc with snapshots
  • vattr97/packages/ertp-ledgerguise/test/snapshots/design-doc.test.ts.md — narrative snapshot report
  • vattr97/packages/ertp-ledgerguise/docs-dev/escrow-accounting.md — escrow accounting model
  • vattr97/packages/ertp-ledgerguise/docs-dev/ocap-discipline.md — capability discipline guide

Clone this wiki locally