@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 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 (response.status >= 400 && response.status !== 402) {
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/pipeline/flows/static/static-invoke.ts
873
- function invokePaidStatic(ctx, wallet, account, body, payment) {
874
- return runHandler(ctx, buildHandlerCtx(ctx, wallet, account, body, payment));
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
- function invokeUnauthed(ctx, wallet, account, body) {
877
- const base = buildHandlerCtx(ctx, wallet, account, body, null);
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 buildHandlerCtx(ctx, wallet, account, body, payment) {
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
- if (info.payloadType === "transaction" && args.deps.tempoClient) {
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 import_types2 = require("@x402/core/types");
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 import_types2.VerifyError && err.statusCode >= 400 && err.statusCode < 500) {
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(ctx, wallet, account, body, payment);
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) next.#s.mppInfo = 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. For handler-computed billing use `.upTo()`;
580
- * for per-tick billing use `.metered()`.
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. For handler-computed billing use `.upTo()`;
580
- * for per-tick billing use `.metered()`.
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 (response.status >= 400 && response.status !== 402) {
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/pipeline/flows/static/static-invoke.ts
831
- function invokePaidStatic(ctx, wallet, account, body, payment) {
832
- return runHandler(ctx, buildHandlerCtx(ctx, wallet, account, body, payment));
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
- function invokeUnauthed(ctx, wallet, account, body) {
835
- const base = buildHandlerCtx(ctx, wallet, account, body, null);
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 buildHandlerCtx(ctx, wallet, account, body, payment) {
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
- if (info.payloadType === "transaction" && args.deps.tempoClient) {
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(ctx, wallet, account, body, payment);
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) next.#s.mppInfo = 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.10.6",
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"