@agentcash/router 1.10.4 → 1.10.6
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 +15 -0
- package/dist/index.cjs +101 -25
- package/dist/index.d.cts +25 -3
- package/dist/index.d.ts +25 -3
- package/dist/index.js +101 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -150,6 +150,21 @@ The barrel forces every route module to load before the discovery handler walks
|
|
|
150
150
|
|
|
151
151
|
The `openapi.json` should be hosted at `GET <origin>/openapi.json`.
|
|
152
152
|
|
|
153
|
+
### 4. Unmatched route fallback
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
// app/api/[[...path]]/route.ts
|
|
157
|
+
import { router } from '@/lib/router';
|
|
158
|
+
|
|
159
|
+
export const GET = router.notFound();
|
|
160
|
+
export const POST = router.notFound();
|
|
161
|
+
export const DELETE = router.notFound();
|
|
162
|
+
export const PUT = router.notFound();
|
|
163
|
+
export const PATCH = router.notFound();
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
This catches stale agent calls to API paths that no longer exist and returns a JSON 404 telling the client to rediscover the origin.
|
|
167
|
+
|
|
153
168
|
## Auth modes
|
|
154
169
|
|
|
155
170
|
| Method | Purpose |
|
package/dist/index.cjs
CHANGED
|
@@ -1207,7 +1207,7 @@ async function trySiwxFastPath(ctx, account) {
|
|
|
1207
1207
|
function shouldParseBodyEarly(incomingStrategy, routeEntry, pricing) {
|
|
1208
1208
|
if (incomingStrategy) return false;
|
|
1209
1209
|
if (!routeEntry.bodySchema) return false;
|
|
1210
|
-
return (pricing?.needsBody ?? false) || !!routeEntry.validateFn;
|
|
1210
|
+
return (pricing?.needsBody ?? false) || !!routeEntry.validateFn || !!routeEntry.checkoutSession;
|
|
1211
1211
|
}
|
|
1212
1212
|
|
|
1213
1213
|
// src/pipeline/steps/resolve-early-body.ts
|
|
@@ -2106,6 +2106,7 @@ function invalidPaymentVerification(failure) {
|
|
|
2106
2106
|
}
|
|
2107
2107
|
|
|
2108
2108
|
// src/protocols/x402/strategy.ts
|
|
2109
|
+
var SETTLE_RETRY_DELAYS_MS = [500, 1e3];
|
|
2109
2110
|
function formatVerifyFailureMessage(failure) {
|
|
2110
2111
|
if (failure.reason === "permit2_allowance_required") {
|
|
2111
2112
|
const wallet = failure.payer ?? "<the payer wallet>";
|
|
@@ -2196,7 +2197,17 @@ async function settleX402(args) {
|
|
|
2196
2197
|
const { payload, requirements } = token;
|
|
2197
2198
|
const override = routeEntry.billing === "exact" ? void 0 : { amount: billedAmount };
|
|
2198
2199
|
try {
|
|
2199
|
-
|
|
2200
|
+
let settle = await settleX402Payment(deps.x402Server, payload, requirements, override);
|
|
2201
|
+
for (let attempt = 0; !settle.result?.success && attempt < SETTLE_RETRY_DELAYS_MS.length; attempt++) {
|
|
2202
|
+
report("warn", "Retrying x402 settlement", {
|
|
2203
|
+
attempt: attempt + 1,
|
|
2204
|
+
errorReason: settle.result?.errorReason
|
|
2205
|
+
});
|
|
2206
|
+
await new Promise((resolve) => {
|
|
2207
|
+
setTimeout(resolve, SETTLE_RETRY_DELAYS_MS[attempt]);
|
|
2208
|
+
});
|
|
2209
|
+
settle = await settleX402Payment(deps.x402Server, payload, requirements, override);
|
|
2210
|
+
}
|
|
2200
2211
|
if (!settle.result?.success) {
|
|
2201
2212
|
throw Object.assign(
|
|
2202
2213
|
new Error(settle.result?.errorReason ?? "x402 settlement returned success=false"),
|
|
@@ -2538,30 +2549,37 @@ async function buildChallengeResponse(ctx, pricing, body, failure) {
|
|
|
2538
2549
|
return errorResponse;
|
|
2539
2550
|
}
|
|
2540
2551
|
const extensions = await buildChallengeExtensions(ctx);
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
status: 402,
|
|
2544
|
-
headers: {
|
|
2545
|
-
"Content-Type": "application/json",
|
|
2546
|
-
"Cache-Control": "no-store"
|
|
2547
|
-
}
|
|
2548
|
-
});
|
|
2549
|
-
for (const strategy of getAllowedStrategies(ctx.routeEntry.protocols)) {
|
|
2552
|
+
let checkoutSession;
|
|
2553
|
+
if (ctx.routeEntry.checkoutSession) {
|
|
2550
2554
|
try {
|
|
2551
|
-
|
|
2555
|
+
checkoutSession = await ctx.routeEntry.checkoutSession({
|
|
2552
2556
|
request: ctx.request,
|
|
2553
|
-
|
|
2557
|
+
route: ctx.routeEntry.key,
|
|
2554
2558
|
body,
|
|
2555
|
-
price: challengePrice
|
|
2556
|
-
extensions,
|
|
2557
|
-
deps: ctx.deps,
|
|
2558
|
-
report: ctx.report
|
|
2559
|
+
price: challengePrice
|
|
2559
2560
|
});
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
}
|
|
2561
|
+
} catch (err) {
|
|
2562
|
+
const message = errorMessage(err, "Checkout session build failed");
|
|
2563
|
+
const responseBody2 = { success: false, error: message };
|
|
2564
|
+
const errorResponse = import_server5.NextResponse.json(responseBody2, { status: errorStatus(err, 500) });
|
|
2565
|
+
firePluginResponse(ctx, errorResponse, body, responseBody2, { message, cause: err });
|
|
2566
|
+
return errorResponse;
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
const contributions = [];
|
|
2570
|
+
for (const strategy of getAllowedStrategies(ctx.routeEntry.protocols)) {
|
|
2571
|
+
try {
|
|
2572
|
+
contributions.push(
|
|
2573
|
+
await strategy.buildChallenge({
|
|
2574
|
+
request: ctx.request,
|
|
2575
|
+
routeEntry: ctx.routeEntry,
|
|
2576
|
+
body,
|
|
2577
|
+
price: challengePrice,
|
|
2578
|
+
extensions,
|
|
2579
|
+
deps: ctx.deps,
|
|
2580
|
+
report: ctx.report
|
|
2581
|
+
})
|
|
2582
|
+
);
|
|
2565
2583
|
} catch (err) {
|
|
2566
2584
|
const message = `${strategy.protocol} challenge build failed: ${errorMessage(err, String(err))}`;
|
|
2567
2585
|
ctx.report("critical", message);
|
|
@@ -2573,6 +2591,26 @@ async function buildChallengeResponse(ctx, pricing, body, failure) {
|
|
|
2573
2591
|
}
|
|
2574
2592
|
}
|
|
2575
2593
|
}
|
|
2594
|
+
const responsePayload = {
|
|
2595
|
+
...Object.assign({}, ...contributions.map((contribution) => contribution.body ?? {})),
|
|
2596
|
+
...failure && { error: failure.message ?? null, reason: failure.reason },
|
|
2597
|
+
...checkoutSession && { checkout_session: checkoutSession }
|
|
2598
|
+
};
|
|
2599
|
+
const responseBody = Object.keys(responsePayload).length ? JSON.stringify(responsePayload) : null;
|
|
2600
|
+
const response = new import_server5.NextResponse(responseBody, {
|
|
2601
|
+
status: 402,
|
|
2602
|
+
headers: {
|
|
2603
|
+
"Content-Type": "application/json",
|
|
2604
|
+
"Cache-Control": "no-store"
|
|
2605
|
+
}
|
|
2606
|
+
});
|
|
2607
|
+
for (const contribution of contributions) {
|
|
2608
|
+
if (contribution.headers) {
|
|
2609
|
+
for (const [name, value] of Object.entries(contribution.headers)) {
|
|
2610
|
+
response.headers.set(name, value);
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2576
2614
|
firePluginResponse(ctx, response);
|
|
2577
2615
|
return response;
|
|
2578
2616
|
}
|
|
@@ -3590,7 +3628,9 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3590
3628
|
providerConfig: void 0,
|
|
3591
3629
|
validateFn: void 0,
|
|
3592
3630
|
settlement: void 0,
|
|
3593
|
-
mppInfo: void 0
|
|
3631
|
+
mppInfo: void 0,
|
|
3632
|
+
hasCheckout: false,
|
|
3633
|
+
checkoutSession: void 0
|
|
3594
3634
|
};
|
|
3595
3635
|
}
|
|
3596
3636
|
fork() {
|
|
@@ -3681,6 +3721,8 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3681
3721
|
if (resolvedOptions.minPrice) next.#s.minPrice = resolvedOptions.minPrice;
|
|
3682
3722
|
if (resolvedOptions.payTo) next.#s.payTo = resolvedOptions.payTo;
|
|
3683
3723
|
if (resolvedOptions.mpp) next.#s.mppInfo = resolvedOptions.mpp;
|
|
3724
|
+
if (resolvedOptions.checkout || resolvedOptions.checkoutSession) next.#s.hasCheckout = true;
|
|
3725
|
+
if (resolvedOptions.checkoutSession) next.#s.checkoutSession = resolvedOptions.checkoutSession;
|
|
3684
3726
|
next.#s.billing = billing;
|
|
3685
3727
|
if (tickCost) next.#s.tickCost = tickCost;
|
|
3686
3728
|
if (unitType) next.#s.unitType = unitType;
|
|
@@ -4114,6 +4156,8 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
4114
4156
|
validateFn: this.#s.validateFn,
|
|
4115
4157
|
settlement: this.#s.settlement,
|
|
4116
4158
|
mppInfo: this.#s.mppInfo,
|
|
4159
|
+
hasCheckout: this.#s.hasCheckout ? true : void 0,
|
|
4160
|
+
checkoutSession: this.#s.checkoutSession,
|
|
4117
4161
|
tickCost: this.#s.tickCost,
|
|
4118
4162
|
unitType: this.#s.unitType
|
|
4119
4163
|
};
|
|
@@ -4320,6 +4364,7 @@ function buildOperation(routeKey, entry, tag) {
|
|
|
4320
4364
|
const requiresSiwxScheme = entry.authMode === "siwx" || Boolean(entry.siwxEnabled);
|
|
4321
4365
|
const requiresApiKeyScheme = Boolean(entry.apiKeyResolver) && entry.authMode !== "siwx";
|
|
4322
4366
|
const pricingInfo = buildPricingInfo(entry);
|
|
4367
|
+
const hasCheckout = entry.hasCheckout === true;
|
|
4323
4368
|
const operation = {
|
|
4324
4369
|
operationId: routeKey.replace(/\//g, "_"),
|
|
4325
4370
|
summary: entry.description ?? routeKey,
|
|
@@ -4345,10 +4390,11 @@ function buildOperation(routeKey, entry, tag) {
|
|
|
4345
4390
|
}
|
|
4346
4391
|
}
|
|
4347
4392
|
};
|
|
4348
|
-
if (paymentRequired && (pricingInfo || protocols)) {
|
|
4393
|
+
if (paymentRequired && (pricingInfo || protocols || hasCheckout)) {
|
|
4349
4394
|
operation["x-payment-info"] = {
|
|
4350
4395
|
...pricingInfo ?? {},
|
|
4351
|
-
...protocols && { protocols }
|
|
4396
|
+
...protocols && { protocols },
|
|
4397
|
+
...hasCheckout && { has_checkout: true }
|
|
4352
4398
|
};
|
|
4353
4399
|
}
|
|
4354
4400
|
if (requiresSiwxScheme) {
|
|
@@ -4459,6 +4505,33 @@ function createLlmsTxtHandler(discovery) {
|
|
|
4459
4505
|
};
|
|
4460
4506
|
}
|
|
4461
4507
|
|
|
4508
|
+
// src/discovery/not-found.ts
|
|
4509
|
+
var import_server12 = require("next/server");
|
|
4510
|
+
function createNotFoundHandler(baseUrl) {
|
|
4511
|
+
const normalizedBase = baseUrl.replace(/\/+$/, "");
|
|
4512
|
+
return async (request) => import_server12.NextResponse.json(
|
|
4513
|
+
{
|
|
4514
|
+
success: false,
|
|
4515
|
+
code: "route_not_found",
|
|
4516
|
+
error: "Route not found. Rediscover this origin and retry with the current discovery document.",
|
|
4517
|
+
requestedUrl: request.url,
|
|
4518
|
+
discovery: {
|
|
4519
|
+
openapi: `${normalizedBase}/openapi.json`,
|
|
4520
|
+
wellKnown: `${normalizedBase}/.well-known/x402`,
|
|
4521
|
+
llmsTxt: `${normalizedBase}/llms.txt`
|
|
4522
|
+
}
|
|
4523
|
+
},
|
|
4524
|
+
{
|
|
4525
|
+
status: 404,
|
|
4526
|
+
headers: {
|
|
4527
|
+
"Access-Control-Allow-Origin": "*",
|
|
4528
|
+
"Access-Control-Allow-Methods": "GET, POST, DELETE, PUT, PATCH, OPTIONS",
|
|
4529
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-API-Key, SIGN-IN-WITH-X"
|
|
4530
|
+
}
|
|
4531
|
+
}
|
|
4532
|
+
);
|
|
4533
|
+
}
|
|
4534
|
+
|
|
4462
4535
|
// src/index.ts
|
|
4463
4536
|
init_accepts();
|
|
4464
4537
|
init_constants();
|
|
@@ -5162,6 +5235,9 @@ function createRouter(config) {
|
|
|
5162
5235
|
llmsTxt() {
|
|
5163
5236
|
return createLlmsTxtHandler(config.discovery);
|
|
5164
5237
|
},
|
|
5238
|
+
notFound() {
|
|
5239
|
+
return createNotFoundHandler(resolvedBaseUrl);
|
|
5240
|
+
},
|
|
5165
5241
|
monitors() {
|
|
5166
5242
|
const result = [];
|
|
5167
5243
|
for (const [, entry] of registry.entries()) {
|
package/dist/index.d.cts
CHANGED
|
@@ -219,6 +219,15 @@ interface MppProtocolInfo {
|
|
|
219
219
|
intent?: string;
|
|
220
220
|
currency?: string;
|
|
221
221
|
}
|
|
222
|
+
type CheckoutSessionResponse = Record<string, unknown>;
|
|
223
|
+
interface CheckoutSessionContext<TBody = unknown> {
|
|
224
|
+
request: NextRequest;
|
|
225
|
+
route: string;
|
|
226
|
+
body: TBody | undefined;
|
|
227
|
+
/** Decimal-dollar price quoted for this 402 challenge. */
|
|
228
|
+
price: string;
|
|
229
|
+
}
|
|
230
|
+
type CheckoutSessionFn<TBody = unknown> = (ctx: CheckoutSessionContext<TBody>) => CheckoutSessionResponse | null | undefined | Promise<CheckoutSessionResponse | null | undefined>;
|
|
222
231
|
interface PaidOptions {
|
|
223
232
|
protocols?: ProtocolType[];
|
|
224
233
|
maxPrice?: string;
|
|
@@ -227,6 +236,16 @@ interface PaidOptions {
|
|
|
227
236
|
payTo?: PayToConfig;
|
|
228
237
|
/** Override MPP protocol metadata in x-payment-info discovery. */
|
|
229
238
|
mpp?: MppProtocolInfo;
|
|
239
|
+
/** Signal in discovery that clients should use an explicit checkout flow before payment. */
|
|
240
|
+
checkout?: boolean;
|
|
241
|
+
/**
|
|
242
|
+
* Build dynamic checkout review metadata for the router-owned 402 response body.
|
|
243
|
+
*
|
|
244
|
+
* The returned object is emitted as `{ "checkout_session": ... }` on the unpaid
|
|
245
|
+
* payment challenge response. Payment terms remain authoritative in the x402/MPP
|
|
246
|
+
* challenge headers.
|
|
247
|
+
*/
|
|
248
|
+
checkoutSession?: CheckoutSessionFn;
|
|
230
249
|
}
|
|
231
250
|
type PaidArg = (PaidOptions & {
|
|
232
251
|
price: string;
|
|
@@ -371,6 +390,8 @@ interface RouteEntry {
|
|
|
371
390
|
validateFn?: (body: unknown) => void | Promise<void>;
|
|
372
391
|
settlement?: SettlementLifecycle;
|
|
373
392
|
mppInfo?: MppProtocolInfo;
|
|
393
|
+
hasCheckout?: boolean;
|
|
394
|
+
checkoutSession?: CheckoutSessionFn;
|
|
374
395
|
/** Per-tick cost (decimal-dollar). Required when `metered` is true. */
|
|
375
396
|
tickCost?: string;
|
|
376
397
|
/** Cosmetic unit label for 402 challenges and client UIs. */
|
|
@@ -554,8 +575,8 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, TOutput = unde
|
|
|
554
575
|
* - `{ price }` — fixed price (object form of the string sugar).
|
|
555
576
|
* - `{ field, tiers, default? }` — pick a tier from `body[field]`.
|
|
556
577
|
*
|
|
557
|
-
* Common knobs (`protocols`, `maxPrice`, `minPrice`, `payTo`, `mpp`
|
|
558
|
-
* alongside the pricing shape. For handler-computed billing use `.upTo()`;
|
|
578
|
+
* Common knobs (`protocols`, `maxPrice`, `minPrice`, `payTo`, `mpp`, `checkout`,
|
|
579
|
+
* `checkoutSession`) live alongside the pricing shape. For handler-computed billing use `.upTo()`;
|
|
559
580
|
* for per-tick billing use `.metered()`.
|
|
560
581
|
*
|
|
561
582
|
* @example
|
|
@@ -897,6 +918,7 @@ interface ServiceRouter<TPriceKeys extends string = never> {
|
|
|
897
918
|
wellKnown(): (request: NextRequest) => Promise<NextResponse>;
|
|
898
919
|
openapi(): (request: NextRequest) => Promise<NextResponse>;
|
|
899
920
|
llmsTxt(): (request: NextRequest) => Promise<NextResponse>;
|
|
921
|
+
notFound(): (request: NextRequest) => Promise<NextResponse>;
|
|
900
922
|
monitors(): MonitorEntry[];
|
|
901
923
|
registry: RouteRegistry;
|
|
902
924
|
}
|
|
@@ -925,4 +947,4 @@ declare function createRouter<const P extends Record<string, string> = Record<ne
|
|
|
925
947
|
*/
|
|
926
948
|
declare function createRouterFromEnv<const P extends Record<string, string> = Record<never, string>>(options: CreateRouterFromEnvOptions<P>): ServiceRouter<Extract<keyof P, string>>;
|
|
927
949
|
|
|
928
|
-
export { BASE_MAINNET_NETWORK, BASE_USDC_ADDRESS, BASE_USDC_DECIMALS, type CreateRouterFromEnvOptions, DEFAULT_SOLANA_FACILITATOR_URL, DEFAULT_TEMPO_RPC_URL, type DiscoveryConfig, type HandlerContext, HttpError, type KvStore, type PaidOptions, type ProtocolType, type RouterConfig, RouterConfigError, type RouterConfigIssue, type RouterConfigIssueCode, type RouterConfigIssueSeverity, type RouterPlugin, SOLANA_MAINNET_NETWORK, type ServiceRouter, type SettlementErrorContext, type SettlementLifecycleContext, type SettlementSettledContext, TEMPO_USDC_ADDRESS, TEMPO_USDC_DECIMALS, type X402FacilitatorsConfig, ZERO_EVM_ADDRESS, createRouter, createRouterFromEnv, routerConfigFromEnv };
|
|
950
|
+
export { BASE_MAINNET_NETWORK, BASE_USDC_ADDRESS, BASE_USDC_DECIMALS, type CheckoutSessionContext, type CheckoutSessionFn, type CheckoutSessionResponse, type CreateRouterFromEnvOptions, DEFAULT_SOLANA_FACILITATOR_URL, DEFAULT_TEMPO_RPC_URL, type DiscoveryConfig, type HandlerContext, HttpError, type KvStore, type PaidOptions, type ProtocolType, type RouterConfig, RouterConfigError, type RouterConfigIssue, type RouterConfigIssueCode, type RouterConfigIssueSeverity, type RouterPlugin, SOLANA_MAINNET_NETWORK, type ServiceRouter, type SettlementErrorContext, type SettlementLifecycleContext, type SettlementSettledContext, TEMPO_USDC_ADDRESS, TEMPO_USDC_DECIMALS, type X402FacilitatorsConfig, ZERO_EVM_ADDRESS, createRouter, createRouterFromEnv, routerConfigFromEnv };
|
package/dist/index.d.ts
CHANGED
|
@@ -219,6 +219,15 @@ interface MppProtocolInfo {
|
|
|
219
219
|
intent?: string;
|
|
220
220
|
currency?: string;
|
|
221
221
|
}
|
|
222
|
+
type CheckoutSessionResponse = Record<string, unknown>;
|
|
223
|
+
interface CheckoutSessionContext<TBody = unknown> {
|
|
224
|
+
request: NextRequest;
|
|
225
|
+
route: string;
|
|
226
|
+
body: TBody | undefined;
|
|
227
|
+
/** Decimal-dollar price quoted for this 402 challenge. */
|
|
228
|
+
price: string;
|
|
229
|
+
}
|
|
230
|
+
type CheckoutSessionFn<TBody = unknown> = (ctx: CheckoutSessionContext<TBody>) => CheckoutSessionResponse | null | undefined | Promise<CheckoutSessionResponse | null | undefined>;
|
|
222
231
|
interface PaidOptions {
|
|
223
232
|
protocols?: ProtocolType[];
|
|
224
233
|
maxPrice?: string;
|
|
@@ -227,6 +236,16 @@ interface PaidOptions {
|
|
|
227
236
|
payTo?: PayToConfig;
|
|
228
237
|
/** Override MPP protocol metadata in x-payment-info discovery. */
|
|
229
238
|
mpp?: MppProtocolInfo;
|
|
239
|
+
/** Signal in discovery that clients should use an explicit checkout flow before payment. */
|
|
240
|
+
checkout?: boolean;
|
|
241
|
+
/**
|
|
242
|
+
* Build dynamic checkout review metadata for the router-owned 402 response body.
|
|
243
|
+
*
|
|
244
|
+
* The returned object is emitted as `{ "checkout_session": ... }` on the unpaid
|
|
245
|
+
* payment challenge response. Payment terms remain authoritative in the x402/MPP
|
|
246
|
+
* challenge headers.
|
|
247
|
+
*/
|
|
248
|
+
checkoutSession?: CheckoutSessionFn;
|
|
230
249
|
}
|
|
231
250
|
type PaidArg = (PaidOptions & {
|
|
232
251
|
price: string;
|
|
@@ -371,6 +390,8 @@ interface RouteEntry {
|
|
|
371
390
|
validateFn?: (body: unknown) => void | Promise<void>;
|
|
372
391
|
settlement?: SettlementLifecycle;
|
|
373
392
|
mppInfo?: MppProtocolInfo;
|
|
393
|
+
hasCheckout?: boolean;
|
|
394
|
+
checkoutSession?: CheckoutSessionFn;
|
|
374
395
|
/** Per-tick cost (decimal-dollar). Required when `metered` is true. */
|
|
375
396
|
tickCost?: string;
|
|
376
397
|
/** Cosmetic unit label for 402 challenges and client UIs. */
|
|
@@ -554,8 +575,8 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, TOutput = unde
|
|
|
554
575
|
* - `{ price }` — fixed price (object form of the string sugar).
|
|
555
576
|
* - `{ field, tiers, default? }` — pick a tier from `body[field]`.
|
|
556
577
|
*
|
|
557
|
-
* Common knobs (`protocols`, `maxPrice`, `minPrice`, `payTo`, `mpp`
|
|
558
|
-
* alongside the pricing shape. For handler-computed billing use `.upTo()`;
|
|
578
|
+
* Common knobs (`protocols`, `maxPrice`, `minPrice`, `payTo`, `mpp`, `checkout`,
|
|
579
|
+
* `checkoutSession`) live alongside the pricing shape. For handler-computed billing use `.upTo()`;
|
|
559
580
|
* for per-tick billing use `.metered()`.
|
|
560
581
|
*
|
|
561
582
|
* @example
|
|
@@ -897,6 +918,7 @@ interface ServiceRouter<TPriceKeys extends string = never> {
|
|
|
897
918
|
wellKnown(): (request: NextRequest) => Promise<NextResponse>;
|
|
898
919
|
openapi(): (request: NextRequest) => Promise<NextResponse>;
|
|
899
920
|
llmsTxt(): (request: NextRequest) => Promise<NextResponse>;
|
|
921
|
+
notFound(): (request: NextRequest) => Promise<NextResponse>;
|
|
900
922
|
monitors(): MonitorEntry[];
|
|
901
923
|
registry: RouteRegistry;
|
|
902
924
|
}
|
|
@@ -925,4 +947,4 @@ declare function createRouter<const P extends Record<string, string> = Record<ne
|
|
|
925
947
|
*/
|
|
926
948
|
declare function createRouterFromEnv<const P extends Record<string, string> = Record<never, string>>(options: CreateRouterFromEnvOptions<P>): ServiceRouter<Extract<keyof P, string>>;
|
|
927
949
|
|
|
928
|
-
export { BASE_MAINNET_NETWORK, BASE_USDC_ADDRESS, BASE_USDC_DECIMALS, type CreateRouterFromEnvOptions, DEFAULT_SOLANA_FACILITATOR_URL, DEFAULT_TEMPO_RPC_URL, type DiscoveryConfig, type HandlerContext, HttpError, type KvStore, type PaidOptions, type ProtocolType, type RouterConfig, RouterConfigError, type RouterConfigIssue, type RouterConfigIssueCode, type RouterConfigIssueSeverity, type RouterPlugin, SOLANA_MAINNET_NETWORK, type ServiceRouter, type SettlementErrorContext, type SettlementLifecycleContext, type SettlementSettledContext, TEMPO_USDC_ADDRESS, TEMPO_USDC_DECIMALS, type X402FacilitatorsConfig, ZERO_EVM_ADDRESS, createRouter, createRouterFromEnv, routerConfigFromEnv };
|
|
950
|
+
export { BASE_MAINNET_NETWORK, BASE_USDC_ADDRESS, BASE_USDC_DECIMALS, type CheckoutSessionContext, type CheckoutSessionFn, type CheckoutSessionResponse, type CreateRouterFromEnvOptions, DEFAULT_SOLANA_FACILITATOR_URL, DEFAULT_TEMPO_RPC_URL, type DiscoveryConfig, type HandlerContext, HttpError, type KvStore, type PaidOptions, type ProtocolType, type RouterConfig, RouterConfigError, type RouterConfigIssue, type RouterConfigIssueCode, type RouterConfigIssueSeverity, type RouterPlugin, SOLANA_MAINNET_NETWORK, type ServiceRouter, type SettlementErrorContext, type SettlementLifecycleContext, type SettlementSettledContext, TEMPO_USDC_ADDRESS, TEMPO_USDC_DECIMALS, type X402FacilitatorsConfig, ZERO_EVM_ADDRESS, createRouter, createRouterFromEnv, routerConfigFromEnv };
|
package/dist/index.js
CHANGED
|
@@ -1165,7 +1165,7 @@ async function trySiwxFastPath(ctx, account) {
|
|
|
1165
1165
|
function shouldParseBodyEarly(incomingStrategy, routeEntry, pricing) {
|
|
1166
1166
|
if (incomingStrategy) return false;
|
|
1167
1167
|
if (!routeEntry.bodySchema) return false;
|
|
1168
|
-
return (pricing?.needsBody ?? false) || !!routeEntry.validateFn;
|
|
1168
|
+
return (pricing?.needsBody ?? false) || !!routeEntry.validateFn || !!routeEntry.checkoutSession;
|
|
1169
1169
|
}
|
|
1170
1170
|
|
|
1171
1171
|
// src/pipeline/steps/resolve-early-body.ts
|
|
@@ -2064,6 +2064,7 @@ function invalidPaymentVerification(failure) {
|
|
|
2064
2064
|
}
|
|
2065
2065
|
|
|
2066
2066
|
// src/protocols/x402/strategy.ts
|
|
2067
|
+
var SETTLE_RETRY_DELAYS_MS = [500, 1e3];
|
|
2067
2068
|
function formatVerifyFailureMessage(failure) {
|
|
2068
2069
|
if (failure.reason === "permit2_allowance_required") {
|
|
2069
2070
|
const wallet = failure.payer ?? "<the payer wallet>";
|
|
@@ -2154,7 +2155,17 @@ async function settleX402(args) {
|
|
|
2154
2155
|
const { payload, requirements } = token;
|
|
2155
2156
|
const override = routeEntry.billing === "exact" ? void 0 : { amount: billedAmount };
|
|
2156
2157
|
try {
|
|
2157
|
-
|
|
2158
|
+
let settle = await settleX402Payment(deps.x402Server, payload, requirements, override);
|
|
2159
|
+
for (let attempt = 0; !settle.result?.success && attempt < SETTLE_RETRY_DELAYS_MS.length; attempt++) {
|
|
2160
|
+
report("warn", "Retrying x402 settlement", {
|
|
2161
|
+
attempt: attempt + 1,
|
|
2162
|
+
errorReason: settle.result?.errorReason
|
|
2163
|
+
});
|
|
2164
|
+
await new Promise((resolve) => {
|
|
2165
|
+
setTimeout(resolve, SETTLE_RETRY_DELAYS_MS[attempt]);
|
|
2166
|
+
});
|
|
2167
|
+
settle = await settleX402Payment(deps.x402Server, payload, requirements, override);
|
|
2168
|
+
}
|
|
2158
2169
|
if (!settle.result?.success) {
|
|
2159
2170
|
throw Object.assign(
|
|
2160
2171
|
new Error(settle.result?.errorReason ?? "x402 settlement returned success=false"),
|
|
@@ -2496,30 +2507,37 @@ async function buildChallengeResponse(ctx, pricing, body, failure) {
|
|
|
2496
2507
|
return errorResponse;
|
|
2497
2508
|
}
|
|
2498
2509
|
const extensions = await buildChallengeExtensions(ctx);
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
status: 402,
|
|
2502
|
-
headers: {
|
|
2503
|
-
"Content-Type": "application/json",
|
|
2504
|
-
"Cache-Control": "no-store"
|
|
2505
|
-
}
|
|
2506
|
-
});
|
|
2507
|
-
for (const strategy of getAllowedStrategies(ctx.routeEntry.protocols)) {
|
|
2510
|
+
let checkoutSession;
|
|
2511
|
+
if (ctx.routeEntry.checkoutSession) {
|
|
2508
2512
|
try {
|
|
2509
|
-
|
|
2513
|
+
checkoutSession = await ctx.routeEntry.checkoutSession({
|
|
2510
2514
|
request: ctx.request,
|
|
2511
|
-
|
|
2515
|
+
route: ctx.routeEntry.key,
|
|
2512
2516
|
body,
|
|
2513
|
-
price: challengePrice
|
|
2514
|
-
extensions,
|
|
2515
|
-
deps: ctx.deps,
|
|
2516
|
-
report: ctx.report
|
|
2517
|
+
price: challengePrice
|
|
2517
2518
|
});
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
}
|
|
2519
|
+
} catch (err) {
|
|
2520
|
+
const message = errorMessage(err, "Checkout session build failed");
|
|
2521
|
+
const responseBody2 = { success: false, error: message };
|
|
2522
|
+
const errorResponse = NextResponse5.json(responseBody2, { status: errorStatus(err, 500) });
|
|
2523
|
+
firePluginResponse(ctx, errorResponse, body, responseBody2, { message, cause: err });
|
|
2524
|
+
return errorResponse;
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
const contributions = [];
|
|
2528
|
+
for (const strategy of getAllowedStrategies(ctx.routeEntry.protocols)) {
|
|
2529
|
+
try {
|
|
2530
|
+
contributions.push(
|
|
2531
|
+
await strategy.buildChallenge({
|
|
2532
|
+
request: ctx.request,
|
|
2533
|
+
routeEntry: ctx.routeEntry,
|
|
2534
|
+
body,
|
|
2535
|
+
price: challengePrice,
|
|
2536
|
+
extensions,
|
|
2537
|
+
deps: ctx.deps,
|
|
2538
|
+
report: ctx.report
|
|
2539
|
+
})
|
|
2540
|
+
);
|
|
2523
2541
|
} catch (err) {
|
|
2524
2542
|
const message = `${strategy.protocol} challenge build failed: ${errorMessage(err, String(err))}`;
|
|
2525
2543
|
ctx.report("critical", message);
|
|
@@ -2531,6 +2549,26 @@ async function buildChallengeResponse(ctx, pricing, body, failure) {
|
|
|
2531
2549
|
}
|
|
2532
2550
|
}
|
|
2533
2551
|
}
|
|
2552
|
+
const responsePayload = {
|
|
2553
|
+
...Object.assign({}, ...contributions.map((contribution) => contribution.body ?? {})),
|
|
2554
|
+
...failure && { error: failure.message ?? null, reason: failure.reason },
|
|
2555
|
+
...checkoutSession && { checkout_session: checkoutSession }
|
|
2556
|
+
};
|
|
2557
|
+
const responseBody = Object.keys(responsePayload).length ? JSON.stringify(responsePayload) : null;
|
|
2558
|
+
const response = new NextResponse5(responseBody, {
|
|
2559
|
+
status: 402,
|
|
2560
|
+
headers: {
|
|
2561
|
+
"Content-Type": "application/json",
|
|
2562
|
+
"Cache-Control": "no-store"
|
|
2563
|
+
}
|
|
2564
|
+
});
|
|
2565
|
+
for (const contribution of contributions) {
|
|
2566
|
+
if (contribution.headers) {
|
|
2567
|
+
for (const [name, value] of Object.entries(contribution.headers)) {
|
|
2568
|
+
response.headers.set(name, value);
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2534
2572
|
firePluginResponse(ctx, response);
|
|
2535
2573
|
return response;
|
|
2536
2574
|
}
|
|
@@ -3548,7 +3586,9 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3548
3586
|
providerConfig: void 0,
|
|
3549
3587
|
validateFn: void 0,
|
|
3550
3588
|
settlement: void 0,
|
|
3551
|
-
mppInfo: void 0
|
|
3589
|
+
mppInfo: void 0,
|
|
3590
|
+
hasCheckout: false,
|
|
3591
|
+
checkoutSession: void 0
|
|
3552
3592
|
};
|
|
3553
3593
|
}
|
|
3554
3594
|
fork() {
|
|
@@ -3639,6 +3679,8 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3639
3679
|
if (resolvedOptions.minPrice) next.#s.minPrice = resolvedOptions.minPrice;
|
|
3640
3680
|
if (resolvedOptions.payTo) next.#s.payTo = resolvedOptions.payTo;
|
|
3641
3681
|
if (resolvedOptions.mpp) next.#s.mppInfo = resolvedOptions.mpp;
|
|
3682
|
+
if (resolvedOptions.checkout || resolvedOptions.checkoutSession) next.#s.hasCheckout = true;
|
|
3683
|
+
if (resolvedOptions.checkoutSession) next.#s.checkoutSession = resolvedOptions.checkoutSession;
|
|
3642
3684
|
next.#s.billing = billing;
|
|
3643
3685
|
if (tickCost) next.#s.tickCost = tickCost;
|
|
3644
3686
|
if (unitType) next.#s.unitType = unitType;
|
|
@@ -4072,6 +4114,8 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
4072
4114
|
validateFn: this.#s.validateFn,
|
|
4073
4115
|
settlement: this.#s.settlement,
|
|
4074
4116
|
mppInfo: this.#s.mppInfo,
|
|
4117
|
+
hasCheckout: this.#s.hasCheckout ? true : void 0,
|
|
4118
|
+
checkoutSession: this.#s.checkoutSession,
|
|
4075
4119
|
tickCost: this.#s.tickCost,
|
|
4076
4120
|
unitType: this.#s.unitType
|
|
4077
4121
|
};
|
|
@@ -4278,6 +4322,7 @@ function buildOperation(routeKey, entry, tag) {
|
|
|
4278
4322
|
const requiresSiwxScheme = entry.authMode === "siwx" || Boolean(entry.siwxEnabled);
|
|
4279
4323
|
const requiresApiKeyScheme = Boolean(entry.apiKeyResolver) && entry.authMode !== "siwx";
|
|
4280
4324
|
const pricingInfo = buildPricingInfo(entry);
|
|
4325
|
+
const hasCheckout = entry.hasCheckout === true;
|
|
4281
4326
|
const operation = {
|
|
4282
4327
|
operationId: routeKey.replace(/\//g, "_"),
|
|
4283
4328
|
summary: entry.description ?? routeKey,
|
|
@@ -4303,10 +4348,11 @@ function buildOperation(routeKey, entry, tag) {
|
|
|
4303
4348
|
}
|
|
4304
4349
|
}
|
|
4305
4350
|
};
|
|
4306
|
-
if (paymentRequired && (pricingInfo || protocols)) {
|
|
4351
|
+
if (paymentRequired && (pricingInfo || protocols || hasCheckout)) {
|
|
4307
4352
|
operation["x-payment-info"] = {
|
|
4308
4353
|
...pricingInfo ?? {},
|
|
4309
|
-
...protocols && { protocols }
|
|
4354
|
+
...protocols && { protocols },
|
|
4355
|
+
...hasCheckout && { has_checkout: true }
|
|
4310
4356
|
};
|
|
4311
4357
|
}
|
|
4312
4358
|
if (requiresSiwxScheme) {
|
|
@@ -4417,6 +4463,33 @@ function createLlmsTxtHandler(discovery) {
|
|
|
4417
4463
|
};
|
|
4418
4464
|
}
|
|
4419
4465
|
|
|
4466
|
+
// src/discovery/not-found.ts
|
|
4467
|
+
import { NextResponse as NextResponse12 } from "next/server";
|
|
4468
|
+
function createNotFoundHandler(baseUrl) {
|
|
4469
|
+
const normalizedBase = baseUrl.replace(/\/+$/, "");
|
|
4470
|
+
return async (request) => NextResponse12.json(
|
|
4471
|
+
{
|
|
4472
|
+
success: false,
|
|
4473
|
+
code: "route_not_found",
|
|
4474
|
+
error: "Route not found. Rediscover this origin and retry with the current discovery document.",
|
|
4475
|
+
requestedUrl: request.url,
|
|
4476
|
+
discovery: {
|
|
4477
|
+
openapi: `${normalizedBase}/openapi.json`,
|
|
4478
|
+
wellKnown: `${normalizedBase}/.well-known/x402`,
|
|
4479
|
+
llmsTxt: `${normalizedBase}/llms.txt`
|
|
4480
|
+
}
|
|
4481
|
+
},
|
|
4482
|
+
{
|
|
4483
|
+
status: 404,
|
|
4484
|
+
headers: {
|
|
4485
|
+
"Access-Control-Allow-Origin": "*",
|
|
4486
|
+
"Access-Control-Allow-Methods": "GET, POST, DELETE, PUT, PATCH, OPTIONS",
|
|
4487
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-API-Key, SIGN-IN-WITH-X"
|
|
4488
|
+
}
|
|
4489
|
+
}
|
|
4490
|
+
);
|
|
4491
|
+
}
|
|
4492
|
+
|
|
4420
4493
|
// src/index.ts
|
|
4421
4494
|
init_accepts();
|
|
4422
4495
|
init_constants();
|
|
@@ -5120,6 +5193,9 @@ function createRouter(config) {
|
|
|
5120
5193
|
llmsTxt() {
|
|
5121
5194
|
return createLlmsTxtHandler(config.discovery);
|
|
5122
5195
|
},
|
|
5196
|
+
notFound() {
|
|
5197
|
+
return createNotFoundHandler(resolvedBaseUrl);
|
|
5198
|
+
},
|
|
5123
5199
|
monitors() {
|
|
5124
5200
|
const result = [];
|
|
5125
5201
|
for (const [, entry] of registry.entries()) {
|