agent402-tollbooth 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +23 -4
  2. package/index.js +79 -2
  3. package/package.json +16 -3
package/README.md CHANGED
@@ -94,9 +94,24 @@ Point it at your existing site - no code changes there:
94
94
  ```bash
95
95
  TOLLBOOTH_UPSTREAM=https://your-site.com \
96
96
  TOLLBOOTH_PAYTO=0xYourWallet \
97
+ TOLLBOOTH_FACILITATOR_URL=https://x402.org/facilitator \
97
98
  npx agent402-tollbooth # listens on :4021, proxies humans free, charges bots
98
99
  ```
99
100
 
101
+ With `TOLLBOOTH_PAYTO` **and** `TOLLBOOTH_FACILITATOR_URL` set, the proxy
102
+ settles real money over **both wires** from env alone (0.8.0): it builds the
103
+ standard `@x402/express` v2 stack in-process (verify, proxy the request, settle
104
+ only on a successful response, exactly once) and mints MPP challenges on the
105
+ same 402, so a stock `@x402/fetch` client and a stock `mppx` client both pay.
106
+ Install the x402 packages next to it once (`npm i @x402/express @x402/core
107
+ @x402/evm`; they are optional peers so the package stays dependency-free
108
+ otherwise) and pick a facilitator that settles your network - keyless free
109
+ tiers exist for mainnet chains, and `https://x402.org/facilitator` covers
110
+ base-sepolia for a dry run. USDC on the EVM chains in `TOLLBOOTH_NETWORK`'s
111
+ list (`base` default); other assets and rails use the library API below.
112
+ `TOLLBOOTH_PAYTO` alone still only *advertises* a quote (nothing can verify or
113
+ settle it, every paid request is refused) and now says so loudly at boot.
114
+
100
115
  ## Run on the edge (Cloudflare Workers, Next.js, Deno, Bun)
101
116
 
102
117
  The same gate is also built on the Web Crypto + Fetch APIs (`edge.js`), so it runs
@@ -238,9 +253,11 @@ Read by the bundled proxy / Express entry point (`index.js`):
238
253
  | env | default | meaning |
239
254
  |---|---|---|
240
255
  | `TOLLBOOTH_UPSTREAM` | – | Origin the built-in reverse proxy forwards to |
241
- | `TOLLBOOTH_PAYTO` | – | Wallet address; set to advertise a USDC x402 quote |
256
+ | `TOLLBOOTH_PAYTO` | – | Wallet address; advertises a USDC x402 quote (and, with `TOLLBOOTH_FACILITATOR_URL`, settles it) |
257
+ | `TOLLBOOTH_FACILITATOR_URL` | – | x402 facilitator that settles your network. With `TOLLBOOTH_PAYTO` the CLI builds a real `@x402/express` middleware and takes payment over x402 **and** MPP (0.8.0). Needs `@x402/express @x402/core @x402/evm` installed; refuses to start without them |
258
+ | `TOLLBOOTH_FACILITATOR_HEADERS` | – | Optional JSON object of auth headers sent on the facilitator's `/verify`, `/settle`, `/supported` (e.g. `{"X-API-Key":"…"}`) |
242
259
  | `TOLLBOOTH_PRICE` | `"$0.001"` | Advertised price per request |
243
- | `TOLLBOOTH_NETWORK` | `"base"` | x402 network |
260
+ | `TOLLBOOTH_NETWORK` | `"base"` | x402 network. CLI settlement mode accepts `base`, `base-sepolia`, `polygon`, `arbitrum`, `optimism`, `avalanche`, `celo`, `sei`, `monad`, or a raw `eip155:<id>` |
244
261
  | `TOLLBOOTH_MPP` | on when `x402` set | `false` to switch MPP off |
245
262
  | `TOLLBOOTH_MPP_SECRET` | `TOLLBOOTH_SECRET` | HMAC secret for MPP challenge ids |
246
263
  | `TOLLBOOTH_MPP_NETWORKS` | `8453,42220` | CSV of EVM chain ids to offer as MPP challenges, or `all` |
@@ -500,10 +517,12 @@ instead of USDC:
500
517
  TOLLBOOTH_PAYTO=0xYourWallet \
501
518
  TOLLBOOTH_NETWORK=eip155:4663 \
502
519
  TOLLBOOTH_ASSET=USDG \
503
- npx agent402-tollbooth
520
+ npx agent402-tollbooth # advertises the USDG quote; settle it via the library API below
504
521
  ```
505
522
 
506
- or in code: `createTollbooth({ payTo, network: "eip155:4663", asset: "USDG", x402 })`,
523
+ (The CLI's built-in settlement mode - `TOLLBOOTH_FACILITATOR_URL` - registers
524
+ the exact **USDC** scheme only and refuses to start with another asset rather
525
+ than advertise a quote it cannot settle.) In code: `createTollbooth({ payTo, network: "eip155:4663", asset: "USDG", x402 })`,
507
526
  where `x402` is a `@x402/express` middleware whose facilitator settles chain
508
527
  4663 (advertise `eip155:4663` in its `accepts`). Defaults are unchanged (USDC
509
528
  on Base). MPP challenges are only minted for chains in `mppNetworks` (Base +
package/index.js CHANGED
@@ -516,6 +516,81 @@ function readBody(req, cap = 10 * 1024 * 1024) {
516
516
  });
517
517
  }
518
518
 
519
+ // Chain names the CLI accepts for TOLLBOOTH_NETWORK, to CAIP-2. A raw
520
+ // `eip155:<id>` passes through. USDC-settling EVM chains only: the middleware
521
+ // built below registers @x402/evm's exact scheme, whose default money parser
522
+ // knows USDC on these networks; anything else (USDG on Robinhood Chain,
523
+ // Solana, Stellar, Algorand) needs the library API with the matching scheme.
524
+ export const CLI_NETWORKS = {
525
+ base: "eip155:8453", "base-sepolia": "eip155:84532", polygon: "eip155:137", arbitrum: "eip155:42161",
526
+ optimism: "eip155:10", avalanche: "eip155:43114", celo: "eip155:42220", sei: "eip155:1329", monad: "eip155:143",
527
+ };
528
+
529
+ /** Build a real @x402/express v2 middleware from env for the CLI, or null.
530
+ *
531
+ * Before 0.8.0, TOLLBOOTH_PAYTO in CLI mode only ADVERTISED a quote: the gate
532
+ * had no verifier there, so every request that carried a payment was refused
533
+ * with a fresh 402 - a price nobody could pay, and the operator saw no error.
534
+ * With TOLLBOOTH_FACILITATOR_URL set as well, the CLI now constructs the
535
+ * standard @x402/express stack (verify -> proxy the request -> settle only on
536
+ * a <400 response, in the middleware's own order, exactly once) and hands it
537
+ * to createTollbooth({ x402 }), which also mints MPP challenges and accepts
538
+ * Authorization: Payment credentials by default - so `npx agent402-tollbooth`
539
+ * takes money over BOTH wires from env alone. @x402/* are optional peers
540
+ * (this package stays dependency-free otherwise); asking for settlement
541
+ * without them installed FAILS the start (fail closed: the operator believes
542
+ * they are charging).
543
+ *
544
+ * Exported (with an injectable `env`) so the test can drive the same code the
545
+ * CLI runs, not a copy of it. */
546
+ export async function buildCliX402Middleware(env = process.env) {
547
+ const payTo = env.TOLLBOOTH_PAYTO || "";
548
+ const facilitatorUrl = env.TOLLBOOTH_FACILITATOR_URL || "";
549
+ if (!payTo) return null;
550
+ if (!facilitatorUrl) {
551
+ console.warn("⚠ TOLLBOOTH_PAYTO is set without TOLLBOOTH_FACILITATOR_URL: the 402 advertises a USDC quote but nothing here can verify or settle a payment, so every paid request is refused. Set TOLLBOOTH_FACILITATOR_URL (a public x402 facilitator that settles your network - keyless free tiers exist; https://x402.org/facilitator for base-sepolia) to actually charge, over x402 and MPP.");
552
+ return null;
553
+ }
554
+ const asset = (env.TOLLBOOTH_ASSET || "USDC").toUpperCase();
555
+ if (asset !== "USDC") {
556
+ console.error(`✖ TOLLBOOTH_FACILITATOR_URL is set but TOLLBOOTH_ASSET=${asset}: the CLI's built-in settlement registers @x402/evm's exact USDC scheme only. For ${asset} use the library API - createTollbooth({ x402: paymentMiddleware(...) }) with the scheme your facilitator settles - or set TOLLBOOTH_ASSET=USDC. Refusing to start rather than advertise a quote it cannot settle.`);
557
+ process.exit(1);
558
+ }
559
+ const rawNet = String(env.TOLLBOOTH_NETWORK || "base").trim().toLowerCase();
560
+ const network = /^eip155:\d+$/.test(rawNet) ? rawNet : CLI_NETWORKS[rawNet];
561
+ if (!network) {
562
+ console.error(`✖ TOLLBOOTH_NETWORK=${rawNet} is not a chain the CLI can settle USDC on. Known: ${Object.keys(CLI_NETWORKS).join(", ")}, or a raw eip155:<chainId>. Refusing to start.`);
563
+ process.exit(1);
564
+ }
565
+ let paymentMiddleware, HTTPFacilitatorClient, x402ResourceServer, ExactEvmScheme;
566
+ try {
567
+ ({ paymentMiddleware } = await import("@x402/express"));
568
+ ({ HTTPFacilitatorClient, x402ResourceServer } = await import("@x402/core/server"));
569
+ ({ ExactEvmScheme } = await import("@x402/evm/exact/server"));
570
+ } catch (e) {
571
+ console.error(`✖ TOLLBOOTH_FACILITATOR_URL is set, so this gate must settle x402 payments, but the x402 packages are not installed (${String(e?.message || e).slice(0, 120)}). Install them next to agent402-tollbooth: npm i @x402/express @x402/core @x402/evm . Refusing to start rather than advertise a quote it cannot settle.`);
572
+ process.exit(1);
573
+ }
574
+ // Optional facilitator auth headers, sent on /verify, /settle and /supported
575
+ // alike (facilitators disagree on the header name, so the operator names it).
576
+ let authHeaders = null;
577
+ if (env.TOLLBOOTH_FACILITATOR_HEADERS) {
578
+ try { authHeaders = JSON.parse(env.TOLLBOOTH_FACILITATOR_HEADERS); } catch { console.error("✖ TOLLBOOTH_FACILITATOR_HEADERS must be a JSON object of header name -> value. Refusing to start."); process.exit(1); }
579
+ }
580
+ const client = new HTTPFacilitatorClient({
581
+ url: facilitatorUrl,
582
+ ...(authHeaders ? { createAuthHeaders: async () => ({ verify: authHeaders, settle: authHeaders, supported: authHeaders }) } : {}),
583
+ });
584
+ const resourceServer = new x402ResourceServer(client).register(network, new ExactEvmScheme());
585
+ const price = env.TOLLBOOTH_PRICE || "$0.001";
586
+ const routes = {
587
+ // Every method, every path (the proxy forwards them all); no verb prefix
588
+ // means "*" in @x402/core's matcher. Never a bazaar extension on a wildcard.
589
+ "/*": { accepts: [{ scheme: "exact", network, price, payTo }], description: "agent402-tollbooth: paid access for automated clients", mimeType: "application/json" },
590
+ };
591
+ return paymentMiddleware(routes, resourceServer);
592
+ }
593
+
519
594
  async function startCli() {
520
595
  const upstream = process.env.TOLLBOOTH_UPSTREAM;
521
596
  const port = Number(process.env.PORT) || 4021;
@@ -584,7 +659,8 @@ async function startCli() {
584
659
  }
585
660
  const { default: express } = await import("express");
586
661
  const app = express();
587
- const gate = createTollbooth({ resourceBaseUrl: process.env.TOLLBOOTH_RESOURCE_BASE || upstream || "", replayStore });
662
+ const x402mw = await buildCliX402Middleware();
663
+ const gate = createTollbooth({ resourceBaseUrl: process.env.TOLLBOOTH_RESOURCE_BASE || upstream || "", replayStore, ...(x402mw ? { x402: x402mw } : {}) });
588
664
  // Operator analytics — aggregate counts only (no per-request data), mounted
589
665
  // before the gate so they're always reachable and never themselves charged.
590
666
  // Two opt-in admin tokens (legacy `TOLLBOOTH_STATS_TOKEN` covers /stats only;
@@ -641,7 +717,8 @@ async function startCli() {
641
717
  app.use((_req, res) => res.json({ ok: true, note: "Bare tollbooth gate (no TOLLBOOTH_UPSTREAM set). Clients that reach here paid or solved a proof-of-work." }));
642
718
  }
643
719
  app.listen(port, () => {
644
- const rails = `${gate.pow ? "proof-of-work" : ""}${process.env.TOLLBOOTH_PAYTO ? (gate.pow ? ` + x402(${process.env.TOLLBOOTH_ASSET || "USDC"})` : `x402(${process.env.TOLLBOOTH_ASSET || "USDC"})`) : ""}`;
720
+ const paidLabel = x402mw ? `x402 + MPP (${process.env.TOLLBOOTH_ASSET || "USDC"}, settling via ${process.env.TOLLBOOTH_FACILITATOR_URL})` : (process.env.TOLLBOOTH_PAYTO ? `x402 quote only (${process.env.TOLLBOOTH_ASSET || "USDC"}, NOT settling - set TOLLBOOTH_FACILITATOR_URL)` : "");
721
+ const rails = [gate.pow ? "proof-of-work" : "", paidLabel].filter(Boolean).join(" + ");
645
722
  console.log(`agent402-tollbooth listening on :${port} — charging AI bots via ${rails || "proof-of-work"}`);
646
723
  if (upstream) console.log(` proxying → ${upstream}`);
647
724
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agent402-tollbooth",
3
- "version": "0.7.0",
4
- "description": "Open-source, self-hostable pay-per-crawl tollbooth for x402 AND MPP: charge AI bots per request (USDC via x402, or MPP - the Machine Payments Protocol - through the same settlement, or USDG on Robinhood Chain, or free proof-of-work) while humans browse free. Drop-in Express middleware or reverse proxy - no Cloudflare, no Stripe, no signup.",
3
+ "version": "0.8.0",
4
+ "description": "Agentic Finance (AIFI) sell side: open-source, self-hostable pay-per-crawl tollbooth for x402 AND MPP: charge AI bots per request (USDC via x402, or MPP - the Machine Payments Protocol - through the same settlement, or USDG on Robinhood Chain, or free proof-of-work) while humans browse free. Drop-in Express middleware or reverse proxy that settles both wires from env alone - no Cloudflare, no Stripe, no signup.",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "bin": {
@@ -45,6 +45,16 @@
45
45
  "dependencies": {
46
46
  "express": "^5.0.0"
47
47
  },
48
+ "peerDependencies": {
49
+ "@x402/core": ">=2.0.0",
50
+ "@x402/evm": ">=2.0.0",
51
+ "@x402/express": ">=2.0.0"
52
+ },
53
+ "peerDependenciesMeta": {
54
+ "@x402/core": { "optional": true },
55
+ "@x402/evm": { "optional": true },
56
+ "@x402/express": { "optional": true }
57
+ },
48
58
  "keywords": [
49
59
  "x402",
50
60
  "pay-per-crawl",
@@ -71,7 +81,10 @@
71
81
  "usdg",
72
82
  "mpp",
73
83
  "machine-payments-protocol",
74
- "payment-auth"
84
+ "payment-auth",
85
+ "agentic-finance",
86
+ "aifi",
87
+ "agentic-payments"
75
88
  ],
76
89
  "author": "Havok Holdings LLC <mike@agent402.tools> (https://agent402.tools)",
77
90
  "license": "MIT",