@nevermined-io/payments 1.12.0 → 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/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 +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -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/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
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detecting the EIP-712 version of a minted x402 access token.
|
|
3
|
+
*
|
|
4
|
+
* The version is a property of the token you were handed, never of the one you
|
|
5
|
+
* asked for. The backend's `ValidationPipe` runs with `whitelist: true` and
|
|
6
|
+
* without `forbidNonWhitelisted`, so a `tokenVersion: 3` sent to a deployment
|
|
7
|
+
* that predates the v3 struct is dropped **without an error** and a v2 token
|
|
8
|
+
* comes back. Branching on the requested version would then silently treat a
|
|
9
|
+
* reusable v2 token as single-use (harmless) or — far worse, once the default
|
|
10
|
+
* flips — a single-use v3 token as reusable, replaying a spent nonce on every
|
|
11
|
+
* call after the first.
|
|
12
|
+
*
|
|
13
|
+
* The discriminator is `payload.authorization.nonce`: v3 is the first version
|
|
14
|
+
* to carry one, and it is exactly what makes the token single-use.
|
|
15
|
+
*/
|
|
16
|
+
import { decodeAccessToken } from '../utils.js';
|
|
17
|
+
/**
|
|
18
|
+
* Read the `authorization` object out of a base64 x402 access token.
|
|
19
|
+
*
|
|
20
|
+
* Returns `undefined` for anything that is not a decodable token with that
|
|
21
|
+
* shape, so callers can treat "not a Nevermined token" and "no nonce" the same
|
|
22
|
+
* way: as not-v3.
|
|
23
|
+
*/
|
|
24
|
+
function readAuthorization(accessToken) {
|
|
25
|
+
if (typeof accessToken !== 'string' || accessToken.length === 0)
|
|
26
|
+
return undefined;
|
|
27
|
+
const decoded = decodeAccessToken(accessToken);
|
|
28
|
+
const authorization = decoded?.payload?.authorization;
|
|
29
|
+
return typeof authorization === 'object' && authorization !== null ? authorization : undefined;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The EIP-712 version the given access token was actually signed under.
|
|
33
|
+
*
|
|
34
|
+
* `3` when `payload.authorization.nonce` is a non-empty string, `2` otherwise.
|
|
35
|
+
*
|
|
36
|
+
* A token that cannot be decoded also reads as `2`. That is the conservative
|
|
37
|
+
* answer for a *version* question — v2 is what every pre-v3 token and every
|
|
38
|
+
* non-Nevermined blob already is — but note it is the permissive answer for the
|
|
39
|
+
* *reuse* question a caller asks next: `2` means "safe to cache and replay".
|
|
40
|
+
* The gap is unreachable today, since an undecodable token is unusable anyway
|
|
41
|
+
* (the backend rejects it on the first presentation, so there is nothing to
|
|
42
|
+
* replay), but a future change to the envelope format would turn every v3 token
|
|
43
|
+
* into a cached one here. Any such change must revisit this function first.
|
|
44
|
+
*
|
|
45
|
+
* Note this is deliberately NOT the same as trusting the token: nothing here
|
|
46
|
+
* is verified. It answers "which settle semantics does this token have", which
|
|
47
|
+
* only the backend's signature check ultimately enforces.
|
|
48
|
+
*
|
|
49
|
+
* @param accessToken - The base64-encoded x402 access token
|
|
50
|
+
* @returns `3` for a single-use, seller/resource-bound token, else `2`
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* const { accessToken } = await payments.x402.getX402AccessToken(planId, agentId, {
|
|
55
|
+
* delegationConfig: { delegationId },
|
|
56
|
+
* // The string the seller advertises, not the URL you fetch.
|
|
57
|
+
* resource: { url: '/api/v1/tasks' },
|
|
58
|
+
* httpVerb: 'POST',
|
|
59
|
+
* tokenVersion: 3,
|
|
60
|
+
* })
|
|
61
|
+
* if (detectAccessTokenVersion(accessToken) === 3) {
|
|
62
|
+
* // Single-use: mint a new one for the next paid request.
|
|
63
|
+
* }
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export function detectAccessTokenVersion(accessToken) {
|
|
67
|
+
const nonce = readAuthorization(accessToken)?.nonce;
|
|
68
|
+
// The string check mirrors the backend's own discriminator verbatim
|
|
69
|
+
// (`AgentX402AccessToken.ts`: `typeof authorization?.nonce === 'string' &&
|
|
70
|
+
// authorization.nonce.trim() !== ''`), and `generateTokenNonce()` emits 32
|
|
71
|
+
// bytes of CSPRNG output hex-encoded — a string on the wire, always.
|
|
72
|
+
// Accepting a non-string nonce as v3 would make this SDK disagree with the
|
|
73
|
+
// server about what a v3 token is, which is worse than either error it could
|
|
74
|
+
// prevent.
|
|
75
|
+
return typeof nonce === 'string' && nonce.trim() !== '' ? 3 : 2;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Whether the token is consumed by its first successful settlement.
|
|
79
|
+
*
|
|
80
|
+
* True exactly for v3 tokens. A caller that caches an access token must test
|
|
81
|
+
* this before reusing one: settling a spent v3 token fails with
|
|
82
|
+
* `BCK.X402.0059` (see {@link isAccessTokenAlreadyUsed}).
|
|
83
|
+
*
|
|
84
|
+
* @param accessToken - The base64-encoded x402 access token
|
|
85
|
+
*/
|
|
86
|
+
export function isSingleUseAccessToken(accessToken) {
|
|
87
|
+
return detectAccessTokenVersion(accessToken) === 3;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Backend code for "this access token was already used".
|
|
91
|
+
*
|
|
92
|
+
* Returned by `POST /x402/settle` when a v3 token is settled a second time.
|
|
93
|
+
* Deliberately not forgery-gated on the backend: it says plainly that the
|
|
94
|
+
* token was spent, so a client can tell a **spent** token apart from a
|
|
95
|
+
* declined one (insufficient credits, wrong plan) and from a **forged** one
|
|
96
|
+
* (`BCK.X402.0005`, an envelope edited to disagree with the signature).
|
|
97
|
+
*/
|
|
98
|
+
export const X402_TOKEN_ALREADY_USED_CODE = 'BCK.X402.0059';
|
|
99
|
+
/**
|
|
100
|
+
* Whether an error is the backend refusing a second settlement of the same
|
|
101
|
+
* single-use token.
|
|
102
|
+
*
|
|
103
|
+
* The one correct response is to **mint a new token** — a retry that replays
|
|
104
|
+
* the same token fails identically forever. Never treat this as a transient
|
|
105
|
+
* settlement failure.
|
|
106
|
+
*
|
|
107
|
+
* @param error - Any thrown value; typically a `PaymentsError` from
|
|
108
|
+
* `payments.facilitator.settlePermissions`
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```typescript
|
|
112
|
+
* try {
|
|
113
|
+
* await payments.facilitator.settlePermissions({ paymentRequired, x402AccessToken })
|
|
114
|
+
* } catch (error) {
|
|
115
|
+
* if (isAccessTokenAlreadyUsed(error)) {
|
|
116
|
+
* // Re-mint, do not replay.
|
|
117
|
+
* }
|
|
118
|
+
* throw error
|
|
119
|
+
* }
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
export function isAccessTokenAlreadyUsed(error) {
|
|
123
|
+
if (typeof error !== 'object' || error === null)
|
|
124
|
+
return false;
|
|
125
|
+
return error.code === X402_TOKEN_ALREADY_USED_CODE;
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=token-version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-version.js","sourceRoot":"","sources":["../../src/x402/token-version.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAG/C;;;;;;GAMG;AACH,SAAS,iBAAiB,CAAC,WAAmB;IAC5C,IAAI,OAAO,WAAW,KAAK,QAAQ,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAA;IACjF,MAAM,OAAO,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAA;IAC9C,MAAM,aAAa,GAAG,OAAO,EAAE,OAAO,EAAE,aAAa,CAAA;IACrD,OAAO,OAAO,aAAa,KAAK,QAAQ,IAAI,aAAa,KAAK,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAA;AAChG,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,UAAU,wBAAwB,CAAC,WAAmB;IAC1D,MAAM,KAAK,GAAG,iBAAiB,CAAC,WAAW,CAAC,EAAE,KAAK,CAAA;IACnD,oEAAoE;IACpE,2EAA2E;IAC3E,2EAA2E;IAC3E,qEAAqE;IACrE,2EAA2E;IAC3E,6EAA6E;IAC7E,WAAW;IACX,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AACjE,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CAAC,WAAmB;IACxD,OAAO,wBAAwB,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;AACpD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,eAAe,CAAA;AAE3D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,wBAAwB,CAAC,KAAc;IACrD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAA;IAC7D,OAAQ,KAA4B,CAAC,IAAI,KAAK,4BAA4B,CAAA;AAC5E,CAAC","sourcesContent":["/**\n * Detecting the EIP-712 version of a minted x402 access token.\n *\n * The version is a property of the token you were handed, never of the one you\n * asked for. The backend's `ValidationPipe` runs with `whitelist: true` and\n * without `forbidNonWhitelisted`, so a `tokenVersion: 3` sent to a deployment\n * that predates the v3 struct is dropped **without an error** and a v2 token\n * comes back. Branching on the requested version would then silently treat a\n * reusable v2 token as single-use (harmless) or — far worse, once the default\n * flips — a single-use v3 token as reusable, replaying a spent nonce on every\n * call after the first.\n *\n * The discriminator is `payload.authorization.nonce`: v3 is the first version\n * to carry one, and it is exactly what makes the token single-use.\n */\n\nimport { decodeAccessToken } from '../utils.js'\nimport type { X402TokenVersion } from '../common/types.js'\n\n/**\n * Read the `authorization` object out of a base64 x402 access token.\n *\n * Returns `undefined` for anything that is not a decodable token with that\n * shape, so callers can treat \"not a Nevermined token\" and \"no nonce\" the same\n * way: as not-v3.\n */\nfunction readAuthorization(accessToken: string): Record<string, any> | undefined {\n if (typeof accessToken !== 'string' || accessToken.length === 0) return undefined\n const decoded = decodeAccessToken(accessToken)\n const authorization = decoded?.payload?.authorization\n return typeof authorization === 'object' && authorization !== null ? authorization : undefined\n}\n\n/**\n * The EIP-712 version the given access token was actually signed under.\n *\n * `3` when `payload.authorization.nonce` is a non-empty string, `2` otherwise.\n *\n * A token that cannot be decoded also reads as `2`. That is the conservative\n * answer for a *version* question — v2 is what every pre-v3 token and every\n * non-Nevermined blob already is — but note it is the permissive answer for the\n * *reuse* question a caller asks next: `2` means \"safe to cache and replay\".\n * The gap is unreachable today, since an undecodable token is unusable anyway\n * (the backend rejects it on the first presentation, so there is nothing to\n * replay), but a future change to the envelope format would turn every v3 token\n * into a cached one here. Any such change must revisit this function first.\n *\n * Note this is deliberately NOT the same as trusting the token: nothing here\n * is verified. It answers \"which settle semantics does this token have\", which\n * only the backend's signature check ultimately enforces.\n *\n * @param accessToken - The base64-encoded x402 access token\n * @returns `3` for a single-use, seller/resource-bound token, else `2`\n *\n * @example\n * ```typescript\n * const { accessToken } = await payments.x402.getX402AccessToken(planId, agentId, {\n * delegationConfig: { delegationId },\n * // The string the seller advertises, not the URL you fetch.\n * resource: { url: '/api/v1/tasks' },\n * httpVerb: 'POST',\n * tokenVersion: 3,\n * })\n * if (detectAccessTokenVersion(accessToken) === 3) {\n * // Single-use: mint a new one for the next paid request.\n * }\n * ```\n */\nexport function detectAccessTokenVersion(accessToken: string): X402TokenVersion {\n const nonce = readAuthorization(accessToken)?.nonce\n // The string check mirrors the backend's own discriminator verbatim\n // (`AgentX402AccessToken.ts`: `typeof authorization?.nonce === 'string' &&\n // authorization.nonce.trim() !== ''`), and `generateTokenNonce()` emits 32\n // bytes of CSPRNG output hex-encoded — a string on the wire, always.\n // Accepting a non-string nonce as v3 would make this SDK disagree with the\n // server about what a v3 token is, which is worse than either error it could\n // prevent.\n return typeof nonce === 'string' && nonce.trim() !== '' ? 3 : 2\n}\n\n/**\n * Whether the token is consumed by its first successful settlement.\n *\n * True exactly for v3 tokens. A caller that caches an access token must test\n * this before reusing one: settling a spent v3 token fails with\n * `BCK.X402.0059` (see {@link isAccessTokenAlreadyUsed}).\n *\n * @param accessToken - The base64-encoded x402 access token\n */\nexport function isSingleUseAccessToken(accessToken: string): boolean {\n return detectAccessTokenVersion(accessToken) === 3\n}\n\n/**\n * Backend code for \"this access token was already used\".\n *\n * Returned by `POST /x402/settle` when a v3 token is settled a second time.\n * Deliberately not forgery-gated on the backend: it says plainly that the\n * token was spent, so a client can tell a **spent** token apart from a\n * declined one (insufficient credits, wrong plan) and from a **forged** one\n * (`BCK.X402.0005`, an envelope edited to disagree with the signature).\n */\nexport const X402_TOKEN_ALREADY_USED_CODE = 'BCK.X402.0059'\n\n/**\n * Whether an error is the backend refusing a second settlement of the same\n * single-use token.\n *\n * The one correct response is to **mint a new token** — a retry that replays\n * the same token fails identically forever. Never treat this as a transient\n * settlement failure.\n *\n * @param error - Any thrown value; typically a `PaymentsError` from\n * `payments.facilitator.settlePermissions`\n *\n * @example\n * ```typescript\n * try {\n * await payments.facilitator.settlePermissions({ paymentRequired, x402AccessToken })\n * } catch (error) {\n * if (isAccessTokenAlreadyUsed(error)) {\n * // Re-mint, do not replay.\n * }\n * throw error\n * }\n * ```\n */\nexport function isAccessTokenAlreadyUsed(error: unknown): boolean {\n if (typeof error !== 'object' || error === null) return false\n return (error as { code?: unknown }).code === X402_TOKEN_ALREADY_USED_CODE\n}\n"]}
|
package/dist/x402/token.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Tokens are used to authorize payment verification and settlement.
|
|
6
6
|
*/
|
|
7
7
|
import { BasePaymentsAPI } from '../api/base-payments.js';
|
|
8
|
-
import { PaymentOptions, X402TokenOptions } from '../common/types.js';
|
|
8
|
+
import { PaymentOptions, X402TokenOptions, X402TokenVersion } from '../common/types.js';
|
|
9
9
|
/**
|
|
10
10
|
* X402 Token API for generating access tokens.
|
|
11
11
|
*
|
|
@@ -32,11 +32,20 @@ export declare class X402TokenAPI extends BasePaymentsAPI {
|
|
|
32
32
|
* (spending limits / `providerPaymentMethodId` / `cardId`, i.e. no
|
|
33
33
|
* `delegationId`) is **deprecated** and emits a runtime warning.
|
|
34
34
|
*
|
|
35
|
+
* Pass `resource` / `httpVerb` to bind the token to one seller endpoint, and
|
|
36
|
+
* `tokenVersion: 3` to ask for the single-use, seller/resource-bound token.
|
|
37
|
+
* v3 is opt-in and the request may be silently downgraded by a backend that
|
|
38
|
+
* predates it, so the returned `tokenVersion` is read off the minted token
|
|
39
|
+
* rather than echoed back from the request — branch on that, never on what
|
|
40
|
+
* you asked for.
|
|
41
|
+
*
|
|
35
42
|
* @param planId - The unique identifier of the payment plan
|
|
36
43
|
* @param agentId - The unique identifier of the AI agent (optional)
|
|
37
|
-
* @param tokenOptions - Options controlling scheme and delegation behavior (optional)
|
|
44
|
+
* @param tokenOptions - Options controlling scheme, resource binding, token version and delegation behavior (optional)
|
|
38
45
|
* @returns A promise that resolves to an object containing:
|
|
39
46
|
* - accessToken: The X402 access token string
|
|
47
|
+
* - tokenVersion: The EIP-712 version the returned token was actually signed
|
|
48
|
+
* under (`3` means single-use — mint a new token per paid request)
|
|
40
49
|
*
|
|
41
50
|
* @throws PaymentsError if the request fails
|
|
42
51
|
*
|
|
@@ -53,9 +62,31 @@ export declare class X402TokenAPI extends BasePaymentsAPI {
|
|
|
53
62
|
* delegationConfig: { delegationId },
|
|
54
63
|
* })
|
|
55
64
|
* ```
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* // Single-use, bound to one seller endpoint. Mint one per paid request.
|
|
69
|
+
* // `resource.url` must be the string the seller advertises in its 402
|
|
70
|
+
* // `paymentRequired` — sellers built on this SDK's `paymentMiddleware`
|
|
71
|
+
* // advertise `req.originalUrl`, i.e. a relative path.
|
|
72
|
+
* const { accessToken, tokenVersion } = await payments.x402.getX402AccessToken(
|
|
73
|
+
* planId,
|
|
74
|
+
* agentId,
|
|
75
|
+
* {
|
|
76
|
+
* delegationConfig: { delegationId },
|
|
77
|
+
* resource: { url: '/api/v1/tasks' },
|
|
78
|
+
* httpVerb: 'POST',
|
|
79
|
+
* tokenVersion: 3,
|
|
80
|
+
* },
|
|
81
|
+
* )
|
|
82
|
+
* if (tokenVersion === 2) {
|
|
83
|
+
* // The backend does not support v3 yet; this token is still reusable.
|
|
84
|
+
* }
|
|
85
|
+
* ```
|
|
56
86
|
*/
|
|
57
87
|
getX402AccessToken(planId: string, agentId?: string, tokenOptions?: X402TokenOptions): Promise<{
|
|
58
88
|
accessToken: string;
|
|
89
|
+
tokenVersion: X402TokenVersion;
|
|
59
90
|
[key: string]: any;
|
|
60
91
|
}>;
|
|
61
92
|
}
|
package/dist/x402/token.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/x402/token.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAGzD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/x402/token.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAGzD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAIvF;;;;;GAKG;AACH,qBAAa,YAAa,SAAQ,eAAe;IAC/C;;;;;OAKG;IACH,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,cAAc,GAAG,YAAY;IAIzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+DG;IACG,kBAAkB,CACtB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,gBAAgB,GAC9B,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,gBAAgB,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE,CAAC;CAmDxF"}
|
package/dist/x402/token.js
CHANGED
|
@@ -8,6 +8,7 @@ import { BasePaymentsAPI } from '../api/base-payments.js';
|
|
|
8
8
|
import { API_URL_CREATE_PERMISSION } from '../api/nvm-api.js';
|
|
9
9
|
import { PaymentsError } from '../common/payments.error.js';
|
|
10
10
|
import { buildX402TokenRequestBody } from './token-request.js';
|
|
11
|
+
import { detectAccessTokenVersion } from './token-version.js';
|
|
11
12
|
/**
|
|
12
13
|
* X402 Token API for generating access tokens.
|
|
13
14
|
*
|
|
@@ -36,11 +37,20 @@ export class X402TokenAPI extends BasePaymentsAPI {
|
|
|
36
37
|
* (spending limits / `providerPaymentMethodId` / `cardId`, i.e. no
|
|
37
38
|
* `delegationId`) is **deprecated** and emits a runtime warning.
|
|
38
39
|
*
|
|
40
|
+
* Pass `resource` / `httpVerb` to bind the token to one seller endpoint, and
|
|
41
|
+
* `tokenVersion: 3` to ask for the single-use, seller/resource-bound token.
|
|
42
|
+
* v3 is opt-in and the request may be silently downgraded by a backend that
|
|
43
|
+
* predates it, so the returned `tokenVersion` is read off the minted token
|
|
44
|
+
* rather than echoed back from the request — branch on that, never on what
|
|
45
|
+
* you asked for.
|
|
46
|
+
*
|
|
39
47
|
* @param planId - The unique identifier of the payment plan
|
|
40
48
|
* @param agentId - The unique identifier of the AI agent (optional)
|
|
41
|
-
* @param tokenOptions - Options controlling scheme and delegation behavior (optional)
|
|
49
|
+
* @param tokenOptions - Options controlling scheme, resource binding, token version and delegation behavior (optional)
|
|
42
50
|
* @returns A promise that resolves to an object containing:
|
|
43
51
|
* - accessToken: The X402 access token string
|
|
52
|
+
* - tokenVersion: The EIP-712 version the returned token was actually signed
|
|
53
|
+
* under (`3` means single-use — mint a new token per paid request)
|
|
44
54
|
*
|
|
45
55
|
* @throws PaymentsError if the request fails
|
|
46
56
|
*
|
|
@@ -57,6 +67,27 @@ export class X402TokenAPI extends BasePaymentsAPI {
|
|
|
57
67
|
* delegationConfig: { delegationId },
|
|
58
68
|
* })
|
|
59
69
|
* ```
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* // Single-use, bound to one seller endpoint. Mint one per paid request.
|
|
74
|
+
* // `resource.url` must be the string the seller advertises in its 402
|
|
75
|
+
* // `paymentRequired` — sellers built on this SDK's `paymentMiddleware`
|
|
76
|
+
* // advertise `req.originalUrl`, i.e. a relative path.
|
|
77
|
+
* const { accessToken, tokenVersion } = await payments.x402.getX402AccessToken(
|
|
78
|
+
* planId,
|
|
79
|
+
* agentId,
|
|
80
|
+
* {
|
|
81
|
+
* delegationConfig: { delegationId },
|
|
82
|
+
* resource: { url: '/api/v1/tasks' },
|
|
83
|
+
* httpVerb: 'POST',
|
|
84
|
+
* tokenVersion: 3,
|
|
85
|
+
* },
|
|
86
|
+
* )
|
|
87
|
+
* if (tokenVersion === 2) {
|
|
88
|
+
* // The backend does not support v3 yet; this token is still reusable.
|
|
89
|
+
* }
|
|
90
|
+
* ```
|
|
60
91
|
*/
|
|
61
92
|
async getX402AccessToken(planId, agentId, tokenOptions) {
|
|
62
93
|
const urlPath = API_URL_CREATE_PERMISSION;
|
|
@@ -81,7 +112,21 @@ export class X402TokenAPI extends BasePaymentsAPI {
|
|
|
81
112
|
}
|
|
82
113
|
throw PaymentsError.internal(`${errorMessage} (HTTP ${response.status})`);
|
|
83
114
|
}
|
|
84
|
-
|
|
115
|
+
const result = await response.json();
|
|
116
|
+
// A 2xx with no usable token is a broken response, not a token: without
|
|
117
|
+
// this guard the caller gets `{ accessToken: undefined }` typed as
|
|
118
|
+
// `string`, sends `undefined` as the `payment-signature`, and the failure
|
|
119
|
+
// surfaces at the seller as an unparseable token — three layers from the
|
|
120
|
+
// cause.
|
|
121
|
+
if (typeof result?.accessToken !== 'string' || result.accessToken.length === 0) {
|
|
122
|
+
throw PaymentsError.internal('X402 token request succeeded but the response carried no accessToken');
|
|
123
|
+
}
|
|
124
|
+
// Detected, never echoed: `tokenVersion: 3` is dropped without an error by
|
|
125
|
+
// a backend that predates the v3 struct, so the only trustworthy source of
|
|
126
|
+
// the version is the token itself. The override after the spread is
|
|
127
|
+
// deliberate — a `tokenVersion` echoed by the backend is discarded in
|
|
128
|
+
// favour of the one read off the token.
|
|
129
|
+
return { ...result, tokenVersion: detectAccessTokenVersion(result.accessToken) };
|
|
85
130
|
}
|
|
86
131
|
catch (error) {
|
|
87
132
|
if (error instanceof PaymentsError) {
|
package/dist/x402/token.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/x402/token.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAA;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAE3D,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/x402/token.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAA;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAE3D,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAA;AAC9D,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAA;AAE7D;;;;;GAKG;AACH,MAAM,OAAO,YAAa,SAAQ,eAAe;IAC/C;;;;;OAKG;IACH,MAAM,CAAC,WAAW,CAAC,OAAuB;QACxC,OAAO,IAAI,YAAY,CAAC,OAAO,CAAC,CAAA;IAClC,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+DG;IACH,KAAK,CAAC,kBAAkB,CACtB,MAAc,EACd,OAAgB,EAChB,YAA+B;QAE/B,MAAM,OAAO,GAAG,yBAAyB,CAAA;QACzC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAA;QAEtD,MAAM,IAAI,GAAG,yBAAyB,CAAC;YACrC,MAAM;YACN,OAAO;YACP,YAAY;YACZ,eAAe,EAAE,IAAI,CAAC,eAAe;SACtC,CAAC,CAAA;QAEF,MAAM,OAAO,GAAG,IAAI,CAAC,qBAAqB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;QAExD,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;YAC1C,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,IAAI,YAAY,GAAG,wCAAwC,CAAA;gBAC3D,IAAI,CAAC;oBACH,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;oBACvC,YAAY,GAAG,SAAS,CAAC,OAAO,IAAI,YAAY,CAAA;gBAClD,CAAC;gBAAC,MAAM,CAAC;oBACP,4BAA4B;gBAC9B,CAAC;gBACD,MAAM,aAAa,CAAC,QAAQ,CAAC,GAAG,YAAY,UAAU,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAA;YAC3E,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;YACpC,wEAAwE;YACxE,mEAAmE;YACnE,0EAA0E;YAC1E,yEAAyE;YACzE,SAAS;YACT,IAAI,OAAO,MAAM,EAAE,WAAW,KAAK,QAAQ,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC/E,MAAM,aAAa,CAAC,QAAQ,CAC1B,sEAAsE,CACvE,CAAA;YACH,CAAC;YACD,2EAA2E;YAC3E,2EAA2E;YAC3E,oEAAoE;YACpE,sEAAsE;YACtE,wCAAwC;YACxC,OAAO,EAAE,GAAG,MAAM,EAAE,YAAY,EAAE,wBAAwB,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAA;QAClF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,aAAa,EAAE,CAAC;gBACnC,MAAM,KAAK,CAAA;YACb,CAAC;YACD,MAAM,aAAa,CAAC,QAAQ,CAC1B,uDAAuD,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAChH,CAAA;QACH,CAAC;IACH,CAAC;CACF","sourcesContent":["/**\n * X402 Token Generation API.\n *\n * Provides X402 access token generation functionality for subscribers.\n * Tokens are used to authorize payment verification and settlement.\n */\n\nimport { BasePaymentsAPI } from '../api/base-payments.js'\nimport { API_URL_CREATE_PERMISSION } from '../api/nvm-api.js'\nimport { PaymentsError } from '../common/payments.error.js'\nimport { PaymentOptions, X402TokenOptions, X402TokenVersion } from '../common/types.js'\nimport { buildX402TokenRequestBody } from './token-request.js'\nimport { detectAccessTokenVersion } from './token-version.js'\n\n/**\n * X402 Token API for generating access tokens.\n *\n * Handles X402 access token generation for subscribers to authorize\n * payment operations with AI agents.\n */\nexport class X402TokenAPI extends BasePaymentsAPI {\n /**\n * Get a singleton instance of the X402TokenAPI class.\n *\n * @param options - The options to initialize the API\n * @returns The instance of the X402TokenAPI class\n */\n static getInstance(options: PaymentOptions): X402TokenAPI {\n return new X402TokenAPI(options)\n }\n\n /**\n * Get an X402 access token for the given plan, backed by a delegation.\n *\n * This token allows the agent to verify and settle delegations on behalf\n * of the subscriber.\n *\n * The supported flow is **create-first**: create the delegation once with\n * {@link DelegationAPI.createDelegation}, then pass its `delegationId` in\n * `tokenOptions.delegationConfig`. Passing inline create-on-the-fly fields\n * (spending limits / `providerPaymentMethodId` / `cardId`, i.e. no\n * `delegationId`) is **deprecated** and emits a runtime warning.\n *\n * Pass `resource` / `httpVerb` to bind the token to one seller endpoint, and\n * `tokenVersion: 3` to ask for the single-use, seller/resource-bound token.\n * v3 is opt-in and the request may be silently downgraded by a backend that\n * predates it, so the returned `tokenVersion` is read off the minted token\n * rather than echoed back from the request — branch on that, never on what\n * you asked for.\n *\n * @param planId - The unique identifier of the payment plan\n * @param agentId - The unique identifier of the AI agent (optional)\n * @param tokenOptions - Options controlling scheme, resource binding, token version and delegation behavior (optional)\n * @returns A promise that resolves to an object containing:\n * - accessToken: The X402 access token string\n * - tokenVersion: The EIP-712 version the returned token was actually signed\n * under (`3` means single-use — mint a new token per paid request)\n *\n * @throws PaymentsError if the request fails\n *\n * @example\n * ```typescript\n * // Supported: create the delegation first, then request the token by id.\n * const { delegationId } = await payments.delegation.createDelegation({\n * provider: 'erc4337',\n * spendingLimitCents: 10000,\n * durationSecs: 604800,\n * currency: 'usdc',\n * })\n * const result = await payments.x402.getX402AccessToken(planId, agentId, {\n * delegationConfig: { delegationId },\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Single-use, bound to one seller endpoint. Mint one per paid request.\n * // `resource.url` must be the string the seller advertises in its 402\n * // `paymentRequired` — sellers built on this SDK's `paymentMiddleware`\n * // advertise `req.originalUrl`, i.e. a relative path.\n * const { accessToken, tokenVersion } = await payments.x402.getX402AccessToken(\n * planId,\n * agentId,\n * {\n * delegationConfig: { delegationId },\n * resource: { url: '/api/v1/tasks' },\n * httpVerb: 'POST',\n * tokenVersion: 3,\n * },\n * )\n * if (tokenVersion === 2) {\n * // The backend does not support v3 yet; this token is still reusable.\n * }\n * ```\n */\n async getX402AccessToken(\n planId: string,\n agentId?: string,\n tokenOptions?: X402TokenOptions,\n ): Promise<{ accessToken: string; tokenVersion: X402TokenVersion; [key: string]: any }> {\n const urlPath = API_URL_CREATE_PERMISSION\n const url = new URL(urlPath, this.environment.backend)\n\n const body = buildX402TokenRequestBody({\n planId,\n agentId,\n tokenOptions,\n environmentName: this.environmentName,\n })\n\n const options = this.getBackendHTTPOptions('POST', body)\n\n try {\n const response = await fetch(url, options)\n if (!response.ok) {\n let errorMessage = 'Failed to create X402 delegation token'\n try {\n const errorData = await response.json()\n errorMessage = errorData.message || errorMessage\n } catch {\n // Use default error message\n }\n throw PaymentsError.internal(`${errorMessage} (HTTP ${response.status})`)\n }\n const result = await response.json()\n // A 2xx with no usable token is a broken response, not a token: without\n // this guard the caller gets `{ accessToken: undefined }` typed as\n // `string`, sends `undefined` as the `payment-signature`, and the failure\n // surfaces at the seller as an unparseable token — three layers from the\n // cause.\n if (typeof result?.accessToken !== 'string' || result.accessToken.length === 0) {\n throw PaymentsError.internal(\n 'X402 token request succeeded but the response carried no accessToken',\n )\n }\n // Detected, never echoed: `tokenVersion: 3` is dropped without an error by\n // a backend that predates the v3 struct, so the only trustworthy source of\n // the version is the token itself. The override after the spread is\n // deliberate — a `tokenVersion` echoed by the backend is discarded in\n // favour of the one read off the token.\n return { ...result, tokenVersion: detectAccessTokenVersion(result.accessToken) }\n } catch (error) {\n if (error instanceof PaymentsError) {\n throw error\n }\n throw PaymentsError.internal(\n `Network error while creating X402 delegation token: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n }\n}\n"]}
|