Contents

The instance economy

The opt-in fast lane for in-world product purchases, layered on session netting. A purchase grants ownership immediately and durably; the money rides the journal and nets to the ledger at settle. The lane lives in the economy service — game servers stay unprivileged callers.

A busy toy shop. You get the toy the moment you pay — nobody makes you wait at the counter. Each receipt goes into a drawer, and the shopkeeper adds the day's numbers into the big book at closing; the drawer means nothing is lost or counted twice.

Source src/instance.tsopenInstanceEconomysrc/instance.tsopenInstanceEconomiestest/instance.test.tstest/instance.prefund.test.ts

A netting session moves money in bulk; the instance economy turns it into a product lane. purchase takes a buyer, a CREDIT price, a sale split, and a product — and returns an accepted order in the time it takes to journal one movement.

The deliberate inversion

The lane inverts the usual order of durability. Ownership is durable immediately: an accepted purchase writes the grant through to the entitlement store that every reader checks, so the world can hand over the product on the spot. The ledger money is settle-deferred: it rides the hash-chained journal and nets into one posting when the epoch settles.

OWNERSHIPpurchaseaccepted at t₀grant durableentitlement storeevery reader sees it from t₀MONEY0000…b4c9…d21e…journalssettleone net postingon the ledger, at epoch endreplay refused → revokedt₀ — purchaseepoch end (≤ 60 s default)The epoch rotates by movements or age; a settle that throws keeps its lane and retries.
Ownership is durable immediately; the ledger money is settle-deferred. The journal — not lane memory — is what settle replays, so a crash between purchase and settle loses nothing, and a movement the replay refuses revokes exactly its own grant.

Both halves are honest. The grant is real the moment purchase returns, and the journal — not lane memory — is what settle replays, so a crash between purchase and settle loses nothing.

Products

A purchase names a product with a sku and a kind:

KindGrant
permanentAn entitlement that persists until revoked
temporaryAn entitlement that lapses at expiresAt (required here, forbidden otherwise)
instantConsumed on the spot — money moves, nothing is granted

The split follows spend’s rule: recipients’ shareBps divide the post-fee net and must sum to exactly 10000 basis points.

Epochs rotate themselves

A session settles once, so the lane manager openInstanceEconomies runs the rotation. laneFor(scope) returns the scope’s current-epoch lane, opened on demand; an epoch becomes due after epochMaxMovements (default 512) or epochMaxAgeMs (default 60 seconds), and sweep() — or the interval start() drives — settles due epochs and lets the next laneFor open fresh ones. settleAll() drains everything at shutdown.

A settle that throws keeps its lane: a settled session refuses new movements on its own and the journal is durable, so the next rotation retries instead of stranding the epoch.

The production screens

Two options bound what the fast lane forgoes from the submit pipeline:

  • perUserCapMinor — a ceiling on what one buyer may spend in a session. It bounds the velocity exposure the lane gives up and the cross-node residual race. Production configs set it.
  • prefund — each buyer’s first purchase moves amountMinor of matured spendable credits into a per-user, per-session escrow account by a real ledger posting, and every movement in the lane debits that escrow instead of spendable. The escrow belongs to this session alone, which makes the accept screen session-local — that is what removes cross-node contention on the buyer’s wallet. Settle refunds the remainder to spendable, and the orphan sweep repairs a crashed refund.

Without prefund, the lane parks each buyer’s immature slice in the reservation registry at first touch, so the affordability screen only ever sees matured funds — the maturity rule holds in either mode.

The settle backstop

Settle replays the journal through the same verification the ledger applies, and a movement the replay refuses takes its grant with it: the report’s revoked list names each buyer, SKU, and order whose entitlement was withdrawn. The lane grants eagerly and settle has the last word.

The whole host program

import { openPorts } from '@pwngh/economy-lab';
import { openInstanceEconomies } from '@pwngh/economy-lab/netting';

const ports = await openPorts(process.env, init);
const lanes = openInstanceEconomies(ports, {
  epochMaxAgeMs: 30_000,
  lane: { perUserCapMinor: 500_000n }, // 5,000.00 CREDIT per buyer per session
});
const stopSettling = lanes.start(5_000); // rotate due epochs on cadence

// Per request from a game server (an unprivileged caller):
await lanes.laneFor(worldInstanceId).purchase({
  buyerId,
  price, // 300.00 CREDIT, say — drawn from spendable only
  recipients: [{ sellerId: creatorId, shareBps: 10_000 }],
  product: { sku: 'world-42:jetpack', kind: 'permanent' },
});

// Shutdown or drain:
stopSettling();
await lanes.settleAll();

The HTTP service exposes this lane as POST /instances/:scope/purchase when the server is constructed with an instances manager; running more than one economy node adds routing and recovery on top.

See also