@agentcash/router 1.10.6 → 1.11.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 +2 -0
- package/dist/index.cjs +193 -19
- package/dist/index.d.cts +25 -3
- package/dist/index.d.ts +25 -3
- package/dist/index.js +195 -17
- package/package.json +2 -1
package/AGENTS.md
CHANGED
|
@@ -39,6 +39,8 @@ src/
|
|
|
39
39
|
|
|
40
40
|
`auth check -> body parse -> validate -> 402 challenge -> payment verify -> handler -> settle -> finalize`
|
|
41
41
|
|
|
42
|
+
Paid `.paid()` routes may set `mpp: { settleBeforeHandler: true }` (or chain `.mpp({ settleBeforeHandler: true })` on auto-priced routes) to broadcast MPP transaction (pull) credentials at verify instead of after the handler. x402 on the same route is unaffected. MPP hash (push) credentials already settle at verify. Use `.settlement({ onSettledHandlerError })` when eager MPP settle can charge before an upstream failure.
|
|
43
|
+
|
|
42
44
|
## Naming
|
|
43
45
|
|
|
44
46
|
Constructor-style functions use `build<Noun>` — one verb, one domain noun. Name them after the domain concept, never after an HTTP status code or transport detail (the function that builds a payment challenge is `buildChallengeResponse`, not `build402`). The challenge family shares the `Challenge` backbone: `buildChallengeResponse`, `buildChallengeExtensions`, `buildSiwxChallenge`, `buildSessionChallenge`, `buildX402Challenge`.
|
package/dist/index.cjs
CHANGED
|
@@ -542,7 +542,8 @@ var HEADERS = {
|
|
|
542
542
|
X402_PAYMENT_REQUIRED: "PAYMENT-REQUIRED",
|
|
543
543
|
X402_PAYMENT_RESPONSE: "PAYMENT-RESPONSE",
|
|
544
544
|
MPP_PAYMENT_RECEIPT: "Payment-Receipt",
|
|
545
|
-
REQUEST_ID: "X-Request-ID"
|
|
545
|
+
REQUEST_ID: "X-Request-ID",
|
|
546
|
+
AGENT_IDENTITY: "X-Agent-Identity"
|
|
546
547
|
};
|
|
547
548
|
var AUTH_SCHEME = {
|
|
548
549
|
BEARER: "Bearer ",
|
|
@@ -677,6 +678,7 @@ function firePaymentSettled(ctx, event) {
|
|
|
677
678
|
}
|
|
678
679
|
function firePluginResponse(ctx, response, requestBody, responseBody, failure) {
|
|
679
680
|
attachRequestId(response, ctx.meta.requestId);
|
|
681
|
+
const error = response.status >= 400 && response.status !== 402 ? buildErrorEvent(ctx, response, failure) : void 0;
|
|
680
682
|
firePluginHook(ctx.deps.plugin, "onResponse", ctx.pluginCtx, {
|
|
681
683
|
statusCode: response.status,
|
|
682
684
|
statusText: response.statusText,
|
|
@@ -684,10 +686,10 @@ function firePluginResponse(ctx, response, requestBody, responseBody, failure) {
|
|
|
684
686
|
contentType: response.headers.get("content-type"),
|
|
685
687
|
headers: Object.fromEntries(response.headers.entries()),
|
|
686
688
|
requestBody,
|
|
687
|
-
responseBody
|
|
689
|
+
responseBody,
|
|
690
|
+
error
|
|
688
691
|
});
|
|
689
|
-
if (
|
|
690
|
-
const error = buildErrorEvent(ctx, response, failure);
|
|
692
|
+
if (error) {
|
|
691
693
|
if (response.status >= 500) logRouterFailure(error);
|
|
692
694
|
firePluginHook(ctx.deps.plugin, "onError", ctx.pluginCtx, error);
|
|
693
695
|
}
|
|
@@ -860,6 +862,9 @@ async function runValidate(ctx, body) {
|
|
|
860
862
|
// src/pipeline/flows/static/static-invoke.ts
|
|
861
863
|
var import_server4 = require("next/server");
|
|
862
864
|
|
|
865
|
+
// src/auth/agent-identity.ts
|
|
866
|
+
var import_did_auth_challenge = require("did-auth-challenge");
|
|
867
|
+
|
|
863
868
|
// src/types.ts
|
|
864
869
|
var HttpError = class extends Error {
|
|
865
870
|
constructor(message, status) {
|
|
@@ -869,18 +874,87 @@ var HttpError = class extends Error {
|
|
|
869
874
|
}
|
|
870
875
|
};
|
|
871
876
|
|
|
872
|
-
// src/
|
|
873
|
-
|
|
874
|
-
|
|
877
|
+
// src/auth/agent-identity.ts
|
|
878
|
+
var PROTOCOL_VERSION = 1;
|
|
879
|
+
function base64urlEncode(value) {
|
|
880
|
+
return Buffer.from(JSON.stringify(value)).toString("base64url");
|
|
881
|
+
}
|
|
882
|
+
function base64urlDecode(value) {
|
|
883
|
+
return JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
884
|
+
}
|
|
885
|
+
function challengeBindings(request) {
|
|
886
|
+
const url = new URL(request.url);
|
|
887
|
+
return { domain: url.hostname, route: url.pathname };
|
|
888
|
+
}
|
|
889
|
+
async function buildAgentIdentityChallengeHeader(request, nonceStore) {
|
|
890
|
+
const bindings = challengeBindings(request);
|
|
891
|
+
const challenge = await (0, import_did_auth_challenge.createChallenge)(bindings, { nonceStore });
|
|
892
|
+
return base64urlEncode({
|
|
893
|
+
v: PROTOCOL_VERSION,
|
|
894
|
+
challenge
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
async function attachAgentIdentityChallenge(response, request, nonceStore) {
|
|
898
|
+
response.headers.set(
|
|
899
|
+
HEADERS.AGENT_IDENTITY,
|
|
900
|
+
await buildAgentIdentityChallengeHeader(request, nonceStore)
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
async function resolveActor(request, nonceStore) {
|
|
904
|
+
const header = request.headers.get(HEADERS.AGENT_IDENTITY);
|
|
905
|
+
if (!header) return null;
|
|
906
|
+
let envelope;
|
|
907
|
+
try {
|
|
908
|
+
envelope = base64urlDecode(header);
|
|
909
|
+
} catch {
|
|
910
|
+
throw new HttpError("Malformed X-Agent-Identity header", 401);
|
|
911
|
+
}
|
|
912
|
+
if (envelope.v !== PROTOCOL_VERSION || !envelope.challenge || !envelope.did || !envelope.signature) {
|
|
913
|
+
throw new HttpError("Invalid X-Agent-Identity proof", 401);
|
|
914
|
+
}
|
|
915
|
+
const bindings = challengeBindings(request);
|
|
916
|
+
try {
|
|
917
|
+
await (0, import_did_auth_challenge.verifySignature)({
|
|
918
|
+
did: envelope.did,
|
|
919
|
+
authenticationKey: envelope.authenticationKey,
|
|
920
|
+
challenge: envelope.challenge,
|
|
921
|
+
signature: envelope.signature,
|
|
922
|
+
options: {
|
|
923
|
+
nonceStore,
|
|
924
|
+
expected: bindings
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
} catch (err) {
|
|
928
|
+
const message = (0, import_did_auth_challenge.isIdentityError)(err) ? err.code : "Invalid agent identity";
|
|
929
|
+
throw new HttpError(message, 401);
|
|
930
|
+
}
|
|
931
|
+
return envelope.did;
|
|
875
932
|
}
|
|
876
|
-
|
|
877
|
-
|
|
933
|
+
|
|
934
|
+
// src/pipeline/flows/static/static-invoke.ts
|
|
935
|
+
async function invokePaidStatic(ctx, wallet, account, body, payment) {
|
|
936
|
+
const actorResult = await resolveActorForHandler(ctx);
|
|
937
|
+
if (actorResult.kind === "error") return actorResult.result;
|
|
938
|
+
return runHandler(ctx, buildHandlerCtx(ctx, wallet, account, body, payment, actorResult.actor));
|
|
939
|
+
}
|
|
940
|
+
async function invokeUnauthed(ctx, wallet, account, body) {
|
|
941
|
+
const actorResult = await resolveActorForHandler(ctx);
|
|
942
|
+
if (actorResult.kind === "error") return actorResult.result;
|
|
943
|
+
const base = buildHandlerCtx(ctx, wallet, account, body, null, actorResult.actor);
|
|
878
944
|
if (ctx.routeEntry.billing !== "upto") return runHandler(ctx, base);
|
|
879
945
|
const uptoCtx = { ...base, charge: async () => {
|
|
880
946
|
} };
|
|
881
947
|
return runHandler(ctx, uptoCtx);
|
|
882
948
|
}
|
|
883
|
-
function
|
|
949
|
+
async function resolveActorForHandler(ctx) {
|
|
950
|
+
try {
|
|
951
|
+
const actor = await resolveActor(ctx.request, ctx.deps.agentIdentityNonceStore);
|
|
952
|
+
return { kind: "ok", actor };
|
|
953
|
+
} catch (error) {
|
|
954
|
+
return { kind: "error", result: errorResult(error) };
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
function buildHandlerCtx(ctx, wallet, account, body, payment, actor) {
|
|
884
958
|
return {
|
|
885
959
|
body,
|
|
886
960
|
query: ctx.query,
|
|
@@ -888,6 +962,7 @@ function buildHandlerCtx(ctx, wallet, account, body, payment) {
|
|
|
888
962
|
requestId: ctx.meta.requestId,
|
|
889
963
|
route: ctx.routeEntry.key,
|
|
890
964
|
wallet,
|
|
965
|
+
actor,
|
|
891
966
|
payment,
|
|
892
967
|
account,
|
|
893
968
|
alert: ctx.report,
|
|
@@ -1709,7 +1784,8 @@ var mppStrategy = {
|
|
|
1709
1784
|
return verifySessionMode(args, info);
|
|
1710
1785
|
}
|
|
1711
1786
|
if (info.sessionAction) return { ok: false, kind: "invalid" };
|
|
1712
|
-
|
|
1787
|
+
const deferTransactionSettlement = info.payloadType === "transaction" && args.deps.tempoClient && !args.routeEntry.mppInfo?.settleBeforeHandler;
|
|
1788
|
+
if (deferTransactionSettlement) {
|
|
1713
1789
|
return verifyTxMode(args, info);
|
|
1714
1790
|
}
|
|
1715
1791
|
return verifyHashMode(args, info);
|
|
@@ -2012,7 +2088,7 @@ function tagBareDecimalAsDollars(amount) {
|
|
|
2012
2088
|
}
|
|
2013
2089
|
|
|
2014
2090
|
// src/protocols/x402/verify.ts
|
|
2015
|
-
var
|
|
2091
|
+
var import_types3 = require("@x402/core/types");
|
|
2016
2092
|
async function verifyX402Payment(opts) {
|
|
2017
2093
|
const { server, request, price, accepts, report } = opts;
|
|
2018
2094
|
const payment = await readPaymentPayload(request);
|
|
@@ -2038,7 +2114,7 @@ async function verifyX402Payment(opts) {
|
|
|
2038
2114
|
try {
|
|
2039
2115
|
verify = await server.verifyPayment(payload, matching);
|
|
2040
2116
|
} catch (err) {
|
|
2041
|
-
if (err instanceof
|
|
2117
|
+
if (err instanceof import_types3.VerifyError && err.statusCode >= 400 && err.statusCode < 500) {
|
|
2042
2118
|
return invalidPaymentVerification({
|
|
2043
2119
|
reason: err.invalidReason ?? "verify_error",
|
|
2044
2120
|
...err.invalidMessage ? { message: err.invalidMessage } : {},
|
|
@@ -2611,6 +2687,7 @@ async function buildChallengeResponse(ctx, pricing, body, failure) {
|
|
|
2611
2687
|
}
|
|
2612
2688
|
}
|
|
2613
2689
|
}
|
|
2690
|
+
await attachAgentIdentityChallenge(response, ctx.request, ctx.deps.agentIdentityNonceStore);
|
|
2614
2691
|
firePluginResponse(ctx, response);
|
|
2615
2692
|
return response;
|
|
2616
2693
|
}
|
|
@@ -2745,7 +2822,7 @@ function createChargeContext(args) {
|
|
|
2745
2822
|
|
|
2746
2823
|
// src/pipeline/flows/dynamic/dynamic-invoke/shared.ts
|
|
2747
2824
|
var import_server7 = require("next/server");
|
|
2748
|
-
function buildBaseHandlerCtx(ctx, wallet, account, body, payment) {
|
|
2825
|
+
function buildBaseHandlerCtx(ctx, wallet, account, body, payment, actor = null) {
|
|
2749
2826
|
return {
|
|
2750
2827
|
body,
|
|
2751
2828
|
query: ctx.query,
|
|
@@ -2753,12 +2830,21 @@ function buildBaseHandlerCtx(ctx, wallet, account, body, payment) {
|
|
|
2753
2830
|
requestId: ctx.meta.requestId,
|
|
2754
2831
|
route: ctx.routeEntry.key,
|
|
2755
2832
|
wallet,
|
|
2833
|
+
actor,
|
|
2756
2834
|
payment,
|
|
2757
2835
|
account,
|
|
2758
2836
|
alert: ctx.report,
|
|
2759
2837
|
setVerifiedWallet: (addr) => ctx.pluginCtx.setVerifiedWallet(addr)
|
|
2760
2838
|
};
|
|
2761
2839
|
}
|
|
2840
|
+
async function resolveActorOrError(ctx) {
|
|
2841
|
+
try {
|
|
2842
|
+
const actor = await resolveActor(ctx.request, ctx.deps.agentIdentityNonceStore);
|
|
2843
|
+
return { actor };
|
|
2844
|
+
} catch (error) {
|
|
2845
|
+
return errorResult2(error);
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2762
2848
|
function toResponse(rawResult) {
|
|
2763
2849
|
return rawResult instanceof Response ? rawResult : import_server7.NextResponse.json(rawResult);
|
|
2764
2850
|
}
|
|
@@ -2782,12 +2868,21 @@ function isThenable2(value) {
|
|
|
2782
2868
|
|
|
2783
2869
|
// src/pipeline/flows/dynamic/dynamic-invoke/metered-invoke.ts
|
|
2784
2870
|
async function invokeMetered(ctx, wallet, account, body, payment) {
|
|
2871
|
+
const actorResult = await resolveActorOrError(ctx);
|
|
2872
|
+
if ("response" in actorResult) return actorResult;
|
|
2785
2873
|
const chargeContext = ctx.routeEntry.streaming ? createChargeContext({
|
|
2786
2874
|
tickCost: ctx.routeEntry.tickCost,
|
|
2787
2875
|
maxPrice: ctx.routeEntry.maxPrice,
|
|
2788
2876
|
route: ctx.routeEntry.key
|
|
2789
2877
|
}) : null;
|
|
2790
|
-
const baseHandlerCtx = buildBaseHandlerCtx(
|
|
2878
|
+
const baseHandlerCtx = buildBaseHandlerCtx(
|
|
2879
|
+
ctx,
|
|
2880
|
+
wallet,
|
|
2881
|
+
account,
|
|
2882
|
+
body,
|
|
2883
|
+
payment,
|
|
2884
|
+
actorResult.actor
|
|
2885
|
+
);
|
|
2791
2886
|
const handlerCtx = chargeContext !== null ? { ...baseHandlerCtx, charge: chargeContext.charge } : baseHandlerCtx;
|
|
2792
2887
|
let returned;
|
|
2793
2888
|
try {
|
|
@@ -2846,12 +2941,14 @@ function createUptoChargeContext(args) {
|
|
|
2846
2941
|
|
|
2847
2942
|
// src/pipeline/flows/dynamic/dynamic-invoke/upto-invoke.ts
|
|
2848
2943
|
async function invokeUpto(ctx, wallet, account, body, payment) {
|
|
2944
|
+
const actorResult = await resolveActorOrError(ctx);
|
|
2945
|
+
if ("response" in actorResult) return actorResult;
|
|
2849
2946
|
const uptoCtx = createUptoChargeContext({
|
|
2850
2947
|
maxPrice: ctx.routeEntry.maxPrice,
|
|
2851
2948
|
route: ctx.routeEntry.key
|
|
2852
2949
|
});
|
|
2853
2950
|
const handlerCtx = {
|
|
2854
|
-
...buildBaseHandlerCtx(ctx, wallet, account, body, payment),
|
|
2951
|
+
...buildBaseHandlerCtx(ctx, wallet, account, body, payment, actorResult.actor),
|
|
2855
2952
|
charge: uptoCtx.charge
|
|
2856
2953
|
};
|
|
2857
2954
|
let returned;
|
|
@@ -3384,6 +3481,45 @@ async function createKvMppStore(kv, options) {
|
|
|
3384
3481
|
});
|
|
3385
3482
|
}
|
|
3386
3483
|
|
|
3484
|
+
// src/kv-store/agent-identity-nonce.ts
|
|
3485
|
+
var import_did_auth_challenge2 = require("did-auth-challenge");
|
|
3486
|
+
function createKvAgentIdentityNonceStore(kv, options) {
|
|
3487
|
+
const prefix = options?.prefix ?? "actor:nonce:";
|
|
3488
|
+
return {
|
|
3489
|
+
async add(nonce, expiresAt, challengeJson) {
|
|
3490
|
+
const ttl = Math.max(1, Math.ceil((expiresAt.getTime() - Date.now()) / 1e3));
|
|
3491
|
+
const record = { challengeJson, expiresAt: expiresAt.getTime() };
|
|
3492
|
+
await kv.setNxEx(`${prefix}${nonce}`, record, ttl);
|
|
3493
|
+
},
|
|
3494
|
+
async get(nonce) {
|
|
3495
|
+
const raw = await kv.get(`${prefix}${nonce}`);
|
|
3496
|
+
if (!raw) return null;
|
|
3497
|
+
const record = raw;
|
|
3498
|
+
if (Date.now() > record.expiresAt) {
|
|
3499
|
+
await kv.del(`${prefix}${nonce}`);
|
|
3500
|
+
return null;
|
|
3501
|
+
}
|
|
3502
|
+
return record;
|
|
3503
|
+
},
|
|
3504
|
+
async has(nonce) {
|
|
3505
|
+
return await this.get(nonce) !== null;
|
|
3506
|
+
},
|
|
3507
|
+
async consume(nonce) {
|
|
3508
|
+
return kv.update(`${prefix}${nonce}`, (current) => {
|
|
3509
|
+
if (!current) return { op: "noop", result: null };
|
|
3510
|
+
const record = current;
|
|
3511
|
+
if (Date.now() > record.expiresAt) {
|
|
3512
|
+
return { op: "delete", result: null };
|
|
3513
|
+
}
|
|
3514
|
+
return { op: "delete", result: record };
|
|
3515
|
+
});
|
|
3516
|
+
}
|
|
3517
|
+
};
|
|
3518
|
+
}
|
|
3519
|
+
function createAgentIdentityNonceStore(kv) {
|
|
3520
|
+
return kv ? createKvAgentIdentityNonceStore(kv) : new import_did_auth_challenge2.NonceStore();
|
|
3521
|
+
}
|
|
3522
|
+
|
|
3387
3523
|
// src/protocols/mpp/siwx-mode.ts
|
|
3388
3524
|
var import_mppx3 = require("mppx");
|
|
3389
3525
|
async function verifyMppSiwx(request, mppx) {
|
|
@@ -3510,6 +3646,7 @@ async function buildSiwxChallenge(ctx) {
|
|
|
3510
3646
|
} catch {
|
|
3511
3647
|
}
|
|
3512
3648
|
}
|
|
3649
|
+
await attachAgentIdentityChallenge(response, request, deps.agentIdentityNonceStore);
|
|
3513
3650
|
firePluginResponse(ctx, response);
|
|
3514
3651
|
return response;
|
|
3515
3652
|
}
|
|
@@ -3720,7 +3857,9 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3720
3857
|
if (maxPrice) next.#s.maxPrice = maxPrice;
|
|
3721
3858
|
if (resolvedOptions.minPrice) next.#s.minPrice = resolvedOptions.minPrice;
|
|
3722
3859
|
if (resolvedOptions.payTo) next.#s.payTo = resolvedOptions.payTo;
|
|
3723
|
-
if (resolvedOptions.mpp)
|
|
3860
|
+
if (resolvedOptions.mpp) {
|
|
3861
|
+
next.#s.mppInfo = { ...next.#s.mppInfo, ...resolvedOptions.mpp };
|
|
3862
|
+
}
|
|
3724
3863
|
if (resolvedOptions.checkout || resolvedOptions.checkoutSession) next.#s.hasCheckout = true;
|
|
3725
3864
|
if (resolvedOptions.checkoutSession) next.#s.checkoutSession = resolvedOptions.checkoutSession;
|
|
3726
3865
|
next.#s.billing = billing;
|
|
@@ -4015,6 +4154,24 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
4015
4154
|
next.#s.validateFn = fn;
|
|
4016
4155
|
return next;
|
|
4017
4156
|
}
|
|
4157
|
+
/**
|
|
4158
|
+
* Override MPP protocol metadata and per-route MPP settlement options.
|
|
4159
|
+
* Merges with any `mpp` options passed to `.paid()`. Use on auto-priced routes
|
|
4160
|
+
* (from `RouterConfig.prices`) when you cannot call `.paid()` again.
|
|
4161
|
+
*
|
|
4162
|
+
* @example
|
|
4163
|
+
* ```ts
|
|
4164
|
+
* router.route('exa/answer')
|
|
4165
|
+
* .mpp({ settleBeforeHandler: true })
|
|
4166
|
+
* .body(schema)
|
|
4167
|
+
* .handler(handler);
|
|
4168
|
+
* ```
|
|
4169
|
+
*/
|
|
4170
|
+
mpp(info) {
|
|
4171
|
+
const next = this.fork();
|
|
4172
|
+
next.#s.mppInfo = { ...this.#s.mppInfo, ...info };
|
|
4173
|
+
return next;
|
|
4174
|
+
}
|
|
4018
4175
|
/**
|
|
4019
4176
|
* Hook into the settlement lifecycle. `beforeSettle` runs after the handler
|
|
4020
4177
|
* succeeds but before on-chain settlement and can cancel the charge;
|
|
@@ -4030,7 +4187,7 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
4030
4187
|
*/
|
|
4031
4188
|
settlement(lifecycle) {
|
|
4032
4189
|
const next = this.fork();
|
|
4033
|
-
next.#s.settlement = lifecycle;
|
|
4190
|
+
next.#s.settlement = { ...this.#s.settlement, ...lifecycle };
|
|
4034
4191
|
return next;
|
|
4035
4192
|
}
|
|
4036
4193
|
/**
|
|
@@ -4086,6 +4243,21 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
4086
4243
|
if (this.#s.settlement && !this.#s.pricing) {
|
|
4087
4244
|
throw new Error(`route '${this.#s.key}': .settlement() requires a paid route`);
|
|
4088
4245
|
}
|
|
4246
|
+
if (this.#s.mppInfo?.settleBeforeHandler) {
|
|
4247
|
+
if (!this.#s.pricing) {
|
|
4248
|
+
throw new Error(`route '${this.#s.key}': mpp.settleBeforeHandler requires a paid route`);
|
|
4249
|
+
}
|
|
4250
|
+
if (this.#s.billing !== "exact") {
|
|
4251
|
+
throw new Error(
|
|
4252
|
+
`route '${this.#s.key}': mpp.settleBeforeHandler is only supported on .paid() routes`
|
|
4253
|
+
);
|
|
4254
|
+
}
|
|
4255
|
+
if (this.#s.settlement?.beforeSettle) {
|
|
4256
|
+
throw new Error(
|
|
4257
|
+
`route '${this.#s.key}': mpp.settleBeforeHandler is incompatible with .settlement({ beforeSettle }) \u2014 MPP payment is already broadcast before the handler runs`
|
|
4258
|
+
);
|
|
4259
|
+
}
|
|
4260
|
+
}
|
|
4089
4261
|
if (this.#s.billing === "upto") {
|
|
4090
4262
|
const hasUpto = this.#s.deps.x402Accepts.some((accept) => accept.scheme === "upto");
|
|
4091
4263
|
if (!hasUpto) {
|
|
@@ -4526,7 +4698,7 @@ function createNotFoundHandler(baseUrl) {
|
|
|
4526
4698
|
headers: {
|
|
4527
4699
|
"Access-Control-Allow-Origin": "*",
|
|
4528
4700
|
"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"
|
|
4701
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-API-Key, SIGN-IN-WITH-X, X-Agent-Identity"
|
|
4530
4702
|
}
|
|
4531
4703
|
}
|
|
4532
4704
|
);
|
|
@@ -5140,6 +5312,7 @@ function createRouter(config) {
|
|
|
5140
5312
|
const registry = new RouteRegistry();
|
|
5141
5313
|
const kvStore = resolveKvStore(config.kvStore);
|
|
5142
5314
|
const nonceStore = kvStore ? createKvNonceStore(kvStore) : new MemoryNonceStore();
|
|
5315
|
+
const agentIdentityNonceStore = createAgentIdentityNonceStore(kvStore);
|
|
5143
5316
|
const entitlementStore = kvStore ? createKvEntitlementStore(kvStore) : new MemoryEntitlementStore();
|
|
5144
5317
|
const network = config.network ?? BASE_MAINNET_NETWORK;
|
|
5145
5318
|
const x402Accepts = getConfiguredX402Accepts(config);
|
|
@@ -5174,6 +5347,7 @@ function createRouter(config) {
|
|
|
5174
5347
|
initPromise: Promise.resolve(),
|
|
5175
5348
|
plugin: config.plugin,
|
|
5176
5349
|
nonceStore,
|
|
5350
|
+
agentIdentityNonceStore,
|
|
5177
5351
|
entitlementStore,
|
|
5178
5352
|
payeeAddress: config.payeeAddress ?? "",
|
|
5179
5353
|
mppRecipient: config.mpp?.recipient ?? config.payeeAddress,
|
package/dist/index.d.cts
CHANGED
|
@@ -4,6 +4,7 @@ import { ZodType } from 'zod';
|
|
|
4
4
|
import { PaymentRequirements, PaymentRequired, VerifyResponse, SettleResponse, Network } from '@x402/core/types';
|
|
5
5
|
import * as viem from 'viem';
|
|
6
6
|
import { Transport } from 'mppx/server';
|
|
7
|
+
import { NonceStoreInterface } from 'did-auth-challenge';
|
|
7
8
|
|
|
8
9
|
interface KvStore {
|
|
9
10
|
get(key: string): Promise<unknown>;
|
|
@@ -79,6 +80,8 @@ interface ResponseMeta {
|
|
|
79
80
|
requestBody?: unknown;
|
|
80
81
|
/** Handler return value or structured router-generated error body. */
|
|
81
82
|
responseBody?: unknown;
|
|
83
|
+
/** Rich error details for non-402 failure responses. */
|
|
84
|
+
error?: ErrorEvent;
|
|
82
85
|
}
|
|
83
86
|
interface ErrorEvent {
|
|
84
87
|
status: number;
|
|
@@ -218,6 +221,7 @@ interface MppProtocolInfo {
|
|
|
218
221
|
method?: string;
|
|
219
222
|
intent?: string;
|
|
220
223
|
currency?: string;
|
|
224
|
+
settleBeforeHandler?: boolean;
|
|
221
225
|
}
|
|
222
226
|
type CheckoutSessionResponse = Record<string, unknown>;
|
|
223
227
|
interface CheckoutSessionContext<TBody = unknown> {
|
|
@@ -320,6 +324,8 @@ interface HandlerContext<TBody = undefined, TQuery = undefined> {
|
|
|
320
324
|
requestId: string;
|
|
321
325
|
route: string;
|
|
322
326
|
wallet: string | null;
|
|
327
|
+
/** Optional DID from `X-Agent-Identity` proof. Null when the client omits identity. */
|
|
328
|
+
actor: string | null;
|
|
323
329
|
payment: HandlerPaymentContext | null;
|
|
324
330
|
account: unknown;
|
|
325
331
|
alert: AlertFn;
|
|
@@ -498,6 +504,7 @@ interface RouterDeps {
|
|
|
498
504
|
mppInitError?: string;
|
|
499
505
|
plugin?: RouterPlugin;
|
|
500
506
|
nonceStore: NonceStore;
|
|
507
|
+
agentIdentityNonceStore: NonceStoreInterface;
|
|
501
508
|
entitlementStore: EntitlementStore;
|
|
502
509
|
payeeAddress: string;
|
|
503
510
|
mppRecipient?: string;
|
|
@@ -576,8 +583,9 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, TOutput = unde
|
|
|
576
583
|
* - `{ field, tiers, default? }` — pick a tier from `body[field]`.
|
|
577
584
|
*
|
|
578
585
|
* Common knobs (`protocols`, `maxPrice`, `minPrice`, `payTo`, `mpp`, `checkout`,
|
|
579
|
-
* `checkoutSession`) live alongside the pricing shape.
|
|
580
|
-
*
|
|
586
|
+
* `checkoutSession`) live alongside the pricing shape. Set `mpp.settleBeforeHandler`
|
|
587
|
+
* on slow upstream routes so MPP transaction (pull) credentials broadcast at verify.
|
|
588
|
+
* For auto-priced routes from `RouterConfig.prices`, chain `.mpp({ settleBeforeHandler: true })`.
|
|
581
589
|
*
|
|
582
590
|
* @example
|
|
583
591
|
* ```ts
|
|
@@ -773,6 +781,20 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, TOutput = unde
|
|
|
773
781
|
* ```
|
|
774
782
|
*/
|
|
775
783
|
validate(fn: (body: TBody) => void | Promise<void>): RouteBuilder<TBody, TQuery, TOutput, HasAuth, NeedsBody, HasBody, Bill>;
|
|
784
|
+
/**
|
|
785
|
+
* Override MPP protocol metadata and per-route MPP settlement options.
|
|
786
|
+
* Merges with any `mpp` options passed to `.paid()`. Use on auto-priced routes
|
|
787
|
+
* (from `RouterConfig.prices`) when you cannot call `.paid()` again.
|
|
788
|
+
*
|
|
789
|
+
* @example
|
|
790
|
+
* ```ts
|
|
791
|
+
* router.route('exa/answer')
|
|
792
|
+
* .mpp({ settleBeforeHandler: true })
|
|
793
|
+
* .body(schema)
|
|
794
|
+
* .handler(handler);
|
|
795
|
+
* ```
|
|
796
|
+
*/
|
|
797
|
+
mpp(info: MppProtocolInfo): RouteBuilder<TBody, TQuery, TOutput, HasAuth, NeedsBody, HasBody, Bill>;
|
|
776
798
|
/**
|
|
777
799
|
* Hook into the settlement lifecycle. `beforeSettle` runs after the handler
|
|
778
800
|
* succeeds but before on-chain settlement and can cancel the charge;
|
|
@@ -947,4 +969,4 @@ declare function createRouter<const P extends Record<string, string> = Record<ne
|
|
|
947
969
|
*/
|
|
948
970
|
declare function createRouterFromEnv<const P extends Record<string, string> = Record<never, string>>(options: CreateRouterFromEnvOptions<P>): ServiceRouter<Extract<keyof P, string>>;
|
|
949
971
|
|
|
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 };
|
|
972
|
+
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 MppProtocolInfo, 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
|
@@ -4,6 +4,7 @@ import { ZodType } from 'zod';
|
|
|
4
4
|
import { PaymentRequirements, PaymentRequired, VerifyResponse, SettleResponse, Network } from '@x402/core/types';
|
|
5
5
|
import * as viem from 'viem';
|
|
6
6
|
import { Transport } from 'mppx/server';
|
|
7
|
+
import { NonceStoreInterface } from 'did-auth-challenge';
|
|
7
8
|
|
|
8
9
|
interface KvStore {
|
|
9
10
|
get(key: string): Promise<unknown>;
|
|
@@ -79,6 +80,8 @@ interface ResponseMeta {
|
|
|
79
80
|
requestBody?: unknown;
|
|
80
81
|
/** Handler return value or structured router-generated error body. */
|
|
81
82
|
responseBody?: unknown;
|
|
83
|
+
/** Rich error details for non-402 failure responses. */
|
|
84
|
+
error?: ErrorEvent;
|
|
82
85
|
}
|
|
83
86
|
interface ErrorEvent {
|
|
84
87
|
status: number;
|
|
@@ -218,6 +221,7 @@ interface MppProtocolInfo {
|
|
|
218
221
|
method?: string;
|
|
219
222
|
intent?: string;
|
|
220
223
|
currency?: string;
|
|
224
|
+
settleBeforeHandler?: boolean;
|
|
221
225
|
}
|
|
222
226
|
type CheckoutSessionResponse = Record<string, unknown>;
|
|
223
227
|
interface CheckoutSessionContext<TBody = unknown> {
|
|
@@ -320,6 +324,8 @@ interface HandlerContext<TBody = undefined, TQuery = undefined> {
|
|
|
320
324
|
requestId: string;
|
|
321
325
|
route: string;
|
|
322
326
|
wallet: string | null;
|
|
327
|
+
/** Optional DID from `X-Agent-Identity` proof. Null when the client omits identity. */
|
|
328
|
+
actor: string | null;
|
|
323
329
|
payment: HandlerPaymentContext | null;
|
|
324
330
|
account: unknown;
|
|
325
331
|
alert: AlertFn;
|
|
@@ -498,6 +504,7 @@ interface RouterDeps {
|
|
|
498
504
|
mppInitError?: string;
|
|
499
505
|
plugin?: RouterPlugin;
|
|
500
506
|
nonceStore: NonceStore;
|
|
507
|
+
agentIdentityNonceStore: NonceStoreInterface;
|
|
501
508
|
entitlementStore: EntitlementStore;
|
|
502
509
|
payeeAddress: string;
|
|
503
510
|
mppRecipient?: string;
|
|
@@ -576,8 +583,9 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, TOutput = unde
|
|
|
576
583
|
* - `{ field, tiers, default? }` — pick a tier from `body[field]`.
|
|
577
584
|
*
|
|
578
585
|
* Common knobs (`protocols`, `maxPrice`, `minPrice`, `payTo`, `mpp`, `checkout`,
|
|
579
|
-
* `checkoutSession`) live alongside the pricing shape.
|
|
580
|
-
*
|
|
586
|
+
* `checkoutSession`) live alongside the pricing shape. Set `mpp.settleBeforeHandler`
|
|
587
|
+
* on slow upstream routes so MPP transaction (pull) credentials broadcast at verify.
|
|
588
|
+
* For auto-priced routes from `RouterConfig.prices`, chain `.mpp({ settleBeforeHandler: true })`.
|
|
581
589
|
*
|
|
582
590
|
* @example
|
|
583
591
|
* ```ts
|
|
@@ -773,6 +781,20 @@ declare class RouteBuilder<TBody = undefined, TQuery = undefined, TOutput = unde
|
|
|
773
781
|
* ```
|
|
774
782
|
*/
|
|
775
783
|
validate(fn: (body: TBody) => void | Promise<void>): RouteBuilder<TBody, TQuery, TOutput, HasAuth, NeedsBody, HasBody, Bill>;
|
|
784
|
+
/**
|
|
785
|
+
* Override MPP protocol metadata and per-route MPP settlement options.
|
|
786
|
+
* Merges with any `mpp` options passed to `.paid()`. Use on auto-priced routes
|
|
787
|
+
* (from `RouterConfig.prices`) when you cannot call `.paid()` again.
|
|
788
|
+
*
|
|
789
|
+
* @example
|
|
790
|
+
* ```ts
|
|
791
|
+
* router.route('exa/answer')
|
|
792
|
+
* .mpp({ settleBeforeHandler: true })
|
|
793
|
+
* .body(schema)
|
|
794
|
+
* .handler(handler);
|
|
795
|
+
* ```
|
|
796
|
+
*/
|
|
797
|
+
mpp(info: MppProtocolInfo): RouteBuilder<TBody, TQuery, TOutput, HasAuth, NeedsBody, HasBody, Bill>;
|
|
776
798
|
/**
|
|
777
799
|
* Hook into the settlement lifecycle. `beforeSettle` runs after the handler
|
|
778
800
|
* succeeds but before on-chain settlement and can cancel the charge;
|
|
@@ -947,4 +969,4 @@ declare function createRouter<const P extends Record<string, string> = Record<ne
|
|
|
947
969
|
*/
|
|
948
970
|
declare function createRouterFromEnv<const P extends Record<string, string> = Record<never, string>>(options: CreateRouterFromEnvOptions<P>): ServiceRouter<Extract<keyof P, string>>;
|
|
949
971
|
|
|
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 };
|
|
972
|
+
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 MppProtocolInfo, 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
|
@@ -500,7 +500,8 @@ var HEADERS = {
|
|
|
500
500
|
X402_PAYMENT_REQUIRED: "PAYMENT-REQUIRED",
|
|
501
501
|
X402_PAYMENT_RESPONSE: "PAYMENT-RESPONSE",
|
|
502
502
|
MPP_PAYMENT_RECEIPT: "Payment-Receipt",
|
|
503
|
-
REQUEST_ID: "X-Request-ID"
|
|
503
|
+
REQUEST_ID: "X-Request-ID",
|
|
504
|
+
AGENT_IDENTITY: "X-Agent-Identity"
|
|
504
505
|
};
|
|
505
506
|
var AUTH_SCHEME = {
|
|
506
507
|
BEARER: "Bearer ",
|
|
@@ -635,6 +636,7 @@ function firePaymentSettled(ctx, event) {
|
|
|
635
636
|
}
|
|
636
637
|
function firePluginResponse(ctx, response, requestBody, responseBody, failure) {
|
|
637
638
|
attachRequestId(response, ctx.meta.requestId);
|
|
639
|
+
const error = response.status >= 400 && response.status !== 402 ? buildErrorEvent(ctx, response, failure) : void 0;
|
|
638
640
|
firePluginHook(ctx.deps.plugin, "onResponse", ctx.pluginCtx, {
|
|
639
641
|
statusCode: response.status,
|
|
640
642
|
statusText: response.statusText,
|
|
@@ -642,10 +644,10 @@ function firePluginResponse(ctx, response, requestBody, responseBody, failure) {
|
|
|
642
644
|
contentType: response.headers.get("content-type"),
|
|
643
645
|
headers: Object.fromEntries(response.headers.entries()),
|
|
644
646
|
requestBody,
|
|
645
|
-
responseBody
|
|
647
|
+
responseBody,
|
|
648
|
+
error
|
|
646
649
|
});
|
|
647
|
-
if (
|
|
648
|
-
const error = buildErrorEvent(ctx, response, failure);
|
|
650
|
+
if (error) {
|
|
649
651
|
if (response.status >= 500) logRouterFailure(error);
|
|
650
652
|
firePluginHook(ctx.deps.plugin, "onError", ctx.pluginCtx, error);
|
|
651
653
|
}
|
|
@@ -818,6 +820,13 @@ async function runValidate(ctx, body) {
|
|
|
818
820
|
// src/pipeline/flows/static/static-invoke.ts
|
|
819
821
|
import { NextResponse as NextResponse4 } from "next/server";
|
|
820
822
|
|
|
823
|
+
// src/auth/agent-identity.ts
|
|
824
|
+
import {
|
|
825
|
+
createChallenge,
|
|
826
|
+
isIdentityError,
|
|
827
|
+
verifySignature
|
|
828
|
+
} from "did-auth-challenge";
|
|
829
|
+
|
|
821
830
|
// src/types.ts
|
|
822
831
|
var HttpError = class extends Error {
|
|
823
832
|
constructor(message, status) {
|
|
@@ -827,18 +836,87 @@ var HttpError = class extends Error {
|
|
|
827
836
|
}
|
|
828
837
|
};
|
|
829
838
|
|
|
830
|
-
// src/
|
|
831
|
-
|
|
832
|
-
|
|
839
|
+
// src/auth/agent-identity.ts
|
|
840
|
+
var PROTOCOL_VERSION = 1;
|
|
841
|
+
function base64urlEncode(value) {
|
|
842
|
+
return Buffer.from(JSON.stringify(value)).toString("base64url");
|
|
843
|
+
}
|
|
844
|
+
function base64urlDecode(value) {
|
|
845
|
+
return JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
|
|
846
|
+
}
|
|
847
|
+
function challengeBindings(request) {
|
|
848
|
+
const url = new URL(request.url);
|
|
849
|
+
return { domain: url.hostname, route: url.pathname };
|
|
850
|
+
}
|
|
851
|
+
async function buildAgentIdentityChallengeHeader(request, nonceStore) {
|
|
852
|
+
const bindings = challengeBindings(request);
|
|
853
|
+
const challenge = await createChallenge(bindings, { nonceStore });
|
|
854
|
+
return base64urlEncode({
|
|
855
|
+
v: PROTOCOL_VERSION,
|
|
856
|
+
challenge
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
async function attachAgentIdentityChallenge(response, request, nonceStore) {
|
|
860
|
+
response.headers.set(
|
|
861
|
+
HEADERS.AGENT_IDENTITY,
|
|
862
|
+
await buildAgentIdentityChallengeHeader(request, nonceStore)
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
async function resolveActor(request, nonceStore) {
|
|
866
|
+
const header = request.headers.get(HEADERS.AGENT_IDENTITY);
|
|
867
|
+
if (!header) return null;
|
|
868
|
+
let envelope;
|
|
869
|
+
try {
|
|
870
|
+
envelope = base64urlDecode(header);
|
|
871
|
+
} catch {
|
|
872
|
+
throw new HttpError("Malformed X-Agent-Identity header", 401);
|
|
873
|
+
}
|
|
874
|
+
if (envelope.v !== PROTOCOL_VERSION || !envelope.challenge || !envelope.did || !envelope.signature) {
|
|
875
|
+
throw new HttpError("Invalid X-Agent-Identity proof", 401);
|
|
876
|
+
}
|
|
877
|
+
const bindings = challengeBindings(request);
|
|
878
|
+
try {
|
|
879
|
+
await verifySignature({
|
|
880
|
+
did: envelope.did,
|
|
881
|
+
authenticationKey: envelope.authenticationKey,
|
|
882
|
+
challenge: envelope.challenge,
|
|
883
|
+
signature: envelope.signature,
|
|
884
|
+
options: {
|
|
885
|
+
nonceStore,
|
|
886
|
+
expected: bindings
|
|
887
|
+
}
|
|
888
|
+
});
|
|
889
|
+
} catch (err) {
|
|
890
|
+
const message = isIdentityError(err) ? err.code : "Invalid agent identity";
|
|
891
|
+
throw new HttpError(message, 401);
|
|
892
|
+
}
|
|
893
|
+
return envelope.did;
|
|
833
894
|
}
|
|
834
|
-
|
|
835
|
-
|
|
895
|
+
|
|
896
|
+
// src/pipeline/flows/static/static-invoke.ts
|
|
897
|
+
async function invokePaidStatic(ctx, wallet, account, body, payment) {
|
|
898
|
+
const actorResult = await resolveActorForHandler(ctx);
|
|
899
|
+
if (actorResult.kind === "error") return actorResult.result;
|
|
900
|
+
return runHandler(ctx, buildHandlerCtx(ctx, wallet, account, body, payment, actorResult.actor));
|
|
901
|
+
}
|
|
902
|
+
async function invokeUnauthed(ctx, wallet, account, body) {
|
|
903
|
+
const actorResult = await resolveActorForHandler(ctx);
|
|
904
|
+
if (actorResult.kind === "error") return actorResult.result;
|
|
905
|
+
const base = buildHandlerCtx(ctx, wallet, account, body, null, actorResult.actor);
|
|
836
906
|
if (ctx.routeEntry.billing !== "upto") return runHandler(ctx, base);
|
|
837
907
|
const uptoCtx = { ...base, charge: async () => {
|
|
838
908
|
} };
|
|
839
909
|
return runHandler(ctx, uptoCtx);
|
|
840
910
|
}
|
|
841
|
-
function
|
|
911
|
+
async function resolveActorForHandler(ctx) {
|
|
912
|
+
try {
|
|
913
|
+
const actor = await resolveActor(ctx.request, ctx.deps.agentIdentityNonceStore);
|
|
914
|
+
return { kind: "ok", actor };
|
|
915
|
+
} catch (error) {
|
|
916
|
+
return { kind: "error", result: errorResult(error) };
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
function buildHandlerCtx(ctx, wallet, account, body, payment, actor) {
|
|
842
920
|
return {
|
|
843
921
|
body,
|
|
844
922
|
query: ctx.query,
|
|
@@ -846,6 +924,7 @@ function buildHandlerCtx(ctx, wallet, account, body, payment) {
|
|
|
846
924
|
requestId: ctx.meta.requestId,
|
|
847
925
|
route: ctx.routeEntry.key,
|
|
848
926
|
wallet,
|
|
927
|
+
actor,
|
|
849
928
|
payment,
|
|
850
929
|
account,
|
|
851
930
|
alert: ctx.report,
|
|
@@ -1667,7 +1746,8 @@ var mppStrategy = {
|
|
|
1667
1746
|
return verifySessionMode(args, info);
|
|
1668
1747
|
}
|
|
1669
1748
|
if (info.sessionAction) return { ok: false, kind: "invalid" };
|
|
1670
|
-
|
|
1749
|
+
const deferTransactionSettlement = info.payloadType === "transaction" && args.deps.tempoClient && !args.routeEntry.mppInfo?.settleBeforeHandler;
|
|
1750
|
+
if (deferTransactionSettlement) {
|
|
1671
1751
|
return verifyTxMode(args, info);
|
|
1672
1752
|
}
|
|
1673
1753
|
return verifyHashMode(args, info);
|
|
@@ -2569,6 +2649,7 @@ async function buildChallengeResponse(ctx, pricing, body, failure) {
|
|
|
2569
2649
|
}
|
|
2570
2650
|
}
|
|
2571
2651
|
}
|
|
2652
|
+
await attachAgentIdentityChallenge(response, ctx.request, ctx.deps.agentIdentityNonceStore);
|
|
2572
2653
|
firePluginResponse(ctx, response);
|
|
2573
2654
|
return response;
|
|
2574
2655
|
}
|
|
@@ -2703,7 +2784,7 @@ function createChargeContext(args) {
|
|
|
2703
2784
|
|
|
2704
2785
|
// src/pipeline/flows/dynamic/dynamic-invoke/shared.ts
|
|
2705
2786
|
import { NextResponse as NextResponse7 } from "next/server";
|
|
2706
|
-
function buildBaseHandlerCtx(ctx, wallet, account, body, payment) {
|
|
2787
|
+
function buildBaseHandlerCtx(ctx, wallet, account, body, payment, actor = null) {
|
|
2707
2788
|
return {
|
|
2708
2789
|
body,
|
|
2709
2790
|
query: ctx.query,
|
|
@@ -2711,12 +2792,21 @@ function buildBaseHandlerCtx(ctx, wallet, account, body, payment) {
|
|
|
2711
2792
|
requestId: ctx.meta.requestId,
|
|
2712
2793
|
route: ctx.routeEntry.key,
|
|
2713
2794
|
wallet,
|
|
2795
|
+
actor,
|
|
2714
2796
|
payment,
|
|
2715
2797
|
account,
|
|
2716
2798
|
alert: ctx.report,
|
|
2717
2799
|
setVerifiedWallet: (addr) => ctx.pluginCtx.setVerifiedWallet(addr)
|
|
2718
2800
|
};
|
|
2719
2801
|
}
|
|
2802
|
+
async function resolveActorOrError(ctx) {
|
|
2803
|
+
try {
|
|
2804
|
+
const actor = await resolveActor(ctx.request, ctx.deps.agentIdentityNonceStore);
|
|
2805
|
+
return { actor };
|
|
2806
|
+
} catch (error) {
|
|
2807
|
+
return errorResult2(error);
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2720
2810
|
function toResponse(rawResult) {
|
|
2721
2811
|
return rawResult instanceof Response ? rawResult : NextResponse7.json(rawResult);
|
|
2722
2812
|
}
|
|
@@ -2740,12 +2830,21 @@ function isThenable2(value) {
|
|
|
2740
2830
|
|
|
2741
2831
|
// src/pipeline/flows/dynamic/dynamic-invoke/metered-invoke.ts
|
|
2742
2832
|
async function invokeMetered(ctx, wallet, account, body, payment) {
|
|
2833
|
+
const actorResult = await resolveActorOrError(ctx);
|
|
2834
|
+
if ("response" in actorResult) return actorResult;
|
|
2743
2835
|
const chargeContext = ctx.routeEntry.streaming ? createChargeContext({
|
|
2744
2836
|
tickCost: ctx.routeEntry.tickCost,
|
|
2745
2837
|
maxPrice: ctx.routeEntry.maxPrice,
|
|
2746
2838
|
route: ctx.routeEntry.key
|
|
2747
2839
|
}) : null;
|
|
2748
|
-
const baseHandlerCtx = buildBaseHandlerCtx(
|
|
2840
|
+
const baseHandlerCtx = buildBaseHandlerCtx(
|
|
2841
|
+
ctx,
|
|
2842
|
+
wallet,
|
|
2843
|
+
account,
|
|
2844
|
+
body,
|
|
2845
|
+
payment,
|
|
2846
|
+
actorResult.actor
|
|
2847
|
+
);
|
|
2749
2848
|
const handlerCtx = chargeContext !== null ? { ...baseHandlerCtx, charge: chargeContext.charge } : baseHandlerCtx;
|
|
2750
2849
|
let returned;
|
|
2751
2850
|
try {
|
|
@@ -2804,12 +2903,14 @@ function createUptoChargeContext(args) {
|
|
|
2804
2903
|
|
|
2805
2904
|
// src/pipeline/flows/dynamic/dynamic-invoke/upto-invoke.ts
|
|
2806
2905
|
async function invokeUpto(ctx, wallet, account, body, payment) {
|
|
2906
|
+
const actorResult = await resolveActorOrError(ctx);
|
|
2907
|
+
if ("response" in actorResult) return actorResult;
|
|
2807
2908
|
const uptoCtx = createUptoChargeContext({
|
|
2808
2909
|
maxPrice: ctx.routeEntry.maxPrice,
|
|
2809
2910
|
route: ctx.routeEntry.key
|
|
2810
2911
|
});
|
|
2811
2912
|
const handlerCtx = {
|
|
2812
|
-
...buildBaseHandlerCtx(ctx, wallet, account, body, payment),
|
|
2913
|
+
...buildBaseHandlerCtx(ctx, wallet, account, body, payment, actorResult.actor),
|
|
2813
2914
|
charge: uptoCtx.charge
|
|
2814
2915
|
};
|
|
2815
2916
|
let returned;
|
|
@@ -3342,6 +3443,45 @@ async function createKvMppStore(kv, options) {
|
|
|
3342
3443
|
});
|
|
3343
3444
|
}
|
|
3344
3445
|
|
|
3446
|
+
// src/kv-store/agent-identity-nonce.ts
|
|
3447
|
+
import { NonceStore } from "did-auth-challenge";
|
|
3448
|
+
function createKvAgentIdentityNonceStore(kv, options) {
|
|
3449
|
+
const prefix = options?.prefix ?? "actor:nonce:";
|
|
3450
|
+
return {
|
|
3451
|
+
async add(nonce, expiresAt, challengeJson) {
|
|
3452
|
+
const ttl = Math.max(1, Math.ceil((expiresAt.getTime() - Date.now()) / 1e3));
|
|
3453
|
+
const record = { challengeJson, expiresAt: expiresAt.getTime() };
|
|
3454
|
+
await kv.setNxEx(`${prefix}${nonce}`, record, ttl);
|
|
3455
|
+
},
|
|
3456
|
+
async get(nonce) {
|
|
3457
|
+
const raw = await kv.get(`${prefix}${nonce}`);
|
|
3458
|
+
if (!raw) return null;
|
|
3459
|
+
const record = raw;
|
|
3460
|
+
if (Date.now() > record.expiresAt) {
|
|
3461
|
+
await kv.del(`${prefix}${nonce}`);
|
|
3462
|
+
return null;
|
|
3463
|
+
}
|
|
3464
|
+
return record;
|
|
3465
|
+
},
|
|
3466
|
+
async has(nonce) {
|
|
3467
|
+
return await this.get(nonce) !== null;
|
|
3468
|
+
},
|
|
3469
|
+
async consume(nonce) {
|
|
3470
|
+
return kv.update(`${prefix}${nonce}`, (current) => {
|
|
3471
|
+
if (!current) return { op: "noop", result: null };
|
|
3472
|
+
const record = current;
|
|
3473
|
+
if (Date.now() > record.expiresAt) {
|
|
3474
|
+
return { op: "delete", result: null };
|
|
3475
|
+
}
|
|
3476
|
+
return { op: "delete", result: record };
|
|
3477
|
+
});
|
|
3478
|
+
}
|
|
3479
|
+
};
|
|
3480
|
+
}
|
|
3481
|
+
function createAgentIdentityNonceStore(kv) {
|
|
3482
|
+
return kv ? createKvAgentIdentityNonceStore(kv) : new NonceStore();
|
|
3483
|
+
}
|
|
3484
|
+
|
|
3345
3485
|
// src/protocols/mpp/siwx-mode.ts
|
|
3346
3486
|
import { Credential as Credential2 } from "mppx";
|
|
3347
3487
|
async function verifyMppSiwx(request, mppx) {
|
|
@@ -3468,6 +3608,7 @@ async function buildSiwxChallenge(ctx) {
|
|
|
3468
3608
|
} catch {
|
|
3469
3609
|
}
|
|
3470
3610
|
}
|
|
3611
|
+
await attachAgentIdentityChallenge(response, request, deps.agentIdentityNonceStore);
|
|
3471
3612
|
firePluginResponse(ctx, response);
|
|
3472
3613
|
return response;
|
|
3473
3614
|
}
|
|
@@ -3678,7 +3819,9 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3678
3819
|
if (maxPrice) next.#s.maxPrice = maxPrice;
|
|
3679
3820
|
if (resolvedOptions.minPrice) next.#s.minPrice = resolvedOptions.minPrice;
|
|
3680
3821
|
if (resolvedOptions.payTo) next.#s.payTo = resolvedOptions.payTo;
|
|
3681
|
-
if (resolvedOptions.mpp)
|
|
3822
|
+
if (resolvedOptions.mpp) {
|
|
3823
|
+
next.#s.mppInfo = { ...next.#s.mppInfo, ...resolvedOptions.mpp };
|
|
3824
|
+
}
|
|
3682
3825
|
if (resolvedOptions.checkout || resolvedOptions.checkoutSession) next.#s.hasCheckout = true;
|
|
3683
3826
|
if (resolvedOptions.checkoutSession) next.#s.checkoutSession = resolvedOptions.checkoutSession;
|
|
3684
3827
|
next.#s.billing = billing;
|
|
@@ -3973,6 +4116,24 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3973
4116
|
next.#s.validateFn = fn;
|
|
3974
4117
|
return next;
|
|
3975
4118
|
}
|
|
4119
|
+
/**
|
|
4120
|
+
* Override MPP protocol metadata and per-route MPP settlement options.
|
|
4121
|
+
* Merges with any `mpp` options passed to `.paid()`. Use on auto-priced routes
|
|
4122
|
+
* (from `RouterConfig.prices`) when you cannot call `.paid()` again.
|
|
4123
|
+
*
|
|
4124
|
+
* @example
|
|
4125
|
+
* ```ts
|
|
4126
|
+
* router.route('exa/answer')
|
|
4127
|
+
* .mpp({ settleBeforeHandler: true })
|
|
4128
|
+
* .body(schema)
|
|
4129
|
+
* .handler(handler);
|
|
4130
|
+
* ```
|
|
4131
|
+
*/
|
|
4132
|
+
mpp(info) {
|
|
4133
|
+
const next = this.fork();
|
|
4134
|
+
next.#s.mppInfo = { ...this.#s.mppInfo, ...info };
|
|
4135
|
+
return next;
|
|
4136
|
+
}
|
|
3976
4137
|
/**
|
|
3977
4138
|
* Hook into the settlement lifecycle. `beforeSettle` runs after the handler
|
|
3978
4139
|
* succeeds but before on-chain settlement and can cancel the charge;
|
|
@@ -3988,7 +4149,7 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
3988
4149
|
*/
|
|
3989
4150
|
settlement(lifecycle) {
|
|
3990
4151
|
const next = this.fork();
|
|
3991
|
-
next.#s.settlement = lifecycle;
|
|
4152
|
+
next.#s.settlement = { ...this.#s.settlement, ...lifecycle };
|
|
3992
4153
|
return next;
|
|
3993
4154
|
}
|
|
3994
4155
|
/**
|
|
@@ -4044,6 +4205,21 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
4044
4205
|
if (this.#s.settlement && !this.#s.pricing) {
|
|
4045
4206
|
throw new Error(`route '${this.#s.key}': .settlement() requires a paid route`);
|
|
4046
4207
|
}
|
|
4208
|
+
if (this.#s.mppInfo?.settleBeforeHandler) {
|
|
4209
|
+
if (!this.#s.pricing) {
|
|
4210
|
+
throw new Error(`route '${this.#s.key}': mpp.settleBeforeHandler requires a paid route`);
|
|
4211
|
+
}
|
|
4212
|
+
if (this.#s.billing !== "exact") {
|
|
4213
|
+
throw new Error(
|
|
4214
|
+
`route '${this.#s.key}': mpp.settleBeforeHandler is only supported on .paid() routes`
|
|
4215
|
+
);
|
|
4216
|
+
}
|
|
4217
|
+
if (this.#s.settlement?.beforeSettle) {
|
|
4218
|
+
throw new Error(
|
|
4219
|
+
`route '${this.#s.key}': mpp.settleBeforeHandler is incompatible with .settlement({ beforeSettle }) \u2014 MPP payment is already broadcast before the handler runs`
|
|
4220
|
+
);
|
|
4221
|
+
}
|
|
4222
|
+
}
|
|
4047
4223
|
if (this.#s.billing === "upto") {
|
|
4048
4224
|
const hasUpto = this.#s.deps.x402Accepts.some((accept) => accept.scheme === "upto");
|
|
4049
4225
|
if (!hasUpto) {
|
|
@@ -4484,7 +4660,7 @@ function createNotFoundHandler(baseUrl) {
|
|
|
4484
4660
|
headers: {
|
|
4485
4661
|
"Access-Control-Allow-Origin": "*",
|
|
4486
4662
|
"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"
|
|
4663
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-API-Key, SIGN-IN-WITH-X, X-Agent-Identity"
|
|
4488
4664
|
}
|
|
4489
4665
|
}
|
|
4490
4666
|
);
|
|
@@ -5098,6 +5274,7 @@ function createRouter(config) {
|
|
|
5098
5274
|
const registry = new RouteRegistry();
|
|
5099
5275
|
const kvStore = resolveKvStore(config.kvStore);
|
|
5100
5276
|
const nonceStore = kvStore ? createKvNonceStore(kvStore) : new MemoryNonceStore();
|
|
5277
|
+
const agentIdentityNonceStore = createAgentIdentityNonceStore(kvStore);
|
|
5101
5278
|
const entitlementStore = kvStore ? createKvEntitlementStore(kvStore) : new MemoryEntitlementStore();
|
|
5102
5279
|
const network = config.network ?? BASE_MAINNET_NETWORK;
|
|
5103
5280
|
const x402Accepts = getConfiguredX402Accepts(config);
|
|
@@ -5132,6 +5309,7 @@ function createRouter(config) {
|
|
|
5132
5309
|
initPromise: Promise.resolve(),
|
|
5133
5310
|
plugin: config.plugin,
|
|
5134
5311
|
nonceStore,
|
|
5312
|
+
agentIdentityNonceStore,
|
|
5135
5313
|
entitlementStore,
|
|
5136
5314
|
payeeAddress: config.payeeAddress ?? "",
|
|
5137
5315
|
mppRecipient: config.mpp?.recipient ?? config.payeeAddress,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentcash/router",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "Unified route builder for Next.js App Router APIs with x402, MPP, SIWX, and API key auth",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"@x402/evm": "^2.13.0",
|
|
33
33
|
"@x402/extensions": "^2.13.0",
|
|
34
34
|
"@x402/svm": "^2.13.0",
|
|
35
|
+
"did-auth-challenge": "^0.0.1",
|
|
35
36
|
"mppx": "^0.6.16",
|
|
36
37
|
"viem": "^2.47.6",
|
|
37
38
|
"zod-openapi": "^5.0.0"
|