@nevermined-io/payments 1.12.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +33 -0
  2. package/dist/a2a/clientRegistry.d.ts +10 -1
  3. package/dist/a2a/clientRegistry.d.ts.map +1 -1
  4. package/dist/a2a/clientRegistry.js +13 -4
  5. package/dist/a2a/clientRegistry.js.map +1 -1
  6. package/dist/a2a/paymentsClient.d.ts +60 -3
  7. package/dist/a2a/paymentsClient.d.ts.map +1 -1
  8. package/dist/a2a/paymentsClient.js +129 -8
  9. package/dist/a2a/paymentsClient.js.map +1 -1
  10. package/dist/a2a/paymentsRequestHandler.d.ts +5 -1
  11. package/dist/a2a/paymentsRequestHandler.d.ts.map +1 -1
  12. package/dist/a2a/paymentsRequestHandler.js +156 -13
  13. package/dist/a2a/paymentsRequestHandler.js.map +1 -1
  14. package/dist/a2a/types.d.ts +30 -0
  15. package/dist/a2a/types.d.ts.map +1 -1
  16. package/dist/a2a/types.js.map +1 -1
  17. package/dist/api/agents-api.d.ts +4 -3
  18. package/dist/api/agents-api.d.ts.map +1 -1
  19. package/dist/api/agents-api.js +7 -4
  20. package/dist/api/agents-api.js.map +1 -1
  21. package/dist/api/base-payments.d.ts +7 -2
  22. package/dist/api/base-payments.d.ts.map +1 -1
  23. package/dist/api/base-payments.js +15 -8
  24. package/dist/api/base-payments.js.map +1 -1
  25. package/dist/api/nvm-api.d.ts +5 -5
  26. package/dist/api/observability-api/types.d.ts +34 -6
  27. package/dist/api/observability-api/types.d.ts.map +1 -1
  28. package/dist/api/observability-api/types.js.map +1 -1
  29. package/dist/api/plans-api.d.ts +15 -12
  30. package/dist/api/plans-api.d.ts.map +1 -1
  31. package/dist/api/plans-api.js +17 -14
  32. package/dist/api/plans-api.js.map +1 -1
  33. package/dist/api/query-api.d.ts +1 -1
  34. package/dist/common/types.d.ts +175 -18
  35. package/dist/common/types.d.ts.map +1 -1
  36. package/dist/common/types.js.map +1 -1
  37. package/dist/index.d.ts +2 -1
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +1 -0
  40. package/dist/index.js.map +1 -1
  41. package/dist/mcp/core/paywall.d.ts.map +1 -1
  42. package/dist/mcp/core/paywall.js +69 -8
  43. package/dist/mcp/core/paywall.js.map +1 -1
  44. package/dist/mcp/http/index.d.ts +1 -1
  45. package/dist/mcp/http/index.d.ts.map +1 -1
  46. package/dist/mcp/http/index.js +1 -1
  47. package/dist/mcp/http/index.js.map +1 -1
  48. package/dist/mcp/http/oauth-metadata.d.ts +32 -0
  49. package/dist/mcp/http/oauth-metadata.d.ts.map +1 -1
  50. package/dist/mcp/http/oauth-metadata.js +329 -34
  51. package/dist/mcp/http/oauth-metadata.js.map +1 -1
  52. package/dist/mcp/index.d.ts +4 -4
  53. package/dist/mcp/index.d.ts.map +1 -1
  54. package/dist/mcp/index.js +5 -5
  55. package/dist/mcp/index.js.map +1 -1
  56. package/dist/mcp/types/http.types.d.ts +38 -3
  57. package/dist/mcp/types/http.types.d.ts.map +1 -1
  58. package/dist/mcp/types/http.types.js.map +1 -1
  59. package/dist/mcp/types/paywall.types.d.ts +19 -3
  60. package/dist/mcp/types/paywall.types.d.ts.map +1 -1
  61. package/dist/mcp/types/paywall.types.js.map +1 -1
  62. package/dist/mpp/fetch.d.ts +8 -3
  63. package/dist/mpp/fetch.d.ts.map +1 -1
  64. package/dist/mpp/fetch.js +6 -0
  65. package/dist/mpp/fetch.js.map +1 -1
  66. package/dist/mpp/mpp-api.d.ts +11 -5
  67. package/dist/mpp/mpp-api.d.ts.map +1 -1
  68. package/dist/mpp/mpp-api.js +10 -3
  69. package/dist/mpp/mpp-api.js.map +1 -1
  70. package/dist/payments.d.ts.map +1 -1
  71. package/dist/x402/delegation-api.d.ts +5 -17
  72. package/dist/x402/delegation-api.d.ts.map +1 -1
  73. package/dist/x402/delegation-api.js +2 -2
  74. package/dist/x402/delegation-api.js.map +1 -1
  75. package/dist/x402/express/middleware.d.ts.map +1 -1
  76. package/dist/x402/express/middleware.js +98 -1
  77. package/dist/x402/express/middleware.js.map +1 -1
  78. package/dist/x402/facilitator-api.d.ts +90 -7
  79. package/dist/x402/facilitator-api.d.ts.map +1 -1
  80. package/dist/x402/facilitator-api.js +64 -3
  81. package/dist/x402/facilitator-api.js.map +1 -1
  82. package/dist/x402/index.d.ts +3 -2
  83. package/dist/x402/index.d.ts.map +1 -1
  84. package/dist/x402/index.js +1 -0
  85. package/dist/x402/index.js.map +1 -1
  86. package/dist/x402/token-request.d.ts +8 -0
  87. package/dist/x402/token-request.d.ts.map +1 -1
  88. package/dist/x402/token-request.js +101 -1
  89. package/dist/x402/token-request.js.map +1 -1
  90. package/dist/x402/token-version.d.ts +97 -0
  91. package/dist/x402/token-version.d.ts.map +1 -0
  92. package/dist/x402/token-version.js +127 -0
  93. package/dist/x402/token-version.js.map +1 -0
  94. package/dist/x402/token.d.ts +33 -2
  95. package/dist/x402/token.d.ts.map +1 -1
  96. package/dist/x402/token.js +47 -2
  97. package/dist/x402/token.js.map +1 -1
  98. package/package.json +22 -13
package/README.md CHANGED
@@ -286,6 +286,39 @@ const agentHTTPOptions = {
286
286
  const response = await fetch(new URL(agentURL), agentHTTPOptions)
287
287
  ```
288
288
 
289
+ #### Single-Use Access Tokens (v3)
290
+
291
+ By default an access token is reusable for as long as the plan has credits. Opt
292
+ into a **single-use, seller-bound** token by passing the resource it is for plus
293
+ `tokenVersion: 3`:
294
+
295
+ ```typescript
296
+ // `resource.url` must be the exact string the seller advertises in its 402
297
+ // `paymentRequired`, NOT the URL you fetch — sellers built on this SDK's
298
+ // paymentMiddleware advertise `req.originalUrl`, i.e. a relative path.
299
+ const { accessToken, tokenVersion } = await payments.x402.getX402AccessToken(
300
+ creditsPlanId,
301
+ agentId,
302
+ {
303
+ delegationConfig: { delegationId },
304
+ resource: { url: paymentRequired.resource.url }, // e.g. '/ask'
305
+ httpVerb: 'POST',
306
+ tokenVersion: 3,
307
+ },
308
+ )
309
+ ```
310
+
311
+ A v3 token signs `agentId`, `resourceUrl`, `httpVerb` and a one-time `nonce`, so
312
+ it only settles against that one endpoint and is consumed by its first
313
+ settlement — mint a new one per paid request, and never cache it. The
314
+ `resource.url` must be the exact string the seller advertises in its
315
+ `paymentRequired` (origin + path), otherwise verification fails with
316
+ `BCK.X402.0013` — a check that applies to any token carrying a resource, not
317
+ just v3. Settling the token twice fails with `BCK.X402.0059`, which
318
+ `isAccessTokenAlreadyUsed(error)` identifies. Always read the returned `tokenVersion` rather than assuming the
319
+ version you requested: a backend without v3 support silently returns v2. See
320
+ [markdown/x402.md](./markdown/x402.md) for the full contract.
321
+
289
322
  ## MCP (Model Context Protocol)
290
323
 
291
324
  ### What is MCP?
@@ -15,8 +15,17 @@ export declare class ClientRegistry {
15
15
  constructor(payments: Payments);
16
16
  /**
17
17
  * Gets (or creates) a PaymentsClient for the given agentBaseUrl, agentId, and planId.
18
- * The combination of these three is used as a unique key.
18
+ * That triple plus the requested `tokenVersion` is the cache key.
19
19
  * It derives the Agent Card path when needed.
20
+ *
21
+ * `tokenVersion` is part of the key rather than ignored on a hit: it decides
22
+ * whether the client mints a single-use, seller-bound token per paid call or
23
+ * caches one reusable token for its lifetime. Keying without it would hand a
24
+ * caller who asked for v3 a client that mints v2 — the security control
25
+ * dropped silently, with nothing on the returned client to detect it — and,
26
+ * in the other direction, would put a caller who wanted the cached v2 path on
27
+ * per-call v3 mints because someone else asked first.
28
+ *
20
29
  * @param options - ClientRegistryOptions with agentBaseUrl, agentId, planId (all required).
21
30
  * @returns The PaymentsClient instance
22
31
  */
@@ -1 +1 @@
1
- {"version":3,"file":"clientRegistry.d.ts","sourceRoot":"","sources":["../../src/a2a/clientRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAGvD;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAyC;IACxD,OAAO,CAAC,QAAQ,CAAU;IAE1B;;;OAGG;gBACS,QAAQ,EAAE,QAAQ;IAI9B;;;;;;OAMG;IACU,SAAS,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,cAAc,CAAC;CAqBhF"}
1
+ {"version":3,"file":"clientRegistry.d.ts","sourceRoot":"","sources":["../../src/a2a/clientRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAGvD;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAyC;IACxD,OAAO,CAAC,QAAQ,CAAU;IAE1B;;;OAGG;gBACS,QAAQ,EAAE,QAAQ;IAI9B;;;;;;;;;;;;;;;OAeG;IACU,SAAS,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,cAAc,CAAC;CAuBhF"}
@@ -15,20 +15,29 @@ export class ClientRegistry {
15
15
  }
16
16
  /**
17
17
  * Gets (or creates) a PaymentsClient for the given agentBaseUrl, agentId, and planId.
18
- * The combination of these three is used as a unique key.
18
+ * That triple plus the requested `tokenVersion` is the cache key.
19
19
  * It derives the Agent Card path when needed.
20
+ *
21
+ * `tokenVersion` is part of the key rather than ignored on a hit: it decides
22
+ * whether the client mints a single-use, seller-bound token per paid call or
23
+ * caches one reusable token for its lifetime. Keying without it would hand a
24
+ * caller who asked for v3 a client that mints v2 — the security control
25
+ * dropped silently, with nothing on the returned client to detect it — and,
26
+ * in the other direction, would put a caller who wanted the cached v2 path on
27
+ * per-call v3 mints because someone else asked first.
28
+ *
20
29
  * @param options - ClientRegistryOptions with agentBaseUrl, agentId, planId (all required).
21
30
  * @returns The PaymentsClient instance
22
31
  */
23
32
  async getClient(options) {
24
- const { agentBaseUrl, agentId, planId, agentCardPath, delegationConfig } = options;
33
+ const { agentBaseUrl, agentId, planId, agentCardPath, delegationConfig, tokenVersion } = options;
25
34
  if (!agentBaseUrl || !agentId || !planId) {
26
35
  throw PaymentsError.validation('Missing required fields');
27
36
  }
28
- const key = `${agentBaseUrl}::${agentId}::${planId}`;
37
+ const key = `${agentBaseUrl}::${agentId}::${planId}::v${tokenVersion ?? 'default'}`;
29
38
  let client = this.clients.get(key);
30
39
  if (!client) {
31
- client = await PaymentsClient.create(agentBaseUrl, this.payments, agentId, planId, agentCardPath, delegationConfig);
40
+ client = await PaymentsClient.create(agentBaseUrl, this.payments, agentId, planId, agentCardPath, delegationConfig, tokenVersion);
32
41
  this.clients.set(key, client);
33
42
  }
34
43
  return client;
@@ -1 +1 @@
1
- {"version":3,"file":"clientRegistry.js","sourceRoot":"","sources":["../../src/a2a/clientRegistry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAEpD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAE3D;;;GAGG;AACH,MAAM,OAAO,cAAc;IAIzB;;;OAGG;IACH,YAAY,QAAkB;QAPtB,YAAO,GAAgC,IAAI,GAAG,EAAE,CAAA;QAQtD,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;IAC1B,CAAC;IAED;;;;;;OAMG;IACI,KAAK,CAAC,SAAS,CAAC,OAA8B;QACnD,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAA;QAClF,IAAI,CAAC,YAAY,IAAI,CAAC,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;YACzC,MAAM,aAAa,CAAC,UAAU,CAAC,yBAAyB,CAAC,CAAA;QAC3D,CAAC;QAED,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,OAAO,KAAK,MAAM,EAAE,CAAA;QACpD,IAAI,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAClC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,GAAG,MAAM,cAAc,CAAC,MAAM,CAClC,YAAY,EACZ,IAAI,CAAC,QAAQ,EACb,OAAO,EACP,MAAM,EACN,aAAa,EACb,gBAAgB,CACjB,CAAA;YACD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;QAC/B,CAAC;QACD,OAAO,MAAM,CAAA;IACf,CAAC;CACF","sourcesContent":["import { Payments } from '../index.js'\nimport { PaymentsClient } from './paymentsClient.js'\nimport type { ClientRegistryOptions } from './types.js'\nimport { PaymentsError } from '../common/payments.error.js'\n\n/**\n * Registry for managing multiple PaymentsClient instances by agentId+planId+baseUrl.\n * If a client does not exist for a given combination, it is created and cached.\n */\nexport class ClientRegistry {\n private clients: Map<string, PaymentsClient> = new Map()\n private payments: Payments\n\n /**\n * Constructs a ClientRegistry.\n * @param payments - The Payments instance to use for all clients.\n */\n constructor(payments: Payments) {\n this.payments = payments\n }\n\n /**\n * Gets (or creates) a PaymentsClient for the given agentBaseUrl, agentId, and planId.\n * The combination of these three is used as a unique key.\n * It derives the Agent Card path when needed.\n * @param options - ClientRegistryOptions with agentBaseUrl, agentId, planId (all required).\n * @returns The PaymentsClient instance\n */\n public async getClient(options: ClientRegistryOptions): Promise<PaymentsClient> {\n const { agentBaseUrl, agentId, planId, agentCardPath, delegationConfig } = options\n if (!agentBaseUrl || !agentId || !planId) {\n throw PaymentsError.validation('Missing required fields')\n }\n\n const key = `${agentBaseUrl}::${agentId}::${planId}`\n let client = this.clients.get(key)\n if (!client) {\n client = await PaymentsClient.create(\n agentBaseUrl,\n this.payments,\n agentId,\n planId,\n agentCardPath,\n delegationConfig,\n )\n this.clients.set(key, client)\n }\n return client\n }\n}\n"]}
1
+ {"version":3,"file":"clientRegistry.js","sourceRoot":"","sources":["../../src/a2a/clientRegistry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAEpD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAE3D;;;GAGG;AACH,MAAM,OAAO,cAAc;IAIzB;;;OAGG;IACH,YAAY,QAAkB;QAPtB,YAAO,GAAgC,IAAI,GAAG,EAAE,CAAA;QAQtD,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;IAC1B,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACI,KAAK,CAAC,SAAS,CAAC,OAA8B;QACnD,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,gBAAgB,EAAE,YAAY,EAAE,GACpF,OAAO,CAAA;QACT,IAAI,CAAC,YAAY,IAAI,CAAC,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;YACzC,MAAM,aAAa,CAAC,UAAU,CAAC,yBAAyB,CAAC,CAAA;QAC3D,CAAC;QAED,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,OAAO,KAAK,MAAM,MAAM,YAAY,IAAI,SAAS,EAAE,CAAA;QACnF,IAAI,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAClC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,GAAG,MAAM,cAAc,CAAC,MAAM,CAClC,YAAY,EACZ,IAAI,CAAC,QAAQ,EACb,OAAO,EACP,MAAM,EACN,aAAa,EACb,gBAAgB,EAChB,YAAY,CACb,CAAA;YACD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;QAC/B,CAAC;QACD,OAAO,MAAM,CAAA;IACf,CAAC;CACF","sourcesContent":["import { Payments } from '../index.js'\nimport { PaymentsClient } from './paymentsClient.js'\nimport type { ClientRegistryOptions } from './types.js'\nimport { PaymentsError } from '../common/payments.error.js'\n\n/**\n * Registry for managing multiple PaymentsClient instances by agentId+planId+baseUrl.\n * If a client does not exist for a given combination, it is created and cached.\n */\nexport class ClientRegistry {\n private clients: Map<string, PaymentsClient> = new Map()\n private payments: Payments\n\n /**\n * Constructs a ClientRegistry.\n * @param payments - The Payments instance to use for all clients.\n */\n constructor(payments: Payments) {\n this.payments = payments\n }\n\n /**\n * Gets (or creates) a PaymentsClient for the given agentBaseUrl, agentId, and planId.\n * That triple plus the requested `tokenVersion` is the cache key.\n * It derives the Agent Card path when needed.\n *\n * `tokenVersion` is part of the key rather than ignored on a hit: it decides\n * whether the client mints a single-use, seller-bound token per paid call or\n * caches one reusable token for its lifetime. Keying without it would hand a\n * caller who asked for v3 a client that mints v2 — the security control\n * dropped silently, with nothing on the returned client to detect it — and,\n * in the other direction, would put a caller who wanted the cached v2 path on\n * per-call v3 mints because someone else asked first.\n *\n * @param options - ClientRegistryOptions with agentBaseUrl, agentId, planId (all required).\n * @returns The PaymentsClient instance\n */\n public async getClient(options: ClientRegistryOptions): Promise<PaymentsClient> {\n const { agentBaseUrl, agentId, planId, agentCardPath, delegationConfig, tokenVersion } =\n options\n if (!agentBaseUrl || !agentId || !planId) {\n throw PaymentsError.validation('Missing required fields')\n }\n\n const key = `${agentBaseUrl}::${agentId}::${planId}::v${tokenVersion ?? 'default'}`\n let client = this.clients.get(key)\n if (!client) {\n client = await PaymentsClient.create(\n agentBaseUrl,\n this.payments,\n agentId,\n planId,\n agentCardPath,\n delegationConfig,\n tokenVersion,\n )\n this.clients.set(key, client)\n }\n return client\n }\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { Payments } from '../index.js';
2
- import type { DelegationConfig } from '../common/types.js';
2
+ import type { DelegationConfig, X402TokenVersion } from '../common/types.js';
3
3
  import { MessageSendParams, SendMessageResponse, TaskQueryParams, GetTaskResponse, TaskPushNotificationConfig, SetTaskPushNotificationConfigResponse, TaskIdParams, GetTaskPushNotificationConfigResponse, JSONRPCResponse, JSONRPCErrorResponse } from '@a2a-js/sdk';
4
4
  import { A2AClient } from '@a2a-js/sdk/client';
5
5
  /**
@@ -11,7 +11,10 @@ export declare class PaymentsClient extends A2AClient {
11
11
  private readonly agentId;
12
12
  private readonly planId;
13
13
  private readonly delegationConfig?;
14
+ private readonly tokenVersion?;
14
15
  private accessToken;
16
+ private lastMintedTokenVersion;
17
+ private downgradeWarned;
15
18
  /**
16
19
  * Creates a new PaymentsClient instance.
17
20
  * @param agentBaseUrl - The base URL of the agent (e.g. http://localhost:3005/a2a/).
@@ -19,13 +22,14 @@ export declare class PaymentsClient extends A2AClient {
19
22
  * @param agentId - The ID of the agent.
20
23
  * @param planId - The ID of the plan.
21
24
  * @param agentCardPath - Optional path to the agent card relative to base URL (defaults to '.well-known/agent-card.json').
25
+ * @param tokenVersion - Optional EIP-712 token version to request (`3` = single-use, bound to this agent's endpoint).
22
26
  */
23
27
  private constructor();
24
28
  /**
25
29
  * Creates a PaymentsClient by fetching the AgentCard first and then
26
30
  * constructing the underlying A2AClient with the AgentCard object.
27
31
  */
28
- static create(agentBaseUrl: string, payments: Payments, agentId: string, planId: string, agentCardPath?: string, delegationConfig?: DelegationConfig): Promise<PaymentsClient>;
32
+ static create(agentBaseUrl: string, payments: Payments, agentId: string, planId: string, agentCardPath?: string, delegationConfig?: DelegationConfig, tokenVersion?: X402TokenVersion): Promise<PaymentsClient>;
29
33
  /**
30
34
  * Fetches a remote agent card, trying the requested (canonical) path first and
31
35
  * falling back to the legacy '.well-known/agent.json' so updated clients keep
@@ -34,12 +38,65 @@ export declare class PaymentsClient extends A2AClient {
34
38
  */
35
39
  private static _fetchAgentCard;
36
40
  /**
37
- * Gets and caches the access token for this client instance.
41
+ * The A2A service endpoint this client talks to — the `resource.url` a v3
42
+ * token is bound to.
43
+ *
44
+ * Resolution failures are RAISED, not swallowed. This runs only on the v3
45
+ * path, where the caller explicitly asked for a seller-bound token: minting
46
+ * without the resource would hand them a single-use token bound to nothing,
47
+ * reporting `tokenVersion: 3`, with no signal that half the guarantee was
48
+ * dropped. Failing here loses a call; failing quietly loses the control.
49
+ *
50
+ * The lookup reaches into `_getServiceEndpoint`, which `@a2a-js/sdk` declares
51
+ * `private` and whose own source says it "can be made synchronous or
52
+ * deleted" — so a dependency bump can break it. That is exactly why this must
53
+ * be loud: the alternative is every v3 token silently becoming unbound.
54
+ */
55
+ private _resolveResourceUrl;
56
+ /**
57
+ * Gets the access token for this client instance, caching it only when the
58
+ * token is reusable.
59
+ *
60
+ * A v2 token is a reusable bearer credential, so it is cached for the client's
61
+ * lifetime exactly as before. A **v3 token is consumed by its first
62
+ * settlement**, so caching one would make every paid call after the first
63
+ * settle against a spent nonce (`BCK.X402.0059`) — a v3 token is therefore
64
+ * minted per request and never stored.
65
+ *
66
+ * The version is read off the minted token, never inferred from what was
67
+ * requested: a backend that predates the v3 struct silently mints v2, and a
68
+ * backend whose default has flipped to v3 mints v3 without being asked.
69
+ *
38
70
  * @returns The access token string.
39
71
  */
40
72
  private _getX402AccessToken;
73
+ /**
74
+ * The EIP-712 version of the token this client minted most recently, or
75
+ * `null` before the first paid call.
76
+ *
77
+ * The version a client was constructed with is a *request*; this is what the
78
+ * backend actually minted. They differ whenever a deployment predates the v3
79
+ * struct (it drops the field and mints v2) or once its default flips the
80
+ * other way — so a caller that needs single-use semantics to hold should
81
+ * check this rather than assume its own argument was honoured.
82
+ *
83
+ * Per-client state answering a per-call question: there is no mint lock, so
84
+ * two concurrent paid calls on a fresh client both mint and the last writer
85
+ * wins. Today they agree — the backend does not change its answer mid-flight
86
+ * — but read this before or after a call, not racing one.
87
+ */
88
+ getLastMintedTokenVersion(): X402TokenVersion | null;
41
89
  /**
42
90
  * Clears the cached access token for this client instance.
91
+ *
92
+ * A no-op for the token itself when the last one was single-use (v3): those
93
+ * are never cached. `getLastMintedTokenVersion()` resets to `null` either
94
+ * way — after a clear there is no token in use, and reporting the version of
95
+ * one that has been discarded is worse than reporting nothing.
96
+ *
97
+ * The downgrade flag is deliberately NOT reset here: it tracks the current
98
+ * downgrade episode, which a clear does not end. It is cleared when a v3
99
+ * token is actually minted.
43
100
  */
44
101
  clearToken(): void;
45
102
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"paymentsClient.d.ts","sourceRoot":"","sources":["../../src/a2a/paymentsClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAGtC,OAAO,KAAK,EAAoB,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAC5E,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,0BAA0B,EAC1B,qCAAqC,EACrC,YAAY,EACZ,qCAAqC,EACrC,eAAe,EACf,oBAAoB,EACrB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAO9C;;;GAGG;AACH,qBAAa,cAAe,SAAQ,SAAS;IACpC,QAAQ,EAAE,QAAQ,CAAA;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAkB;IACpD,OAAO,CAAC,WAAW,CAAe;IAElC;;;;;;;OAOG;IACH,OAAO;IAeP;;;OAGG;WACiB,MAAM,CACxB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,aAAa,SAA6B,EAC1C,gBAAgB,CAAC,EAAE,gBAAgB,GAClC,OAAO,CAAC,cAAc,CAAC;IAK1B;;;;;OAKG;mBACkB,eAAe;IAsCpC;;;OAGG;YACW,mBAAmB;IAyBjC;;OAEG;IACI,UAAU;IAIjB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,2BAA2B;IAQnC;;;;;;;;;OASG;IACH,OAAO,CAAC,kBAAkB;IAkB1B;;;;;;;OAOG;IACH,OAAO,CAAC,sBAAsB;IAY9B;;;;OAIG;IACH,eAAe,CAAC,QAAQ,EAAE,eAAe,GAAG,QAAQ,IAAI,oBAAoB;IAI5E;;;;OAIG;IACU,cAAc,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IA4BpF;;;;;;;;OAQG;IACW,oBAAoB,CAChC,MAAM,EAAE,iBAAiB,GACxB,cAAc,CAAC,GAAG,EAAE,IAAI,EAAE,SAAS,CAAC;IAyDvC;;;;;;;;OAQG;YACY,eAAe;IAoD9B;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAuDzB;;;;;;OAMG;IACW,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,cAAc,CAAC,GAAG,EAAE,IAAI,EAAE,SAAS,CAAC;IAsD5F;;;;OAIG;IACU,UAAU,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC;IAU1E;;;;OAIG;IACU,gCAAgC,CAC3C,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC,qCAAqC,CAAC;IASjD;;;;OAIG;IACU,gCAAgC,CAC3C,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,qCAAqC,CAAC;IAUjD;;;;;;OAMG;cACa,0BAA0B,CAAC,OAAO,EAAE,SAAS,EAC3D,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,OAAO,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,OAAO,CAAC,SAAS,CAAC;CA2CtB;AAED,YAAY,EACV,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,0BAA0B,EAC1B,qCAAqC,EACrC,YAAY,EACZ,qCAAqC,GACtC,MAAM,aAAa,CAAA"}
1
+ {"version":3,"file":"paymentsClient.d.ts","sourceRoot":"","sources":["../../src/a2a/paymentsClient.ts"],"names":[],"mappings":"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"}
@@ -17,22 +17,26 @@ export class PaymentsClient extends A2AClient {
17
17
  * @param agentId - The ID of the agent.
18
18
  * @param planId - The ID of the plan.
19
19
  * @param agentCardPath - Optional path to the agent card relative to base URL (defaults to '.well-known/agent-card.json').
20
+ * @param tokenVersion - Optional EIP-712 token version to request (`3` = single-use, bound to this agent's endpoint).
20
21
  */
21
- constructor(agentCard, payments, agentId, planId, delegationConfig) {
22
+ constructor(agentCard, payments, agentId, planId, delegationConfig, tokenVersion) {
22
23
  super(agentCard);
24
+ this.lastMintedTokenVersion = null;
25
+ this.downgradeWarned = false;
23
26
  this.payments = payments;
24
27
  this.agentId = agentId;
25
28
  this.planId = planId;
26
29
  this.delegationConfig = delegationConfig;
30
+ this.tokenVersion = tokenVersion;
27
31
  this.accessToken = null;
28
32
  }
29
33
  /**
30
34
  * Creates a PaymentsClient by fetching the AgentCard first and then
31
35
  * constructing the underlying A2AClient with the AgentCard object.
32
36
  */
33
- static async create(agentBaseUrl, payments, agentId, planId, agentCardPath = AGENT_CARD_WELL_KNOWN_PATH, delegationConfig) {
37
+ static async create(agentBaseUrl, payments, agentId, planId, agentCardPath = AGENT_CARD_WELL_KNOWN_PATH, delegationConfig, tokenVersion) {
34
38
  const agentCard = await PaymentsClient._fetchAgentCard(agentBaseUrl, agentCardPath);
35
- return new PaymentsClient(agentCard, payments, agentId, planId, delegationConfig);
39
+ return new PaymentsClient(agentCard, payments, agentId, planId, delegationConfig, tokenVersion);
36
40
  }
37
41
  /**
38
42
  * Fetches a remote agent card, trying the requested (canonical) path first and
@@ -69,7 +73,51 @@ export class PaymentsClient extends A2AClient {
69
73
  }
70
74
  }
71
75
  /**
72
- * Gets and caches the access token for this client instance.
76
+ * The A2A service endpoint this client talks to — the `resource.url` a v3
77
+ * token is bound to.
78
+ *
79
+ * Resolution failures are RAISED, not swallowed. This runs only on the v3
80
+ * path, where the caller explicitly asked for a seller-bound token: minting
81
+ * without the resource would hand them a single-use token bound to nothing,
82
+ * reporting `tokenVersion: 3`, with no signal that half the guarantee was
83
+ * dropped. Failing here loses a call; failing quietly loses the control.
84
+ *
85
+ * The lookup reaches into `_getServiceEndpoint`, which `@a2a-js/sdk` declares
86
+ * `private` and whose own source says it "can be made synchronous or
87
+ * deleted" — so a dependency bump can break it. That is exactly why this must
88
+ * be loud: the alternative is every v3 token silently becoming unbound.
89
+ */
90
+ async _resolveResourceUrl() {
91
+ let endpoint;
92
+ try {
93
+ endpoint = await this._getServiceEndpoint();
94
+ }
95
+ catch (error) {
96
+ throw PaymentsError.internal('Could not resolve the A2A service endpoint to bind a tokenVersion 3 access token to: ' +
97
+ `${error instanceof Error ? error.message : String(error)}. A v3 token is bound to one ` +
98
+ 'resource URL, so it cannot be minted without it. Use tokenVersion 2, or fix the agent ' +
99
+ 'card / service endpoint.');
100
+ }
101
+ if (typeof endpoint !== 'string' || endpoint.length === 0) {
102
+ throw PaymentsError.internal('The A2A service endpoint resolved to an empty value, so a tokenVersion 3 access token ' +
103
+ 'cannot be bound to it. Use tokenVersion 2, or fix the agent card / service endpoint.');
104
+ }
105
+ return endpoint;
106
+ }
107
+ /**
108
+ * Gets the access token for this client instance, caching it only when the
109
+ * token is reusable.
110
+ *
111
+ * A v2 token is a reusable bearer credential, so it is cached for the client's
112
+ * lifetime exactly as before. A **v3 token is consumed by its first
113
+ * settlement**, so caching one would make every paid call after the first
114
+ * settle against a spent nonce (`BCK.X402.0059`) — a v3 token is therefore
115
+ * minted per request and never stored.
116
+ *
117
+ * The version is read off the minted token, never inferred from what was
118
+ * requested: a backend that predates the v3 struct silently mints v2, and a
119
+ * backend whose default has flipped to v3 mints v3 without being asked.
120
+ *
73
121
  * @returns The access token string.
74
122
  */
75
123
  async _getX402AccessToken() {
@@ -80,22 +128,95 @@ export class PaymentsClient extends A2AClient {
80
128
  if (!this.delegationConfig) {
81
129
  throw PaymentsError.internal(`${scheme} scheme requires delegationConfig. Pass it to PaymentsClient.create().`);
82
130
  }
131
+ // The resource binding is attached ONLY when v3 was explicitly requested.
132
+ // It is not inert on a v2 token: the backend compares the token's
133
+ // `resource.url` against the seller's `paymentRequired.resource.url`
134
+ // (origin + path, falling back to an exact string compare for anything it
135
+ // cannot parse as a URL), so a resource that does not match what the seller
136
+ // advertises fails verification with `BCK.X402.0013` — a binding nobody
137
+ // asked for can only cost the caller a failed verify.
138
+ //
139
+ // The ABSOLUTE service endpoint is the right string for this client's
140
+ // counterparty specifically: an A2A seller built on this SDK advertises
141
+ // `new URL(req.originalUrl, protocol + host)` (`src/a2a/server.ts`), not
142
+ // the relative `req.originalUrl` that the Express `paymentMiddleware`
143
+ // advertises for plain HTTP agents. Every A2A call is a POST to the one
144
+ // JSON-RPC service endpoint, so a single binding covers all of them.
145
+ let binding = {};
146
+ if (this.tokenVersion === 3) {
147
+ binding = {
148
+ resource: { url: await this._resolveResourceUrl() },
149
+ httpVerb: 'POST',
150
+ tokenVersion: 3,
151
+ };
152
+ }
83
153
  let tokenOptions;
84
154
  if (scheme !== 'nvm:erc4337') {
85
- tokenOptions = { scheme, delegationConfig: this.delegationConfig };
155
+ tokenOptions = { scheme, delegationConfig: this.delegationConfig, ...binding };
86
156
  }
87
157
  else {
88
- tokenOptions = { delegationConfig: this.delegationConfig };
158
+ tokenOptions = { delegationConfig: this.delegationConfig, ...binding };
89
159
  }
90
160
  const accessParams = await this.payments.x402.getX402AccessToken(this.planId, this.agentId, tokenOptions);
91
- this.accessToken = accessParams.accessToken;
92
- return this.accessToken;
161
+ this.lastMintedTokenVersion = accessParams.tokenVersion ?? null;
162
+ // A caller who asked for v3 and got v2 asked for single-use + seller
163
+ // binding and received neither. Caching it is still the correct handling of
164
+ // a genuinely reusable token, but it must not be the only trace: say so
165
+ // once, and expose the minted version so callers can branch the way
166
+ // getX402AccessToken's own return value lets them.
167
+ if (accessParams.tokenVersion === 3) {
168
+ // The current downgrade episode is over: a later one is new information
169
+ // and gets its own warning, rather than being swallowed by a flag set
170
+ // once for the lifetime of the client.
171
+ this.downgradeWarned = false;
172
+ }
173
+ else if (this.tokenVersion === 3 && !this.downgradeWarned) {
174
+ this.downgradeWarned = true;
175
+ console.warn(`[x402] tokenVersion 3 was requested but the backend minted v${accessParams.tokenVersion}. ` +
176
+ 'This token is reusable and bound to nothing, and it is cached for the lifetime of this ' +
177
+ 'client. Read PaymentsClient.getLastMintedTokenVersion() to branch on it.');
178
+ }
179
+ // Cache unless the token is single-use. Phrased as "not v3" rather than
180
+ // "is v2" so an unrecognised or missing version keeps today's caching
181
+ // behaviour instead of silently turning every call into a fresh mint.
182
+ if (accessParams.tokenVersion !== 3) {
183
+ this.accessToken = accessParams.accessToken;
184
+ }
185
+ return accessParams.accessToken;
186
+ }
187
+ /**
188
+ * The EIP-712 version of the token this client minted most recently, or
189
+ * `null` before the first paid call.
190
+ *
191
+ * The version a client was constructed with is a *request*; this is what the
192
+ * backend actually minted. They differ whenever a deployment predates the v3
193
+ * struct (it drops the field and mints v2) or once its default flips the
194
+ * other way — so a caller that needs single-use semantics to hold should
195
+ * check this rather than assume its own argument was honoured.
196
+ *
197
+ * Per-client state answering a per-call question: there is no mint lock, so
198
+ * two concurrent paid calls on a fresh client both mint and the last writer
199
+ * wins. Today they agree — the backend does not change its answer mid-flight
200
+ * — but read this before or after a call, not racing one.
201
+ */
202
+ getLastMintedTokenVersion() {
203
+ return this.lastMintedTokenVersion;
93
204
  }
94
205
  /**
95
206
  * Clears the cached access token for this client instance.
207
+ *
208
+ * A no-op for the token itself when the last one was single-use (v3): those
209
+ * are never cached. `getLastMintedTokenVersion()` resets to `null` either
210
+ * way — after a clear there is no token in use, and reporting the version of
211
+ * one that has been discarded is worse than reporting nothing.
212
+ *
213
+ * The downgrade flag is deliberately NOT reset here: it tracks the current
214
+ * downgrade episode, which a clear does not end. It is cleared when a v3
215
+ * token is actually minted.
96
216
  */
97
217
  clearToken() {
98
218
  this.accessToken = null;
219
+ this.lastMintedTokenVersion = null;
99
220
  }
100
221
  /**
101
222
  * Build the in-band x402 message metadata (x402 v2 A2A transport).