@agentcash/router 0.3.0 → 0.4.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/.claude/CLAUDE.md +86 -0
- package/.claude/skills/router-guide/SKILL.md +533 -0
- package/README.md +52 -4
- package/dist/index.cjs +166 -66
- package/dist/index.d.cts +12 -7
- package/dist/index.d.ts +12 -7
- package/dist/index.js +152 -52
- package/package.json +5 -7
package/README.md
CHANGED
|
@@ -141,11 +141,28 @@ The fluent builder ensures compile-time safety:
|
|
|
141
141
|
|
|
142
142
|
### Pricing Modes
|
|
143
143
|
|
|
144
|
-
**Static
|
|
144
|
+
**Static** - Fixed price for all requests:
|
|
145
|
+
```typescript
|
|
146
|
+
router.route('search').paid('0.02')
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Dynamic** - Calculate price based on request body:
|
|
150
|
+
```typescript
|
|
151
|
+
router.route('gen')
|
|
152
|
+
.paid((body) => calculateCost(body.imageSize, body.quality))
|
|
153
|
+
.body(imageGenSchema)
|
|
154
|
+
.handler(async ({ body }) => generate(body));
|
|
155
|
+
```
|
|
145
156
|
|
|
146
|
-
**Dynamic
|
|
157
|
+
**Dynamic with safety net** - Cap at maxPrice if calculation exceeds, fallback to maxPrice on errors:
|
|
158
|
+
```typescript
|
|
159
|
+
router.route('compute')
|
|
160
|
+
.paid((body) => calculateExpensiveOperation(body), { maxPrice: '10.00' })
|
|
161
|
+
.body(computeSchema)
|
|
162
|
+
.handler(async ({ body }) => compute(body));
|
|
163
|
+
```
|
|
147
164
|
|
|
148
|
-
**Tiered
|
|
165
|
+
**Tiered** - Price based on a specific field value:
|
|
149
166
|
```typescript
|
|
150
167
|
router.route('upload').paid({
|
|
151
168
|
field: 'tier',
|
|
@@ -153,7 +170,38 @@ router.route('upload').paid({
|
|
|
153
170
|
'10mb': { price: '0.02', label: '10 MB' },
|
|
154
171
|
'100mb': { price: '0.20', label: '100 MB' },
|
|
155
172
|
},
|
|
156
|
-
}).body(
|
|
173
|
+
}).body(uploadSchema)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
#### maxPrice Semantics (v0.3.1+)
|
|
177
|
+
|
|
178
|
+
`maxPrice` is **optional** for dynamic pricing and acts as a safety net:
|
|
179
|
+
|
|
180
|
+
1. **Capping**: If `calculateCost(body)` returns `"15.00"` but `maxPrice: "10.00"`, the client is charged `$10.00` (capped) and a warning alert fires.
|
|
181
|
+
|
|
182
|
+
2. **Fallback**: If `calculateCost(body)` throws an error and `maxPrice` is set, the route falls back to `maxPrice` (degraded mode) and an alert fires. Without `maxPrice`, the route returns 500.
|
|
183
|
+
|
|
184
|
+
3. **Trust mode**: No `maxPrice` means full trust in your pricing function (no cap, no fallback).
|
|
185
|
+
|
|
186
|
+
**Best practices:**
|
|
187
|
+
- ✅ Always set `maxPrice` for production routes (safety net)
|
|
188
|
+
- ✅ Use `maxPrice` for routes with external dependencies (pricing APIs)
|
|
189
|
+
- ✅ Monitor alerts for capping events (indicates pricing bug)
|
|
190
|
+
- ⚠️ Skip `maxPrice` only for well-tested, unbounded pricing (e.g., per-GB storage)
|
|
191
|
+
|
|
192
|
+
**Example with safety net:**
|
|
193
|
+
```typescript
|
|
194
|
+
router.route('ai-gen')
|
|
195
|
+
.paid(async (body) => {
|
|
196
|
+
// External pricing API (can fail)
|
|
197
|
+
const res = await fetch('https://pricing.example.com/calculate', {
|
|
198
|
+
method: 'POST',
|
|
199
|
+
body: JSON.stringify(body),
|
|
200
|
+
});
|
|
201
|
+
return res.json().price;
|
|
202
|
+
}, { maxPrice: '5.00' }) // Fallback if API is down
|
|
203
|
+
.body(genSchema)
|
|
204
|
+
.handler(async ({ body }) => generate(body));
|
|
157
205
|
```
|
|
158
206
|
|
|
159
207
|
### Dual Protocol (x402 + MPP)
|
package/dist/index.cjs
CHANGED
|
@@ -120,7 +120,7 @@ var RouteRegistry = class {
|
|
|
120
120
|
};
|
|
121
121
|
|
|
122
122
|
// src/orchestrate.ts
|
|
123
|
-
var
|
|
123
|
+
var import_server3 = require("next/server");
|
|
124
124
|
|
|
125
125
|
// src/plugin.ts
|
|
126
126
|
function createDefaultContext(meta) {
|
|
@@ -354,70 +354,113 @@ async function settleX402Payment(server, payload, requirements) {
|
|
|
354
354
|
}
|
|
355
355
|
|
|
356
356
|
// src/protocols/mpp.ts
|
|
357
|
-
var
|
|
358
|
-
var
|
|
359
|
-
var
|
|
360
|
-
var
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
if (
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
mpayLoaded = true;
|
|
372
|
-
} catch {
|
|
373
|
-
throw new Error("mpay package is required for MPP protocol support. Install it: pnpm add mpay");
|
|
357
|
+
var import_mpay = require("mpay");
|
|
358
|
+
var import_server2 = require("mpay/server");
|
|
359
|
+
var import_viem = require("viem");
|
|
360
|
+
var import_chains = require("viem/chains");
|
|
361
|
+
function buildGetClient(rpcUrl) {
|
|
362
|
+
const url = rpcUrl ?? process.env.TEMPO_RPC_URL;
|
|
363
|
+
if (!url) return {};
|
|
364
|
+
return {
|
|
365
|
+
getClient: () => (0, import_viem.createClient)({ chain: import_chains.tempo, transport: (0, import_viem.http)(url) })
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
function toStandardRequest(request) {
|
|
369
|
+
if (request.constructor.name === "Request") {
|
|
370
|
+
return request;
|
|
374
371
|
}
|
|
372
|
+
return new Request(request.url, {
|
|
373
|
+
method: request.method,
|
|
374
|
+
headers: request.headers,
|
|
375
|
+
body: request.body,
|
|
376
|
+
// @ts-expect-error - Request.duplex is required for streaming bodies but not in types yet
|
|
377
|
+
duplex: "half"
|
|
378
|
+
});
|
|
375
379
|
}
|
|
380
|
+
var DEFAULT_DECIMALS = 6;
|
|
376
381
|
async function buildMPPChallenge(routeEntry, request, mppConfig, price) {
|
|
377
|
-
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
+
const standardRequest = toStandardRequest(request);
|
|
383
|
+
const currency = mppConfig.currency;
|
|
384
|
+
const recipient = mppConfig.recipient ?? "";
|
|
385
|
+
const methodIntent = import_server2.tempo.charge({
|
|
386
|
+
currency,
|
|
387
|
+
recipient
|
|
382
388
|
});
|
|
383
|
-
const challenge = Challenge.fromIntent(methodIntent, {
|
|
389
|
+
const challenge = import_mpay.Challenge.fromIntent(methodIntent, {
|
|
384
390
|
secretKey: mppConfig.secretKey,
|
|
385
|
-
realm: new URL(
|
|
386
|
-
request
|
|
391
|
+
realm: new URL(standardRequest.url).origin,
|
|
392
|
+
request: {
|
|
393
|
+
amount: price,
|
|
394
|
+
currency,
|
|
395
|
+
recipient,
|
|
396
|
+
decimals: DEFAULT_DECIMALS
|
|
397
|
+
}
|
|
387
398
|
});
|
|
388
|
-
return Challenge.serialize(challenge);
|
|
399
|
+
return import_mpay.Challenge.serialize(challenge);
|
|
389
400
|
}
|
|
390
401
|
async function verifyMPPCredential(request, _routeEntry, mppConfig, price) {
|
|
391
|
-
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
402
|
+
const standardRequest = toStandardRequest(request);
|
|
403
|
+
const currency = mppConfig.currency;
|
|
404
|
+
const recipient = mppConfig.recipient ?? "";
|
|
405
|
+
try {
|
|
406
|
+
const authHeader = standardRequest.headers.get("Authorization");
|
|
407
|
+
if (!authHeader) {
|
|
408
|
+
console.error("[MPP] No Authorization header found");
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
const credential = import_mpay.Credential.fromRequest(standardRequest);
|
|
412
|
+
if (!credential?.challenge) {
|
|
413
|
+
console.error("[MPP] Invalid credential structure");
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
const isValid = import_mpay.Challenge.verify(credential.challenge, { secretKey: mppConfig.secretKey });
|
|
417
|
+
if (!isValid) {
|
|
418
|
+
console.error("[MPP] Challenge HMAC verification failed");
|
|
419
|
+
return { valid: false, payer: null };
|
|
420
|
+
}
|
|
421
|
+
const methodIntent = import_server2.tempo.charge({
|
|
422
|
+
currency,
|
|
423
|
+
recipient,
|
|
424
|
+
...buildGetClient(mppConfig.rpcUrl)
|
|
425
|
+
});
|
|
426
|
+
const paymentRequest = {
|
|
427
|
+
amount: price,
|
|
428
|
+
currency,
|
|
429
|
+
recipient,
|
|
430
|
+
decimals: DEFAULT_DECIMALS
|
|
431
|
+
};
|
|
432
|
+
const resolvedRequest = methodIntent.request ? await methodIntent.request({ credential, request: paymentRequest }) : paymentRequest;
|
|
433
|
+
const receipt = await methodIntent.verify({
|
|
434
|
+
credential,
|
|
435
|
+
request: resolvedRequest
|
|
436
|
+
});
|
|
437
|
+
if (!receipt || receipt.status !== "success") {
|
|
438
|
+
console.error("[MPP] Tempo verification failed:", receipt);
|
|
439
|
+
return { valid: false, payer: null };
|
|
440
|
+
}
|
|
441
|
+
const payer = receipt.reference ?? "";
|
|
442
|
+
return {
|
|
443
|
+
valid: true,
|
|
444
|
+
payer,
|
|
445
|
+
txHash: receipt.reference
|
|
446
|
+
};
|
|
447
|
+
} catch (error) {
|
|
448
|
+
console.error("[MPP] Credential verification error:", {
|
|
449
|
+
message: error instanceof Error ? error.message : String(error),
|
|
450
|
+
stack: error instanceof Error ? error.stack : void 0,
|
|
451
|
+
errorType: error?.constructor?.name
|
|
452
|
+
});
|
|
453
|
+
return null;
|
|
406
454
|
}
|
|
407
|
-
return {
|
|
408
|
-
valid: true,
|
|
409
|
-
payer: verifyResult.payer
|
|
410
|
-
};
|
|
411
455
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const receipt = Receipt.from({
|
|
456
|
+
function buildMPPReceipt(reference) {
|
|
457
|
+
const receipt = import_mpay.Receipt.from({
|
|
415
458
|
method: "tempo",
|
|
416
459
|
status: "success",
|
|
417
460
|
reference,
|
|
418
461
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
419
462
|
});
|
|
420
|
-
return Receipt.serialize(receipt);
|
|
463
|
+
return import_mpay.Receipt.serialize(receipt);
|
|
421
464
|
}
|
|
422
465
|
|
|
423
466
|
// src/auth/siwx.ts
|
|
@@ -495,7 +538,7 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
495
538
|
firePluginResponse(deps, pluginCtx, meta, response);
|
|
496
539
|
}
|
|
497
540
|
function fail(status, message, meta, pluginCtx) {
|
|
498
|
-
const response =
|
|
541
|
+
const response = import_server3.NextResponse.json({ success: false, error: message }, { status });
|
|
499
542
|
firePluginResponse(deps, pluginCtx, meta, response);
|
|
500
543
|
return response;
|
|
501
544
|
}
|
|
@@ -523,7 +566,7 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
523
566
|
if (routeEntry.authMode === "unprotected") {
|
|
524
567
|
return handleAuth(null, void 0);
|
|
525
568
|
}
|
|
526
|
-
let account
|
|
569
|
+
let account;
|
|
527
570
|
if (routeEntry.authMode === "apiKey" || routeEntry.apiKeyResolver) {
|
|
528
571
|
if (!routeEntry.apiKeyResolver) {
|
|
529
572
|
return fail(401, "API key resolver not configured", meta, pluginCtx);
|
|
@@ -538,6 +581,16 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
538
581
|
}
|
|
539
582
|
}
|
|
540
583
|
const protocol = detectProtocol(request);
|
|
584
|
+
let earlyBodyData;
|
|
585
|
+
if (!protocol && typeof routeEntry.pricing === "function" && routeEntry.bodySchema) {
|
|
586
|
+
const requestForPricing = request.clone();
|
|
587
|
+
const earlyBodyResult = await parseBody(requestForPricing, routeEntry);
|
|
588
|
+
if (!earlyBodyResult.ok) {
|
|
589
|
+
firePluginResponse(deps, pluginCtx, meta, earlyBodyResult.response);
|
|
590
|
+
return earlyBodyResult.response;
|
|
591
|
+
}
|
|
592
|
+
earlyBodyData = earlyBodyResult.data;
|
|
593
|
+
}
|
|
541
594
|
if (routeEntry.authMode === "siwx") {
|
|
542
595
|
if (!request.headers.get("SIGN-IN-WITH-X")) {
|
|
543
596
|
const url = new URL(request.url);
|
|
@@ -585,7 +638,7 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
585
638
|
route: routeEntry.key
|
|
586
639
|
});
|
|
587
640
|
}
|
|
588
|
-
const response = new
|
|
641
|
+
const response = new import_server3.NextResponse(JSON.stringify(paymentRequired), {
|
|
589
642
|
status: 402,
|
|
590
643
|
headers: { "Content-Type": "application/json" }
|
|
591
644
|
});
|
|
@@ -606,7 +659,7 @@ function createRequestHandler(routeEntry, handler, deps) {
|
|
|
606
659
|
return handleAuth(siwx.wallet, void 0);
|
|
607
660
|
}
|
|
608
661
|
if (!protocol || protocol === "siwx") {
|
|
609
|
-
return await build402(request, routeEntry, deps, meta, pluginCtx);
|
|
662
|
+
return await build402(request, routeEntry, deps, meta, pluginCtx, earlyBodyData);
|
|
610
663
|
}
|
|
611
664
|
const body = await parseBody(request, routeEntry);
|
|
612
665
|
if (!body.ok) {
|
|
@@ -717,7 +770,7 @@ async function parseBody(request, routeEntry) {
|
|
|
717
770
|
if (result.success) return { ok: true, data: result.data };
|
|
718
771
|
return {
|
|
719
772
|
ok: false,
|
|
720
|
-
response:
|
|
773
|
+
response: import_server3.NextResponse.json(
|
|
721
774
|
{ success: false, error: result.error, issues: result.issues },
|
|
722
775
|
{ status: 400 }
|
|
723
776
|
)
|
|
@@ -744,10 +797,60 @@ function parseQuery(request, routeEntry) {
|
|
|
744
797
|
const result = routeEntry.querySchema.safeParse(params);
|
|
745
798
|
return result.success ? result.data : params;
|
|
746
799
|
}
|
|
747
|
-
async function
|
|
748
|
-
|
|
800
|
+
async function resolveDynamicPrice(bodyData, routeEntry, deps, pluginCtx, meta) {
|
|
801
|
+
try {
|
|
802
|
+
let price = await resolvePrice(routeEntry.pricing, bodyData);
|
|
803
|
+
if (routeEntry.maxPrice) {
|
|
804
|
+
const calculated = parseFloat(price);
|
|
805
|
+
const max = parseFloat(routeEntry.maxPrice);
|
|
806
|
+
if (calculated > max) {
|
|
807
|
+
firePluginHook(deps.plugin, "onAlert", pluginCtx, {
|
|
808
|
+
level: "warn",
|
|
809
|
+
message: `Price ${price} exceeds maxPrice ${routeEntry.maxPrice}, capping`,
|
|
810
|
+
route: routeEntry.key,
|
|
811
|
+
meta: { calculated: price, maxPrice: routeEntry.maxPrice, body: bodyData }
|
|
812
|
+
});
|
|
813
|
+
price = routeEntry.maxPrice;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
return { price };
|
|
817
|
+
} catch (err) {
|
|
818
|
+
firePluginHook(deps.plugin, "onAlert", pluginCtx, {
|
|
819
|
+
level: "error",
|
|
820
|
+
message: `Pricing function failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
821
|
+
route: routeEntry.key,
|
|
822
|
+
meta: { error: err instanceof Error ? err.stack : String(err), body: bodyData }
|
|
823
|
+
});
|
|
824
|
+
if (routeEntry.maxPrice) {
|
|
825
|
+
firePluginHook(deps.plugin, "onAlert", pluginCtx, {
|
|
826
|
+
level: "warn",
|
|
827
|
+
message: `Using maxPrice ${routeEntry.maxPrice} as fallback after pricing error`,
|
|
828
|
+
route: routeEntry.key
|
|
829
|
+
});
|
|
830
|
+
return { price: routeEntry.maxPrice };
|
|
831
|
+
} else {
|
|
832
|
+
const errorResponse = import_server3.NextResponse.json(
|
|
833
|
+
{ success: false, error: "Price calculation failed" },
|
|
834
|
+
{ status: 500 }
|
|
835
|
+
);
|
|
836
|
+
firePluginResponse(deps, pluginCtx, meta, errorResponse);
|
|
837
|
+
return { error: errorResponse };
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
async function build402(request, routeEntry, deps, meta, pluginCtx, bodyData) {
|
|
842
|
+
const response = new import_server3.NextResponse(null, {
|
|
843
|
+
status: 402,
|
|
844
|
+
headers: {
|
|
845
|
+
"Content-Type": "application/json"
|
|
846
|
+
}
|
|
847
|
+
});
|
|
749
848
|
let challengePrice;
|
|
750
|
-
if (routeEntry.
|
|
849
|
+
if (bodyData !== void 0 && typeof routeEntry.pricing === "function") {
|
|
850
|
+
const result = await resolveDynamicPrice(bodyData, routeEntry, deps, pluginCtx, meta);
|
|
851
|
+
if ("error" in result) return result.error;
|
|
852
|
+
challengePrice = result.price;
|
|
853
|
+
} else if (routeEntry.maxPrice) {
|
|
751
854
|
challengePrice = routeEntry.maxPrice;
|
|
752
855
|
} else if (routeEntry.pricing) {
|
|
753
856
|
try {
|
|
@@ -908,9 +1011,6 @@ var RouteBuilder = class {
|
|
|
908
1011
|
next._pricing = pricing;
|
|
909
1012
|
if (options?.protocols) next._protocols = options.protocols;
|
|
910
1013
|
if (options?.maxPrice) next._maxPrice = options.maxPrice;
|
|
911
|
-
if (typeof pricing === "function" && !options?.maxPrice) {
|
|
912
|
-
throw new Error(`route '${this._key}': dynamic pricing requires maxPrice option`);
|
|
913
|
-
}
|
|
914
1014
|
if (typeof pricing === "object" && "tiers" in pricing) {
|
|
915
1015
|
for (const [tierKey, tierConfig] of Object.entries(pricing.tiers)) {
|
|
916
1016
|
if (!tierKey) {
|
|
@@ -1035,7 +1135,7 @@ var MemoryNonceStore = class {
|
|
|
1035
1135
|
};
|
|
1036
1136
|
|
|
1037
1137
|
// src/discovery/well-known.ts
|
|
1038
|
-
var
|
|
1138
|
+
var import_server4 = require("next/server");
|
|
1039
1139
|
function createWellKnownHandler(registry, baseUrl, pricesKeys, options = {}) {
|
|
1040
1140
|
let validated = false;
|
|
1041
1141
|
return async (_request) => {
|
|
@@ -1073,7 +1173,7 @@ function createWellKnownHandler(registry, baseUrl, pricesKeys, options = {}) {
|
|
|
1073
1173
|
if (instructions) {
|
|
1074
1174
|
body.instructions = instructions;
|
|
1075
1175
|
}
|
|
1076
|
-
return
|
|
1176
|
+
return import_server4.NextResponse.json(body, {
|
|
1077
1177
|
headers: {
|
|
1078
1178
|
"Access-Control-Allow-Origin": "*",
|
|
1079
1179
|
"Access-Control-Allow-Methods": "GET",
|
|
@@ -1084,12 +1184,12 @@ function createWellKnownHandler(registry, baseUrl, pricesKeys, options = {}) {
|
|
|
1084
1184
|
}
|
|
1085
1185
|
|
|
1086
1186
|
// src/discovery/openapi.ts
|
|
1087
|
-
var
|
|
1187
|
+
var import_server5 = require("next/server");
|
|
1088
1188
|
function createOpenAPIHandler(registry, baseUrl, pricesKeys, options) {
|
|
1089
1189
|
let cached = null;
|
|
1090
1190
|
let validated = false;
|
|
1091
1191
|
return async (_request) => {
|
|
1092
|
-
if (cached) return
|
|
1192
|
+
if (cached) return import_server5.NextResponse.json(cached);
|
|
1093
1193
|
if (!validated && pricesKeys) {
|
|
1094
1194
|
registry.validate(pricesKeys);
|
|
1095
1195
|
validated = true;
|
|
@@ -1116,7 +1216,7 @@ function createOpenAPIHandler(registry, baseUrl, pricesKeys, options) {
|
|
|
1116
1216
|
tags: Array.from(tagSet).sort().map((name) => ({ name })),
|
|
1117
1217
|
paths
|
|
1118
1218
|
});
|
|
1119
|
-
return
|
|
1219
|
+
return import_server5.NextResponse.json(cached);
|
|
1120
1220
|
};
|
|
1121
1221
|
}
|
|
1122
1222
|
function deriveTag(routeKey) {
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
2
|
import { ZodType } from 'zod';
|
|
3
|
+
import { PaymentRequirements, PaymentRequired, SettleResponse } from '@x402/core/types';
|
|
3
4
|
|
|
4
5
|
interface NonceStore {
|
|
5
6
|
check(nonce: string): Promise<boolean>;
|
|
@@ -82,6 +83,7 @@ interface AlertEvent {
|
|
|
82
83
|
meta?: Record<string, unknown>;
|
|
83
84
|
}
|
|
84
85
|
type AlertFn = (level: AlertLevel, message: string, meta?: Record<string, unknown>) => void;
|
|
86
|
+
|
|
85
87
|
interface X402Server {
|
|
86
88
|
initialize(): Promise<void>;
|
|
87
89
|
buildPaymentRequirementsFromOptions(options: Array<{
|
|
@@ -91,18 +93,18 @@ interface X402Server {
|
|
|
91
93
|
payTo: string;
|
|
92
94
|
}>, context: {
|
|
93
95
|
request: Request;
|
|
94
|
-
}): Promise<
|
|
95
|
-
createPaymentRequiredResponse(requirements:
|
|
96
|
+
}): Promise<PaymentRequirements[]>;
|
|
97
|
+
createPaymentRequiredResponse(requirements: PaymentRequirements[], resource: {
|
|
96
98
|
url: string;
|
|
97
99
|
method: string;
|
|
98
100
|
description?: string;
|
|
99
|
-
}, error: string | null, extensions?: Record<string, unknown>): Promise<
|
|
100
|
-
findMatchingRequirements(requirements:
|
|
101
|
-
verifyPayment(payload: unknown, requirements:
|
|
101
|
+
}, error: string | null, extensions?: Record<string, unknown>): Promise<PaymentRequired>;
|
|
102
|
+
findMatchingRequirements(requirements: PaymentRequirements[], payload: unknown): PaymentRequirements;
|
|
103
|
+
verifyPayment(payload: unknown, requirements: PaymentRequirements): Promise<{
|
|
102
104
|
isValid: boolean;
|
|
103
105
|
payer?: string;
|
|
104
106
|
}>;
|
|
105
|
-
settlePayment(payload: unknown, requirements:
|
|
107
|
+
settlePayment(payload: unknown, requirements: PaymentRequirements): Promise<SettleResponse>;
|
|
106
108
|
}
|
|
107
109
|
type ProtocolType = 'x402' | 'mpp';
|
|
108
110
|
type AuthMode = 'paid' | 'siwx' | 'apiKey' | 'unprotected';
|
|
@@ -183,6 +185,8 @@ interface RouterConfig {
|
|
|
183
185
|
secretKey: string;
|
|
184
186
|
currency: string;
|
|
185
187
|
recipient?: string;
|
|
188
|
+
/** Tempo RPC URL for on-chain verification. Falls back to TEMPO_RPC_URL env var. */
|
|
189
|
+
rpcUrl?: string;
|
|
186
190
|
};
|
|
187
191
|
/**
|
|
188
192
|
* Payment protocols to accept on auto-priced routes (those using the `prices` config).
|
|
@@ -239,6 +243,7 @@ interface OrchestrateDeps {
|
|
|
239
243
|
secretKey: string;
|
|
240
244
|
currency: string;
|
|
241
245
|
recipient?: string;
|
|
246
|
+
rpcUrl?: string;
|
|
242
247
|
};
|
|
243
248
|
}
|
|
244
249
|
|
|
@@ -265,7 +270,7 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, HasAuth extend
|
|
|
265
270
|
private fork;
|
|
266
271
|
paid(pricing: string, options?: PaidOptions): RouteBuilder<TBody, TQuery, True, False, HasBody>;
|
|
267
272
|
paid<TBodyIn>(pricing: (body: TBodyIn) => string | Promise<string>, options?: PaidOptions & {
|
|
268
|
-
maxPrice
|
|
273
|
+
maxPrice?: string;
|
|
269
274
|
}): RouteBuilder<TBody, TQuery, True, True, HasBody>;
|
|
270
275
|
paid(pricing: {
|
|
271
276
|
field: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
2
|
import { ZodType } from 'zod';
|
|
3
|
+
import { PaymentRequirements, PaymentRequired, SettleResponse } from '@x402/core/types';
|
|
3
4
|
|
|
4
5
|
interface NonceStore {
|
|
5
6
|
check(nonce: string): Promise<boolean>;
|
|
@@ -82,6 +83,7 @@ interface AlertEvent {
|
|
|
82
83
|
meta?: Record<string, unknown>;
|
|
83
84
|
}
|
|
84
85
|
type AlertFn = (level: AlertLevel, message: string, meta?: Record<string, unknown>) => void;
|
|
86
|
+
|
|
85
87
|
interface X402Server {
|
|
86
88
|
initialize(): Promise<void>;
|
|
87
89
|
buildPaymentRequirementsFromOptions(options: Array<{
|
|
@@ -91,18 +93,18 @@ interface X402Server {
|
|
|
91
93
|
payTo: string;
|
|
92
94
|
}>, context: {
|
|
93
95
|
request: Request;
|
|
94
|
-
}): Promise<
|
|
95
|
-
createPaymentRequiredResponse(requirements:
|
|
96
|
+
}): Promise<PaymentRequirements[]>;
|
|
97
|
+
createPaymentRequiredResponse(requirements: PaymentRequirements[], resource: {
|
|
96
98
|
url: string;
|
|
97
99
|
method: string;
|
|
98
100
|
description?: string;
|
|
99
|
-
}, error: string | null, extensions?: Record<string, unknown>): Promise<
|
|
100
|
-
findMatchingRequirements(requirements:
|
|
101
|
-
verifyPayment(payload: unknown, requirements:
|
|
101
|
+
}, error: string | null, extensions?: Record<string, unknown>): Promise<PaymentRequired>;
|
|
102
|
+
findMatchingRequirements(requirements: PaymentRequirements[], payload: unknown): PaymentRequirements;
|
|
103
|
+
verifyPayment(payload: unknown, requirements: PaymentRequirements): Promise<{
|
|
102
104
|
isValid: boolean;
|
|
103
105
|
payer?: string;
|
|
104
106
|
}>;
|
|
105
|
-
settlePayment(payload: unknown, requirements:
|
|
107
|
+
settlePayment(payload: unknown, requirements: PaymentRequirements): Promise<SettleResponse>;
|
|
106
108
|
}
|
|
107
109
|
type ProtocolType = 'x402' | 'mpp';
|
|
108
110
|
type AuthMode = 'paid' | 'siwx' | 'apiKey' | 'unprotected';
|
|
@@ -183,6 +185,8 @@ interface RouterConfig {
|
|
|
183
185
|
secretKey: string;
|
|
184
186
|
currency: string;
|
|
185
187
|
recipient?: string;
|
|
188
|
+
/** Tempo RPC URL for on-chain verification. Falls back to TEMPO_RPC_URL env var. */
|
|
189
|
+
rpcUrl?: string;
|
|
186
190
|
};
|
|
187
191
|
/**
|
|
188
192
|
* Payment protocols to accept on auto-priced routes (those using the `prices` config).
|
|
@@ -239,6 +243,7 @@ interface OrchestrateDeps {
|
|
|
239
243
|
secretKey: string;
|
|
240
244
|
currency: string;
|
|
241
245
|
recipient?: string;
|
|
246
|
+
rpcUrl?: string;
|
|
242
247
|
};
|
|
243
248
|
}
|
|
244
249
|
|
|
@@ -265,7 +270,7 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, HasAuth extend
|
|
|
265
270
|
private fork;
|
|
266
271
|
paid(pricing: string, options?: PaidOptions): RouteBuilder<TBody, TQuery, True, False, HasBody>;
|
|
267
272
|
paid<TBodyIn>(pricing: (body: TBodyIn) => string | Promise<string>, options?: PaidOptions & {
|
|
268
|
-
maxPrice
|
|
273
|
+
maxPrice?: string;
|
|
269
274
|
}): RouteBuilder<TBody, TQuery, True, True, HasBody>;
|
|
270
275
|
paid(pricing: {
|
|
271
276
|
field: string;
|