@agentcash/router 1.10.6 → 1.10.7
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/dist/index.cjs +154 -16
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +156 -14
- package/package.json +2 -1
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,
|
|
@@ -2012,7 +2087,7 @@ function tagBareDecimalAsDollars(amount) {
|
|
|
2012
2087
|
}
|
|
2013
2088
|
|
|
2014
2089
|
// src/protocols/x402/verify.ts
|
|
2015
|
-
var
|
|
2090
|
+
var import_types3 = require("@x402/core/types");
|
|
2016
2091
|
async function verifyX402Payment(opts) {
|
|
2017
2092
|
const { server, request, price, accepts, report } = opts;
|
|
2018
2093
|
const payment = await readPaymentPayload(request);
|
|
@@ -2038,7 +2113,7 @@ async function verifyX402Payment(opts) {
|
|
|
2038
2113
|
try {
|
|
2039
2114
|
verify = await server.verifyPayment(payload, matching);
|
|
2040
2115
|
} catch (err) {
|
|
2041
|
-
if (err instanceof
|
|
2116
|
+
if (err instanceof import_types3.VerifyError && err.statusCode >= 400 && err.statusCode < 500) {
|
|
2042
2117
|
return invalidPaymentVerification({
|
|
2043
2118
|
reason: err.invalidReason ?? "verify_error",
|
|
2044
2119
|
...err.invalidMessage ? { message: err.invalidMessage } : {},
|
|
@@ -2611,6 +2686,7 @@ async function buildChallengeResponse(ctx, pricing, body, failure) {
|
|
|
2611
2686
|
}
|
|
2612
2687
|
}
|
|
2613
2688
|
}
|
|
2689
|
+
await attachAgentIdentityChallenge(response, ctx.request, ctx.deps.agentIdentityNonceStore);
|
|
2614
2690
|
firePluginResponse(ctx, response);
|
|
2615
2691
|
return response;
|
|
2616
2692
|
}
|
|
@@ -2745,7 +2821,7 @@ function createChargeContext(args) {
|
|
|
2745
2821
|
|
|
2746
2822
|
// src/pipeline/flows/dynamic/dynamic-invoke/shared.ts
|
|
2747
2823
|
var import_server7 = require("next/server");
|
|
2748
|
-
function buildBaseHandlerCtx(ctx, wallet, account, body, payment) {
|
|
2824
|
+
function buildBaseHandlerCtx(ctx, wallet, account, body, payment, actor = null) {
|
|
2749
2825
|
return {
|
|
2750
2826
|
body,
|
|
2751
2827
|
query: ctx.query,
|
|
@@ -2753,12 +2829,21 @@ function buildBaseHandlerCtx(ctx, wallet, account, body, payment) {
|
|
|
2753
2829
|
requestId: ctx.meta.requestId,
|
|
2754
2830
|
route: ctx.routeEntry.key,
|
|
2755
2831
|
wallet,
|
|
2832
|
+
actor,
|
|
2756
2833
|
payment,
|
|
2757
2834
|
account,
|
|
2758
2835
|
alert: ctx.report,
|
|
2759
2836
|
setVerifiedWallet: (addr) => ctx.pluginCtx.setVerifiedWallet(addr)
|
|
2760
2837
|
};
|
|
2761
2838
|
}
|
|
2839
|
+
async function resolveActorOrError(ctx) {
|
|
2840
|
+
try {
|
|
2841
|
+
const actor = await resolveActor(ctx.request, ctx.deps.agentIdentityNonceStore);
|
|
2842
|
+
return { actor };
|
|
2843
|
+
} catch (error) {
|
|
2844
|
+
return errorResult2(error);
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2762
2847
|
function toResponse(rawResult) {
|
|
2763
2848
|
return rawResult instanceof Response ? rawResult : import_server7.NextResponse.json(rawResult);
|
|
2764
2849
|
}
|
|
@@ -2782,12 +2867,21 @@ function isThenable2(value) {
|
|
|
2782
2867
|
|
|
2783
2868
|
// src/pipeline/flows/dynamic/dynamic-invoke/metered-invoke.ts
|
|
2784
2869
|
async function invokeMetered(ctx, wallet, account, body, payment) {
|
|
2870
|
+
const actorResult = await resolveActorOrError(ctx);
|
|
2871
|
+
if ("response" in actorResult) return actorResult;
|
|
2785
2872
|
const chargeContext = ctx.routeEntry.streaming ? createChargeContext({
|
|
2786
2873
|
tickCost: ctx.routeEntry.tickCost,
|
|
2787
2874
|
maxPrice: ctx.routeEntry.maxPrice,
|
|
2788
2875
|
route: ctx.routeEntry.key
|
|
2789
2876
|
}) : null;
|
|
2790
|
-
const baseHandlerCtx = buildBaseHandlerCtx(
|
|
2877
|
+
const baseHandlerCtx = buildBaseHandlerCtx(
|
|
2878
|
+
ctx,
|
|
2879
|
+
wallet,
|
|
2880
|
+
account,
|
|
2881
|
+
body,
|
|
2882
|
+
payment,
|
|
2883
|
+
actorResult.actor
|
|
2884
|
+
);
|
|
2791
2885
|
const handlerCtx = chargeContext !== null ? { ...baseHandlerCtx, charge: chargeContext.charge } : baseHandlerCtx;
|
|
2792
2886
|
let returned;
|
|
2793
2887
|
try {
|
|
@@ -2846,12 +2940,14 @@ function createUptoChargeContext(args) {
|
|
|
2846
2940
|
|
|
2847
2941
|
// src/pipeline/flows/dynamic/dynamic-invoke/upto-invoke.ts
|
|
2848
2942
|
async function invokeUpto(ctx, wallet, account, body, payment) {
|
|
2943
|
+
const actorResult = await resolveActorOrError(ctx);
|
|
2944
|
+
if ("response" in actorResult) return actorResult;
|
|
2849
2945
|
const uptoCtx = createUptoChargeContext({
|
|
2850
2946
|
maxPrice: ctx.routeEntry.maxPrice,
|
|
2851
2947
|
route: ctx.routeEntry.key
|
|
2852
2948
|
});
|
|
2853
2949
|
const handlerCtx = {
|
|
2854
|
-
...buildBaseHandlerCtx(ctx, wallet, account, body, payment),
|
|
2950
|
+
...buildBaseHandlerCtx(ctx, wallet, account, body, payment, actorResult.actor),
|
|
2855
2951
|
charge: uptoCtx.charge
|
|
2856
2952
|
};
|
|
2857
2953
|
let returned;
|
|
@@ -3384,6 +3480,45 @@ async function createKvMppStore(kv, options) {
|
|
|
3384
3480
|
});
|
|
3385
3481
|
}
|
|
3386
3482
|
|
|
3483
|
+
// src/kv-store/agent-identity-nonce.ts
|
|
3484
|
+
var import_did_auth_challenge2 = require("did-auth-challenge");
|
|
3485
|
+
function createKvAgentIdentityNonceStore(kv, options) {
|
|
3486
|
+
const prefix = options?.prefix ?? "actor:nonce:";
|
|
3487
|
+
return {
|
|
3488
|
+
async add(nonce, expiresAt, challengeJson) {
|
|
3489
|
+
const ttl = Math.max(1, Math.ceil((expiresAt.getTime() - Date.now()) / 1e3));
|
|
3490
|
+
const record = { challengeJson, expiresAt: expiresAt.getTime() };
|
|
3491
|
+
await kv.setNxEx(`${prefix}${nonce}`, record, ttl);
|
|
3492
|
+
},
|
|
3493
|
+
async get(nonce) {
|
|
3494
|
+
const raw = await kv.get(`${prefix}${nonce}`);
|
|
3495
|
+
if (!raw) return null;
|
|
3496
|
+
const record = raw;
|
|
3497
|
+
if (Date.now() > record.expiresAt) {
|
|
3498
|
+
await kv.del(`${prefix}${nonce}`);
|
|
3499
|
+
return null;
|
|
3500
|
+
}
|
|
3501
|
+
return record;
|
|
3502
|
+
},
|
|
3503
|
+
async has(nonce) {
|
|
3504
|
+
return await this.get(nonce) !== null;
|
|
3505
|
+
},
|
|
3506
|
+
async consume(nonce) {
|
|
3507
|
+
return kv.update(`${prefix}${nonce}`, (current) => {
|
|
3508
|
+
if (!current) return { op: "noop", result: null };
|
|
3509
|
+
const record = current;
|
|
3510
|
+
if (Date.now() > record.expiresAt) {
|
|
3511
|
+
return { op: "delete", result: null };
|
|
3512
|
+
}
|
|
3513
|
+
return { op: "delete", result: record };
|
|
3514
|
+
});
|
|
3515
|
+
}
|
|
3516
|
+
};
|
|
3517
|
+
}
|
|
3518
|
+
function createAgentIdentityNonceStore(kv) {
|
|
3519
|
+
return kv ? createKvAgentIdentityNonceStore(kv) : new import_did_auth_challenge2.NonceStore();
|
|
3520
|
+
}
|
|
3521
|
+
|
|
3387
3522
|
// src/protocols/mpp/siwx-mode.ts
|
|
3388
3523
|
var import_mppx3 = require("mppx");
|
|
3389
3524
|
async function verifyMppSiwx(request, mppx) {
|
|
@@ -3510,6 +3645,7 @@ async function buildSiwxChallenge(ctx) {
|
|
|
3510
3645
|
} catch {
|
|
3511
3646
|
}
|
|
3512
3647
|
}
|
|
3648
|
+
await attachAgentIdentityChallenge(response, request, deps.agentIdentityNonceStore);
|
|
3513
3649
|
firePluginResponse(ctx, response);
|
|
3514
3650
|
return response;
|
|
3515
3651
|
}
|
|
@@ -4526,7 +4662,7 @@ function createNotFoundHandler(baseUrl) {
|
|
|
4526
4662
|
headers: {
|
|
4527
4663
|
"Access-Control-Allow-Origin": "*",
|
|
4528
4664
|
"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"
|
|
4665
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-API-Key, SIGN-IN-WITH-X, X-Agent-Identity"
|
|
4530
4666
|
}
|
|
4531
4667
|
}
|
|
4532
4668
|
);
|
|
@@ -5140,6 +5276,7 @@ function createRouter(config) {
|
|
|
5140
5276
|
const registry = new RouteRegistry();
|
|
5141
5277
|
const kvStore = resolveKvStore(config.kvStore);
|
|
5142
5278
|
const nonceStore = kvStore ? createKvNonceStore(kvStore) : new MemoryNonceStore();
|
|
5279
|
+
const agentIdentityNonceStore = createAgentIdentityNonceStore(kvStore);
|
|
5143
5280
|
const entitlementStore = kvStore ? createKvEntitlementStore(kvStore) : new MemoryEntitlementStore();
|
|
5144
5281
|
const network = config.network ?? BASE_MAINNET_NETWORK;
|
|
5145
5282
|
const x402Accepts = getConfiguredX402Accepts(config);
|
|
@@ -5174,6 +5311,7 @@ function createRouter(config) {
|
|
|
5174
5311
|
initPromise: Promise.resolve(),
|
|
5175
5312
|
plugin: config.plugin,
|
|
5176
5313
|
nonceStore,
|
|
5314
|
+
agentIdentityNonceStore,
|
|
5177
5315
|
entitlementStore,
|
|
5178
5316
|
payeeAddress: config.payeeAddress ?? "",
|
|
5179
5317
|
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;
|
|
@@ -320,6 +323,8 @@ interface HandlerContext<TBody = undefined, TQuery = undefined> {
|
|
|
320
323
|
requestId: string;
|
|
321
324
|
route: string;
|
|
322
325
|
wallet: string | null;
|
|
326
|
+
/** Optional DID from `X-Agent-Identity` proof. Null when the client omits identity. */
|
|
327
|
+
actor: string | null;
|
|
323
328
|
payment: HandlerPaymentContext | null;
|
|
324
329
|
account: unknown;
|
|
325
330
|
alert: AlertFn;
|
|
@@ -498,6 +503,7 @@ interface RouterDeps {
|
|
|
498
503
|
mppInitError?: string;
|
|
499
504
|
plugin?: RouterPlugin;
|
|
500
505
|
nonceStore: NonceStore;
|
|
506
|
+
agentIdentityNonceStore: NonceStoreInterface;
|
|
501
507
|
entitlementStore: EntitlementStore;
|
|
502
508
|
payeeAddress: string;
|
|
503
509
|
mppRecipient?: string;
|
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;
|
|
@@ -320,6 +323,8 @@ interface HandlerContext<TBody = undefined, TQuery = undefined> {
|
|
|
320
323
|
requestId: string;
|
|
321
324
|
route: string;
|
|
322
325
|
wallet: string | null;
|
|
326
|
+
/** Optional DID from `X-Agent-Identity` proof. Null when the client omits identity. */
|
|
327
|
+
actor: string | null;
|
|
323
328
|
payment: HandlerPaymentContext | null;
|
|
324
329
|
account: unknown;
|
|
325
330
|
alert: AlertFn;
|
|
@@ -498,6 +503,7 @@ interface RouterDeps {
|
|
|
498
503
|
mppInitError?: string;
|
|
499
504
|
plugin?: RouterPlugin;
|
|
500
505
|
nonceStore: NonceStore;
|
|
506
|
+
agentIdentityNonceStore: NonceStoreInterface;
|
|
501
507
|
entitlementStore: EntitlementStore;
|
|
502
508
|
payeeAddress: string;
|
|
503
509
|
mppRecipient?: string;
|
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,
|
|
@@ -2569,6 +2648,7 @@ async function buildChallengeResponse(ctx, pricing, body, failure) {
|
|
|
2569
2648
|
}
|
|
2570
2649
|
}
|
|
2571
2650
|
}
|
|
2651
|
+
await attachAgentIdentityChallenge(response, ctx.request, ctx.deps.agentIdentityNonceStore);
|
|
2572
2652
|
firePluginResponse(ctx, response);
|
|
2573
2653
|
return response;
|
|
2574
2654
|
}
|
|
@@ -2703,7 +2783,7 @@ function createChargeContext(args) {
|
|
|
2703
2783
|
|
|
2704
2784
|
// src/pipeline/flows/dynamic/dynamic-invoke/shared.ts
|
|
2705
2785
|
import { NextResponse as NextResponse7 } from "next/server";
|
|
2706
|
-
function buildBaseHandlerCtx(ctx, wallet, account, body, payment) {
|
|
2786
|
+
function buildBaseHandlerCtx(ctx, wallet, account, body, payment, actor = null) {
|
|
2707
2787
|
return {
|
|
2708
2788
|
body,
|
|
2709
2789
|
query: ctx.query,
|
|
@@ -2711,12 +2791,21 @@ function buildBaseHandlerCtx(ctx, wallet, account, body, payment) {
|
|
|
2711
2791
|
requestId: ctx.meta.requestId,
|
|
2712
2792
|
route: ctx.routeEntry.key,
|
|
2713
2793
|
wallet,
|
|
2794
|
+
actor,
|
|
2714
2795
|
payment,
|
|
2715
2796
|
account,
|
|
2716
2797
|
alert: ctx.report,
|
|
2717
2798
|
setVerifiedWallet: (addr) => ctx.pluginCtx.setVerifiedWallet(addr)
|
|
2718
2799
|
};
|
|
2719
2800
|
}
|
|
2801
|
+
async function resolveActorOrError(ctx) {
|
|
2802
|
+
try {
|
|
2803
|
+
const actor = await resolveActor(ctx.request, ctx.deps.agentIdentityNonceStore);
|
|
2804
|
+
return { actor };
|
|
2805
|
+
} catch (error) {
|
|
2806
|
+
return errorResult2(error);
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2720
2809
|
function toResponse(rawResult) {
|
|
2721
2810
|
return rawResult instanceof Response ? rawResult : NextResponse7.json(rawResult);
|
|
2722
2811
|
}
|
|
@@ -2740,12 +2829,21 @@ function isThenable2(value) {
|
|
|
2740
2829
|
|
|
2741
2830
|
// src/pipeline/flows/dynamic/dynamic-invoke/metered-invoke.ts
|
|
2742
2831
|
async function invokeMetered(ctx, wallet, account, body, payment) {
|
|
2832
|
+
const actorResult = await resolveActorOrError(ctx);
|
|
2833
|
+
if ("response" in actorResult) return actorResult;
|
|
2743
2834
|
const chargeContext = ctx.routeEntry.streaming ? createChargeContext({
|
|
2744
2835
|
tickCost: ctx.routeEntry.tickCost,
|
|
2745
2836
|
maxPrice: ctx.routeEntry.maxPrice,
|
|
2746
2837
|
route: ctx.routeEntry.key
|
|
2747
2838
|
}) : null;
|
|
2748
|
-
const baseHandlerCtx = buildBaseHandlerCtx(
|
|
2839
|
+
const baseHandlerCtx = buildBaseHandlerCtx(
|
|
2840
|
+
ctx,
|
|
2841
|
+
wallet,
|
|
2842
|
+
account,
|
|
2843
|
+
body,
|
|
2844
|
+
payment,
|
|
2845
|
+
actorResult.actor
|
|
2846
|
+
);
|
|
2749
2847
|
const handlerCtx = chargeContext !== null ? { ...baseHandlerCtx, charge: chargeContext.charge } : baseHandlerCtx;
|
|
2750
2848
|
let returned;
|
|
2751
2849
|
try {
|
|
@@ -2804,12 +2902,14 @@ function createUptoChargeContext(args) {
|
|
|
2804
2902
|
|
|
2805
2903
|
// src/pipeline/flows/dynamic/dynamic-invoke/upto-invoke.ts
|
|
2806
2904
|
async function invokeUpto(ctx, wallet, account, body, payment) {
|
|
2905
|
+
const actorResult = await resolveActorOrError(ctx);
|
|
2906
|
+
if ("response" in actorResult) return actorResult;
|
|
2807
2907
|
const uptoCtx = createUptoChargeContext({
|
|
2808
2908
|
maxPrice: ctx.routeEntry.maxPrice,
|
|
2809
2909
|
route: ctx.routeEntry.key
|
|
2810
2910
|
});
|
|
2811
2911
|
const handlerCtx = {
|
|
2812
|
-
...buildBaseHandlerCtx(ctx, wallet, account, body, payment),
|
|
2912
|
+
...buildBaseHandlerCtx(ctx, wallet, account, body, payment, actorResult.actor),
|
|
2813
2913
|
charge: uptoCtx.charge
|
|
2814
2914
|
};
|
|
2815
2915
|
let returned;
|
|
@@ -3342,6 +3442,45 @@ async function createKvMppStore(kv, options) {
|
|
|
3342
3442
|
});
|
|
3343
3443
|
}
|
|
3344
3444
|
|
|
3445
|
+
// src/kv-store/agent-identity-nonce.ts
|
|
3446
|
+
import { NonceStore } from "did-auth-challenge";
|
|
3447
|
+
function createKvAgentIdentityNonceStore(kv, options) {
|
|
3448
|
+
const prefix = options?.prefix ?? "actor:nonce:";
|
|
3449
|
+
return {
|
|
3450
|
+
async add(nonce, expiresAt, challengeJson) {
|
|
3451
|
+
const ttl = Math.max(1, Math.ceil((expiresAt.getTime() - Date.now()) / 1e3));
|
|
3452
|
+
const record = { challengeJson, expiresAt: expiresAt.getTime() };
|
|
3453
|
+
await kv.setNxEx(`${prefix}${nonce}`, record, ttl);
|
|
3454
|
+
},
|
|
3455
|
+
async get(nonce) {
|
|
3456
|
+
const raw = await kv.get(`${prefix}${nonce}`);
|
|
3457
|
+
if (!raw) return null;
|
|
3458
|
+
const record = raw;
|
|
3459
|
+
if (Date.now() > record.expiresAt) {
|
|
3460
|
+
await kv.del(`${prefix}${nonce}`);
|
|
3461
|
+
return null;
|
|
3462
|
+
}
|
|
3463
|
+
return record;
|
|
3464
|
+
},
|
|
3465
|
+
async has(nonce) {
|
|
3466
|
+
return await this.get(nonce) !== null;
|
|
3467
|
+
},
|
|
3468
|
+
async consume(nonce) {
|
|
3469
|
+
return kv.update(`${prefix}${nonce}`, (current) => {
|
|
3470
|
+
if (!current) return { op: "noop", result: null };
|
|
3471
|
+
const record = current;
|
|
3472
|
+
if (Date.now() > record.expiresAt) {
|
|
3473
|
+
return { op: "delete", result: null };
|
|
3474
|
+
}
|
|
3475
|
+
return { op: "delete", result: record };
|
|
3476
|
+
});
|
|
3477
|
+
}
|
|
3478
|
+
};
|
|
3479
|
+
}
|
|
3480
|
+
function createAgentIdentityNonceStore(kv) {
|
|
3481
|
+
return kv ? createKvAgentIdentityNonceStore(kv) : new NonceStore();
|
|
3482
|
+
}
|
|
3483
|
+
|
|
3345
3484
|
// src/protocols/mpp/siwx-mode.ts
|
|
3346
3485
|
import { Credential as Credential2 } from "mppx";
|
|
3347
3486
|
async function verifyMppSiwx(request, mppx) {
|
|
@@ -3468,6 +3607,7 @@ async function buildSiwxChallenge(ctx) {
|
|
|
3468
3607
|
} catch {
|
|
3469
3608
|
}
|
|
3470
3609
|
}
|
|
3610
|
+
await attachAgentIdentityChallenge(response, request, deps.agentIdentityNonceStore);
|
|
3471
3611
|
firePluginResponse(ctx, response);
|
|
3472
3612
|
return response;
|
|
3473
3613
|
}
|
|
@@ -4484,7 +4624,7 @@ function createNotFoundHandler(baseUrl) {
|
|
|
4484
4624
|
headers: {
|
|
4485
4625
|
"Access-Control-Allow-Origin": "*",
|
|
4486
4626
|
"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"
|
|
4627
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-API-Key, SIGN-IN-WITH-X, X-Agent-Identity"
|
|
4488
4628
|
}
|
|
4489
4629
|
}
|
|
4490
4630
|
);
|
|
@@ -5098,6 +5238,7 @@ function createRouter(config) {
|
|
|
5098
5238
|
const registry = new RouteRegistry();
|
|
5099
5239
|
const kvStore = resolveKvStore(config.kvStore);
|
|
5100
5240
|
const nonceStore = kvStore ? createKvNonceStore(kvStore) : new MemoryNonceStore();
|
|
5241
|
+
const agentIdentityNonceStore = createAgentIdentityNonceStore(kvStore);
|
|
5101
5242
|
const entitlementStore = kvStore ? createKvEntitlementStore(kvStore) : new MemoryEntitlementStore();
|
|
5102
5243
|
const network = config.network ?? BASE_MAINNET_NETWORK;
|
|
5103
5244
|
const x402Accepts = getConfiguredX402Accepts(config);
|
|
@@ -5132,6 +5273,7 @@ function createRouter(config) {
|
|
|
5132
5273
|
initPromise: Promise.resolve(),
|
|
5133
5274
|
plugin: config.plugin,
|
|
5134
5275
|
nonceStore,
|
|
5276
|
+
agentIdentityNonceStore,
|
|
5135
5277
|
entitlementStore,
|
|
5136
5278
|
payeeAddress: config.payeeAddress ?? "",
|
|
5137
5279
|
mppRecipient: config.mpp?.recipient ?? config.payeeAddress,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentcash/router",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.7",
|
|
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"
|