@peaje/next 0.1.0 → 0.2.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.
package/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # @peaje/next
2
2
 
3
- Peaje for Next.js. Two pieces:
3
+ Peaje for Next.js. Three pieces:
4
4
 
5
- - `withPeaje(nextConfig, { slug })` adds the proxy rewrites that make your domain serve the Peaje catalog, the MCP server, the OpenAPI spec and the priced links (HTTP 402) that your gateway already publishes.
5
+ - `peajeProxy({ slug })` (from `@peaje/next/proxy`) is the middleware that answers HTTP 402 on your own domain for every priced route in your Peaje dashboard, including API routes you publish later. It reads the route list from your gateway at runtime, cached for a minute, so a new route needs no redeploy.
6
+ - `withPeaje(nextConfig, { slug })` adds the build-time rewrites for the fixed catalog paths (llms.txt, openapi.json, /developers, the MCP server, the `/r/` priced links).
6
7
  - `<PeajeHead slug="..." />` renders the JSON-LD `WebAPI` block and the discovery `<link>` tags in your `<head>`.
7
8
 
8
9
  Both are additive: your existing rewrites, config and head stay as they are.
@@ -32,7 +33,24 @@ export default withPeaje(nextConfig, { slug: 'cafe-andino' })
32
33
 
33
34
  Options: `slug` (required, the one from your Peaje dashboard) and `gateway` (defaults to `https://peaje-gateway.up.railway.app`, overridable with the `PEAJE_GATEWAY_URL` environment variable).
34
35
 
35
- If you have a `middleware.ts` or `proxy.ts` with a `matcher`, exclude `mcp`, `r/`, `discovery/`, `checkout_sessions`, `agentic_commerce/` and `docs` from it: middleware runs before rewrites.
36
+ ## 1b. Runtime proxy (priced API routes on your domain)
37
+
38
+ ```ts
39
+ // proxy.ts (Next 16) or middleware.ts (Next 14/15), at the project root or in src/
40
+ import { peajeProxy } from '@peaje/next/proxy'
41
+
42
+ export default peajeProxy({ slug: 'cafe-andino' })
43
+
44
+ export const config = {
45
+ matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
46
+ }
47
+ ```
48
+
49
+ Set `PEAJE_ORIGIN_SECRET` in your hosting environment variables (copy it from the Kit page of your Peaje dashboard). After an agent pays, the gateway forwards the request to your site with that secret; it is how the proxy tells the gateway apart from someone pretending to be it. Without it, priced API routes are not forwarded (they would be free to anyone who spoofs a header) and the proxy logs a warning; discovery files and `/r/` links work anyway. You can also pass it as `peajeProxy({ slug, secret })`.
50
+
51
+ The rewrites above are fixed at build time, so they cannot know about an API route you add to your dashboard tomorrow. `peajeProxy` fetches `/{slug}/kit/manifest.json` from the gateway, keeps it for 60 seconds, and rewrites any request that matches a priced route (`/api/forecast`, `/api/history/:city`, `/api/batch/*`) to the gateway, which answers 402 or, once paid, forwards to your origin. Catch-all patterns (`/*`, `/:id` at the root) are not forwarded from your domain: they would send the whole site to the gateway.
52
+
53
+ With your own middleware: `export default peajeProxy({ slug }, myMiddleware)`. Peaje runs first; when the request is not one of its routes, yours runs.
36
54
 
37
55
  ## 2. Head
38
56
 
@@ -91,6 +91,97 @@ var LINKS_HEAD = [
91
91
  { rel: "ard", type: "application/ai-catalog+json", href: "/.well-known/ard.json" }
92
92
  ];
93
93
 
94
+ // ../shared/src/kit/proxy-runtime.ts
95
+ var PEAJE_ORIGIN_HEADER = "x-peaje-origin";
96
+ function iguales(a, b) {
97
+ if (a.length !== b.length) return false;
98
+ let d = 0;
99
+ for (let i = 0; i < a.length; i++) d |= a.charCodeAt(i) ^ b.charCodeAt(i);
100
+ return d === 0;
101
+ }
102
+ function coincidePatron(patron, pathname) {
103
+ const p = patron.split("/").filter(Boolean);
104
+ const parts = pathname.split("/").filter(Boolean);
105
+ for (let i = 0; i < p.length; i++) {
106
+ const seg = p[i];
107
+ if (seg === "*") return true;
108
+ const v = parts[i];
109
+ if (v === void 0) return false;
110
+ if (seg.startsWith(":")) continue;
111
+ if (seg !== v) return false;
112
+ }
113
+ return p.length === parts.length;
114
+ }
115
+ function patronReenviable(patron) {
116
+ const segs = patron.split("/").filter(Boolean);
117
+ if (segs.length === 0) return false;
118
+ return !(segs.length === 1 && (segs[0] === "*" || segs[0].startsWith(":")));
119
+ }
120
+ function crearProxyRuntime(opciones) {
121
+ const base = opciones.base.replace(/\/+$/, "");
122
+ const ttl = opciones.ttlMs ?? 6e4;
123
+ const fetchImpl = opciones.fetchImpl ?? fetch;
124
+ let cache = {
125
+ at: 0,
126
+ rutas: opciones.respaldo?.rutas ?? [],
127
+ prefijos: opciones.respaldo?.prefijos ?? [],
128
+ patrones: opciones.respaldo?.patrones ?? []
129
+ };
130
+ let refrescando = null;
131
+ async function refrescar() {
132
+ try {
133
+ const res = await fetchImpl(`${base}/kit/manifest.json`, { signal: AbortSignal.timeout(5e3) });
134
+ if (!res.ok) throw new Error(String(res.status));
135
+ const m = await res.json();
136
+ cache = {
137
+ at: Date.now(),
138
+ rutas: Array.isArray(m.rutas) ? m.rutas : cache.rutas,
139
+ prefijos: Array.isArray(m.prefijos) ? m.prefijos : cache.prefijos,
140
+ patrones: Array.isArray(m.patrones) ? m.patrones : cache.patrones
141
+ };
142
+ } catch {
143
+ cache = { ...cache, at: Date.now() };
144
+ } finally {
145
+ refrescando = null;
146
+ }
147
+ }
148
+ async function listas() {
149
+ const vencido = Date.now() - cache.at >= ttl;
150
+ if (vencido) {
151
+ refrescando ??= refrescar();
152
+ if (cache.at === 0) await refrescando;
153
+ }
154
+ return cache;
155
+ }
156
+ return {
157
+ /** Rutas de API con precio que este sitio no puede cobrar por falta de secreto. */
158
+ async sinSecreto() {
159
+ if (opciones.secreto) return [];
160
+ return (await listas()).patrones.filter((p) => patronReenviable(p.path));
161
+ },
162
+ /** Fuerza la carga del manifiesto (útil al arrancar o en tests). */
163
+ cargar: () => refrescando ??= refrescar(),
164
+ /**
165
+ * Decide si la request va al gateway. `method` y `pathname` de la request,
166
+ * `search` con el `?` incluido, y los headers para cortar bucles.
167
+ */
168
+ async decidir(input) {
169
+ const secreto = opciones.secreto || null;
170
+ const prueba = input.headers.get(PEAJE_ORIGIN_HEADER);
171
+ if (secreto && prueba && iguales(prueba, secreto)) return null;
172
+ const { rutas, prefijos, patrones } = await listas();
173
+ const path = input.pathname;
174
+ const esFetchDePeaje = input.headers.get(PEAJE_FETCH_HEADER) !== null;
175
+ const fija = rutas.includes(path) && !(path === "/llms.txt" && esFetchDePeaje);
176
+ const prefijo = prefijos.some((p) => path.startsWith(p));
177
+ const method = input.method.toUpperCase();
178
+ const patron = secreto !== null && patrones.some((p) => p.method.toUpperCase() === method && patronReenviable(p.path) && coincidePatron(p.path, path));
179
+ if (!fija && !prefijo && !patron) return null;
180
+ return { destino: `${base}${path}${input.search ?? ""}` };
181
+ }
182
+ };
183
+ }
184
+
94
185
  // ../shared/src/index.ts
95
186
  var TEMPO = {
96
187
  mainnet: {
@@ -187,6 +278,36 @@ var NETWORKS = {
187
278
  }
188
279
  };
189
280
  var NETWORK_IDS = Object.keys(NETWORKS);
281
+ var CHAIN_LABELS = [
282
+ ...new Map(NETWORK_IDS.map((id) => [NETWORKS[id].testnet.chainId, NETWORKS[id].label.split(" \xB7 ")[0] ?? NETWORKS[id].label])).values()
283
+ ];
284
+ var GATEWAY_RAILS = {
285
+ "gateway-arbitrum": {
286
+ id: "gateway-arbitrum",
287
+ label: "Arbitrum \xB7 Nanopayments",
288
+ chainId: 421614,
289
+ rpcUrl: "https://sepolia-rollup.arbitrum.io/rpc",
290
+ token: "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d",
291
+ decimals: 6,
292
+ verifyingContract: "0x0077777d7EBA4688BDeF3E311b846F25870A19B9",
293
+ facilitatorUrl: "https://gateway-api-testnet.circle.com/v1/x402",
294
+ sdkChain: "arbitrumSepolia",
295
+ explorerUrl: "https://sepolia.arbiscan.io"
296
+ },
297
+ "gateway-arc": {
298
+ id: "gateway-arc",
299
+ label: "Arc \xB7 Nanopayments",
300
+ chainId: 5042002,
301
+ rpcUrl: "https://rpc.testnet.arc.network",
302
+ token: "0x3600000000000000000000000000000000000000",
303
+ decimals: 6,
304
+ verifyingContract: "0x0077777d7EBA4688BDeF3E311b846F25870A19B9",
305
+ facilitatorUrl: "https://gateway-api-testnet.circle.com/v1/x402",
306
+ sdkChain: "arcTestnet",
307
+ explorerUrl: "https://testnet.arcscan.app"
308
+ }
309
+ };
310
+ var GATEWAY_RAIL_IDS = Object.keys(GATEWAY_RAILS);
190
311
 
191
312
  // src/index.ts
192
313
  var GATEWAY_POR_DEFECTO = "https://peaje-gateway.up.railway.app";
@@ -222,9 +343,12 @@ function withPeaje(nextConfig, opciones) {
222
343
  var src_default = withPeaje;
223
344
 
224
345
  export {
346
+ RUTAS_PROXY,
347
+ RUTAS_DINAMICAS,
225
348
  jsonParaScript,
226
349
  jsonLdWebApi,
227
350
  LINKS_HEAD,
351
+ crearProxyRuntime,
228
352
  GATEWAY_POR_DEFECTO,
229
353
  baseDe,
230
354
  fusionarRewrites,
package/dist/head.cjs CHANGED
@@ -186,6 +186,36 @@ var NETWORKS = {
186
186
  }
187
187
  };
188
188
  var NETWORK_IDS = Object.keys(NETWORKS);
189
+ var CHAIN_LABELS = [
190
+ ...new Map(NETWORK_IDS.map((id) => [NETWORKS[id].testnet.chainId, NETWORKS[id].label.split(" \xB7 ")[0] ?? NETWORKS[id].label])).values()
191
+ ];
192
+ var GATEWAY_RAILS = {
193
+ "gateway-arbitrum": {
194
+ id: "gateway-arbitrum",
195
+ label: "Arbitrum \xB7 Nanopayments",
196
+ chainId: 421614,
197
+ rpcUrl: "https://sepolia-rollup.arbitrum.io/rpc",
198
+ token: "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d",
199
+ decimals: 6,
200
+ verifyingContract: "0x0077777d7EBA4688BDeF3E311b846F25870A19B9",
201
+ facilitatorUrl: "https://gateway-api-testnet.circle.com/v1/x402",
202
+ sdkChain: "arbitrumSepolia",
203
+ explorerUrl: "https://sepolia.arbiscan.io"
204
+ },
205
+ "gateway-arc": {
206
+ id: "gateway-arc",
207
+ label: "Arc \xB7 Nanopayments",
208
+ chainId: 5042002,
209
+ rpcUrl: "https://rpc.testnet.arc.network",
210
+ token: "0x3600000000000000000000000000000000000000",
211
+ decimals: 6,
212
+ verifyingContract: "0x0077777d7EBA4688BDeF3E311b846F25870A19B9",
213
+ facilitatorUrl: "https://gateway-api-testnet.circle.com/v1/x402",
214
+ sdkChain: "arcTestnet",
215
+ explorerUrl: "https://testnet.arcscan.app"
216
+ }
217
+ };
218
+ var GATEWAY_RAIL_IDS = Object.keys(GATEWAY_RAILS);
189
219
 
190
220
  // src/index.ts
191
221
  var GATEWAY_POR_DEFECTO = "https://peaje-gateway.up.railway.app";
package/dist/head.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  baseDe,
4
4
  jsonLdWebApi,
5
5
  jsonParaScript
6
- } from "./chunk-7KJLR3FN.js";
6
+ } from "./chunk-XUCHMX2L.js";
7
7
 
8
8
  // src/head.tsx
9
9
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
package/dist/index.cjs CHANGED
@@ -163,6 +163,36 @@ var NETWORKS = {
163
163
  }
164
164
  };
165
165
  var NETWORK_IDS = Object.keys(NETWORKS);
166
+ var CHAIN_LABELS = [
167
+ ...new Map(NETWORK_IDS.map((id) => [NETWORKS[id].testnet.chainId, NETWORKS[id].label.split(" \xB7 ")[0] ?? NETWORKS[id].label])).values()
168
+ ];
169
+ var GATEWAY_RAILS = {
170
+ "gateway-arbitrum": {
171
+ id: "gateway-arbitrum",
172
+ label: "Arbitrum \xB7 Nanopayments",
173
+ chainId: 421614,
174
+ rpcUrl: "https://sepolia-rollup.arbitrum.io/rpc",
175
+ token: "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d",
176
+ decimals: 6,
177
+ verifyingContract: "0x0077777d7EBA4688BDeF3E311b846F25870A19B9",
178
+ facilitatorUrl: "https://gateway-api-testnet.circle.com/v1/x402",
179
+ sdkChain: "arbitrumSepolia",
180
+ explorerUrl: "https://sepolia.arbiscan.io"
181
+ },
182
+ "gateway-arc": {
183
+ id: "gateway-arc",
184
+ label: "Arc \xB7 Nanopayments",
185
+ chainId: 5042002,
186
+ rpcUrl: "https://rpc.testnet.arc.network",
187
+ token: "0x3600000000000000000000000000000000000000",
188
+ decimals: 6,
189
+ verifyingContract: "0x0077777d7EBA4688BDeF3E311b846F25870A19B9",
190
+ facilitatorUrl: "https://gateway-api-testnet.circle.com/v1/x402",
191
+ sdkChain: "arcTestnet",
192
+ explorerUrl: "https://testnet.arcscan.app"
193
+ }
194
+ };
195
+ var GATEWAY_RAIL_IDS = Object.keys(GATEWAY_RAILS);
166
196
 
167
197
  // src/index.ts
168
198
  var GATEWAY_POR_DEFECTO = "https://peaje-gateway.up.railway.app";
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  fusionarRewrites,
5
5
  src_default,
6
6
  withPeaje
7
- } from "./chunk-7KJLR3FN.js";
7
+ } from "./chunk-XUCHMX2L.js";
8
8
  export {
9
9
  GATEWAY_POR_DEFECTO,
10
10
  baseDe,
package/dist/proxy.cjs ADDED
@@ -0,0 +1,336 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/proxy.ts
21
+ var proxy_exports = {};
22
+ __export(proxy_exports, {
23
+ default: () => proxy_default,
24
+ peajeProxy: () => peajeProxy
25
+ });
26
+ module.exports = __toCommonJS(proxy_exports);
27
+
28
+ // ../shared/src/kit/rutas.ts
29
+ var RUTAS_PROXY = [
30
+ { path: "/llms.txt", nota: "gu\xEDa para agentes (fusionada con la tuya)" },
31
+ { path: "/pricing.md", nota: "precios por endpoint" },
32
+ { path: "/auth.md", nota: 'c\xF3mo pagar (el "auth" ac\xE1 es el pago)' },
33
+ { path: "/agents.md", nota: "qu\xE9 es y cu\xE1ndo usarte" },
34
+ { path: "/openapi.json", nota: "discovery con precios" },
35
+ { path: "/mcp", nota: "MCP server con tools pagas, en TU dominio" },
36
+ { path: "/.well-known/mcp", nota: "el MCP tambi\xE9n ac\xE1: es donde lo buscan los auditores" },
37
+ { path: "/.well-known/ard.json", nota: "cat\xE1logo ARD (path can\xF3nico)" },
38
+ { path: "/.well-known/ai-catalog.json", nota: "cat\xE1logo ARD (alias legacy)" },
39
+ { path: "/.well-known/api-catalog", nota: "linkset RFC 9727" },
40
+ { path: "/.well-known/agent-card.json", nota: "agent card A2A" },
41
+ { path: "/.well-known/mcp/server-card.json", nota: "server card MCP" },
42
+ { path: "/discovery/resources", nota: "lista x402 Bazaar: lo que cobra 402 de verdad" },
43
+ { path: "/.well-known/ucp", nota: "perfil UCP: qu\xE9 sabe hacer tu negocio" },
44
+ { path: "/checkout_sessions", nota: "checkout ACP (el de OpenAI)" },
45
+ { path: "/agentic_commerce/delegate_payment", nota: "delegate payment ACP" },
46
+ { path: "/developers", nota: "portal de desarrolladores, renderizado con tus precios" },
47
+ { path: "/.well-known/peaje-indexnow.txt", nota: "clave IndexNow: avisa a Bing y Copilot cuando cambian precios" },
48
+ // Los auditores prueban /docs y /developers; el gateway redirige uno al otro.
49
+ { path: "/docs", nota: "alias del portal: los auditores prueban ambos" }
50
+ ];
51
+ var RUTAS_DINAMICAS = [
52
+ { path: "/r/:slug", prefijo: "/r/", nota: "links con precio: el 402 en tu dominio" },
53
+ { path: "/checkout_sessions/:id", prefijo: "/checkout_sessions/", nota: "leer una sesi\xF3n de checkout ACP" }
54
+ ];
55
+ var PEAJE_FETCH_HEADER = "x-peaje-fetch";
56
+
57
+ // ../shared/src/kit/proxy-runtime.ts
58
+ var PEAJE_ORIGIN_HEADER = "x-peaje-origin";
59
+ function iguales(a, b) {
60
+ if (a.length !== b.length) return false;
61
+ let d = 0;
62
+ for (let i = 0; i < a.length; i++) d |= a.charCodeAt(i) ^ b.charCodeAt(i);
63
+ return d === 0;
64
+ }
65
+ function coincidePatron(patron, pathname) {
66
+ const p = patron.split("/").filter(Boolean);
67
+ const parts = pathname.split("/").filter(Boolean);
68
+ for (let i = 0; i < p.length; i++) {
69
+ const seg = p[i];
70
+ if (seg === "*") return true;
71
+ const v = parts[i];
72
+ if (v === void 0) return false;
73
+ if (seg.startsWith(":")) continue;
74
+ if (seg !== v) return false;
75
+ }
76
+ return p.length === parts.length;
77
+ }
78
+ function patronReenviable(patron) {
79
+ const segs = patron.split("/").filter(Boolean);
80
+ if (segs.length === 0) return false;
81
+ return !(segs.length === 1 && (segs[0] === "*" || segs[0].startsWith(":")));
82
+ }
83
+ function crearProxyRuntime(opciones) {
84
+ const base = opciones.base.replace(/\/+$/, "");
85
+ const ttl = opciones.ttlMs ?? 6e4;
86
+ const fetchImpl = opciones.fetchImpl ?? fetch;
87
+ let cache = {
88
+ at: 0,
89
+ rutas: opciones.respaldo?.rutas ?? [],
90
+ prefijos: opciones.respaldo?.prefijos ?? [],
91
+ patrones: opciones.respaldo?.patrones ?? []
92
+ };
93
+ let refrescando = null;
94
+ async function refrescar() {
95
+ try {
96
+ const res = await fetchImpl(`${base}/kit/manifest.json`, { signal: AbortSignal.timeout(5e3) });
97
+ if (!res.ok) throw new Error(String(res.status));
98
+ const m = await res.json();
99
+ cache = {
100
+ at: Date.now(),
101
+ rutas: Array.isArray(m.rutas) ? m.rutas : cache.rutas,
102
+ prefijos: Array.isArray(m.prefijos) ? m.prefijos : cache.prefijos,
103
+ patrones: Array.isArray(m.patrones) ? m.patrones : cache.patrones
104
+ };
105
+ } catch {
106
+ cache = { ...cache, at: Date.now() };
107
+ } finally {
108
+ refrescando = null;
109
+ }
110
+ }
111
+ async function listas() {
112
+ const vencido = Date.now() - cache.at >= ttl;
113
+ if (vencido) {
114
+ refrescando ??= refrescar();
115
+ if (cache.at === 0) await refrescando;
116
+ }
117
+ return cache;
118
+ }
119
+ return {
120
+ /** Rutas de API con precio que este sitio no puede cobrar por falta de secreto. */
121
+ async sinSecreto() {
122
+ if (opciones.secreto) return [];
123
+ return (await listas()).patrones.filter((p) => patronReenviable(p.path));
124
+ },
125
+ /** Fuerza la carga del manifiesto (útil al arrancar o en tests). */
126
+ cargar: () => refrescando ??= refrescar(),
127
+ /**
128
+ * Decide si la request va al gateway. `method` y `pathname` de la request,
129
+ * `search` con el `?` incluido, y los headers para cortar bucles.
130
+ */
131
+ async decidir(input) {
132
+ const secreto = opciones.secreto || null;
133
+ const prueba = input.headers.get(PEAJE_ORIGIN_HEADER);
134
+ if (secreto && prueba && iguales(prueba, secreto)) return null;
135
+ const { rutas, prefijos, patrones } = await listas();
136
+ const path = input.pathname;
137
+ const esFetchDePeaje = input.headers.get(PEAJE_FETCH_HEADER) !== null;
138
+ const fija = rutas.includes(path) && !(path === "/llms.txt" && esFetchDePeaje);
139
+ const prefijo = prefijos.some((p) => path.startsWith(p));
140
+ const method = input.method.toUpperCase();
141
+ const patron = secreto !== null && patrones.some((p) => p.method.toUpperCase() === method && patronReenviable(p.path) && coincidePatron(p.path, path));
142
+ if (!fija && !prefijo && !patron) return null;
143
+ return { destino: `${base}${path}${input.search ?? ""}` };
144
+ }
145
+ };
146
+ }
147
+
148
+ // ../shared/src/index.ts
149
+ var TEMPO = {
150
+ mainnet: {
151
+ chainId: 4217,
152
+ rpcUrl: "https://rpc.tempo.xyz",
153
+ explorerUrl: "https://explore.tempo.xyz"
154
+ },
155
+ testnet: {
156
+ chainId: 42431,
157
+ rpcUrl: "https://rpc.moderato.tempo.xyz",
158
+ explorerUrl: "https://explore.testnet.tempo.xyz"
159
+ }
160
+ };
161
+ var TOKENS = {
162
+ pathUsd: "0x20c0000000000000000000000000000000000000",
163
+ usdc: "0x20C000000000000000000000b9537d11c60E8b50"
164
+ };
165
+ var DEFAULT_CURRENCY = TOKENS.pathUsd;
166
+ var NETWORKS = {
167
+ tempo: {
168
+ id: "tempo",
169
+ label: "Tempo",
170
+ token: TOKENS.pathUsd,
171
+ tokenSymbol: "pathUSD",
172
+ decimals: 6,
173
+ mainnet: TEMPO.mainnet,
174
+ testnet: TEMPO.testnet
175
+ },
176
+ arc: {
177
+ id: "arc",
178
+ label: "Arc",
179
+ // Interfaz ERC-20 del USDC nativo de Arc; el gas de la red se paga en ese mismo USDC.
180
+ token: "0x3600000000000000000000000000000000000000",
181
+ tokenSymbol: "USDC",
182
+ decimals: 6,
183
+ // Leído on-chain del contrato: name() = "USDC", version() = "2".
184
+ eip3009: { name: "USDC", version: "2" },
185
+ // Arc no publica direcciones de mainnet todavía (sept 2026).
186
+ mainnet: null,
187
+ testnet: {
188
+ chainId: 5042002,
189
+ rpcUrl: "https://rpc.testnet.arc.io",
190
+ explorerUrl: "https://testnet.arcscan.app"
191
+ }
192
+ },
193
+ arbitrum: {
194
+ id: "arbitrum",
195
+ label: "Arbitrum",
196
+ // USDC de Circle en Arbitrum Sepolia. En Arbitrum One vive en otra dirección,
197
+ // por eso mainnet queda en null hasta que el gateway opere allá.
198
+ token: "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d",
199
+ tokenSymbol: "USDC",
200
+ decimals: 6,
201
+ // Leído on-chain: name() = "USD Coin", version() = "2" (distinto al USDC de Arc).
202
+ eip3009: { name: "USD Coin", version: "2" },
203
+ mainnet: null,
204
+ testnet: {
205
+ chainId: 421614,
206
+ rpcUrl: "https://sepolia-rollup.arbitrum.io/rpc",
207
+ explorerUrl: "https://sepolia.arbiscan.io"
208
+ }
209
+ },
210
+ "arbitrum-usdg": {
211
+ id: "arbitrum-usdg",
212
+ label: "Arbitrum \xB7 USDG",
213
+ // Mismo contrato y misma cadena que el riel USDC: cambia el stablecoin.
214
+ // Un riel es (cadena, token), y el saldo del negocio se lleva por token.
215
+ token: "0xFFC95faa3d63Cde504a05B567C600B78C0b41892",
216
+ tokenSymbol: "USDG",
217
+ decimals: 6,
218
+ eip3009: { name: "Global Dollar", version: "1" },
219
+ mainnet: null,
220
+ testnet: {
221
+ chainId: 421614,
222
+ rpcUrl: "https://sepolia-rollup.arbitrum.io/rpc",
223
+ explorerUrl: "https://sepolia.arbiscan.io"
224
+ }
225
+ },
226
+ robinhood: {
227
+ id: "robinhood",
228
+ label: "Robinhood",
229
+ // USDG de Paxos en Robinhood Chain Testnet (el "USDC" de esa testnet es un
230
+ // mock sin EIP-3009). Leído on-chain: dominio "Global Dollar", versión "1".
231
+ token: "0x7E955252E15c84f5768B83c41a71F9eba181802F",
232
+ tokenSymbol: "USDG",
233
+ decimals: 6,
234
+ eip3009: { name: "Global Dollar", version: "1" },
235
+ mainnet: null,
236
+ testnet: {
237
+ chainId: 46630,
238
+ rpcUrl: "https://rpc.testnet.chain.robinhood.com",
239
+ explorerUrl: "https://explorer.testnet.chain.robinhood.com"
240
+ }
241
+ }
242
+ };
243
+ var NETWORK_IDS = Object.keys(NETWORKS);
244
+ var CHAIN_LABELS = [
245
+ ...new Map(NETWORK_IDS.map((id) => [NETWORKS[id].testnet.chainId, NETWORKS[id].label.split(" \xB7 ")[0] ?? NETWORKS[id].label])).values()
246
+ ];
247
+ var GATEWAY_RAILS = {
248
+ "gateway-arbitrum": {
249
+ id: "gateway-arbitrum",
250
+ label: "Arbitrum \xB7 Nanopayments",
251
+ chainId: 421614,
252
+ rpcUrl: "https://sepolia-rollup.arbitrum.io/rpc",
253
+ token: "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d",
254
+ decimals: 6,
255
+ verifyingContract: "0x0077777d7EBA4688BDeF3E311b846F25870A19B9",
256
+ facilitatorUrl: "https://gateway-api-testnet.circle.com/v1/x402",
257
+ sdkChain: "arbitrumSepolia",
258
+ explorerUrl: "https://sepolia.arbiscan.io"
259
+ },
260
+ "gateway-arc": {
261
+ id: "gateway-arc",
262
+ label: "Arc \xB7 Nanopayments",
263
+ chainId: 5042002,
264
+ rpcUrl: "https://rpc.testnet.arc.network",
265
+ token: "0x3600000000000000000000000000000000000000",
266
+ decimals: 6,
267
+ verifyingContract: "0x0077777d7EBA4688BDeF3E311b846F25870A19B9",
268
+ facilitatorUrl: "https://gateway-api-testnet.circle.com/v1/x402",
269
+ sdkChain: "arcTestnet",
270
+ explorerUrl: "https://testnet.arcscan.app"
271
+ }
272
+ };
273
+ var GATEWAY_RAIL_IDS = Object.keys(GATEWAY_RAILS);
274
+
275
+ // src/proxy.ts
276
+ var import_server = require("next/server");
277
+
278
+ // src/index.ts
279
+ var GATEWAY_POR_DEFECTO = "https://peaje-gateway.up.railway.app";
280
+ function baseDe({ slug, gateway }) {
281
+ const raiz = (gateway ?? process.env.PEAJE_GATEWAY_URL ?? GATEWAY_POR_DEFECTO).replace(/\/+$/, "");
282
+ return `${raiz}/${slug}`;
283
+ }
284
+
285
+ // src/proxy.ts
286
+ var runtimes = /* @__PURE__ */ new Map();
287
+ function runtimeDe(base, secreto) {
288
+ const clave = `${base}|${secreto ? "con" : "sin"}`;
289
+ let r = runtimes.get(clave);
290
+ if (!r) {
291
+ r = crearProxyRuntime({
292
+ base,
293
+ secreto,
294
+ respaldo: { rutas: RUTAS_PROXY.map((x) => x.path), prefijos: RUTAS_DINAMICAS.map((x) => x.prefijo), patrones: [] }
295
+ });
296
+ runtimes.set(clave, r);
297
+ }
298
+ return r;
299
+ }
300
+ var avisado = false;
301
+ function peajeProxy(opciones, siguiente) {
302
+ if (!opciones?.slug) throw new Error("peajeProxy: `slug` is required (the business slug from your Peaje dashboard).");
303
+ const base = baseDe(opciones);
304
+ const secreto = opciones.secret ?? process.env.PEAJE_ORIGIN_SECRET ?? null;
305
+ return async (req, ev) => {
306
+ const runtime = runtimeDe(base, secreto);
307
+ if (!secreto && !avisado) {
308
+ avisado = true;
309
+ void runtime.sinSecreto().then((faltan) => {
310
+ if (faltan.length > 0) {
311
+ console.warn(
312
+ `[peaje] ${faltan.length} priced API route(s) are not charged on this domain because PEAJE_ORIGIN_SECRET is not set: ${faltan.map((p) => `${p.method} ${p.path}`).join(", ")}. Copy it from the Kit page of your Peaje dashboard into your hosting environment variables.`
313
+ );
314
+ }
315
+ });
316
+ }
317
+ const decision = await runtime.decidir({
318
+ method: req.method,
319
+ pathname: req.nextUrl.pathname,
320
+ search: req.nextUrl.search,
321
+ headers: req.headers
322
+ });
323
+ if (decision) {
324
+ const headers = new Headers(req.headers);
325
+ headers.set("x-forwarded-host", req.nextUrl.host);
326
+ headers.set("x-forwarded-proto", req.nextUrl.protocol.replace(":", ""));
327
+ return import_server.NextResponse.rewrite(new URL(decision.destino), { request: { headers } });
328
+ }
329
+ return siguiente ? siguiente(req, ev) : import_server.NextResponse.next();
330
+ };
331
+ }
332
+ var proxy_default = peajeProxy;
333
+ // Annotate the CommonJS export names for ESM import in node:
334
+ 0 && (module.exports = {
335
+ peajeProxy
336
+ });
@@ -0,0 +1,11 @@
1
+ import { NextRequest, NextFetchEvent } from 'next/server';
2
+ import { OpcionesPeaje } from './index.cjs';
3
+
4
+ type Middleware = (req: NextRequest, ev: NextFetchEvent) => Response | undefined | void | Promise<Response | undefined | void>;
5
+ type OpcionesProxy = OpcionesPeaje & {
6
+ /** Por defecto `process.env.PEAJE_ORIGIN_SECRET`. */
7
+ secret?: string;
8
+ };
9
+ declare function peajeProxy(opciones: OpcionesProxy, siguiente?: Middleware): Middleware;
10
+
11
+ export { type OpcionesProxy, peajeProxy as default, peajeProxy };
@@ -0,0 +1,11 @@
1
+ import { NextRequest, NextFetchEvent } from 'next/server';
2
+ import { OpcionesPeaje } from './index.js';
3
+
4
+ type Middleware = (req: NextRequest, ev: NextFetchEvent) => Response | undefined | void | Promise<Response | undefined | void>;
5
+ type OpcionesProxy = OpcionesPeaje & {
6
+ /** Por defecto `process.env.PEAJE_ORIGIN_SECRET`. */
7
+ secret?: string;
8
+ };
9
+ declare function peajeProxy(opciones: OpcionesProxy, siguiente?: Middleware): Middleware;
10
+
11
+ export { type OpcionesProxy, peajeProxy as default, peajeProxy };
package/dist/proxy.js ADDED
@@ -0,0 +1,60 @@
1
+ import {
2
+ RUTAS_DINAMICAS,
3
+ RUTAS_PROXY,
4
+ baseDe,
5
+ crearProxyRuntime
6
+ } from "./chunk-XUCHMX2L.js";
7
+
8
+ // src/proxy.ts
9
+ import { NextResponse } from "next/server";
10
+ var runtimes = /* @__PURE__ */ new Map();
11
+ function runtimeDe(base, secreto) {
12
+ const clave = `${base}|${secreto ? "con" : "sin"}`;
13
+ let r = runtimes.get(clave);
14
+ if (!r) {
15
+ r = crearProxyRuntime({
16
+ base,
17
+ secreto,
18
+ respaldo: { rutas: RUTAS_PROXY.map((x) => x.path), prefijos: RUTAS_DINAMICAS.map((x) => x.prefijo), patrones: [] }
19
+ });
20
+ runtimes.set(clave, r);
21
+ }
22
+ return r;
23
+ }
24
+ var avisado = false;
25
+ function peajeProxy(opciones, siguiente) {
26
+ if (!opciones?.slug) throw new Error("peajeProxy: `slug` is required (the business slug from your Peaje dashboard).");
27
+ const base = baseDe(opciones);
28
+ const secreto = opciones.secret ?? process.env.PEAJE_ORIGIN_SECRET ?? null;
29
+ return async (req, ev) => {
30
+ const runtime = runtimeDe(base, secreto);
31
+ if (!secreto && !avisado) {
32
+ avisado = true;
33
+ void runtime.sinSecreto().then((faltan) => {
34
+ if (faltan.length > 0) {
35
+ console.warn(
36
+ `[peaje] ${faltan.length} priced API route(s) are not charged on this domain because PEAJE_ORIGIN_SECRET is not set: ${faltan.map((p) => `${p.method} ${p.path}`).join(", ")}. Copy it from the Kit page of your Peaje dashboard into your hosting environment variables.`
37
+ );
38
+ }
39
+ });
40
+ }
41
+ const decision = await runtime.decidir({
42
+ method: req.method,
43
+ pathname: req.nextUrl.pathname,
44
+ search: req.nextUrl.search,
45
+ headers: req.headers
46
+ });
47
+ if (decision) {
48
+ const headers = new Headers(req.headers);
49
+ headers.set("x-forwarded-host", req.nextUrl.host);
50
+ headers.set("x-forwarded-proto", req.nextUrl.protocol.replace(":", ""));
51
+ return NextResponse.rewrite(new URL(decision.destino), { request: { headers } });
52
+ }
53
+ return siguiente ? siguiente(req, ev) : NextResponse.next();
54
+ };
55
+ }
56
+ var proxy_default = peajeProxy;
57
+ export {
58
+ proxy_default as default,
59
+ peajeProxy
60
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@peaje/next",
3
- "version": "0.1.0",
4
- "description": "Peaje for Next.js: proxy rewrites and the head block that make your site speak MPP, MCP and OpenAPI on your own domain.",
3
+ "version": "0.2.0",
4
+ "description": "Peaje for Next.js: runtime proxy, rewrites and the head block that make your site answer 402 (MPP, x402), MCP and OpenAPI on your own domain.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "sideEffects": false,
@@ -31,13 +31,18 @@
31
31
  "types": "./dist/head.d.cts",
32
32
  "default": "./dist/head.cjs"
33
33
  }
34
+ },
35
+ "./proxy": {
36
+ "import": {
37
+ "types": "./dist/proxy.d.ts",
38
+ "default": "./dist/proxy.js"
39
+ },
40
+ "require": {
41
+ "types": "./dist/proxy.d.cts",
42
+ "default": "./dist/proxy.cjs"
43
+ }
34
44
  }
35
45
  },
36
- "scripts": {
37
- "build": "tsup",
38
- "typecheck": "tsc --noEmit",
39
- "test": "tsx --test test/*.test.ts"
40
- },
41
46
  "keywords": [
42
47
  "peaje",
43
48
  "next",
@@ -59,13 +64,21 @@
59
64
  }
60
65
  },
61
66
  "devDependencies": {
62
- "@peaje/shared": "workspace:*",
63
67
  "@types/node": "^20",
64
68
  "@types/react": "^19",
65
69
  "next": "16.3.1",
66
70
  "react": "19.2.8",
67
71
  "tsup": "^8.5.1",
68
72
  "tsx": "^4.23.12",
69
- "typescript": "^5.9.0"
73
+ "typescript": "^5.9.0",
74
+ "@peaje/shared": "0.0.0"
75
+ },
76
+ "publishConfig": {
77
+ "access": "public"
78
+ },
79
+ "scripts": {
80
+ "build": "tsup",
81
+ "typecheck": "tsc --noEmit",
82
+ "test": "tsx --test test/*.test.ts"
70
83
  }
71
- }
84
+ }