VERIFIED RUNBOOK — EVERY COMMAND RAN GREEN NODE ≥22 · v0.1 · MIT

From npm install to a
governed agent payment.

Rein governs your agent's authority to spend — it never holds the funds. This runbook takes you from an empty folder to watching a policy engine allow four payments and refuse the fifth before any payment exists. Everything below was run, verbatim, against the published packages before this page shipped.

The quickstart runs 100% offline — no accounts, no chain, no funds. The live track at the end settles real testnet USDC.

WHY THIS EXISTS

Agents can spend money now.
Someone has to hold the reins.

WHY WE BUILT IT

x402 turned HTTP 402 into a payment rail: agents pay per call, no cards, no invoices. But an agent transacts at machine speed — and a poisoned prompt or a runaway retry loop spends at machine speed too. “The agent will behave” is not a security model.

WHAT YOU CAN DO WITH IT
  • budgets & per-tx caps
  • instant kill switch
  • signed, hash-chained audit log
  • price your API per call (x402)
  • screen counterparties by reputation
  • catch guard-bypassing spend

Guard the agent that spends, gate the API that earns, and let one reputation graph feed evidence back into enforcement on both sides.

WHY USE IT
  • Non-custodial: authority moves through Rein, money doesn't
  • Fail-closed: engine unreachable → payments deny, not allow
  • Denies happen before a payment is constructed
  • Offline dev loop: the whole stack runs with zero setup
  • MIT, 8 packages on npm, live on Base Sepolia — with links, not adjectives
CHOOSE YOUR PATH

Two quickstarts. One prep.

Both quickstarts share the three prep commands below and run entirely offline against simulated rails — the same code paths, no real money.

01
Check Node

Rein targets Node 22+. Any recent LTS works.

terminal
$ node -v
v22.14.0   # anything ≥22
02
Make a folder, install the packages

Four packages: the agent-side guard, the vendor-side gate, the offline rails they'll run on, and the core schemas (for newId).

terminal
$ mkdir rein-quickstart
$ cd rein-quickstart
$ npm init -y
$ npm install @reinconsole/sdk @reinconsole/gate @reinconsole/mock-rails @reinconsole/core   # ~15 s
03
Boot the policy engine — in a second terminal

Open a new, second terminal window for this step — the engine stays running there for the rest of the runbook, and every other command goes in your first terminal. The engine is the referee: it holds your agents, policies, and the signed decision log, and the quickstarts talk to it on port 8787.

terminal 2 — leave this running
$ npx -p @reinconsole/policy-engine rein-policy-engine
[rein] policy-engine listening on http://0.0.0.0:8787
QUICKSTART · DEMAND SIDE

Put reins on an agent.

One file: register an agent, wrap its fetch, set a $0.50 per-tx cap and a $0.04 rolling hourly budget — then watch call five get refused.

04
Save guard-quickstart.mjs

Copy this file into the rein-quickstart folder from step 02, next to package.json — that's how Node finds the packages you installed there. Then run it from that folder.

rein-quickstart/guard-quickstart.mjs
// guard-quickstart.mjs — cap an agent's spend, offline, in one file.
// Prereq: the policy engine is running:  npx -p @reinconsole/policy-engine rein-policy-engine
import { newId } from '@reinconsole/core';
import { createGuard, EngineClient, PaymentBlockedError } from '@reinconsole/sdk';
import { MockLedger, MockFacilitator, createMockVendor } from '@reinconsole/mock-rails';

const engineUrl = 'http://127.0.0.1:8787';
const wallet = '0xResearch01';

// A simulated x402 world: a ledger, a settlement facilitator, and a
// paywalled vendor charging $0.01 per call. No accounts, no chain, no funds.
const ledger = new MockLedger();
const facilitator = new MockFacilitator({ ledger, name: 'mock-facilitator' });
const vendor = createMockVendor({ facilitator, atomicPrice: '10000', payTo: '0xVendorTreasury' });

// 1. Register the agent with the policy engine
const client = new EngineClient({ baseUrl: engineUrl });
const agent = await client.registerAgent({
  orgId: newId('org'),
  name: 'research-agent',
  wallets: [{ chain: 'base', address: wallet, mode: 'sdk' }],
});

// 2. Wrap fetch with the guard
const guard = createGuard({
  engineUrl,
  agentId: agent.id,
  fetch: vendor.fetch,                    // swap for globalThis.fetch against real vendors
  payer: facilitator.payerFor(wallet),
});

// 3. Set the rules: max $0.50 per tx, max $0.04 per rolling hour
await guard.client.addPolicy({
  policyId: 'starter-policy',
  appliesTo: { agents: [agent.id] },
  rules: [
    { id: 'tx-cap', deny: { amountGt: '0.50' } },
    { id: 'hour-budget', deny: { rollingSum: { window: '1h', gt: '0.04' } } },
  ],
  default: 'allow',
});

// 4. Spend within budget — four $0.01 calls sail through
const guarded = guard.wrap();
for (let i = 1; i <= 4; i++) {
  await guarded('https://api.data.test/v1/query');
  const r = guard.receipts().at(-1);
  console.log(`call ${i}  ALLOW  $0.0${i}/$0.04 spent · tx ${r.settlement?.txHash?.slice(0, 12)}…`);
}

// 5. The fifth call would breach the budget — denied BEFORE any payment exists
try {
  await guarded('https://api.data.test/v1/query');
} catch (e) {
  if (!(e instanceof PaymentBlockedError)) throw e;
  console.log(`call 5  DENY   ${e.decision.reason}`);
  console.log(`ledger has ${ledger.entries().length} entries — the denied payment was never constructed`);
}
05
Run it
terminal
$ node guard-quickstart.mjs
what you'll see — verbatim from our run
call 1  ALLOW  $0.01/$0.04 spent · tx 0x5ab6460ff8…
call 2  ALLOW  $0.02/$0.04 spent · tx 0xa8725d31cb…
call 3  ALLOW  $0.03/$0.04 spent · tx 0x5de05ac3a4…
call 4  ALLOW  $0.04/$0.04 spent · tx 0x565141762f…
call 5  DENY   denied by: hour-budget
ledger has 4 entries — the denied payment was never constructed

That last line is the whole product: the deny happened at intent time — no payment was signed, sent, or settled. The engine also recorded every decision in an ed25519-signed, sha256-chained audit log. Point fetch: globalThis.fetch at a real x402 vendor and the same policy loop governs real payments.

QUICKSTART · SUPPLY SIDE

Put a paywall on an API.

The vendor side is two calls: createGate to price your routes, gateMiddleware in front of your handler. This file does both — then buys through its own paywall with a guarded agent, so you see both sides of one payment. Keep the engine from step 03 running.

06
Save gate-quickstart.mjs

Same place as before: inside rein-quickstart, next to guard-quickstart.mjs.

rein-quickstart/gate-quickstart.mjs
// gate-quickstart.mjs — put a paywall on any Node API, offline, in one file.
// Prereq: the policy engine is running:  npx -p @reinconsole/policy-engine rein-policy-engine
import { createServer } from 'node:http';
import { newId } from '@reinconsole/core';
import { createGuard, EngineClient } from '@reinconsole/sdk';
import { createGate, gateMiddleware, mockFacilitatorRails } from '@reinconsole/gate';
import { MockLedger, MockFacilitator } from '@reinconsole/mock-rails';

// A simulated settlement network (swap for facilitatorClientRails(...) on real rails)
const ledger = new MockLedger();
const facilitator = new MockFacilitator({ ledger, name: 'mock-facilitator' });

// 1. Price your routes
const gate = createGate({
  routes: [
    { path: '/api/answer', price: '0.05', description: 'one research answer' },
    { path: '/api/premium/*', method: 'POST', price: '0.25' },
  ],
  rails: mockFacilitatorRails(facilitator),
  payTo: '0xVendorTreasury',
  network: 'base',
  asset: 'USDC',
});

// 2. Put the middleware in front of your handler (Express: app.use(gateMiddleware(gate)))
const paywall = gateMiddleware(gate);
const server = createServer((req, res) => {
  paywall(req, res, () => {
    res.writeHead(200, { 'content-type': 'application/json' });
    res.end(JSON.stringify({ answer: 42 }));
  });
});
await new Promise((r) => server.listen(8402, '127.0.0.1', r));

// 3. An unpaid caller gets a machine-readable 402 quote, not a dead end
const unpaid = await fetch('http://127.0.0.1:8402/api/answer');
const quote = await unpaid.json();
console.log(`unpaid  HTTP ${unpaid.status} — quote: ${quote.accepts[0].maxAmountRequired} atomic USDC to ${quote.accepts[0].payTo}`);

// 4. A guarded agent buys through the paywall — Rein on both sides of the wire
const engineUrl = 'http://127.0.0.1:8787';
const wallet = '0xBuyerWallet01';
const client = new EngineClient({ baseUrl: engineUrl });
const agent = await client.registerAgent({
  orgId: newId('org'),
  name: 'buyer-agent',
  wallets: [{ chain: 'base', address: wallet, mode: 'sdk' }],
});
const guard = createGuard({
  engineUrl,
  agentId: agent.id,
  payer: facilitator.payerFor(wallet),
  fetch: (input, init) => globalThis.fetch(input, init),
});
await guard.client.addPolicy({
  policyId: 'buyer-policy',
  appliesTo: { agents: [agent.id] },
  rules: [{ id: 'tx-cap', deny: { amountGt: '1.00' } }],
  default: 'allow',
});

const paid = await guard.wrap()('http://127.0.0.1:8402/api/answer');
console.log(`paid    HTTP ${paid.status} · ${JSON.stringify(await paid.json())}`);

// One payment, receipts on BOTH sides that agree
const buyerReceipt = guard.receipts().at(-1);
const gateReceipt = gate.receipts.at(-1);
console.log(`buyer receipt  ${buyerReceipt.id.slice(0, 16)}… · $${buyerReceipt.amount} · tx ${buyerReceipt.settlement?.txHash?.slice(0, 12)}…`);
console.log(`gate receipt   ${gateReceipt.id.slice(0, 16)}… · $${gateReceipt.amount} · tx ${gateReceipt.transaction?.slice(0, 12)}…`);

server.close();
07
Run it
terminal
$ node gate-quickstart.mjs
what you'll see — verbatim from our run
unpaid  HTTP 402 — quote: 50000 atomic USDC to 0xVendorTreasury
paid    HTTP 200 · {"answer":42}
buyer receipt  rcp_01KX3SM05MS3… · $0.05 · tx 0xa650338ff1…
gate receipt   grc_01KX3SM05GDR… · $0.05 · tx 0xa650338ff1…

Same transaction hash on both receipts: the buyer's guard and the vendor's gate each produced an independent record of one payment — and they agree. In production the gate also screens payers, rate-limits, burns replayed payments at the door, and enforces per-payer velocity caps; swap mockFacilitatorRails for facilitatorClientRails to settle through the hosted x402.org facilitator (free, no API key).

THE WHOLE LOOP

Five failure modes, one run,
under a second.

The repo ships demos that exercise everything the quickstarts didn't — offline, with a 435-test suite behind them.

terminal
$ git clone https://github.com/bugiiiii11/rein
$ cd rein
$ npm install -g pnpm       # skip if `pnpm -v` works · corepack enable is the alternative (needs an admin shell on Windows)
$ pnpm install              # ~40 s
$ pnpm build                # ~2 min · 12 packages
$ node apps/demo/dist/index.js
What the demo plays out < 1s · offline
  • no. 1 ALLOW $0.01 ×4 normal calls, within budget settled + receipted
  • no. 2 DENY $0.01 rolling budget would break before payment exists
  • no. 3 DENY $5.00 over the per-tx cap before payment exists
  • no. 4 FROZEN $0.01 kill switch engaged, then released deny → allow
  • no. 5 SHADOW $2.50 guard bypassed — indexer catches it flagged, unreconciled

Then keep going: signer.js — six rogue paths, every one refused · gate.js — the vendor side over real local HTTP · graph.js — reputation closing the loop on both sides · pnpm test — the full 435 (~3 min).

GO LIVE · BASE SEPOLIA

The same loop, real chain,
free testnet money.

Everything above, but the settlement is a real USDC transfer on Base Sepolia through the hosted x402.org facilitator. Costs nothing: the USDC is from a faucet and the facilitator pays the gas. You'll need the repo clone from the section above.

08
First run — it sets itself up

The first run generates an agent wallet into .env and prints faucet instructions.

terminal
$ pnpm --filter @reinconsole/demo demo:sepolia
09
Fund the wallet — free

Grab testnet USDC for the printed address at faucet.circle.com (pick Base Sepolia). No ETH needed — the facilitator pays gas on this path.

10
Run it again — and read the chain

A full run spends $0.02 of testnet USDC: one guarded payment the policy engine allowed (settled on-chain, reconciled to the exact intent via a keccak256(intent.id) nonce memo), and one rogue payment that bypasses the guard — settled anyway, then flagged as a shadow spend. It ends with two BaseScan links like these, from our runs:

Then the vendor side on the same rails — pnpm --filter @reinconsole/demo demo:sepolia-gate settles a gate-priced payment through the hosted facilitator (a real one) and burns a replayed copy at the door.

11
Give the agent an on-chain identity and reputation

ERC-8004 registers your agent as an NFT on a singleton registry; Rein then publishes the reputation graph's score to the on-chain Reputation Registry. Try it offline first (demo:erc8004), then for real — this one needs a little Base Sepolia ETH for gas; re-runs are read-only.

terminal
$ pnpm --filter @reinconsole/demo demo:sepolia-8004

Ours is agent #7393, carrying a 82/100 rein-score published on-chain.

Behind a corporate proxy or TLS-intercepting antivirus? Point Node at your local root CA before any live run: set NODE_EXTRA_CA_CERTS to your CA bundle — details in env.example. Offline quickstarts and demos are unaffected.
READ BEFORE DEPENDING ON IT

What Rein is — and isn't. Yet.

IT IS
  • Non-custodial by architecture — funds never pass through Rein
  • Fail-closed — an unreachable engine denies, it doesn't shrug
  • Open source, MIT, 435 offline tests, live on Base Sepolia
  • Two-sided: the same stack guards buyers and gates sellers
  • Auditable — every decision ed25519-signed and hash-chained
IT ISN'T
  • A wallet or custodian — it never holds keys to your funds
  • Hard enforcement in SDK mode — an agent holding its own key can bypass the guard; Rein catches that (shadow spend). Key-less enforcement is the session-signer tier, in the repo today, published at GA
  • 1.0 — this is v0.1; APIs may change
  • Mainnet-proven — everything on-chain so far is Base Sepolia testnet

Now put reins on yours.

$ npm install @reinconsole/sdk

v0.1 — early open-source infrastructure, live on testnet. APIs may change before 1.0.
No waitlist. No email capture. The code is public.