@nevermined-io/payments 1.11.2 → 1.13.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/README.md +33 -0
- package/dist/a2a/clientRegistry.d.ts +10 -1
- package/dist/a2a/clientRegistry.d.ts.map +1 -1
- package/dist/a2a/clientRegistry.js +13 -4
- package/dist/a2a/clientRegistry.js.map +1 -1
- package/dist/a2a/paymentsClient.d.ts +60 -3
- package/dist/a2a/paymentsClient.d.ts.map +1 -1
- package/dist/a2a/paymentsClient.js +129 -8
- package/dist/a2a/paymentsClient.js.map +1 -1
- package/dist/a2a/types.d.ts +5 -0
- package/dist/a2a/types.d.ts.map +1 -1
- package/dist/a2a/types.js.map +1 -1
- package/dist/api/nvm-api.d.ts +2 -0
- package/dist/api/nvm-api.d.ts.map +1 -1
- package/dist/api/nvm-api.js +4 -0
- package/dist/api/nvm-api.js.map +1 -1
- package/dist/api/orders-api.d.ts +146 -0
- package/dist/api/orders-api.d.ts.map +1 -0
- package/dist/api/orders-api.js +107 -0
- package/dist/api/orders-api.js.map +1 -0
- package/dist/common/types.d.ts +87 -0
- package/dist/common/types.d.ts.map +1 -1
- package/dist/common/types.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/core/paywall.d.ts.map +1 -1
- package/dist/mcp/core/paywall.js +10 -0
- package/dist/mcp/core/paywall.js.map +1 -1
- package/dist/mpp/fetch.d.ts +8 -3
- package/dist/mpp/fetch.d.ts.map +1 -1
- package/dist/mpp/fetch.js +6 -0
- package/dist/mpp/fetch.js.map +1 -1
- package/dist/mpp/mpp-api.d.ts +11 -5
- package/dist/mpp/mpp-api.d.ts.map +1 -1
- package/dist/mpp/mpp-api.js +10 -3
- package/dist/mpp/mpp-api.js.map +1 -1
- package/dist/payments.d.ts +3 -0
- package/dist/payments.d.ts.map +1 -1
- package/dist/payments.js +4 -0
- package/dist/payments.js.map +1 -1
- package/dist/x402/express/middleware.d.ts.map +1 -1
- package/dist/x402/express/middleware.js +98 -1
- package/dist/x402/express/middleware.js.map +1 -1
- package/dist/x402/facilitator-api.d.ts +90 -7
- package/dist/x402/facilitator-api.d.ts.map +1 -1
- package/dist/x402/facilitator-api.js +64 -3
- package/dist/x402/facilitator-api.js.map +1 -1
- package/dist/x402/index.d.ts +3 -2
- package/dist/x402/index.d.ts.map +1 -1
- package/dist/x402/index.js +1 -0
- package/dist/x402/index.js.map +1 -1
- package/dist/x402/token-request.d.ts +8 -0
- package/dist/x402/token-request.d.ts.map +1 -1
- package/dist/x402/token-request.js +101 -1
- package/dist/x402/token-request.js.map +1 -1
- package/dist/x402/token-version.d.ts +97 -0
- package/dist/x402/token-version.d.ts.map +1 -0
- package/dist/x402/token-version.js +127 -0
- package/dist/x402/token-version.js.map +1 -0
- package/dist/x402/token.d.ts +33 -2
- package/dist/x402/token.d.ts.map +1 -1
- package/dist/x402/token.js +47 -2
- package/dist/x402/token.js.map +1 -1
- package/package.json +1 -1
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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;
|
|
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
|
-
*
|
|
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.
|
|
92
|
-
|
|
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).
|