VERIFIED RUNBOOK — EVERY COMMAND RAN GREEN NODE ≥22 · v0.5 · 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. Prefer MCP? npx -y @reinconsole/mcp — see the agent quickstart.

Runtime Node ≥ 22
Quickstarts 100% offline
Installs 4 packages · 0.5.1
Live track Base Sepolia · free USDC
Verified Every command ran green
01WHY 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, 12 packages on npm, live on Base mainnet — with links, not adjectives
02CHOOSE 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
03QUICKSTART · 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.

04QUICKSTART · 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).

05THE WHOLE LOOP

Five failure modes, one run,
under a second.

The repo ships demos that exercise everything the quickstarts didn't — offline, with a 1111-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 · 15 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 1111 (~3 min).

06GO 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.
07READ 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, 1111 offline tests, live on Base mainnet
  • 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.5; APIs may change
  • Multi-chain — Base and Base Sepolia only, paying USDC; any other network is refused rather than guessed at. Self-hosting is open to everyone, no account

Now put reins on yours.

$ npm install @reinconsole/sdk

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