@agentcash/router 1.12.0 → 1.14.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/AGENTS.md +15 -4
- package/README.md +40 -6
- package/dist/index.cjs +328 -289
- package/dist/index.d.cts +92 -37
- package/dist/index.d.ts +92 -37
- package/dist/index.js +327 -289
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -42,7 +42,7 @@ var init_constants = __esm({
|
|
|
42
42
|
BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
43
43
|
BASE_USDC_DECIMALS = 6;
|
|
44
44
|
ZERO_EVM_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
45
|
-
DEFAULT_SOLANA_FACILITATOR_URL = "https://facilitator.
|
|
45
|
+
DEFAULT_SOLANA_FACILITATOR_URL = "https://facilitator.payai.network";
|
|
46
46
|
DEFAULT_TEMPO_RPC_URL = "https://rpc.tempo.xyz";
|
|
47
47
|
}
|
|
48
48
|
});
|
|
@@ -151,31 +151,64 @@ function buildSolanaExactOptions(accepts, price) {
|
|
|
151
151
|
function hasSolanaAccepts(accepts) {
|
|
152
152
|
return accepts.some((accept) => isSolanaNetwork(accept.network));
|
|
153
153
|
}
|
|
154
|
-
|
|
154
|
+
function facilitatorBaseUrl(facilitator) {
|
|
155
155
|
if (!facilitator.url) {
|
|
156
|
-
throw new Error(`Facilitator for ${facilitator.network} is missing a URL
|
|
156
|
+
throw new Error(`Facilitator for ${facilitator.network} is missing a URL`);
|
|
157
157
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
accepts: requirements
|
|
169
|
-
})
|
|
158
|
+
return facilitator.url.replace(/\/+$/, "");
|
|
159
|
+
}
|
|
160
|
+
function matchesSupportedKind(requirement, kind) {
|
|
161
|
+
const scheme = requirement.scheme ?? "exact";
|
|
162
|
+
return kind.network === requirement.network && (kind.scheme ?? "exact") === scheme;
|
|
163
|
+
}
|
|
164
|
+
async function fetchFacilitatorSupported(facilitator) {
|
|
165
|
+
const authHeaders = await getSupportedHeadersForFacilitator(facilitator);
|
|
166
|
+
const response = await fetch(`${facilitatorBaseUrl(facilitator)}/supported`, {
|
|
167
|
+
headers: authHeaders
|
|
170
168
|
});
|
|
171
169
|
if (!response.ok) {
|
|
172
|
-
throw new Error(`Facilitator /
|
|
170
|
+
throw new Error(`Facilitator /supported failed with status ${response.status}`);
|
|
173
171
|
}
|
|
174
172
|
const body = await response.json();
|
|
175
|
-
if (!Array.isArray(body.
|
|
176
|
-
throw new Error("Facilitator /
|
|
173
|
+
if (!Array.isArray(body.kinds)) {
|
|
174
|
+
throw new Error("Facilitator /supported response did not include kinds");
|
|
175
|
+
}
|
|
176
|
+
return body.kinds;
|
|
177
|
+
}
|
|
178
|
+
function enrichRequirementFromKind(requirement, kinds) {
|
|
179
|
+
const kind = kinds.find((candidate) => matchesSupportedKind(requirement, candidate));
|
|
180
|
+
if (!kind) {
|
|
181
|
+
throw new Error(
|
|
182
|
+
`Facilitator /supported has no kind for ${requirement.scheme} on ${requirement.network}`
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
const feePayer = kind.extra?.feePayer;
|
|
186
|
+
if (!feePayer) {
|
|
187
|
+
throw new Error(
|
|
188
|
+
`Facilitator /supported kind for ${requirement.network} is missing extra.feePayer`
|
|
189
|
+
);
|
|
177
190
|
}
|
|
178
|
-
|
|
191
|
+
const requirementExtra = requirement.extra ?? {};
|
|
192
|
+
const kindExtra = kind.extra ?? {};
|
|
193
|
+
const requirementFeatures = requirementExtra.features ?? {};
|
|
194
|
+
const kindFeatures = kindExtra.features ?? {};
|
|
195
|
+
return {
|
|
196
|
+
...requirement,
|
|
197
|
+
...kind.asset && !requirement.asset ? { asset: kind.asset } : {},
|
|
198
|
+
extra: {
|
|
199
|
+
...requirementExtra,
|
|
200
|
+
...kindExtra,
|
|
201
|
+
features: {
|
|
202
|
+
...requirementFeatures,
|
|
203
|
+
...kindFeatures,
|
|
204
|
+
xSettlementAccountSupported: true
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
async function enrichRequirementsFromFacilitatorSupported(facilitator, requirements) {
|
|
210
|
+
const kinds = await fetchFacilitatorSupported(facilitator);
|
|
211
|
+
return requirements.map((requirement) => enrichRequirementFromKind(requirement, kinds));
|
|
179
212
|
}
|
|
180
213
|
function isSolanaRequirement(requirement) {
|
|
181
214
|
return isSolanaNetwork(requirement.network);
|
|
@@ -232,10 +265,7 @@ function getFacilitatorForRequirement(facilitatorsByNetwork, requirement) {
|
|
|
232
265
|
function sameResolvedX402Facilitator(a, b) {
|
|
233
266
|
return sameFacilitatorConfig(a.config, b.config);
|
|
234
267
|
}
|
|
235
|
-
async function
|
|
236
|
-
if (facilitator.config.createAcceptsHeaders) {
|
|
237
|
-
return facilitator.config.createAcceptsHeaders();
|
|
238
|
-
}
|
|
268
|
+
async function getSupportedHeadersForFacilitator(facilitator) {
|
|
239
269
|
if (facilitator.config.createAuthHeaders) {
|
|
240
270
|
const headers = await facilitator.config.createAuthHeaders();
|
|
241
271
|
return headers.supported;
|
|
@@ -257,7 +287,7 @@ function getNetworkFamily(network) {
|
|
|
257
287
|
return null;
|
|
258
288
|
}
|
|
259
289
|
function sameFacilitatorConfig(a, b) {
|
|
260
|
-
return a.url === b.url && a.createAuthHeaders === b.createAuthHeaders
|
|
290
|
+
return a.url === b.url && a.createAuthHeaders === b.createAuthHeaders;
|
|
261
291
|
}
|
|
262
292
|
var init_facilitators = __esm({
|
|
263
293
|
"src/protocols/x402/facilitators.ts"() {
|
|
@@ -473,6 +503,7 @@ __export(index_exports, {
|
|
|
473
503
|
DEFAULT_SOLANA_FACILITATOR_URL: () => DEFAULT_SOLANA_FACILITATOR_URL,
|
|
474
504
|
DEFAULT_TEMPO_RPC_URL: () => DEFAULT_TEMPO_RPC_URL,
|
|
475
505
|
HttpError: () => HttpError,
|
|
506
|
+
RouteDefinitionError: () => RouteDefinitionError,
|
|
476
507
|
RouterConfigError: () => RouterConfigError,
|
|
477
508
|
SOLANA_MAINNET_NETWORK: () => SOLANA_MAINNET_NETWORK,
|
|
478
509
|
TEMPO_USDC_ADDRESS: () => TEMPO_USDC_ADDRESS,
|
|
@@ -873,6 +904,13 @@ var HttpError = class extends Error {
|
|
|
873
904
|
this.name = "HttpError";
|
|
874
905
|
}
|
|
875
906
|
};
|
|
907
|
+
var RouteDefinitionError = class extends Error {
|
|
908
|
+
constructor(route, detail) {
|
|
909
|
+
super(`route '${route}': ${detail}`);
|
|
910
|
+
this.route = route;
|
|
911
|
+
this.name = "RouteDefinitionError";
|
|
912
|
+
}
|
|
913
|
+
};
|
|
876
914
|
|
|
877
915
|
// src/auth/agent-identity.ts
|
|
878
916
|
var PROTOCOL_VERSION = 1;
|
|
@@ -1400,6 +1438,23 @@ function multiplyDecimal(decimal, factor) {
|
|
|
1400
1438
|
return fracPart ? `${intPart}.${fracPart}` : intPart;
|
|
1401
1439
|
}
|
|
1402
1440
|
|
|
1441
|
+
// src/protocols/detect.ts
|
|
1442
|
+
function hasX402Payment(request) {
|
|
1443
|
+
return Boolean(
|
|
1444
|
+
request.headers.get(HEADERS.X402_PAYMENT_SIGNATURE) ?? request.headers.get(HEADERS.X402_PAYMENT_LEGACY)
|
|
1445
|
+
);
|
|
1446
|
+
}
|
|
1447
|
+
function hasMppPayment(request) {
|
|
1448
|
+
const auth = request.headers.get(HEADERS.AUTHORIZATION);
|
|
1449
|
+
return Boolean(auth && auth.startsWith(AUTH_SCHEME.MPP_PAYMENT));
|
|
1450
|
+
}
|
|
1451
|
+
function detectProtocol(request) {
|
|
1452
|
+
if (hasX402Payment(request)) return "x402";
|
|
1453
|
+
if (hasMppPayment(request)) return "mpp";
|
|
1454
|
+
if (request.headers.get(HEADERS.SIWX)) return "siwx";
|
|
1455
|
+
return null;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1403
1458
|
// src/protocols/mpp/credential.ts
|
|
1404
1459
|
var import_mppx = require("mppx");
|
|
1405
1460
|
var import_viem = require("viem");
|
|
@@ -1766,10 +1821,7 @@ function settleHashMode(args) {
|
|
|
1766
1821
|
// src/protocols/mpp/strategy.ts
|
|
1767
1822
|
var mppStrategy = {
|
|
1768
1823
|
protocol: "mpp",
|
|
1769
|
-
detects
|
|
1770
|
-
const auth = request.headers.get(HEADERS.AUTHORIZATION);
|
|
1771
|
-
return Boolean(auth && auth.startsWith(AUTH_SCHEME.MPP_PAYMENT));
|
|
1772
|
-
},
|
|
1824
|
+
detects: hasMppPayment,
|
|
1773
1825
|
preflight(request, _routeEntry) {
|
|
1774
1826
|
const info = readMppCredential(request);
|
|
1775
1827
|
if (!info?.sessionAction) return null;
|
|
@@ -1974,15 +2026,14 @@ async function buildChallengeRequirements(server, request, price, accepts, resou
|
|
|
1974
2026
|
function needsFacilitatorEnrichment(accepts) {
|
|
1975
2027
|
return hasSolanaAccepts(accepts);
|
|
1976
2028
|
}
|
|
1977
|
-
async function enrichGroup(group
|
|
1978
|
-
const accepted = await
|
|
2029
|
+
async function enrichGroup(group) {
|
|
2030
|
+
const accepted = await enrichRequirementsFromFacilitatorSupported(
|
|
1979
2031
|
group.facilitator,
|
|
1980
|
-
resource,
|
|
1981
2032
|
group.items.map(({ requirement }) => requirement)
|
|
1982
2033
|
);
|
|
1983
2034
|
if (accepted.length !== group.items.length) {
|
|
1984
2035
|
throw new Error(
|
|
1985
|
-
`Facilitator /
|
|
2036
|
+
`Facilitator /supported enrichment returned ${accepted.length} requirements for ${group.items.length} inputs on ${group.facilitator.url ?? group.facilitator.network}`
|
|
1986
2037
|
);
|
|
1987
2038
|
}
|
|
1988
2039
|
return accepted;
|
|
@@ -1993,13 +2044,13 @@ async function enrichChallengeRequirements(requirements, resource, facilitatorsB
|
|
|
1993
2044
|
const results = await Promise.all(
|
|
1994
2045
|
groups.map(async (group) => {
|
|
1995
2046
|
try {
|
|
1996
|
-
return { success: true, group, accepted: await enrichGroup(group
|
|
2047
|
+
return { success: true, group, accepted: await enrichGroup(group) };
|
|
1997
2048
|
} catch (err) {
|
|
1998
2049
|
const label = group.facilitator.url ?? group.facilitator.network;
|
|
1999
2050
|
const reason = err instanceof Error ? err.message : String(err);
|
|
2000
2051
|
report?.(
|
|
2001
2052
|
"warn",
|
|
2002
|
-
`${label} /
|
|
2053
|
+
`${label} /supported enrichment failed, dropping ${group.items.length} requirement(s): ${reason}`
|
|
2003
2054
|
);
|
|
2004
2055
|
return { success: false, group };
|
|
2005
2056
|
}
|
|
@@ -2204,11 +2255,7 @@ function formatVerifyFailureMessage(failure) {
|
|
|
2204
2255
|
}
|
|
2205
2256
|
var x402Strategy = {
|
|
2206
2257
|
protocol: "x402",
|
|
2207
|
-
detects
|
|
2208
|
-
return Boolean(
|
|
2209
|
-
request.headers.get(HEADERS.X402_PAYMENT_SIGNATURE) ?? request.headers.get(HEADERS.X402_PAYMENT_LEGACY)
|
|
2210
|
-
);
|
|
2211
|
-
},
|
|
2258
|
+
detects: hasX402Payment,
|
|
2212
2259
|
verify: (args) => verifyX402(args),
|
|
2213
2260
|
settle: (args) => settleX402(args),
|
|
2214
2261
|
buildChallenge: (args) => buildX402ChallengeContribution(args)
|
|
@@ -2355,21 +2402,6 @@ function getAllowedStrategies(allowed) {
|
|
|
2355
2402
|
return allowed.map((name) => STRATEGIES[name]);
|
|
2356
2403
|
}
|
|
2357
2404
|
|
|
2358
|
-
// src/protocols/detect.ts
|
|
2359
|
-
function detectProtocol(request) {
|
|
2360
|
-
if (request.headers.get(HEADERS.X402_PAYMENT_SIGNATURE) ?? request.headers.get(HEADERS.X402_PAYMENT_LEGACY)) {
|
|
2361
|
-
return "x402";
|
|
2362
|
-
}
|
|
2363
|
-
const auth = request.headers.get(HEADERS.AUTHORIZATION);
|
|
2364
|
-
if (auth && auth.startsWith(AUTH_SCHEME.MPP_PAYMENT)) {
|
|
2365
|
-
return "mpp";
|
|
2366
|
-
}
|
|
2367
|
-
if (request.headers.get(HEADERS.SIWX)) {
|
|
2368
|
-
return "siwx";
|
|
2369
|
-
}
|
|
2370
|
-
return null;
|
|
2371
|
-
}
|
|
2372
|
-
|
|
2373
2405
|
// src/pipeline/flows/api-key-only.ts
|
|
2374
2406
|
async function runApiKeyOnlyFlow(ctx) {
|
|
2375
2407
|
if (!ctx.routeEntry.apiKeyResolver) {
|
|
@@ -2692,6 +2724,61 @@ async function buildChallengeResponse(ctx, pricing, body, failure) {
|
|
|
2692
2724
|
return response;
|
|
2693
2725
|
}
|
|
2694
2726
|
|
|
2727
|
+
// src/pipeline/flows/paid-preamble.ts
|
|
2728
|
+
async function runPaidPreamble(ctx) {
|
|
2729
|
+
const { request, routeEntry, deps, report } = ctx;
|
|
2730
|
+
const apiKeyGate = await runApiKeyGate(ctx);
|
|
2731
|
+
if (!apiKeyGate.ok) return { done: true, response: apiKeyGate.response };
|
|
2732
|
+
const { account } = apiKeyGate;
|
|
2733
|
+
const pricing = selectPricing(routeEntry.pricing, {
|
|
2734
|
+
alert: report,
|
|
2735
|
+
maxPrice: routeEntry.maxPrice,
|
|
2736
|
+
minPrice: routeEntry.minPrice,
|
|
2737
|
+
route: routeEntry.key
|
|
2738
|
+
});
|
|
2739
|
+
const incomingStrategy = selectIncomingStrategy(request, routeEntry.protocols);
|
|
2740
|
+
const earlyResolution = await resolveEarlyBody({ ctx, pricing, incomingStrategy });
|
|
2741
|
+
if (!earlyResolution.ok) return { done: true, response: earlyResolution.response };
|
|
2742
|
+
const { earlyBody } = earlyResolution;
|
|
2743
|
+
const siwxFastPath = await trySiwxFastPath(ctx, account);
|
|
2744
|
+
if (siwxFastPath) return { done: true, response: siwxFastPath };
|
|
2745
|
+
if (!incomingStrategy) {
|
|
2746
|
+
const initError = protocolInitError(routeEntry, deps);
|
|
2747
|
+
if (initError) return { done: true, response: fail(ctx, 500, initError) };
|
|
2748
|
+
return { done: true, response: await buildChallengeResponse(ctx, pricing, earlyBody) };
|
|
2749
|
+
}
|
|
2750
|
+
return { done: false, account, pricing, incomingStrategy, earlyBody };
|
|
2751
|
+
}
|
|
2752
|
+
async function runPaidVerify(args) {
|
|
2753
|
+
const { ctx, strategy, pricing, parsedBody, price } = args;
|
|
2754
|
+
const { request, routeEntry, deps, report } = ctx;
|
|
2755
|
+
const verifyOutcome = await strategy.verify({
|
|
2756
|
+
request,
|
|
2757
|
+
body: parsedBody,
|
|
2758
|
+
price,
|
|
2759
|
+
routeEntry,
|
|
2760
|
+
deps,
|
|
2761
|
+
report
|
|
2762
|
+
});
|
|
2763
|
+
if (verifyOutcome.ok === false) {
|
|
2764
|
+
if (verifyOutcome.kind === "config") {
|
|
2765
|
+
return { ok: false, response: fail(ctx, 500, verifyOutcome.message, parsedBody) };
|
|
2766
|
+
}
|
|
2767
|
+
return {
|
|
2768
|
+
ok: false,
|
|
2769
|
+
response: await buildChallengeResponse(ctx, pricing, parsedBody, verifyOutcome.failure)
|
|
2770
|
+
};
|
|
2771
|
+
}
|
|
2772
|
+
ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
|
|
2773
|
+
firePaymentVerified(ctx, {
|
|
2774
|
+
protocol: strategy.protocol,
|
|
2775
|
+
payer: verifyOutcome.wallet,
|
|
2776
|
+
amount: price,
|
|
2777
|
+
network: verifyOutcome.payment.network
|
|
2778
|
+
});
|
|
2779
|
+
return { ok: true, verifyOutcome };
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2695
2782
|
// src/pipeline/flows/dynamic/dynamic-body-and-price.ts
|
|
2696
2783
|
async function resolveDynamicBodyAndPrice(args) {
|
|
2697
2784
|
const { ctx, pricing, skipBody } = args;
|
|
@@ -3058,27 +3145,10 @@ async function runDynamicStreamFlow(args) {
|
|
|
3058
3145
|
|
|
3059
3146
|
// src/pipeline/flows/dynamic/dynamic-paid.ts
|
|
3060
3147
|
async function runDynamicPaidFlow(ctx) {
|
|
3061
|
-
const { request, routeEntry
|
|
3062
|
-
const
|
|
3063
|
-
if (
|
|
3064
|
-
const { account } =
|
|
3065
|
-
const pricing = selectPricing(routeEntry.pricing, {
|
|
3066
|
-
alert: report,
|
|
3067
|
-
maxPrice: routeEntry.maxPrice,
|
|
3068
|
-
minPrice: routeEntry.minPrice,
|
|
3069
|
-
route: routeEntry.key
|
|
3070
|
-
});
|
|
3071
|
-
const incomingStrategy = selectIncomingStrategy(request, routeEntry.protocols);
|
|
3072
|
-
const earlyResolution = await resolveEarlyBody({ ctx, pricing, incomingStrategy });
|
|
3073
|
-
if (!earlyResolution.ok) return earlyResolution.response;
|
|
3074
|
-
const { earlyBody } = earlyResolution;
|
|
3075
|
-
const siwxFastPath = await trySiwxFastPath(ctx, account);
|
|
3076
|
-
if (siwxFastPath) return siwxFastPath;
|
|
3077
|
-
if (!incomingStrategy) {
|
|
3078
|
-
const initError = protocolInitError(routeEntry, deps);
|
|
3079
|
-
if (initError) return fail(ctx, 500, initError);
|
|
3080
|
-
return buildChallengeResponse(ctx, pricing, earlyBody);
|
|
3081
|
-
}
|
|
3148
|
+
const { request, routeEntry } = ctx;
|
|
3149
|
+
const preamble = await runPaidPreamble(ctx);
|
|
3150
|
+
if (preamble.done) return preamble.response;
|
|
3151
|
+
const { account, pricing, incomingStrategy } = preamble;
|
|
3082
3152
|
const { skipBody, skipHandler } = resolveDynamicPreflight(incomingStrategy, request, routeEntry);
|
|
3083
3153
|
if (skipHandler) {
|
|
3084
3154
|
return runDynamicChannelMgmtFlow({
|
|
@@ -3092,27 +3162,15 @@ async function runDynamicPaidFlow(ctx) {
|
|
|
3092
3162
|
const bodyAndPrice = await resolveDynamicBodyAndPrice({ ctx, pricing, skipBody });
|
|
3093
3163
|
if (!bodyAndPrice.ok) return bodyAndPrice.response;
|
|
3094
3164
|
const { parsedBody, price } = bodyAndPrice;
|
|
3095
|
-
const
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
report
|
|
3102
|
-
});
|
|
3103
|
-
if (verifyOutcome.ok === false) {
|
|
3104
|
-
if (verifyOutcome.kind === "config") {
|
|
3105
|
-
return fail(ctx, 500, verifyOutcome.message, parsedBody);
|
|
3106
|
-
}
|
|
3107
|
-
return buildChallengeResponse(ctx, pricing, parsedBody, verifyOutcome.failure);
|
|
3108
|
-
}
|
|
3109
|
-
ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
|
|
3110
|
-
firePaymentVerified(ctx, {
|
|
3111
|
-
protocol: incomingStrategy.protocol,
|
|
3112
|
-
payer: verifyOutcome.wallet,
|
|
3113
|
-
amount: price,
|
|
3114
|
-
network: verifyOutcome.payment.network
|
|
3165
|
+
const verify = await runPaidVerify({
|
|
3166
|
+
ctx,
|
|
3167
|
+
strategy: incomingStrategy,
|
|
3168
|
+
pricing,
|
|
3169
|
+
parsedBody,
|
|
3170
|
+
price
|
|
3115
3171
|
});
|
|
3172
|
+
if (!verify.ok) return verify.response;
|
|
3173
|
+
const { verifyOutcome } = verify;
|
|
3116
3174
|
const result = await invokeDynamic(ctx, verifyOutcome, account, parsedBody);
|
|
3117
3175
|
switch (result.kind) {
|
|
3118
3176
|
case "stream":
|
|
@@ -3244,51 +3302,21 @@ function failureFromCause2(cause) {
|
|
|
3244
3302
|
|
|
3245
3303
|
// src/pipeline/flows/static/static-paid.ts
|
|
3246
3304
|
async function runStaticPaidFlow(ctx) {
|
|
3247
|
-
const
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
const { account } = apiKeyGate;
|
|
3251
|
-
const pricing = selectPricing(routeEntry.pricing, {
|
|
3252
|
-
alert: report,
|
|
3253
|
-
maxPrice: routeEntry.maxPrice,
|
|
3254
|
-
minPrice: routeEntry.minPrice,
|
|
3255
|
-
route: routeEntry.key
|
|
3256
|
-
});
|
|
3257
|
-
const incomingStrategy = selectIncomingStrategy(request, routeEntry.protocols);
|
|
3258
|
-
const earlyResolution = await resolveEarlyBody({ ctx, pricing, incomingStrategy });
|
|
3259
|
-
if (!earlyResolution.ok) return earlyResolution.response;
|
|
3260
|
-
const { earlyBody } = earlyResolution;
|
|
3261
|
-
const siwxFastPath = await trySiwxFastPath(ctx, account);
|
|
3262
|
-
if (siwxFastPath) return siwxFastPath;
|
|
3263
|
-
if (!incomingStrategy) {
|
|
3264
|
-
const initError = protocolInitError(routeEntry, deps);
|
|
3265
|
-
if (initError) return fail(ctx, 500, initError);
|
|
3266
|
-
return buildChallengeResponse(ctx, pricing, earlyBody);
|
|
3267
|
-
}
|
|
3305
|
+
const preamble = await runPaidPreamble(ctx);
|
|
3306
|
+
if (preamble.done) return preamble.response;
|
|
3307
|
+
const { account, pricing, incomingStrategy } = preamble;
|
|
3268
3308
|
const bodyAndPrice = await resolveStaticBodyAndPrice({ ctx, pricing });
|
|
3269
3309
|
if (!bodyAndPrice.ok) return bodyAndPrice.response;
|
|
3270
3310
|
const { parsedBody, price } = bodyAndPrice;
|
|
3271
|
-
const
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
report
|
|
3278
|
-
});
|
|
3279
|
-
if (verifyOutcome.ok === false) {
|
|
3280
|
-
if (verifyOutcome.kind === "config") {
|
|
3281
|
-
return fail(ctx, 500, verifyOutcome.message, parsedBody);
|
|
3282
|
-
}
|
|
3283
|
-
return buildChallengeResponse(ctx, pricing, parsedBody, verifyOutcome.failure);
|
|
3284
|
-
}
|
|
3285
|
-
ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
|
|
3286
|
-
firePaymentVerified(ctx, {
|
|
3287
|
-
protocol: incomingStrategy.protocol,
|
|
3288
|
-
payer: verifyOutcome.wallet,
|
|
3289
|
-
amount: price,
|
|
3290
|
-
network: verifyOutcome.payment.network
|
|
3311
|
+
const verify = await runPaidVerify({
|
|
3312
|
+
ctx,
|
|
3313
|
+
strategy: incomingStrategy,
|
|
3314
|
+
pricing,
|
|
3315
|
+
parsedBody,
|
|
3316
|
+
price
|
|
3291
3317
|
});
|
|
3318
|
+
if (!verify.ok) return verify.response;
|
|
3319
|
+
const { verifyOutcome } = verify;
|
|
3292
3320
|
const result = await invokePaidStatic(
|
|
3293
3321
|
ctx,
|
|
3294
3322
|
verifyOutcome.wallet,
|
|
@@ -3780,55 +3808,35 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3780
3808
|
return next;
|
|
3781
3809
|
}
|
|
3782
3810
|
paid(arg, options) {
|
|
3783
|
-
|
|
3811
|
+
const self = this;
|
|
3812
|
+
return self.applyPaid(normalizePaidArg(self.#s.key, arg, options), "paid");
|
|
3784
3813
|
}
|
|
3785
|
-
/**
|
|
3786
|
-
* x402-only handler-computed billing. The handler receives `charge(amount)`
|
|
3787
|
-
* and the request settles once for the accumulated total, capped at
|
|
3788
|
-
* `maxPrice`. Requires an `'upto'` accept on at least one configured network.
|
|
3789
|
-
* Pass a bare string as sugar for `{ maxPrice }`.
|
|
3790
|
-
*
|
|
3791
|
-
* @example
|
|
3792
|
-
* ```ts
|
|
3793
|
-
* router.route('llm')
|
|
3794
|
-
* .upTo('0.05')
|
|
3795
|
-
* .body(schema)
|
|
3796
|
-
* .handler(async ({ body, charge }) => { await charge('0.001'); ... });
|
|
3797
|
-
* ```
|
|
3798
|
-
*/
|
|
3799
3814
|
upTo(arg) {
|
|
3800
|
-
|
|
3815
|
+
const self = this;
|
|
3816
|
+
return self.applyPaid(normalizeUpToArg(self.#s.key, arg), "upTo");
|
|
3801
3817
|
}
|
|
3802
|
-
/**
|
|
3803
|
-
* MPP-only per-tick billing. `.handler()` bills exactly `tickCost`;
|
|
3804
|
-
* `.stream()` calls `charge()` (no-arg) per yield, settling per tick up to
|
|
3805
|
-
* `maxPrice`. Requires `RouterConfig.mpp.session`.
|
|
3806
|
-
*
|
|
3807
|
-
* @example
|
|
3808
|
-
* ```ts
|
|
3809
|
-
* router.route('llm/stream')
|
|
3810
|
-
* .metered({ tickCost: '0.0001', maxPrice: '0.05', unitType: 'token' })
|
|
3811
|
-
* .stream(async function* ({ charge }) { await charge(); yield 'hi'; });
|
|
3812
|
-
* ```
|
|
3813
|
-
*/
|
|
3814
3818
|
metered(options) {
|
|
3815
|
-
|
|
3819
|
+
const self = this;
|
|
3820
|
+
return self.applyPaid(normalizeMeteredArg(self.#s.key, options), "metered");
|
|
3816
3821
|
}
|
|
3817
3822
|
applyPaid(normalized, method) {
|
|
3818
3823
|
const { pricing, resolvedOptions, billing, tickCost, unitType, maxPrice } = normalized;
|
|
3819
3824
|
if (this.#s.authMode === "unprotected") {
|
|
3820
|
-
throw new
|
|
3821
|
-
|
|
3825
|
+
throw new RouteDefinitionError(
|
|
3826
|
+
this.#s.key,
|
|
3827
|
+
`Cannot combine .unprotected() and .${method}() on the same route.`
|
|
3822
3828
|
);
|
|
3823
3829
|
}
|
|
3824
3830
|
if (this.#s.pricing !== void 0) {
|
|
3825
|
-
throw new
|
|
3826
|
-
|
|
3831
|
+
throw new RouteDefinitionError(
|
|
3832
|
+
this.#s.key,
|
|
3833
|
+
`Cannot combine .paid(), .upTo(), and .metered() \u2014 pick one pricing mode.`
|
|
3827
3834
|
);
|
|
3828
3835
|
}
|
|
3829
3836
|
if (this.#s.siwxEnabled && billing === "metered") {
|
|
3830
|
-
throw new
|
|
3831
|
-
|
|
3837
|
+
throw new RouteDefinitionError(
|
|
3838
|
+
this.#s.key,
|
|
3839
|
+
`Cannot combine .siwx() and .metered() \u2014 per-tick MPP billing has no entitlement model. Use .paid() or .upTo() with .siwx(), or drop .siwx() for metered routes.`
|
|
3832
3840
|
);
|
|
3833
3841
|
}
|
|
3834
3842
|
const next = this.fork();
|
|
@@ -3836,15 +3844,17 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3836
3844
|
next.#s.pricing = pricing;
|
|
3837
3845
|
if (billing === "upto") {
|
|
3838
3846
|
if (resolvedOptions.protocols?.some((p) => p !== "x402")) {
|
|
3839
|
-
throw new
|
|
3840
|
-
|
|
3847
|
+
throw new RouteDefinitionError(
|
|
3848
|
+
this.#s.key,
|
|
3849
|
+
`.upTo() is x402-only \u2014 remove the conflicting protocols override.`
|
|
3841
3850
|
);
|
|
3842
3851
|
}
|
|
3843
3852
|
next.#s.protocols = ["x402"];
|
|
3844
3853
|
} else if (billing === "metered") {
|
|
3845
3854
|
if (resolvedOptions.protocols?.some((p) => p !== "mpp")) {
|
|
3846
|
-
throw new
|
|
3847
|
-
|
|
3855
|
+
throw new RouteDefinitionError(
|
|
3856
|
+
this.#s.key,
|
|
3857
|
+
`.metered() is MPP-only \u2014 remove the conflicting protocols override.`
|
|
3848
3858
|
);
|
|
3849
3859
|
}
|
|
3850
3860
|
next.#s.protocols = ["mpp"];
|
|
@@ -3868,73 +3878,69 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3868
3878
|
if (typeof pricing === "object" && "tiers" in pricing) {
|
|
3869
3879
|
for (const [tierKey, tierConfig] of Object.entries(pricing.tiers)) {
|
|
3870
3880
|
if (!tierKey) {
|
|
3871
|
-
throw new
|
|
3881
|
+
throw new RouteDefinitionError(this.#s.key, `tier key cannot be empty`);
|
|
3872
3882
|
}
|
|
3873
3883
|
if (!isPositiveDecimal(tierConfig.price)) {
|
|
3874
|
-
throw new
|
|
3875
|
-
|
|
3884
|
+
throw new RouteDefinitionError(
|
|
3885
|
+
this.#s.key,
|
|
3886
|
+
`tier '${tierKey}' price '${tierConfig.price}' must be a positive decimal string`
|
|
3876
3887
|
);
|
|
3877
3888
|
}
|
|
3878
3889
|
}
|
|
3879
3890
|
}
|
|
3880
3891
|
if (billing === "exact" && typeof pricing === "string" && !isPositiveDecimal(pricing)) {
|
|
3881
|
-
throw new
|
|
3882
|
-
|
|
3892
|
+
throw new RouteDefinitionError(
|
|
3893
|
+
this.#s.key,
|
|
3894
|
+
`price '${pricing}' must be a positive decimal string`
|
|
3883
3895
|
);
|
|
3884
3896
|
}
|
|
3885
3897
|
if (typeof pricing === "function" && next.#s.maxPrice === void 0) {
|
|
3886
|
-
throw new
|
|
3887
|
-
|
|
3898
|
+
throw new RouteDefinitionError(
|
|
3899
|
+
this.#s.key,
|
|
3900
|
+
`dynamic pricing requires maxPrice \u2014 without it, bare probes would advertise a $0 challenge`
|
|
3888
3901
|
);
|
|
3889
3902
|
}
|
|
3890
3903
|
if (next.#s.maxPrice !== void 0 && !isPositiveDecimal(next.#s.maxPrice)) {
|
|
3891
|
-
throw new
|
|
3892
|
-
|
|
3904
|
+
throw new RouteDefinitionError(
|
|
3905
|
+
this.#s.key,
|
|
3906
|
+
`maxPrice '${next.#s.maxPrice}' must be a positive decimal string`
|
|
3893
3907
|
);
|
|
3894
3908
|
}
|
|
3895
3909
|
if (next.#s.minPrice !== void 0 && !isPositiveDecimal(next.#s.minPrice)) {
|
|
3896
|
-
throw new
|
|
3897
|
-
|
|
3910
|
+
throw new RouteDefinitionError(
|
|
3911
|
+
this.#s.key,
|
|
3912
|
+
`minPrice '${next.#s.minPrice}' must be a positive decimal string`
|
|
3898
3913
|
);
|
|
3899
3914
|
}
|
|
3900
3915
|
if (next.#s.tickCost !== void 0 && !isPositiveDecimal(next.#s.tickCost)) {
|
|
3901
|
-
throw new
|
|
3902
|
-
|
|
3916
|
+
throw new RouteDefinitionError(
|
|
3917
|
+
this.#s.key,
|
|
3918
|
+
`tickCost '${next.#s.tickCost}' must be a positive decimal string`
|
|
3903
3919
|
);
|
|
3904
3920
|
}
|
|
3905
3921
|
return next;
|
|
3906
3922
|
}
|
|
3907
|
-
/**
|
|
3908
|
-
* Require Sign-In-with-X wallet identity on this route — clients prove
|
|
3909
|
-
* control of a wallet via a signed challenge. Composes with `.paid()` and
|
|
3910
|
-
* `.upTo()` for pay-once-then-replay: the first request settles normally,
|
|
3911
|
-
* subsequent requests with a valid SIWX signature for the same wallet skip
|
|
3912
|
-
* payment (on `.upTo()`, `charge(amount)` becomes a no-op on the replay).
|
|
3913
|
-
* Mutually exclusive with `.metered()`.
|
|
3914
|
-
*
|
|
3915
|
-
* @example
|
|
3916
|
-
* ```ts
|
|
3917
|
-
* router.route('profile').siwx().handler(async ({ wallet }) => getProfile(wallet));
|
|
3918
|
-
* router.route('inbox').paid('0.01').siwx().handler(async ({ wallet }) => getInbox(wallet));
|
|
3919
|
-
* ```
|
|
3920
|
-
*/
|
|
3921
3923
|
siwx() {
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3924
|
+
const self = this;
|
|
3925
|
+
if (self.#s.authMode === "unprotected") {
|
|
3926
|
+
throw new RouteDefinitionError(
|
|
3927
|
+
self.#s.key,
|
|
3928
|
+
`Cannot combine .unprotected() and .siwx() on the same route.`
|
|
3925
3929
|
);
|
|
3926
3930
|
}
|
|
3927
|
-
if (
|
|
3928
|
-
throw new
|
|
3929
|
-
|
|
3931
|
+
if (self.#s.apiKeyResolver) {
|
|
3932
|
+
throw new RouteDefinitionError(
|
|
3933
|
+
self.#s.key,
|
|
3934
|
+
`Combining .siwx() and .apiKey() is not supported on the same route.`
|
|
3930
3935
|
);
|
|
3931
3936
|
}
|
|
3932
|
-
if (
|
|
3933
|
-
throw new
|
|
3934
|
-
|
|
3937
|
+
if (self.#s.billing === "metered") {
|
|
3938
|
+
throw new RouteDefinitionError(
|
|
3939
|
+
self.#s.key,
|
|
3940
|
+
`Cannot combine .metered() and .siwx() \u2014 per-tick MPP billing has no entitlement model. Use .paid() or .upTo() with .siwx(), or drop .siwx() for metered routes.`
|
|
3935
3941
|
);
|
|
3936
3942
|
}
|
|
3937
|
-
const next =
|
|
3943
|
+
const next = self.fork();
|
|
3938
3944
|
next.#s.siwxEnabled = true;
|
|
3939
3945
|
if (next.#s.authMode === "paid" || next.#s.pricing) {
|
|
3940
3946
|
next.#s.authMode = "paid";
|
|
@@ -3945,59 +3951,48 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3945
3951
|
next.#s.protocols = [];
|
|
3946
3952
|
return next;
|
|
3947
3953
|
}
|
|
3948
|
-
/**
|
|
3949
|
-
* Require an `X-API-Key` header (or `Authorization: Bearer <key>`); the
|
|
3950
|
-
* resolver returns the account record, or `null` for 401. Composes with
|
|
3951
|
-
* `.paid()` — key is checked first, payment second.
|
|
3952
|
-
*
|
|
3953
|
-
* @example
|
|
3954
|
-
* ```ts
|
|
3955
|
-
* router
|
|
3956
|
-
* .route('admin/users')
|
|
3957
|
-
* .apiKey(async (key) => db.admin.findByKey(key))
|
|
3958
|
-
* .handler(async ({ account }) => db.user.list(account.orgId));
|
|
3959
|
-
* ```
|
|
3960
|
-
*/
|
|
3961
3954
|
apiKey(resolver) {
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3955
|
+
const self = this;
|
|
3956
|
+
if (self.#s.authMode === "unprotected") {
|
|
3957
|
+
throw new RouteDefinitionError(
|
|
3958
|
+
self.#s.key,
|
|
3959
|
+
`Cannot combine .unprotected() and .apiKey() on the same route.`
|
|
3965
3960
|
);
|
|
3966
3961
|
}
|
|
3967
|
-
|
|
3962
|
+
if (self.#s.siwxEnabled) {
|
|
3963
|
+
throw new RouteDefinitionError(
|
|
3964
|
+
self.#s.key,
|
|
3965
|
+
`Combining .apiKey() and .siwx() is not supported on the same route.`
|
|
3966
|
+
);
|
|
3967
|
+
}
|
|
3968
|
+
const next = self.fork();
|
|
3968
3969
|
next.#s.authMode = "apiKey";
|
|
3969
3970
|
next.#s.apiKeyResolver = resolver;
|
|
3970
3971
|
return next;
|
|
3971
3972
|
}
|
|
3972
|
-
/**
|
|
3973
|
-
* Mark the route as public — no auth, no payment, no SIWX. The handler
|
|
3974
|
-
* receives `null` for `wallet`, `payment`, and `account`.
|
|
3975
|
-
*
|
|
3976
|
-
* @example
|
|
3977
|
-
* ```ts
|
|
3978
|
-
* router.route('health').unprotected().handler(async () => ({ status: 'ok' }));
|
|
3979
|
-
* ```
|
|
3980
|
-
*/
|
|
3981
3973
|
unprotected() {
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3974
|
+
const self = this;
|
|
3975
|
+
if (self.#s.authMode && self.#s.authMode !== "unprotected") {
|
|
3976
|
+
throw new RouteDefinitionError(
|
|
3977
|
+
self.#s.key,
|
|
3978
|
+
`Cannot combine .unprotected() and .${self.#s.authMode}() on the same route.`
|
|
3985
3979
|
);
|
|
3986
3980
|
}
|
|
3987
|
-
if (
|
|
3988
|
-
throw new
|
|
3989
|
-
|
|
3981
|
+
if (self.#s.pricing) {
|
|
3982
|
+
throw new RouteDefinitionError(
|
|
3983
|
+
self.#s.key,
|
|
3984
|
+
`Cannot combine .unprotected() and .paid() on the same route.`
|
|
3990
3985
|
);
|
|
3991
3986
|
}
|
|
3992
|
-
const next =
|
|
3987
|
+
const next = self.fork();
|
|
3993
3988
|
next.#s.authMode = "unprotected";
|
|
3994
3989
|
next.#s.protocols = [];
|
|
3995
3990
|
return next;
|
|
3996
3991
|
}
|
|
3997
3992
|
/**
|
|
3998
3993
|
* Tag the route with an upstream provider for discovery and provider-side
|
|
3999
|
-
* monitoring. The provider name and config surface in
|
|
4000
|
-
*
|
|
3994
|
+
* monitoring. The provider name and config surface in OpenAPI discovery
|
|
3995
|
+
* output.
|
|
4001
3996
|
*
|
|
4002
3997
|
* @example
|
|
4003
3998
|
* ```ts
|
|
@@ -4094,8 +4089,8 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
4094
4089
|
return next;
|
|
4095
4090
|
}
|
|
4096
4091
|
/**
|
|
4097
|
-
* Set a human-readable summary of the route. Surfaces in OpenAPI
|
|
4098
|
-
* `
|
|
4092
|
+
* Set a human-readable summary of the route. Surfaces in OpenAPI and
|
|
4093
|
+
* `llms.txt` discovery output.
|
|
4099
4094
|
*
|
|
4100
4095
|
* @example
|
|
4101
4096
|
* ```ts
|
|
@@ -4231,38 +4226,46 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
4231
4226
|
}
|
|
4232
4227
|
register(handlerFn, streaming) {
|
|
4233
4228
|
if (!this.#s.authMode) {
|
|
4234
|
-
throw new
|
|
4235
|
-
|
|
4229
|
+
throw new RouteDefinitionError(
|
|
4230
|
+
this.#s.key,
|
|
4231
|
+
`Select an auth mode: .paid(pricing), .upTo(maxPrice), .metered(options), .siwx(), .apiKey(resolver), or .unprotected()`
|
|
4236
4232
|
);
|
|
4237
4233
|
}
|
|
4238
4234
|
if (this.#s.validateFn && !this.#s.bodySchema) {
|
|
4239
|
-
throw new
|
|
4240
|
-
|
|
4235
|
+
throw new RouteDefinitionError(
|
|
4236
|
+
this.#s.key,
|
|
4237
|
+
`.validate() requires .body() \u2014 validation runs on parsed body`
|
|
4241
4238
|
);
|
|
4242
4239
|
}
|
|
4243
4240
|
if (this.#s.settlement && !this.#s.pricing) {
|
|
4244
|
-
throw new
|
|
4241
|
+
throw new RouteDefinitionError(this.#s.key, `.settlement() requires a paid route`);
|
|
4245
4242
|
}
|
|
4246
4243
|
if (this.#s.mppInfo?.settleBeforeHandler) {
|
|
4247
4244
|
if (!this.#s.pricing) {
|
|
4248
|
-
throw new
|
|
4245
|
+
throw new RouteDefinitionError(
|
|
4246
|
+
this.#s.key,
|
|
4247
|
+
`mpp.settleBeforeHandler requires a paid route`
|
|
4248
|
+
);
|
|
4249
4249
|
}
|
|
4250
4250
|
if (this.#s.billing !== "exact") {
|
|
4251
|
-
throw new
|
|
4252
|
-
|
|
4251
|
+
throw new RouteDefinitionError(
|
|
4252
|
+
this.#s.key,
|
|
4253
|
+
`mpp.settleBeforeHandler is only supported on .paid() routes`
|
|
4253
4254
|
);
|
|
4254
4255
|
}
|
|
4255
4256
|
if (this.#s.settlement?.beforeSettle) {
|
|
4256
|
-
throw new
|
|
4257
|
-
|
|
4257
|
+
throw new RouteDefinitionError(
|
|
4258
|
+
this.#s.key,
|
|
4259
|
+
`mpp.settleBeforeHandler is incompatible with .settlement({ beforeSettle }) \u2014 MPP payment is already broadcast before the handler runs`
|
|
4258
4260
|
);
|
|
4259
4261
|
}
|
|
4260
4262
|
}
|
|
4261
4263
|
if (this.#s.billing === "upto") {
|
|
4262
4264
|
const hasUpto = this.#s.deps.x402Accepts.some((accept) => accept.scheme === "upto");
|
|
4263
4265
|
if (!hasUpto) {
|
|
4264
|
-
throw new
|
|
4265
|
-
|
|
4266
|
+
throw new RouteDefinitionError(
|
|
4267
|
+
this.#s.key,
|
|
4268
|
+
`.upTo() requires an 'upto' accept on at least one configured network. Add { scheme: 'upto', network, asset } to RouterConfig.x402.accepts.`
|
|
4266
4269
|
);
|
|
4267
4270
|
}
|
|
4268
4271
|
}
|
|
@@ -4271,26 +4274,30 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
4271
4274
|
(accept) => (accept.scheme ?? "exact") !== "upto"
|
|
4272
4275
|
);
|
|
4273
4276
|
if (!hasExact) {
|
|
4274
|
-
throw new
|
|
4275
|
-
|
|
4277
|
+
throw new RouteDefinitionError(
|
|
4278
|
+
this.#s.key,
|
|
4279
|
+
`.paid() needs a non-'upto' x402 accept \u2014 an 'upto'-only accept list cannot serve a fixed-price route. Add { scheme: 'exact', network } to RouterConfig.x402.accepts, or use .upTo() for handler-computed billing.`
|
|
4276
4280
|
);
|
|
4277
4281
|
}
|
|
4278
4282
|
}
|
|
4279
4283
|
if (this.#s.billing === "metered") {
|
|
4280
4284
|
if (!this.#s.deps.mppSessionConfig) {
|
|
4281
|
-
throw new
|
|
4282
|
-
|
|
4285
|
+
throw new RouteDefinitionError(
|
|
4286
|
+
this.#s.key,
|
|
4287
|
+
`.metered() requires MPP session mode. Set RouterConfig.mpp.session = {} and provide mpp.operatorKey.`
|
|
4283
4288
|
);
|
|
4284
4289
|
}
|
|
4285
4290
|
}
|
|
4286
4291
|
if (streaming && this.#s.billing !== "metered") {
|
|
4287
|
-
throw new
|
|
4288
|
-
|
|
4292
|
+
throw new RouteDefinitionError(
|
|
4293
|
+
this.#s.key,
|
|
4294
|
+
`.stream() requires .metered() \u2014 static/free/upto routes can't meter per-chunk billing.`
|
|
4289
4295
|
);
|
|
4290
4296
|
}
|
|
4291
4297
|
if (this.#s.description !== void 0 && this.#s.description.length > MAX_X402_DESCRIPTION_LENGTH && this.#s.pricing !== void 0 && this.#s.protocols.includes("x402")) {
|
|
4292
|
-
throw new
|
|
4293
|
-
|
|
4298
|
+
throw new RouteDefinitionError(
|
|
4299
|
+
this.#s.key,
|
|
4300
|
+
`.description() is ${this.#s.description.length} chars; must be \u2264 ${MAX_X402_DESCRIPTION_LENGTH} chars \u2014 the CDP x402 facilitator rejects payments whose 402 challenge resource.description exceeds ~500 chars.`
|
|
4294
4301
|
);
|
|
4295
4302
|
}
|
|
4296
4303
|
validateExamples(
|
|
@@ -4358,14 +4365,15 @@ function normalizePaidArg(routeKey, arg, options) {
|
|
|
4358
4365
|
if ("price" in arg && typeof arg.price === "string") {
|
|
4359
4366
|
return { pricing: arg.price, resolvedOptions: arg, billing: "exact" };
|
|
4360
4367
|
}
|
|
4361
|
-
throw new
|
|
4362
|
-
|
|
4368
|
+
throw new RouteDefinitionError(
|
|
4369
|
+
routeKey,
|
|
4370
|
+
`.paid() requires one of: a price string, a (body) => string function, { price }, or { field, tiers }. For handler-computed billing use .upTo(); for per-tick billing use .metered().`
|
|
4363
4371
|
);
|
|
4364
4372
|
}
|
|
4365
4373
|
function normalizeUpToArg(routeKey, arg) {
|
|
4366
4374
|
const options = typeof arg === "string" ? { maxPrice: arg } : arg;
|
|
4367
4375
|
if (!options.maxPrice) {
|
|
4368
|
-
throw new
|
|
4376
|
+
throw new RouteDefinitionError(routeKey, `.upTo() requires maxPrice`);
|
|
4369
4377
|
}
|
|
4370
4378
|
return {
|
|
4371
4379
|
pricing: options.maxPrice,
|
|
@@ -4377,10 +4385,10 @@ function normalizeUpToArg(routeKey, arg) {
|
|
|
4377
4385
|
}
|
|
4378
4386
|
function normalizeMeteredArg(routeKey, options) {
|
|
4379
4387
|
if (!options.maxPrice) {
|
|
4380
|
-
throw new
|
|
4388
|
+
throw new RouteDefinitionError(routeKey, `.metered() requires maxPrice`);
|
|
4381
4389
|
}
|
|
4382
4390
|
if (!options.tickCost) {
|
|
4383
|
-
throw new
|
|
4391
|
+
throw new RouteDefinitionError(routeKey, `.metered() requires tickCost`);
|
|
4384
4392
|
}
|
|
4385
4393
|
return {
|
|
4386
4394
|
pricing: options.maxPrice,
|
|
@@ -4687,9 +4695,9 @@ function createNotFoundHandler(baseUrl) {
|
|
|
4687
4695
|
code: "route_not_found",
|
|
4688
4696
|
error: "Route not found. Rediscover this origin and retry with the current discovery document.",
|
|
4689
4697
|
requestedUrl: request.url,
|
|
4698
|
+
// The deprecated /.well-known/x402 surface is intentionally not advertised.
|
|
4690
4699
|
discovery: {
|
|
4691
4700
|
openapi: `${normalizedBase}/openapi.json`,
|
|
4692
|
-
wellKnown: `${normalizedBase}/.well-known/x402`,
|
|
4693
4701
|
llmsTxt: `${normalizedBase}/llms.txt`
|
|
4694
4702
|
}
|
|
4695
4703
|
},
|
|
@@ -4723,6 +4731,7 @@ function formatRouterConfigIssues(issues) {
|
|
|
4723
4731
|
|
|
4724
4732
|
// src/config/schema.ts
|
|
4725
4733
|
var import_zod = require("zod");
|
|
4734
|
+
init_accepts();
|
|
4726
4735
|
init_constants();
|
|
4727
4736
|
|
|
4728
4737
|
// src/config/utils.ts
|
|
@@ -4911,18 +4920,8 @@ function deriveBaseUrlEnv(env) {
|
|
|
4911
4920
|
BASE_URL: vercelBaseUrl
|
|
4912
4921
|
};
|
|
4913
4922
|
}
|
|
4914
|
-
function getConfiguredX402Accepts2(config) {
|
|
4915
|
-
if (config.x402?.accepts?.length) return [...config.x402.accepts];
|
|
4916
|
-
return [
|
|
4917
|
-
{
|
|
4918
|
-
scheme: "exact",
|
|
4919
|
-
network: config.network ?? BASE_MAINNET_NETWORK,
|
|
4920
|
-
payTo: config.payeeAddress
|
|
4921
|
-
}
|
|
4922
|
-
];
|
|
4923
|
-
}
|
|
4924
4923
|
function validateX402Config(config, env) {
|
|
4925
|
-
const accepts =
|
|
4924
|
+
const accepts = getConfiguredX402Accepts(config);
|
|
4926
4925
|
const issues = [];
|
|
4927
4926
|
const push = (code, message) => issues.push({ code, protocol: "x402", message });
|
|
4928
4927
|
if (accepts.length === 0) {
|
|
@@ -5223,7 +5222,8 @@ function getMppxRequestContext(args) {
|
|
|
5223
5222
|
recipient: mppConfig.recipient ?? payeeAddress,
|
|
5224
5223
|
getClient,
|
|
5225
5224
|
...feePayerAccount ? { feePayer: feePayerAccount } : {},
|
|
5226
|
-
...resolvedStore ? { store: resolvedStore } : {}
|
|
5225
|
+
...resolvedStore ? { store: resolvedStore } : {},
|
|
5226
|
+
...mppConfig.feePayerPolicy ? { feePayerPolicy: mppConfig.feePayerPolicy } : {}
|
|
5227
5227
|
}),
|
|
5228
5228
|
...sessionEnabled ? [
|
|
5229
5229
|
tempo.session({
|
|
@@ -5261,7 +5261,7 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
|
|
|
5261
5261
|
try {
|
|
5262
5262
|
const { Mppx, tempo } = await import("mppx/server");
|
|
5263
5263
|
const { createClient, http } = await import("viem");
|
|
5264
|
-
const { tempo: tempoChain } = await import("viem/chains");
|
|
5264
|
+
const { tempo: tempoChain } = await import("viem/tempo/chains");
|
|
5265
5265
|
const { privateKeyToAccount: privateKeyToAccount2 } = await import("viem/accounts");
|
|
5266
5266
|
const rpcUrl = config.mpp.rpcUrl ?? process.env.TEMPO_RPC_URL ?? DEFAULT_TEMPO_RPC_URL;
|
|
5267
5267
|
const tempoClient = createClient({ chain: tempoChain, transport: http(rpcUrl) });
|
|
@@ -5279,7 +5279,9 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
|
|
|
5279
5279
|
getClient,
|
|
5280
5280
|
...operatorAccount ? { account: operatorAccount } : {},
|
|
5281
5281
|
...feePayerAccount ? { feePayer: feePayerAccount } : {},
|
|
5282
|
-
...resolvedStore ? { store: resolvedStore } : {}
|
|
5282
|
+
...resolvedStore ? { store: resolvedStore } : {},
|
|
5283
|
+
...mppConfig.feePayerPolicy ? { feePayerPolicy: mppConfig.feePayerPolicy } : {},
|
|
5284
|
+
...mppConfig.session?.settlementSchedule ? { settlementSchedule: mppConfig.session.settlementSchedule } : {}
|
|
5283
5285
|
};
|
|
5284
5286
|
const mppxArgs = {
|
|
5285
5287
|
Mppx,
|
|
@@ -5295,6 +5297,7 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
|
|
|
5295
5297
|
};
|
|
5296
5298
|
const primary = getMppxRequestContext(mppxArgs);
|
|
5297
5299
|
const streaming = getMppxStreamingContext(mppxArgs);
|
|
5300
|
+
subscribePaymentFailedAlerts(config.plugin, [primary, streaming]);
|
|
5298
5301
|
const mppx = {
|
|
5299
5302
|
charge: primary.charge,
|
|
5300
5303
|
...primary.session ? { sessionRequest: primary.session } : {},
|
|
@@ -5305,6 +5308,41 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
|
|
|
5305
5308
|
return { initError: err instanceof Error ? err.message : String(err) };
|
|
5306
5309
|
}
|
|
5307
5310
|
}
|
|
5311
|
+
function subscribePaymentFailedAlerts(plugin, instances) {
|
|
5312
|
+
if (!plugin?.onAlert) return;
|
|
5313
|
+
const ctx = {
|
|
5314
|
+
requestId: "mppx",
|
|
5315
|
+
route: "mpp",
|
|
5316
|
+
walletAddress: null,
|
|
5317
|
+
clientId: null,
|
|
5318
|
+
sessionId: null,
|
|
5319
|
+
verifiedWallet: null,
|
|
5320
|
+
setVerifiedWallet() {
|
|
5321
|
+
}
|
|
5322
|
+
};
|
|
5323
|
+
for (const instance of instances) {
|
|
5324
|
+
const events = instance;
|
|
5325
|
+
if (typeof events?.onPaymentFailed !== "function") continue;
|
|
5326
|
+
events.onPaymentFailed((payload) => {
|
|
5327
|
+
try {
|
|
5328
|
+
const error = payload?.error ?? {};
|
|
5329
|
+
firePluginHook(plugin, "onAlert", ctx, {
|
|
5330
|
+
level: (error.status ?? 402) >= 500 ? "error" : "warn",
|
|
5331
|
+
message: `MPP payment failed: ${error.message ?? error.name ?? "unknown error"}`,
|
|
5332
|
+
route: "mpp",
|
|
5333
|
+
meta: {
|
|
5334
|
+
...payload?.method ? { method: `${payload.method.name}/${payload.method.intent}` } : {},
|
|
5335
|
+
...error.type ? { errorType: error.type } : {},
|
|
5336
|
+
...error.status !== void 0 ? { status: error.status } : {},
|
|
5337
|
+
...error.hint ? { hint: error.hint } : {},
|
|
5338
|
+
...payload?.credential?.source ? { payer: payload.credential.source } : {}
|
|
5339
|
+
}
|
|
5340
|
+
});
|
|
5341
|
+
} catch {
|
|
5342
|
+
}
|
|
5343
|
+
});
|
|
5344
|
+
}
|
|
5345
|
+
}
|
|
5308
5346
|
|
|
5309
5347
|
// src/index.ts
|
|
5310
5348
|
init_constants();
|
|
@@ -5448,6 +5486,7 @@ function createRouterFromEnv(options) {
|
|
|
5448
5486
|
DEFAULT_SOLANA_FACILITATOR_URL,
|
|
5449
5487
|
DEFAULT_TEMPO_RPC_URL,
|
|
5450
5488
|
HttpError,
|
|
5489
|
+
RouteDefinitionError,
|
|
5451
5490
|
RouterConfigError,
|
|
5452
5491
|
SOLANA_MAINNET_NETWORK,
|
|
5453
5492
|
TEMPO_USDC_ADDRESS,
|