@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.js
CHANGED
|
@@ -20,7 +20,7 @@ var init_constants = __esm({
|
|
|
20
20
|
BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
21
21
|
BASE_USDC_DECIMALS = 6;
|
|
22
22
|
ZERO_EVM_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
23
|
-
DEFAULT_SOLANA_FACILITATOR_URL = "https://facilitator.
|
|
23
|
+
DEFAULT_SOLANA_FACILITATOR_URL = "https://facilitator.payai.network";
|
|
24
24
|
DEFAULT_TEMPO_RPC_URL = "https://rpc.tempo.xyz";
|
|
25
25
|
}
|
|
26
26
|
});
|
|
@@ -129,31 +129,64 @@ function buildSolanaExactOptions(accepts, price) {
|
|
|
129
129
|
function hasSolanaAccepts(accepts) {
|
|
130
130
|
return accepts.some((accept) => isSolanaNetwork(accept.network));
|
|
131
131
|
}
|
|
132
|
-
|
|
132
|
+
function facilitatorBaseUrl(facilitator) {
|
|
133
133
|
if (!facilitator.url) {
|
|
134
|
-
throw new Error(`Facilitator for ${facilitator.network} is missing a URL
|
|
134
|
+
throw new Error(`Facilitator for ${facilitator.network} is missing a URL`);
|
|
135
135
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
accepts: requirements
|
|
147
|
-
})
|
|
136
|
+
return facilitator.url.replace(/\/+$/, "");
|
|
137
|
+
}
|
|
138
|
+
function matchesSupportedKind(requirement, kind) {
|
|
139
|
+
const scheme = requirement.scheme ?? "exact";
|
|
140
|
+
return kind.network === requirement.network && (kind.scheme ?? "exact") === scheme;
|
|
141
|
+
}
|
|
142
|
+
async function fetchFacilitatorSupported(facilitator) {
|
|
143
|
+
const authHeaders = await getSupportedHeadersForFacilitator(facilitator);
|
|
144
|
+
const response = await fetch(`${facilitatorBaseUrl(facilitator)}/supported`, {
|
|
145
|
+
headers: authHeaders
|
|
148
146
|
});
|
|
149
147
|
if (!response.ok) {
|
|
150
|
-
throw new Error(`Facilitator /
|
|
148
|
+
throw new Error(`Facilitator /supported failed with status ${response.status}`);
|
|
151
149
|
}
|
|
152
150
|
const body = await response.json();
|
|
153
|
-
if (!Array.isArray(body.
|
|
154
|
-
throw new Error("Facilitator /
|
|
151
|
+
if (!Array.isArray(body.kinds)) {
|
|
152
|
+
throw new Error("Facilitator /supported response did not include kinds");
|
|
153
|
+
}
|
|
154
|
+
return body.kinds;
|
|
155
|
+
}
|
|
156
|
+
function enrichRequirementFromKind(requirement, kinds) {
|
|
157
|
+
const kind = kinds.find((candidate) => matchesSupportedKind(requirement, candidate));
|
|
158
|
+
if (!kind) {
|
|
159
|
+
throw new Error(
|
|
160
|
+
`Facilitator /supported has no kind for ${requirement.scheme} on ${requirement.network}`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
const feePayer = kind.extra?.feePayer;
|
|
164
|
+
if (!feePayer) {
|
|
165
|
+
throw new Error(
|
|
166
|
+
`Facilitator /supported kind for ${requirement.network} is missing extra.feePayer`
|
|
167
|
+
);
|
|
155
168
|
}
|
|
156
|
-
|
|
169
|
+
const requirementExtra = requirement.extra ?? {};
|
|
170
|
+
const kindExtra = kind.extra ?? {};
|
|
171
|
+
const requirementFeatures = requirementExtra.features ?? {};
|
|
172
|
+
const kindFeatures = kindExtra.features ?? {};
|
|
173
|
+
return {
|
|
174
|
+
...requirement,
|
|
175
|
+
...kind.asset && !requirement.asset ? { asset: kind.asset } : {},
|
|
176
|
+
extra: {
|
|
177
|
+
...requirementExtra,
|
|
178
|
+
...kindExtra,
|
|
179
|
+
features: {
|
|
180
|
+
...requirementFeatures,
|
|
181
|
+
...kindFeatures,
|
|
182
|
+
xSettlementAccountSupported: true
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
async function enrichRequirementsFromFacilitatorSupported(facilitator, requirements) {
|
|
188
|
+
const kinds = await fetchFacilitatorSupported(facilitator);
|
|
189
|
+
return requirements.map((requirement) => enrichRequirementFromKind(requirement, kinds));
|
|
157
190
|
}
|
|
158
191
|
function isSolanaRequirement(requirement) {
|
|
159
192
|
return isSolanaNetwork(requirement.network);
|
|
@@ -210,10 +243,7 @@ function getFacilitatorForRequirement(facilitatorsByNetwork, requirement) {
|
|
|
210
243
|
function sameResolvedX402Facilitator(a, b) {
|
|
211
244
|
return sameFacilitatorConfig(a.config, b.config);
|
|
212
245
|
}
|
|
213
|
-
async function
|
|
214
|
-
if (facilitator.config.createAcceptsHeaders) {
|
|
215
|
-
return facilitator.config.createAcceptsHeaders();
|
|
216
|
-
}
|
|
246
|
+
async function getSupportedHeadersForFacilitator(facilitator) {
|
|
217
247
|
if (facilitator.config.createAuthHeaders) {
|
|
218
248
|
const headers = await facilitator.config.createAuthHeaders();
|
|
219
249
|
return headers.supported;
|
|
@@ -235,7 +265,7 @@ function getNetworkFamily(network) {
|
|
|
235
265
|
return null;
|
|
236
266
|
}
|
|
237
267
|
function sameFacilitatorConfig(a, b) {
|
|
238
|
-
return a.url === b.url && a.createAuthHeaders === b.createAuthHeaders
|
|
268
|
+
return a.url === b.url && a.createAuthHeaders === b.createAuthHeaders;
|
|
239
269
|
}
|
|
240
270
|
var init_facilitators = __esm({
|
|
241
271
|
"src/protocols/x402/facilitators.ts"() {
|
|
@@ -835,6 +865,13 @@ var HttpError = class extends Error {
|
|
|
835
865
|
this.name = "HttpError";
|
|
836
866
|
}
|
|
837
867
|
};
|
|
868
|
+
var RouteDefinitionError = class extends Error {
|
|
869
|
+
constructor(route, detail) {
|
|
870
|
+
super(`route '${route}': ${detail}`);
|
|
871
|
+
this.route = route;
|
|
872
|
+
this.name = "RouteDefinitionError";
|
|
873
|
+
}
|
|
874
|
+
};
|
|
838
875
|
|
|
839
876
|
// src/auth/agent-identity.ts
|
|
840
877
|
var PROTOCOL_VERSION = 1;
|
|
@@ -1362,6 +1399,23 @@ function multiplyDecimal(decimal, factor) {
|
|
|
1362
1399
|
return fracPart ? `${intPart}.${fracPart}` : intPart;
|
|
1363
1400
|
}
|
|
1364
1401
|
|
|
1402
|
+
// src/protocols/detect.ts
|
|
1403
|
+
function hasX402Payment(request) {
|
|
1404
|
+
return Boolean(
|
|
1405
|
+
request.headers.get(HEADERS.X402_PAYMENT_SIGNATURE) ?? request.headers.get(HEADERS.X402_PAYMENT_LEGACY)
|
|
1406
|
+
);
|
|
1407
|
+
}
|
|
1408
|
+
function hasMppPayment(request) {
|
|
1409
|
+
const auth = request.headers.get(HEADERS.AUTHORIZATION);
|
|
1410
|
+
return Boolean(auth && auth.startsWith(AUTH_SCHEME.MPP_PAYMENT));
|
|
1411
|
+
}
|
|
1412
|
+
function detectProtocol(request) {
|
|
1413
|
+
if (hasX402Payment(request)) return "x402";
|
|
1414
|
+
if (hasMppPayment(request)) return "mpp";
|
|
1415
|
+
if (request.headers.get(HEADERS.SIWX)) return "siwx";
|
|
1416
|
+
return null;
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1365
1419
|
// src/protocols/mpp/credential.ts
|
|
1366
1420
|
import { Credential } from "mppx";
|
|
1367
1421
|
import { getAddress, isAddress } from "viem";
|
|
@@ -1728,10 +1782,7 @@ function settleHashMode(args) {
|
|
|
1728
1782
|
// src/protocols/mpp/strategy.ts
|
|
1729
1783
|
var mppStrategy = {
|
|
1730
1784
|
protocol: "mpp",
|
|
1731
|
-
detects
|
|
1732
|
-
const auth = request.headers.get(HEADERS.AUTHORIZATION);
|
|
1733
|
-
return Boolean(auth && auth.startsWith(AUTH_SCHEME.MPP_PAYMENT));
|
|
1734
|
-
},
|
|
1785
|
+
detects: hasMppPayment,
|
|
1735
1786
|
preflight(request, _routeEntry) {
|
|
1736
1787
|
const info = readMppCredential(request);
|
|
1737
1788
|
if (!info?.sessionAction) return null;
|
|
@@ -1936,15 +1987,14 @@ async function buildChallengeRequirements(server, request, price, accepts, resou
|
|
|
1936
1987
|
function needsFacilitatorEnrichment(accepts) {
|
|
1937
1988
|
return hasSolanaAccepts(accepts);
|
|
1938
1989
|
}
|
|
1939
|
-
async function enrichGroup(group
|
|
1940
|
-
const accepted = await
|
|
1990
|
+
async function enrichGroup(group) {
|
|
1991
|
+
const accepted = await enrichRequirementsFromFacilitatorSupported(
|
|
1941
1992
|
group.facilitator,
|
|
1942
|
-
resource,
|
|
1943
1993
|
group.items.map(({ requirement }) => requirement)
|
|
1944
1994
|
);
|
|
1945
1995
|
if (accepted.length !== group.items.length) {
|
|
1946
1996
|
throw new Error(
|
|
1947
|
-
`Facilitator /
|
|
1997
|
+
`Facilitator /supported enrichment returned ${accepted.length} requirements for ${group.items.length} inputs on ${group.facilitator.url ?? group.facilitator.network}`
|
|
1948
1998
|
);
|
|
1949
1999
|
}
|
|
1950
2000
|
return accepted;
|
|
@@ -1955,13 +2005,13 @@ async function enrichChallengeRequirements(requirements, resource, facilitatorsB
|
|
|
1955
2005
|
const results = await Promise.all(
|
|
1956
2006
|
groups.map(async (group) => {
|
|
1957
2007
|
try {
|
|
1958
|
-
return { success: true, group, accepted: await enrichGroup(group
|
|
2008
|
+
return { success: true, group, accepted: await enrichGroup(group) };
|
|
1959
2009
|
} catch (err) {
|
|
1960
2010
|
const label = group.facilitator.url ?? group.facilitator.network;
|
|
1961
2011
|
const reason = err instanceof Error ? err.message : String(err);
|
|
1962
2012
|
report?.(
|
|
1963
2013
|
"warn",
|
|
1964
|
-
`${label} /
|
|
2014
|
+
`${label} /supported enrichment failed, dropping ${group.items.length} requirement(s): ${reason}`
|
|
1965
2015
|
);
|
|
1966
2016
|
return { success: false, group };
|
|
1967
2017
|
}
|
|
@@ -2166,11 +2216,7 @@ function formatVerifyFailureMessage(failure) {
|
|
|
2166
2216
|
}
|
|
2167
2217
|
var x402Strategy = {
|
|
2168
2218
|
protocol: "x402",
|
|
2169
|
-
detects
|
|
2170
|
-
return Boolean(
|
|
2171
|
-
request.headers.get(HEADERS.X402_PAYMENT_SIGNATURE) ?? request.headers.get(HEADERS.X402_PAYMENT_LEGACY)
|
|
2172
|
-
);
|
|
2173
|
-
},
|
|
2219
|
+
detects: hasX402Payment,
|
|
2174
2220
|
verify: (args) => verifyX402(args),
|
|
2175
2221
|
settle: (args) => settleX402(args),
|
|
2176
2222
|
buildChallenge: (args) => buildX402ChallengeContribution(args)
|
|
@@ -2317,21 +2363,6 @@ function getAllowedStrategies(allowed) {
|
|
|
2317
2363
|
return allowed.map((name) => STRATEGIES[name]);
|
|
2318
2364
|
}
|
|
2319
2365
|
|
|
2320
|
-
// src/protocols/detect.ts
|
|
2321
|
-
function detectProtocol(request) {
|
|
2322
|
-
if (request.headers.get(HEADERS.X402_PAYMENT_SIGNATURE) ?? request.headers.get(HEADERS.X402_PAYMENT_LEGACY)) {
|
|
2323
|
-
return "x402";
|
|
2324
|
-
}
|
|
2325
|
-
const auth = request.headers.get(HEADERS.AUTHORIZATION);
|
|
2326
|
-
if (auth && auth.startsWith(AUTH_SCHEME.MPP_PAYMENT)) {
|
|
2327
|
-
return "mpp";
|
|
2328
|
-
}
|
|
2329
|
-
if (request.headers.get(HEADERS.SIWX)) {
|
|
2330
|
-
return "siwx";
|
|
2331
|
-
}
|
|
2332
|
-
return null;
|
|
2333
|
-
}
|
|
2334
|
-
|
|
2335
2366
|
// src/pipeline/flows/api-key-only.ts
|
|
2336
2367
|
async function runApiKeyOnlyFlow(ctx) {
|
|
2337
2368
|
if (!ctx.routeEntry.apiKeyResolver) {
|
|
@@ -2654,6 +2685,61 @@ async function buildChallengeResponse(ctx, pricing, body, failure) {
|
|
|
2654
2685
|
return response;
|
|
2655
2686
|
}
|
|
2656
2687
|
|
|
2688
|
+
// src/pipeline/flows/paid-preamble.ts
|
|
2689
|
+
async function runPaidPreamble(ctx) {
|
|
2690
|
+
const { request, routeEntry, deps, report } = ctx;
|
|
2691
|
+
const apiKeyGate = await runApiKeyGate(ctx);
|
|
2692
|
+
if (!apiKeyGate.ok) return { done: true, response: apiKeyGate.response };
|
|
2693
|
+
const { account } = apiKeyGate;
|
|
2694
|
+
const pricing = selectPricing(routeEntry.pricing, {
|
|
2695
|
+
alert: report,
|
|
2696
|
+
maxPrice: routeEntry.maxPrice,
|
|
2697
|
+
minPrice: routeEntry.minPrice,
|
|
2698
|
+
route: routeEntry.key
|
|
2699
|
+
});
|
|
2700
|
+
const incomingStrategy = selectIncomingStrategy(request, routeEntry.protocols);
|
|
2701
|
+
const earlyResolution = await resolveEarlyBody({ ctx, pricing, incomingStrategy });
|
|
2702
|
+
if (!earlyResolution.ok) return { done: true, response: earlyResolution.response };
|
|
2703
|
+
const { earlyBody } = earlyResolution;
|
|
2704
|
+
const siwxFastPath = await trySiwxFastPath(ctx, account);
|
|
2705
|
+
if (siwxFastPath) return { done: true, response: siwxFastPath };
|
|
2706
|
+
if (!incomingStrategy) {
|
|
2707
|
+
const initError = protocolInitError(routeEntry, deps);
|
|
2708
|
+
if (initError) return { done: true, response: fail(ctx, 500, initError) };
|
|
2709
|
+
return { done: true, response: await buildChallengeResponse(ctx, pricing, earlyBody) };
|
|
2710
|
+
}
|
|
2711
|
+
return { done: false, account, pricing, incomingStrategy, earlyBody };
|
|
2712
|
+
}
|
|
2713
|
+
async function runPaidVerify(args) {
|
|
2714
|
+
const { ctx, strategy, pricing, parsedBody, price } = args;
|
|
2715
|
+
const { request, routeEntry, deps, report } = ctx;
|
|
2716
|
+
const verifyOutcome = await strategy.verify({
|
|
2717
|
+
request,
|
|
2718
|
+
body: parsedBody,
|
|
2719
|
+
price,
|
|
2720
|
+
routeEntry,
|
|
2721
|
+
deps,
|
|
2722
|
+
report
|
|
2723
|
+
});
|
|
2724
|
+
if (verifyOutcome.ok === false) {
|
|
2725
|
+
if (verifyOutcome.kind === "config") {
|
|
2726
|
+
return { ok: false, response: fail(ctx, 500, verifyOutcome.message, parsedBody) };
|
|
2727
|
+
}
|
|
2728
|
+
return {
|
|
2729
|
+
ok: false,
|
|
2730
|
+
response: await buildChallengeResponse(ctx, pricing, parsedBody, verifyOutcome.failure)
|
|
2731
|
+
};
|
|
2732
|
+
}
|
|
2733
|
+
ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
|
|
2734
|
+
firePaymentVerified(ctx, {
|
|
2735
|
+
protocol: strategy.protocol,
|
|
2736
|
+
payer: verifyOutcome.wallet,
|
|
2737
|
+
amount: price,
|
|
2738
|
+
network: verifyOutcome.payment.network
|
|
2739
|
+
});
|
|
2740
|
+
return { ok: true, verifyOutcome };
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2657
2743
|
// src/pipeline/flows/dynamic/dynamic-body-and-price.ts
|
|
2658
2744
|
async function resolveDynamicBodyAndPrice(args) {
|
|
2659
2745
|
const { ctx, pricing, skipBody } = args;
|
|
@@ -3020,27 +3106,10 @@ async function runDynamicStreamFlow(args) {
|
|
|
3020
3106
|
|
|
3021
3107
|
// src/pipeline/flows/dynamic/dynamic-paid.ts
|
|
3022
3108
|
async function runDynamicPaidFlow(ctx) {
|
|
3023
|
-
const { request, routeEntry
|
|
3024
|
-
const
|
|
3025
|
-
if (
|
|
3026
|
-
const { account } =
|
|
3027
|
-
const pricing = selectPricing(routeEntry.pricing, {
|
|
3028
|
-
alert: report,
|
|
3029
|
-
maxPrice: routeEntry.maxPrice,
|
|
3030
|
-
minPrice: routeEntry.minPrice,
|
|
3031
|
-
route: routeEntry.key
|
|
3032
|
-
});
|
|
3033
|
-
const incomingStrategy = selectIncomingStrategy(request, routeEntry.protocols);
|
|
3034
|
-
const earlyResolution = await resolveEarlyBody({ ctx, pricing, incomingStrategy });
|
|
3035
|
-
if (!earlyResolution.ok) return earlyResolution.response;
|
|
3036
|
-
const { earlyBody } = earlyResolution;
|
|
3037
|
-
const siwxFastPath = await trySiwxFastPath(ctx, account);
|
|
3038
|
-
if (siwxFastPath) return siwxFastPath;
|
|
3039
|
-
if (!incomingStrategy) {
|
|
3040
|
-
const initError = protocolInitError(routeEntry, deps);
|
|
3041
|
-
if (initError) return fail(ctx, 500, initError);
|
|
3042
|
-
return buildChallengeResponse(ctx, pricing, earlyBody);
|
|
3043
|
-
}
|
|
3109
|
+
const { request, routeEntry } = ctx;
|
|
3110
|
+
const preamble = await runPaidPreamble(ctx);
|
|
3111
|
+
if (preamble.done) return preamble.response;
|
|
3112
|
+
const { account, pricing, incomingStrategy } = preamble;
|
|
3044
3113
|
const { skipBody, skipHandler } = resolveDynamicPreflight(incomingStrategy, request, routeEntry);
|
|
3045
3114
|
if (skipHandler) {
|
|
3046
3115
|
return runDynamicChannelMgmtFlow({
|
|
@@ -3054,27 +3123,15 @@ async function runDynamicPaidFlow(ctx) {
|
|
|
3054
3123
|
const bodyAndPrice = await resolveDynamicBodyAndPrice({ ctx, pricing, skipBody });
|
|
3055
3124
|
if (!bodyAndPrice.ok) return bodyAndPrice.response;
|
|
3056
3125
|
const { parsedBody, price } = bodyAndPrice;
|
|
3057
|
-
const
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
report
|
|
3064
|
-
});
|
|
3065
|
-
if (verifyOutcome.ok === false) {
|
|
3066
|
-
if (verifyOutcome.kind === "config") {
|
|
3067
|
-
return fail(ctx, 500, verifyOutcome.message, parsedBody);
|
|
3068
|
-
}
|
|
3069
|
-
return buildChallengeResponse(ctx, pricing, parsedBody, verifyOutcome.failure);
|
|
3070
|
-
}
|
|
3071
|
-
ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
|
|
3072
|
-
firePaymentVerified(ctx, {
|
|
3073
|
-
protocol: incomingStrategy.protocol,
|
|
3074
|
-
payer: verifyOutcome.wallet,
|
|
3075
|
-
amount: price,
|
|
3076
|
-
network: verifyOutcome.payment.network
|
|
3126
|
+
const verify = await runPaidVerify({
|
|
3127
|
+
ctx,
|
|
3128
|
+
strategy: incomingStrategy,
|
|
3129
|
+
pricing,
|
|
3130
|
+
parsedBody,
|
|
3131
|
+
price
|
|
3077
3132
|
});
|
|
3133
|
+
if (!verify.ok) return verify.response;
|
|
3134
|
+
const { verifyOutcome } = verify;
|
|
3078
3135
|
const result = await invokeDynamic(ctx, verifyOutcome, account, parsedBody);
|
|
3079
3136
|
switch (result.kind) {
|
|
3080
3137
|
case "stream":
|
|
@@ -3206,51 +3263,21 @@ function failureFromCause2(cause) {
|
|
|
3206
3263
|
|
|
3207
3264
|
// src/pipeline/flows/static/static-paid.ts
|
|
3208
3265
|
async function runStaticPaidFlow(ctx) {
|
|
3209
|
-
const
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
const { account } = apiKeyGate;
|
|
3213
|
-
const pricing = selectPricing(routeEntry.pricing, {
|
|
3214
|
-
alert: report,
|
|
3215
|
-
maxPrice: routeEntry.maxPrice,
|
|
3216
|
-
minPrice: routeEntry.minPrice,
|
|
3217
|
-
route: routeEntry.key
|
|
3218
|
-
});
|
|
3219
|
-
const incomingStrategy = selectIncomingStrategy(request, routeEntry.protocols);
|
|
3220
|
-
const earlyResolution = await resolveEarlyBody({ ctx, pricing, incomingStrategy });
|
|
3221
|
-
if (!earlyResolution.ok) return earlyResolution.response;
|
|
3222
|
-
const { earlyBody } = earlyResolution;
|
|
3223
|
-
const siwxFastPath = await trySiwxFastPath(ctx, account);
|
|
3224
|
-
if (siwxFastPath) return siwxFastPath;
|
|
3225
|
-
if (!incomingStrategy) {
|
|
3226
|
-
const initError = protocolInitError(routeEntry, deps);
|
|
3227
|
-
if (initError) return fail(ctx, 500, initError);
|
|
3228
|
-
return buildChallengeResponse(ctx, pricing, earlyBody);
|
|
3229
|
-
}
|
|
3266
|
+
const preamble = await runPaidPreamble(ctx);
|
|
3267
|
+
if (preamble.done) return preamble.response;
|
|
3268
|
+
const { account, pricing, incomingStrategy } = preamble;
|
|
3230
3269
|
const bodyAndPrice = await resolveStaticBodyAndPrice({ ctx, pricing });
|
|
3231
3270
|
if (!bodyAndPrice.ok) return bodyAndPrice.response;
|
|
3232
3271
|
const { parsedBody, price } = bodyAndPrice;
|
|
3233
|
-
const
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
report
|
|
3240
|
-
});
|
|
3241
|
-
if (verifyOutcome.ok === false) {
|
|
3242
|
-
if (verifyOutcome.kind === "config") {
|
|
3243
|
-
return fail(ctx, 500, verifyOutcome.message, parsedBody);
|
|
3244
|
-
}
|
|
3245
|
-
return buildChallengeResponse(ctx, pricing, parsedBody, verifyOutcome.failure);
|
|
3246
|
-
}
|
|
3247
|
-
ctx.pluginCtx.setVerifiedWallet(verifyOutcome.wallet);
|
|
3248
|
-
firePaymentVerified(ctx, {
|
|
3249
|
-
protocol: incomingStrategy.protocol,
|
|
3250
|
-
payer: verifyOutcome.wallet,
|
|
3251
|
-
amount: price,
|
|
3252
|
-
network: verifyOutcome.payment.network
|
|
3272
|
+
const verify = await runPaidVerify({
|
|
3273
|
+
ctx,
|
|
3274
|
+
strategy: incomingStrategy,
|
|
3275
|
+
pricing,
|
|
3276
|
+
parsedBody,
|
|
3277
|
+
price
|
|
3253
3278
|
});
|
|
3279
|
+
if (!verify.ok) return verify.response;
|
|
3280
|
+
const { verifyOutcome } = verify;
|
|
3254
3281
|
const result = await invokePaidStatic(
|
|
3255
3282
|
ctx,
|
|
3256
3283
|
verifyOutcome.wallet,
|
|
@@ -3742,55 +3769,35 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3742
3769
|
return next;
|
|
3743
3770
|
}
|
|
3744
3771
|
paid(arg, options) {
|
|
3745
|
-
|
|
3772
|
+
const self = this;
|
|
3773
|
+
return self.applyPaid(normalizePaidArg(self.#s.key, arg, options), "paid");
|
|
3746
3774
|
}
|
|
3747
|
-
/**
|
|
3748
|
-
* x402-only handler-computed billing. The handler receives `charge(amount)`
|
|
3749
|
-
* and the request settles once for the accumulated total, capped at
|
|
3750
|
-
* `maxPrice`. Requires an `'upto'` accept on at least one configured network.
|
|
3751
|
-
* Pass a bare string as sugar for `{ maxPrice }`.
|
|
3752
|
-
*
|
|
3753
|
-
* @example
|
|
3754
|
-
* ```ts
|
|
3755
|
-
* router.route('llm')
|
|
3756
|
-
* .upTo('0.05')
|
|
3757
|
-
* .body(schema)
|
|
3758
|
-
* .handler(async ({ body, charge }) => { await charge('0.001'); ... });
|
|
3759
|
-
* ```
|
|
3760
|
-
*/
|
|
3761
3775
|
upTo(arg) {
|
|
3762
|
-
|
|
3776
|
+
const self = this;
|
|
3777
|
+
return self.applyPaid(normalizeUpToArg(self.#s.key, arg), "upTo");
|
|
3763
3778
|
}
|
|
3764
|
-
/**
|
|
3765
|
-
* MPP-only per-tick billing. `.handler()` bills exactly `tickCost`;
|
|
3766
|
-
* `.stream()` calls `charge()` (no-arg) per yield, settling per tick up to
|
|
3767
|
-
* `maxPrice`. Requires `RouterConfig.mpp.session`.
|
|
3768
|
-
*
|
|
3769
|
-
* @example
|
|
3770
|
-
* ```ts
|
|
3771
|
-
* router.route('llm/stream')
|
|
3772
|
-
* .metered({ tickCost: '0.0001', maxPrice: '0.05', unitType: 'token' })
|
|
3773
|
-
* .stream(async function* ({ charge }) { await charge(); yield 'hi'; });
|
|
3774
|
-
* ```
|
|
3775
|
-
*/
|
|
3776
3779
|
metered(options) {
|
|
3777
|
-
|
|
3780
|
+
const self = this;
|
|
3781
|
+
return self.applyPaid(normalizeMeteredArg(self.#s.key, options), "metered");
|
|
3778
3782
|
}
|
|
3779
3783
|
applyPaid(normalized, method) {
|
|
3780
3784
|
const { pricing, resolvedOptions, billing, tickCost, unitType, maxPrice } = normalized;
|
|
3781
3785
|
if (this.#s.authMode === "unprotected") {
|
|
3782
|
-
throw new
|
|
3783
|
-
|
|
3786
|
+
throw new RouteDefinitionError(
|
|
3787
|
+
this.#s.key,
|
|
3788
|
+
`Cannot combine .unprotected() and .${method}() on the same route.`
|
|
3784
3789
|
);
|
|
3785
3790
|
}
|
|
3786
3791
|
if (this.#s.pricing !== void 0) {
|
|
3787
|
-
throw new
|
|
3788
|
-
|
|
3792
|
+
throw new RouteDefinitionError(
|
|
3793
|
+
this.#s.key,
|
|
3794
|
+
`Cannot combine .paid(), .upTo(), and .metered() \u2014 pick one pricing mode.`
|
|
3789
3795
|
);
|
|
3790
3796
|
}
|
|
3791
3797
|
if (this.#s.siwxEnabled && billing === "metered") {
|
|
3792
|
-
throw new
|
|
3793
|
-
|
|
3798
|
+
throw new RouteDefinitionError(
|
|
3799
|
+
this.#s.key,
|
|
3800
|
+
`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.`
|
|
3794
3801
|
);
|
|
3795
3802
|
}
|
|
3796
3803
|
const next = this.fork();
|
|
@@ -3798,15 +3805,17 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3798
3805
|
next.#s.pricing = pricing;
|
|
3799
3806
|
if (billing === "upto") {
|
|
3800
3807
|
if (resolvedOptions.protocols?.some((p) => p !== "x402")) {
|
|
3801
|
-
throw new
|
|
3802
|
-
|
|
3808
|
+
throw new RouteDefinitionError(
|
|
3809
|
+
this.#s.key,
|
|
3810
|
+
`.upTo() is x402-only \u2014 remove the conflicting protocols override.`
|
|
3803
3811
|
);
|
|
3804
3812
|
}
|
|
3805
3813
|
next.#s.protocols = ["x402"];
|
|
3806
3814
|
} else if (billing === "metered") {
|
|
3807
3815
|
if (resolvedOptions.protocols?.some((p) => p !== "mpp")) {
|
|
3808
|
-
throw new
|
|
3809
|
-
|
|
3816
|
+
throw new RouteDefinitionError(
|
|
3817
|
+
this.#s.key,
|
|
3818
|
+
`.metered() is MPP-only \u2014 remove the conflicting protocols override.`
|
|
3810
3819
|
);
|
|
3811
3820
|
}
|
|
3812
3821
|
next.#s.protocols = ["mpp"];
|
|
@@ -3830,73 +3839,69 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3830
3839
|
if (typeof pricing === "object" && "tiers" in pricing) {
|
|
3831
3840
|
for (const [tierKey, tierConfig] of Object.entries(pricing.tiers)) {
|
|
3832
3841
|
if (!tierKey) {
|
|
3833
|
-
throw new
|
|
3842
|
+
throw new RouteDefinitionError(this.#s.key, `tier key cannot be empty`);
|
|
3834
3843
|
}
|
|
3835
3844
|
if (!isPositiveDecimal(tierConfig.price)) {
|
|
3836
|
-
throw new
|
|
3837
|
-
|
|
3845
|
+
throw new RouteDefinitionError(
|
|
3846
|
+
this.#s.key,
|
|
3847
|
+
`tier '${tierKey}' price '${tierConfig.price}' must be a positive decimal string`
|
|
3838
3848
|
);
|
|
3839
3849
|
}
|
|
3840
3850
|
}
|
|
3841
3851
|
}
|
|
3842
3852
|
if (billing === "exact" && typeof pricing === "string" && !isPositiveDecimal(pricing)) {
|
|
3843
|
-
throw new
|
|
3844
|
-
|
|
3853
|
+
throw new RouteDefinitionError(
|
|
3854
|
+
this.#s.key,
|
|
3855
|
+
`price '${pricing}' must be a positive decimal string`
|
|
3845
3856
|
);
|
|
3846
3857
|
}
|
|
3847
3858
|
if (typeof pricing === "function" && next.#s.maxPrice === void 0) {
|
|
3848
|
-
throw new
|
|
3849
|
-
|
|
3859
|
+
throw new RouteDefinitionError(
|
|
3860
|
+
this.#s.key,
|
|
3861
|
+
`dynamic pricing requires maxPrice \u2014 without it, bare probes would advertise a $0 challenge`
|
|
3850
3862
|
);
|
|
3851
3863
|
}
|
|
3852
3864
|
if (next.#s.maxPrice !== void 0 && !isPositiveDecimal(next.#s.maxPrice)) {
|
|
3853
|
-
throw new
|
|
3854
|
-
|
|
3865
|
+
throw new RouteDefinitionError(
|
|
3866
|
+
this.#s.key,
|
|
3867
|
+
`maxPrice '${next.#s.maxPrice}' must be a positive decimal string`
|
|
3855
3868
|
);
|
|
3856
3869
|
}
|
|
3857
3870
|
if (next.#s.minPrice !== void 0 && !isPositiveDecimal(next.#s.minPrice)) {
|
|
3858
|
-
throw new
|
|
3859
|
-
|
|
3871
|
+
throw new RouteDefinitionError(
|
|
3872
|
+
this.#s.key,
|
|
3873
|
+
`minPrice '${next.#s.minPrice}' must be a positive decimal string`
|
|
3860
3874
|
);
|
|
3861
3875
|
}
|
|
3862
3876
|
if (next.#s.tickCost !== void 0 && !isPositiveDecimal(next.#s.tickCost)) {
|
|
3863
|
-
throw new
|
|
3864
|
-
|
|
3877
|
+
throw new RouteDefinitionError(
|
|
3878
|
+
this.#s.key,
|
|
3879
|
+
`tickCost '${next.#s.tickCost}' must be a positive decimal string`
|
|
3865
3880
|
);
|
|
3866
3881
|
}
|
|
3867
3882
|
return next;
|
|
3868
3883
|
}
|
|
3869
|
-
/**
|
|
3870
|
-
* Require Sign-In-with-X wallet identity on this route — clients prove
|
|
3871
|
-
* control of a wallet via a signed challenge. Composes with `.paid()` and
|
|
3872
|
-
* `.upTo()` for pay-once-then-replay: the first request settles normally,
|
|
3873
|
-
* subsequent requests with a valid SIWX signature for the same wallet skip
|
|
3874
|
-
* payment (on `.upTo()`, `charge(amount)` becomes a no-op on the replay).
|
|
3875
|
-
* Mutually exclusive with `.metered()`.
|
|
3876
|
-
*
|
|
3877
|
-
* @example
|
|
3878
|
-
* ```ts
|
|
3879
|
-
* router.route('profile').siwx().handler(async ({ wallet }) => getProfile(wallet));
|
|
3880
|
-
* router.route('inbox').paid('0.01').siwx().handler(async ({ wallet }) => getInbox(wallet));
|
|
3881
|
-
* ```
|
|
3882
|
-
*/
|
|
3883
3884
|
siwx() {
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3885
|
+
const self = this;
|
|
3886
|
+
if (self.#s.authMode === "unprotected") {
|
|
3887
|
+
throw new RouteDefinitionError(
|
|
3888
|
+
self.#s.key,
|
|
3889
|
+
`Cannot combine .unprotected() and .siwx() on the same route.`
|
|
3887
3890
|
);
|
|
3888
3891
|
}
|
|
3889
|
-
if (
|
|
3890
|
-
throw new
|
|
3891
|
-
|
|
3892
|
+
if (self.#s.apiKeyResolver) {
|
|
3893
|
+
throw new RouteDefinitionError(
|
|
3894
|
+
self.#s.key,
|
|
3895
|
+
`Combining .siwx() and .apiKey() is not supported on the same route.`
|
|
3892
3896
|
);
|
|
3893
3897
|
}
|
|
3894
|
-
if (
|
|
3895
|
-
throw new
|
|
3896
|
-
|
|
3898
|
+
if (self.#s.billing === "metered") {
|
|
3899
|
+
throw new RouteDefinitionError(
|
|
3900
|
+
self.#s.key,
|
|
3901
|
+
`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.`
|
|
3897
3902
|
);
|
|
3898
3903
|
}
|
|
3899
|
-
const next =
|
|
3904
|
+
const next = self.fork();
|
|
3900
3905
|
next.#s.siwxEnabled = true;
|
|
3901
3906
|
if (next.#s.authMode === "paid" || next.#s.pricing) {
|
|
3902
3907
|
next.#s.authMode = "paid";
|
|
@@ -3907,59 +3912,48 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3907
3912
|
next.#s.protocols = [];
|
|
3908
3913
|
return next;
|
|
3909
3914
|
}
|
|
3910
|
-
/**
|
|
3911
|
-
* Require an `X-API-Key` header (or `Authorization: Bearer <key>`); the
|
|
3912
|
-
* resolver returns the account record, or `null` for 401. Composes with
|
|
3913
|
-
* `.paid()` — key is checked first, payment second.
|
|
3914
|
-
*
|
|
3915
|
-
* @example
|
|
3916
|
-
* ```ts
|
|
3917
|
-
* router
|
|
3918
|
-
* .route('admin/users')
|
|
3919
|
-
* .apiKey(async (key) => db.admin.findByKey(key))
|
|
3920
|
-
* .handler(async ({ account }) => db.user.list(account.orgId));
|
|
3921
|
-
* ```
|
|
3922
|
-
*/
|
|
3923
3915
|
apiKey(resolver) {
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3916
|
+
const self = this;
|
|
3917
|
+
if (self.#s.authMode === "unprotected") {
|
|
3918
|
+
throw new RouteDefinitionError(
|
|
3919
|
+
self.#s.key,
|
|
3920
|
+
`Cannot combine .unprotected() and .apiKey() on the same route.`
|
|
3927
3921
|
);
|
|
3928
3922
|
}
|
|
3929
|
-
|
|
3923
|
+
if (self.#s.siwxEnabled) {
|
|
3924
|
+
throw new RouteDefinitionError(
|
|
3925
|
+
self.#s.key,
|
|
3926
|
+
`Combining .apiKey() and .siwx() is not supported on the same route.`
|
|
3927
|
+
);
|
|
3928
|
+
}
|
|
3929
|
+
const next = self.fork();
|
|
3930
3930
|
next.#s.authMode = "apiKey";
|
|
3931
3931
|
next.#s.apiKeyResolver = resolver;
|
|
3932
3932
|
return next;
|
|
3933
3933
|
}
|
|
3934
|
-
/**
|
|
3935
|
-
* Mark the route as public — no auth, no payment, no SIWX. The handler
|
|
3936
|
-
* receives `null` for `wallet`, `payment`, and `account`.
|
|
3937
|
-
*
|
|
3938
|
-
* @example
|
|
3939
|
-
* ```ts
|
|
3940
|
-
* router.route('health').unprotected().handler(async () => ({ status: 'ok' }));
|
|
3941
|
-
* ```
|
|
3942
|
-
*/
|
|
3943
3934
|
unprotected() {
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3935
|
+
const self = this;
|
|
3936
|
+
if (self.#s.authMode && self.#s.authMode !== "unprotected") {
|
|
3937
|
+
throw new RouteDefinitionError(
|
|
3938
|
+
self.#s.key,
|
|
3939
|
+
`Cannot combine .unprotected() and .${self.#s.authMode}() on the same route.`
|
|
3947
3940
|
);
|
|
3948
3941
|
}
|
|
3949
|
-
if (
|
|
3950
|
-
throw new
|
|
3951
|
-
|
|
3942
|
+
if (self.#s.pricing) {
|
|
3943
|
+
throw new RouteDefinitionError(
|
|
3944
|
+
self.#s.key,
|
|
3945
|
+
`Cannot combine .unprotected() and .paid() on the same route.`
|
|
3952
3946
|
);
|
|
3953
3947
|
}
|
|
3954
|
-
const next =
|
|
3948
|
+
const next = self.fork();
|
|
3955
3949
|
next.#s.authMode = "unprotected";
|
|
3956
3950
|
next.#s.protocols = [];
|
|
3957
3951
|
return next;
|
|
3958
3952
|
}
|
|
3959
3953
|
/**
|
|
3960
3954
|
* Tag the route with an upstream provider for discovery and provider-side
|
|
3961
|
-
* monitoring. The provider name and config surface in
|
|
3962
|
-
*
|
|
3955
|
+
* monitoring. The provider name and config surface in OpenAPI discovery
|
|
3956
|
+
* output.
|
|
3963
3957
|
*
|
|
3964
3958
|
* @example
|
|
3965
3959
|
* ```ts
|
|
@@ -4056,8 +4050,8 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
4056
4050
|
return next;
|
|
4057
4051
|
}
|
|
4058
4052
|
/**
|
|
4059
|
-
* Set a human-readable summary of the route. Surfaces in OpenAPI
|
|
4060
|
-
* `
|
|
4053
|
+
* Set a human-readable summary of the route. Surfaces in OpenAPI and
|
|
4054
|
+
* `llms.txt` discovery output.
|
|
4061
4055
|
*
|
|
4062
4056
|
* @example
|
|
4063
4057
|
* ```ts
|
|
@@ -4193,38 +4187,46 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
4193
4187
|
}
|
|
4194
4188
|
register(handlerFn, streaming) {
|
|
4195
4189
|
if (!this.#s.authMode) {
|
|
4196
|
-
throw new
|
|
4197
|
-
|
|
4190
|
+
throw new RouteDefinitionError(
|
|
4191
|
+
this.#s.key,
|
|
4192
|
+
`Select an auth mode: .paid(pricing), .upTo(maxPrice), .metered(options), .siwx(), .apiKey(resolver), or .unprotected()`
|
|
4198
4193
|
);
|
|
4199
4194
|
}
|
|
4200
4195
|
if (this.#s.validateFn && !this.#s.bodySchema) {
|
|
4201
|
-
throw new
|
|
4202
|
-
|
|
4196
|
+
throw new RouteDefinitionError(
|
|
4197
|
+
this.#s.key,
|
|
4198
|
+
`.validate() requires .body() \u2014 validation runs on parsed body`
|
|
4203
4199
|
);
|
|
4204
4200
|
}
|
|
4205
4201
|
if (this.#s.settlement && !this.#s.pricing) {
|
|
4206
|
-
throw new
|
|
4202
|
+
throw new RouteDefinitionError(this.#s.key, `.settlement() requires a paid route`);
|
|
4207
4203
|
}
|
|
4208
4204
|
if (this.#s.mppInfo?.settleBeforeHandler) {
|
|
4209
4205
|
if (!this.#s.pricing) {
|
|
4210
|
-
throw new
|
|
4206
|
+
throw new RouteDefinitionError(
|
|
4207
|
+
this.#s.key,
|
|
4208
|
+
`mpp.settleBeforeHandler requires a paid route`
|
|
4209
|
+
);
|
|
4211
4210
|
}
|
|
4212
4211
|
if (this.#s.billing !== "exact") {
|
|
4213
|
-
throw new
|
|
4214
|
-
|
|
4212
|
+
throw new RouteDefinitionError(
|
|
4213
|
+
this.#s.key,
|
|
4214
|
+
`mpp.settleBeforeHandler is only supported on .paid() routes`
|
|
4215
4215
|
);
|
|
4216
4216
|
}
|
|
4217
4217
|
if (this.#s.settlement?.beforeSettle) {
|
|
4218
|
-
throw new
|
|
4219
|
-
|
|
4218
|
+
throw new RouteDefinitionError(
|
|
4219
|
+
this.#s.key,
|
|
4220
|
+
`mpp.settleBeforeHandler is incompatible with .settlement({ beforeSettle }) \u2014 MPP payment is already broadcast before the handler runs`
|
|
4220
4221
|
);
|
|
4221
4222
|
}
|
|
4222
4223
|
}
|
|
4223
4224
|
if (this.#s.billing === "upto") {
|
|
4224
4225
|
const hasUpto = this.#s.deps.x402Accepts.some((accept) => accept.scheme === "upto");
|
|
4225
4226
|
if (!hasUpto) {
|
|
4226
|
-
throw new
|
|
4227
|
-
|
|
4227
|
+
throw new RouteDefinitionError(
|
|
4228
|
+
this.#s.key,
|
|
4229
|
+
`.upTo() requires an 'upto' accept on at least one configured network. Add { scheme: 'upto', network, asset } to RouterConfig.x402.accepts.`
|
|
4228
4230
|
);
|
|
4229
4231
|
}
|
|
4230
4232
|
}
|
|
@@ -4233,26 +4235,30 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
4233
4235
|
(accept) => (accept.scheme ?? "exact") !== "upto"
|
|
4234
4236
|
);
|
|
4235
4237
|
if (!hasExact) {
|
|
4236
|
-
throw new
|
|
4237
|
-
|
|
4238
|
+
throw new RouteDefinitionError(
|
|
4239
|
+
this.#s.key,
|
|
4240
|
+
`.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.`
|
|
4238
4241
|
);
|
|
4239
4242
|
}
|
|
4240
4243
|
}
|
|
4241
4244
|
if (this.#s.billing === "metered") {
|
|
4242
4245
|
if (!this.#s.deps.mppSessionConfig) {
|
|
4243
|
-
throw new
|
|
4244
|
-
|
|
4246
|
+
throw new RouteDefinitionError(
|
|
4247
|
+
this.#s.key,
|
|
4248
|
+
`.metered() requires MPP session mode. Set RouterConfig.mpp.session = {} and provide mpp.operatorKey.`
|
|
4245
4249
|
);
|
|
4246
4250
|
}
|
|
4247
4251
|
}
|
|
4248
4252
|
if (streaming && this.#s.billing !== "metered") {
|
|
4249
|
-
throw new
|
|
4250
|
-
|
|
4253
|
+
throw new RouteDefinitionError(
|
|
4254
|
+
this.#s.key,
|
|
4255
|
+
`.stream() requires .metered() \u2014 static/free/upto routes can't meter per-chunk billing.`
|
|
4251
4256
|
);
|
|
4252
4257
|
}
|
|
4253
4258
|
if (this.#s.description !== void 0 && this.#s.description.length > MAX_X402_DESCRIPTION_LENGTH && this.#s.pricing !== void 0 && this.#s.protocols.includes("x402")) {
|
|
4254
|
-
throw new
|
|
4255
|
-
|
|
4259
|
+
throw new RouteDefinitionError(
|
|
4260
|
+
this.#s.key,
|
|
4261
|
+
`.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.`
|
|
4256
4262
|
);
|
|
4257
4263
|
}
|
|
4258
4264
|
validateExamples(
|
|
@@ -4320,14 +4326,15 @@ function normalizePaidArg(routeKey, arg, options) {
|
|
|
4320
4326
|
if ("price" in arg && typeof arg.price === "string") {
|
|
4321
4327
|
return { pricing: arg.price, resolvedOptions: arg, billing: "exact" };
|
|
4322
4328
|
}
|
|
4323
|
-
throw new
|
|
4324
|
-
|
|
4329
|
+
throw new RouteDefinitionError(
|
|
4330
|
+
routeKey,
|
|
4331
|
+
`.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().`
|
|
4325
4332
|
);
|
|
4326
4333
|
}
|
|
4327
4334
|
function normalizeUpToArg(routeKey, arg) {
|
|
4328
4335
|
const options = typeof arg === "string" ? { maxPrice: arg } : arg;
|
|
4329
4336
|
if (!options.maxPrice) {
|
|
4330
|
-
throw new
|
|
4337
|
+
throw new RouteDefinitionError(routeKey, `.upTo() requires maxPrice`);
|
|
4331
4338
|
}
|
|
4332
4339
|
return {
|
|
4333
4340
|
pricing: options.maxPrice,
|
|
@@ -4339,10 +4346,10 @@ function normalizeUpToArg(routeKey, arg) {
|
|
|
4339
4346
|
}
|
|
4340
4347
|
function normalizeMeteredArg(routeKey, options) {
|
|
4341
4348
|
if (!options.maxPrice) {
|
|
4342
|
-
throw new
|
|
4349
|
+
throw new RouteDefinitionError(routeKey, `.metered() requires maxPrice`);
|
|
4343
4350
|
}
|
|
4344
4351
|
if (!options.tickCost) {
|
|
4345
|
-
throw new
|
|
4352
|
+
throw new RouteDefinitionError(routeKey, `.metered() requires tickCost`);
|
|
4346
4353
|
}
|
|
4347
4354
|
return {
|
|
4348
4355
|
pricing: options.maxPrice,
|
|
@@ -4649,9 +4656,9 @@ function createNotFoundHandler(baseUrl) {
|
|
|
4649
4656
|
code: "route_not_found",
|
|
4650
4657
|
error: "Route not found. Rediscover this origin and retry with the current discovery document.",
|
|
4651
4658
|
requestedUrl: request.url,
|
|
4659
|
+
// The deprecated /.well-known/x402 surface is intentionally not advertised.
|
|
4652
4660
|
discovery: {
|
|
4653
4661
|
openapi: `${normalizedBase}/openapi.json`,
|
|
4654
|
-
wellKnown: `${normalizedBase}/.well-known/x402`,
|
|
4655
4662
|
llmsTxt: `${normalizedBase}/llms.txt`
|
|
4656
4663
|
}
|
|
4657
4664
|
},
|
|
@@ -4684,6 +4691,7 @@ function formatRouterConfigIssues(issues) {
|
|
|
4684
4691
|
}
|
|
4685
4692
|
|
|
4686
4693
|
// src/config/schema.ts
|
|
4694
|
+
init_accepts();
|
|
4687
4695
|
init_constants();
|
|
4688
4696
|
import { z } from "zod";
|
|
4689
4697
|
|
|
@@ -4873,18 +4881,8 @@ function deriveBaseUrlEnv(env) {
|
|
|
4873
4881
|
BASE_URL: vercelBaseUrl
|
|
4874
4882
|
};
|
|
4875
4883
|
}
|
|
4876
|
-
function getConfiguredX402Accepts2(config) {
|
|
4877
|
-
if (config.x402?.accepts?.length) return [...config.x402.accepts];
|
|
4878
|
-
return [
|
|
4879
|
-
{
|
|
4880
|
-
scheme: "exact",
|
|
4881
|
-
network: config.network ?? BASE_MAINNET_NETWORK,
|
|
4882
|
-
payTo: config.payeeAddress
|
|
4883
|
-
}
|
|
4884
|
-
];
|
|
4885
|
-
}
|
|
4886
4884
|
function validateX402Config(config, env) {
|
|
4887
|
-
const accepts =
|
|
4885
|
+
const accepts = getConfiguredX402Accepts(config);
|
|
4888
4886
|
const issues = [];
|
|
4889
4887
|
const push = (code, message) => issues.push({ code, protocol: "x402", message });
|
|
4890
4888
|
if (accepts.length === 0) {
|
|
@@ -5185,7 +5183,8 @@ function getMppxRequestContext(args) {
|
|
|
5185
5183
|
recipient: mppConfig.recipient ?? payeeAddress,
|
|
5186
5184
|
getClient,
|
|
5187
5185
|
...feePayerAccount ? { feePayer: feePayerAccount } : {},
|
|
5188
|
-
...resolvedStore ? { store: resolvedStore } : {}
|
|
5186
|
+
...resolvedStore ? { store: resolvedStore } : {},
|
|
5187
|
+
...mppConfig.feePayerPolicy ? { feePayerPolicy: mppConfig.feePayerPolicy } : {}
|
|
5189
5188
|
}),
|
|
5190
5189
|
...sessionEnabled ? [
|
|
5191
5190
|
tempo.session({
|
|
@@ -5223,7 +5222,7 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
|
|
|
5223
5222
|
try {
|
|
5224
5223
|
const { Mppx, tempo } = await import("mppx/server");
|
|
5225
5224
|
const { createClient, http } = await import("viem");
|
|
5226
|
-
const { tempo: tempoChain } = await import("viem/chains");
|
|
5225
|
+
const { tempo: tempoChain } = await import("viem/tempo/chains");
|
|
5227
5226
|
const { privateKeyToAccount: privateKeyToAccount2 } = await import("viem/accounts");
|
|
5228
5227
|
const rpcUrl = config.mpp.rpcUrl ?? process.env.TEMPO_RPC_URL ?? DEFAULT_TEMPO_RPC_URL;
|
|
5229
5228
|
const tempoClient = createClient({ chain: tempoChain, transport: http(rpcUrl) });
|
|
@@ -5241,7 +5240,9 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
|
|
|
5241
5240
|
getClient,
|
|
5242
5241
|
...operatorAccount ? { account: operatorAccount } : {},
|
|
5243
5242
|
...feePayerAccount ? { feePayer: feePayerAccount } : {},
|
|
5244
|
-
...resolvedStore ? { store: resolvedStore } : {}
|
|
5243
|
+
...resolvedStore ? { store: resolvedStore } : {},
|
|
5244
|
+
...mppConfig.feePayerPolicy ? { feePayerPolicy: mppConfig.feePayerPolicy } : {},
|
|
5245
|
+
...mppConfig.session?.settlementSchedule ? { settlementSchedule: mppConfig.session.settlementSchedule } : {}
|
|
5245
5246
|
};
|
|
5246
5247
|
const mppxArgs = {
|
|
5247
5248
|
Mppx,
|
|
@@ -5257,6 +5258,7 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
|
|
|
5257
5258
|
};
|
|
5258
5259
|
const primary = getMppxRequestContext(mppxArgs);
|
|
5259
5260
|
const streaming = getMppxStreamingContext(mppxArgs);
|
|
5261
|
+
subscribePaymentFailedAlerts(config.plugin, [primary, streaming]);
|
|
5260
5262
|
const mppx = {
|
|
5261
5263
|
charge: primary.charge,
|
|
5262
5264
|
...primary.session ? { sessionRequest: primary.session } : {},
|
|
@@ -5267,6 +5269,41 @@ async function initMpp(config, resolvedBaseUrl, kvStore, configError) {
|
|
|
5267
5269
|
return { initError: err instanceof Error ? err.message : String(err) };
|
|
5268
5270
|
}
|
|
5269
5271
|
}
|
|
5272
|
+
function subscribePaymentFailedAlerts(plugin, instances) {
|
|
5273
|
+
if (!plugin?.onAlert) return;
|
|
5274
|
+
const ctx = {
|
|
5275
|
+
requestId: "mppx",
|
|
5276
|
+
route: "mpp",
|
|
5277
|
+
walletAddress: null,
|
|
5278
|
+
clientId: null,
|
|
5279
|
+
sessionId: null,
|
|
5280
|
+
verifiedWallet: null,
|
|
5281
|
+
setVerifiedWallet() {
|
|
5282
|
+
}
|
|
5283
|
+
};
|
|
5284
|
+
for (const instance of instances) {
|
|
5285
|
+
const events = instance;
|
|
5286
|
+
if (typeof events?.onPaymentFailed !== "function") continue;
|
|
5287
|
+
events.onPaymentFailed((payload) => {
|
|
5288
|
+
try {
|
|
5289
|
+
const error = payload?.error ?? {};
|
|
5290
|
+
firePluginHook(plugin, "onAlert", ctx, {
|
|
5291
|
+
level: (error.status ?? 402) >= 500 ? "error" : "warn",
|
|
5292
|
+
message: `MPP payment failed: ${error.message ?? error.name ?? "unknown error"}`,
|
|
5293
|
+
route: "mpp",
|
|
5294
|
+
meta: {
|
|
5295
|
+
...payload?.method ? { method: `${payload.method.name}/${payload.method.intent}` } : {},
|
|
5296
|
+
...error.type ? { errorType: error.type } : {},
|
|
5297
|
+
...error.status !== void 0 ? { status: error.status } : {},
|
|
5298
|
+
...error.hint ? { hint: error.hint } : {},
|
|
5299
|
+
...payload?.credential?.source ? { payer: payload.credential.source } : {}
|
|
5300
|
+
}
|
|
5301
|
+
});
|
|
5302
|
+
} catch {
|
|
5303
|
+
}
|
|
5304
|
+
});
|
|
5305
|
+
}
|
|
5306
|
+
}
|
|
5270
5307
|
|
|
5271
5308
|
// src/index.ts
|
|
5272
5309
|
init_constants();
|
|
@@ -5409,6 +5446,7 @@ export {
|
|
|
5409
5446
|
DEFAULT_SOLANA_FACILITATOR_URL,
|
|
5410
5447
|
DEFAULT_TEMPO_RPC_URL,
|
|
5411
5448
|
HttpError,
|
|
5449
|
+
RouteDefinitionError,
|
|
5412
5450
|
RouterConfigError,
|
|
5413
5451
|
SOLANA_MAINNET_NETWORK,
|
|
5414
5452
|
TEMPO_USDC_ADDRESS,
|