@nevermined-io/core-kit 1.28.0 → 1.29.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.
|
@@ -44,6 +44,27 @@ export type X402Erc4337Payload = {
|
|
|
44
44
|
* tokens carry no `planId` here and verify against the legacy type list.
|
|
45
45
|
*/
|
|
46
46
|
planId?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Signed seller/resource binding + one-time nonce (#2646). Present on v3
|
|
49
|
+
* tokens and covered by the EIP-712 signature.
|
|
50
|
+
*
|
|
51
|
+
* `agentId` / `resourceUrl` / `httpVerb` are signed as the EMPTY STRING when
|
|
52
|
+
* the mint had no value for them — EIP-712 has no optional members, so
|
|
53
|
+
* "absent" has to be a signed value rather than a missing field. The guard
|
|
54
|
+
* (`isSignedBindingConsistent`) then requires the unsigned envelope copy to
|
|
55
|
+
* be absent too, which is what stops anyone ADDING a binding to a token that
|
|
56
|
+
* was signed without one.
|
|
57
|
+
*
|
|
58
|
+
* `nonce` is always a non-empty random value on a v3 token, and is the
|
|
59
|
+
* single v2/v3 discriminator (see `hasSignedNonce`).
|
|
60
|
+
*
|
|
61
|
+
* All four are OPTIONAL on the wire type only for backwards-compat: v1/v2
|
|
62
|
+
* tokens carry none of them and verify against their own type lists.
|
|
63
|
+
*/
|
|
64
|
+
agentId?: string;
|
|
65
|
+
resourceUrl?: string;
|
|
66
|
+
httpVerb?: string;
|
|
67
|
+
nonce?: string;
|
|
47
68
|
};
|
|
48
69
|
};
|
|
49
70
|
/**
|
|
@@ -190,6 +211,77 @@ export type NeverminedTypedDataV1 = {
|
|
|
190
211
|
];
|
|
191
212
|
};
|
|
192
213
|
export declare const NeverminedTypedDataTypesV1: NeverminedTypedDataV1;
|
|
214
|
+
/**
|
|
215
|
+
* EIP-712 `Authorization` struct v3 (#2646) — WITH the seller/resource binding and
|
|
216
|
+
* a one-time `nonce`, appended after `planId`.
|
|
217
|
+
*
|
|
218
|
+
* Append-only, exactly as #1678 appended `planId` to the v1 struct: the four new
|
|
219
|
+
* members come LAST, so the change is purely additive to the v2 encoding and the
|
|
220
|
+
* older type lists keep verifying the tokens that were signed over them.
|
|
221
|
+
*
|
|
222
|
+
* What each member buys:
|
|
223
|
+
* - `agentId` + `resourceUrl` + `httpVerb` remove the token's CROSS-SELLER
|
|
224
|
+
* property. Before v3 these lived only in `accepted.extra` / `resource`, which
|
|
225
|
+
* sit outside the signature and are demand-matched against a `paymentRequired`
|
|
226
|
+
* the SELLER supplies — so seller A could present a token minted for seller B
|
|
227
|
+
* on the same plan and nothing in the signature contradicted it.
|
|
228
|
+
* - `nonce` removes the BEARER property: a v3 token is single-use at the
|
|
229
|
+
* signature layer, enforced by the replay store in `apps/api` at settle time,
|
|
230
|
+
* independently of any burn-level dedupe.
|
|
231
|
+
*
|
|
232
|
+
* A member the mint had no value for is signed as `''` — EIP-712 has no optional
|
|
233
|
+
* struct members, so absence must itself be a signed value. `isSignedBindingConsistent`
|
|
234
|
+
* pairs that with "the unsigned copy must be absent too", which is what prevents
|
|
235
|
+
* ADDING a binding to a token signed without one.
|
|
236
|
+
*/
|
|
237
|
+
export type NeverminedTypedDataV3 = {
|
|
238
|
+
SessionKey: [{
|
|
239
|
+
name: 'id';
|
|
240
|
+
type: 'string';
|
|
241
|
+
}, {
|
|
242
|
+
name: 'data';
|
|
243
|
+
type: 'string';
|
|
244
|
+
}];
|
|
245
|
+
SessionKeys: [{
|
|
246
|
+
name: 'sessionKeys';
|
|
247
|
+
type: 'SessionKey[]';
|
|
248
|
+
}];
|
|
249
|
+
Authorization: [
|
|
250
|
+
{
|
|
251
|
+
name: 'from';
|
|
252
|
+
type: 'address';
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
name: 'sessionKeysProvider';
|
|
256
|
+
type: 'string';
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
name: 'sessionKeys';
|
|
260
|
+
type: 'SessionKey[]';
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
name: 'planId';
|
|
264
|
+
type: 'string';
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
name: 'agentId';
|
|
268
|
+
type: 'string';
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
name: 'resourceUrl';
|
|
272
|
+
type: 'string';
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
name: 'httpVerb';
|
|
276
|
+
type: 'string';
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
name: 'nonce';
|
|
280
|
+
type: 'string';
|
|
281
|
+
}
|
|
282
|
+
];
|
|
283
|
+
};
|
|
284
|
+
export declare const NeverminedTypedDataTypesV3: NeverminedTypedDataV3;
|
|
193
285
|
/**
|
|
194
286
|
* Legacy (v1) signed message shape — no `planId`. Mirrors `NeverminedTypedDataTypesV1`.
|
|
195
287
|
* Only genuinely-old tokens carry this shape; mint never produces it.
|
|
@@ -211,6 +303,29 @@ export type NeverminedTypedDataMessage = NeverminedTypedDataMessageV1 & {
|
|
|
211
303
|
/** Signed planId (#1678) — always present on v2 (minted) tokens. */
|
|
212
304
|
planId: string;
|
|
213
305
|
};
|
|
306
|
+
/**
|
|
307
|
+
* The v3 signed message shape (#2646) — every binding member is REQUIRED, with
|
|
308
|
+
* `''` standing for "the mint had no value for this". Mirrors
|
|
309
|
+
* {@link NeverminedTypedDataTypesV3}.
|
|
310
|
+
*/
|
|
311
|
+
export type NeverminedTypedDataMessageV3 = NeverminedTypedDataMessage & {
|
|
312
|
+
/** Signed agentId, or `''` when the token is not bound to an agent. */
|
|
313
|
+
agentId: string;
|
|
314
|
+
/** Signed `resource.url`, or `''` when the token is not bound to a resource. */
|
|
315
|
+
resourceUrl: string;
|
|
316
|
+
/** Signed HTTP verb, or `''` when the token is not bound to a verb. */
|
|
317
|
+
httpVerb: string;
|
|
318
|
+
/** One-time nonce — always a non-empty random value on a v3 token. */
|
|
319
|
+
nonce: string;
|
|
320
|
+
};
|
|
321
|
+
/**
|
|
322
|
+
* The EIP-712 struct version a token is signed over.
|
|
323
|
+
*
|
|
324
|
+
* `1` is not mintable — it only exists as a verification branch for tokens minted
|
|
325
|
+
* before #1678. New tokens are v2 (the default, reusable, pre-#2646 semantics) or
|
|
326
|
+
* v3 (seller/resource-bound and single-use).
|
|
327
|
+
*/
|
|
328
|
+
export type NeverminedTokenVersion = 2 | 3;
|
|
214
329
|
/**
|
|
215
330
|
* Options for generating an x402 access token
|
|
216
331
|
*/
|
|
@@ -227,6 +342,23 @@ export type GenerateX402TokenOptions = {
|
|
|
227
342
|
httpVerb?: string;
|
|
228
343
|
/** Token expiration timestamp in seconds (Unix epoch) */
|
|
229
344
|
validUntil?: number;
|
|
345
|
+
/**
|
|
346
|
+
* EIP-712 struct version to sign (#2646). Defaults to `2` — the reusable,
|
|
347
|
+
* pre-#2646 shape — so existing callers are untouched and there is no flag day.
|
|
348
|
+
*
|
|
349
|
+
* `3` binds the token to `agentId` / `resource.url` / `httpVerb` and adds a
|
|
350
|
+
* one-time nonce, which the resource server consumes at settle. A v3 token is
|
|
351
|
+
* therefore SINGLE-USE: a caller that opts in must mint one per paid request.
|
|
352
|
+
*
|
|
353
|
+
* Applies identically to BOTH protocols, and MPP does not pin v3 even though it has no
|
|
354
|
+
* pre-#2646 tokens in the wild to stay compatible with. The reason is GRANULARITY, not
|
|
355
|
+
* retry safety (that is #2667's ledger, a separate layer): one MPP access token is
|
|
356
|
+
* reused across MANY challenges, which are genuinely distinct operations rather than
|
|
357
|
+
* retries of one. A per-token nonce would therefore kill every MPP buyer's second
|
|
358
|
+
* challenge. So MPP crosses to v3 on the same schedule as x402 — when the SDKs mint
|
|
359
|
+
* one token per paid request.
|
|
360
|
+
*/
|
|
361
|
+
tokenVersion?: NeverminedTokenVersion;
|
|
230
362
|
};
|
|
231
363
|
/**
|
|
232
364
|
* Generate an x402 access token aligned with x402 v2 spec.
|
|
@@ -275,6 +407,48 @@ export declare const hasSignedPlanId: (authorization: X402Erc4337Payload["author
|
|
|
275
407
|
* a `false` result to its forgery-gated rejection.
|
|
276
408
|
*/
|
|
277
409
|
export declare const isSignedPlanIdConsistent: (authorization: X402Erc4337Payload["authorization"], acceptedPlanId: string | null | undefined) => boolean;
|
|
410
|
+
/**
|
|
411
|
+
* True when `authorization` carries a GENUINE one-time nonce (#2646) — a present,
|
|
412
|
+
* non-empty string. This is the single v2/v3 discriminator, the exact counterpart of
|
|
413
|
+
* {@link hasSignedPlanId} for the v1/v2 boundary: verification keys off it to choose
|
|
414
|
+
* the matching EIP-712 type list, and the resource server keys off it to decide
|
|
415
|
+
* whether the binding guard and the single-use replay store apply.
|
|
416
|
+
*
|
|
417
|
+
* `nonce` is the discriminator rather than one of the binding members because it is
|
|
418
|
+
* the only v3 member that is always non-empty — `agentId` / `resourceUrl` / `httpVerb`
|
|
419
|
+
* are legitimately signed as `''` when the mint had no value for them.
|
|
420
|
+
*/
|
|
421
|
+
export declare const hasSignedNonce: (authorization: X402Erc4337Payload["authorization"]) => authorization is X402Erc4337Payload["authorization"] & {
|
|
422
|
+
nonce: string;
|
|
423
|
+
};
|
|
424
|
+
/** The unsigned envelope values a v3 binding is checked against. */
|
|
425
|
+
export type SignedBindingEnvelope = {
|
|
426
|
+
/** `accepted.extra.agentId` */
|
|
427
|
+
agentId: string | null | undefined;
|
|
428
|
+
/** `resource.url` */
|
|
429
|
+
resourceUrl: string | null | undefined;
|
|
430
|
+
/** `accepted.extra.httpVerb` */
|
|
431
|
+
httpVerb: string | null | undefined;
|
|
432
|
+
};
|
|
433
|
+
/**
|
|
434
|
+
* The signed-vs-unsigned BINDING invariant (#2646), co-located with
|
|
435
|
+
* {@link hasSignedNonce} so signer, verifier and the API handler share ONE definition —
|
|
436
|
+
* the same reason {@link isSignedPlanIdConsistent} lives here.
|
|
437
|
+
*
|
|
438
|
+
* For a v3 token every binding member must agree with its unsigned envelope copy:
|
|
439
|
+
* - a signed non-empty value must equal the envelope value EXACTLY. Both are our own
|
|
440
|
+
* mint's output, so exact equality is right; the fuzzier, query-insensitive URL
|
|
441
|
+
* comparison belongs to the demand-match against the SELLER-supplied
|
|
442
|
+
* `paymentRequired`, which is a different question.
|
|
443
|
+
* - a signed `''` means "not bound", so the envelope copy must be absent (or empty)
|
|
444
|
+
* too. This is the half that stops someone ADDING an `agentId` to a token that was
|
|
445
|
+
* signed without one — without it, `''` would be a hole rather than a claim.
|
|
446
|
+
*
|
|
447
|
+
* Returns `true` for v1/v2 tokens: there is no signed binding to be consistent with,
|
|
448
|
+
* and the caller handles those on the legacy path. The API handler maps a `false`
|
|
449
|
+
* result to its forgery-gated rejection, exactly as it does for the planId guard.
|
|
450
|
+
*/
|
|
451
|
+
export declare const isSignedBindingConsistent: (authorization: X402Erc4337Payload["authorization"], envelope: SignedBindingEnvelope) => boolean;
|
|
278
452
|
/**
|
|
279
453
|
* Verify an x402 access token signature (see {@link verifyAgentAccessToken} for the v1/v2 type-list
|
|
280
454
|
* detection and its downgrade-safety).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentX402AccessToken.d.ts","sourceRoot":"","sources":["../../src/models/AgentX402AccessToken.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,YAAY,EAAgC,MAAM,MAAM,CAAA;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAEvD;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,aAAa,GAAG,qBAAqB,CAAA;AAE9D;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,UAAU,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,EAAE;QACL,OAAO,EAAE,MAAM,CAAA;QACf,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;KAClB,CAAA;CACF,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,EAAE,KAAK,MAAM,EAAE,CAAA;IACxB,aAAa,EAAE;QACb,IAAI,EAAE,KAAK,MAAM,EAAE,CAAA;QACnB,mBAAmB,EAAE,MAAM,CAAA;QAC3B,WAAW,EAAE;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,EAAE,CAAA;QAC3C;;;;;WAKG;QACH,MAAM,CAAC,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"AgentX402AccessToken.d.ts","sourceRoot":"","sources":["../../src/models/AgentX402AccessToken.ts"],"names":[],"mappings":"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"}
|
|
@@ -93,6 +93,58 @@ export const NeverminedTypedDataTypesV1 = {
|
|
|
93
93
|
}
|
|
94
94
|
]
|
|
95
95
|
};
|
|
96
|
+
export const NeverminedTypedDataTypesV3 = {
|
|
97
|
+
SessionKey: [
|
|
98
|
+
{
|
|
99
|
+
name: 'id',
|
|
100
|
+
type: 'string'
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'data',
|
|
104
|
+
type: 'string'
|
|
105
|
+
}
|
|
106
|
+
],
|
|
107
|
+
SessionKeys: [
|
|
108
|
+
{
|
|
109
|
+
name: 'sessionKeys',
|
|
110
|
+
type: 'SessionKey[]'
|
|
111
|
+
}
|
|
112
|
+
],
|
|
113
|
+
Authorization: [
|
|
114
|
+
{
|
|
115
|
+
name: 'from',
|
|
116
|
+
type: 'address'
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: 'sessionKeysProvider',
|
|
120
|
+
type: 'string'
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'sessionKeys',
|
|
124
|
+
type: 'SessionKey[]'
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: 'planId',
|
|
128
|
+
type: 'string'
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: 'agentId',
|
|
132
|
+
type: 'string'
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: 'resourceUrl',
|
|
136
|
+
type: 'string'
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: 'httpVerb',
|
|
140
|
+
type: 'string'
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: 'nonce',
|
|
144
|
+
type: 'string'
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
};
|
|
96
148
|
/**
|
|
97
149
|
* The domain's `chainId` is a load-bearing part of the separator, and viem drops an `undefined`
|
|
98
150
|
* one from the `EIP712Domain` type list — hashing IDENTICALLY to omitting the field, with no error.
|
|
@@ -106,6 +158,18 @@ export const NeverminedTypedDataTypesV1 = {
|
|
|
106
158
|
}
|
|
107
159
|
return chainId;
|
|
108
160
|
};
|
|
161
|
+
/**
|
|
162
|
+
* 32 bytes of CSPRNG output, hex-encoded — the one-time nonce of a v3 token (#2646).
|
|
163
|
+
*
|
|
164
|
+
* `globalThis.crypto` (WebCrypto) rather than node's `crypto` module so core-kit keeps
|
|
165
|
+
* working in the browser, where this package is also consumed. 256 bits makes a
|
|
166
|
+
* collision in the resource server's replay store (a UNIQUE column) unreachable, so a
|
|
167
|
+
* legitimate mint can never be refused as someone else's replay.
|
|
168
|
+
*/ const generateTokenNonce = ()=>{
|
|
169
|
+
const bytes = new Uint8Array(32);
|
|
170
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
171
|
+
return `0x${Array.from(bytes, (b)=>b.toString(16).padStart(2, '0')).join('')}`;
|
|
172
|
+
};
|
|
109
173
|
/**
|
|
110
174
|
* Shared minter for both protocols. The ONLY difference between an x402 token and an MPP token is
|
|
111
175
|
* `domainName` — same signed struct, same envelope, same session keys. Keeping one implementation
|
|
@@ -117,6 +181,7 @@ export const NeverminedTypedDataTypesV1 = {
|
|
|
117
181
|
* selected by a caller-supplied value.
|
|
118
182
|
*/ const generateAgentAccessToken = async (options, domainName)=>{
|
|
119
183
|
const { subscriber, orderSessionKey, redeemSessionKey, publicClient, planId, schemeVersion = '1', agentId, resource, httpVerb, validUntil } = options;
|
|
184
|
+
const tokenVersion = options.tokenVersion ?? 2;
|
|
120
185
|
// A v2 token MUST carry a non-empty signed planId (#1678) — an empty/blank
|
|
121
186
|
// planId would otherwise be signed into `authorization.planId` and could be
|
|
122
187
|
// misread as a "present but empty" signed claim, blurring the v1/v2 boundary
|
|
@@ -132,7 +197,7 @@ export const NeverminedTypedDataTypesV1 = {
|
|
|
132
197
|
// generate the message with `from` address and the SIGNED planId (#1678).
|
|
133
198
|
// Including `planId` here puts it inside the EIP-712 digest, so it cannot be
|
|
134
199
|
// altered without invalidating the signature.
|
|
135
|
-
const
|
|
200
|
+
const messageV2 = {
|
|
136
201
|
from: subscriber.address,
|
|
137
202
|
sessionKeysProvider: 'zerodev',
|
|
138
203
|
sessionKeys: [
|
|
@@ -147,16 +212,29 @@ export const NeverminedTypedDataTypesV1 = {
|
|
|
147
212
|
],
|
|
148
213
|
planId
|
|
149
214
|
};
|
|
215
|
+
// v3 (#2646) appends the seller/resource binding and the one-time nonce. A member
|
|
216
|
+
// the caller left undefined is signed as '' — EIP-712 has no optional members, so
|
|
217
|
+
// "not bound" must itself be a signed value; `isSignedBindingConsistent` then holds
|
|
218
|
+
// the unsigned envelope copy to the same absence.
|
|
219
|
+
const message = tokenVersion === 3 ? {
|
|
220
|
+
...messageV2,
|
|
221
|
+
agentId: agentId ?? '',
|
|
222
|
+
resourceUrl: resource?.url ?? '',
|
|
223
|
+
httpVerb: httpVerb ?? '',
|
|
224
|
+
nonce: generateTokenNonce()
|
|
225
|
+
} : messageV2;
|
|
150
226
|
const domain = {
|
|
151
227
|
name: domainName,
|
|
152
228
|
version: '1',
|
|
153
229
|
chainId,
|
|
154
230
|
verifyingContract: subscriber.address
|
|
155
231
|
};
|
|
156
|
-
// generate the eip712 data
|
|
232
|
+
// generate the eip712 data. EIP-712 hashes the TYPE SET, so the type list and the
|
|
233
|
+
// message shape must be chosen together — a v3 message signed over the v2 list
|
|
234
|
+
// would produce a digest no verifier reproduces.
|
|
157
235
|
const eip712Data = {
|
|
158
236
|
domain,
|
|
159
|
-
types: NeverminedTypedDataTypes,
|
|
237
|
+
types: tokenVersion === 3 ? NeverminedTypedDataTypesV3 : NeverminedTypedDataTypes,
|
|
160
238
|
primaryType: 'Authorization',
|
|
161
239
|
message
|
|
162
240
|
};
|
|
@@ -238,18 +316,62 @@ export const NeverminedTypedDataTypesV1 = {
|
|
|
238
316
|
if (!hasSignedPlanId(authorization)) return true;
|
|
239
317
|
return acceptedPlanId === authorization.planId;
|
|
240
318
|
};
|
|
319
|
+
/**
|
|
320
|
+
* True when `authorization` carries a GENUINE one-time nonce (#2646) — a present,
|
|
321
|
+
* non-empty string. This is the single v2/v3 discriminator, the exact counterpart of
|
|
322
|
+
* {@link hasSignedPlanId} for the v1/v2 boundary: verification keys off it to choose
|
|
323
|
+
* the matching EIP-712 type list, and the resource server keys off it to decide
|
|
324
|
+
* whether the binding guard and the single-use replay store apply.
|
|
325
|
+
*
|
|
326
|
+
* `nonce` is the discriminator rather than one of the binding members because it is
|
|
327
|
+
* the only v3 member that is always non-empty — `agentId` / `resourceUrl` / `httpVerb`
|
|
328
|
+
* are legitimately signed as `''` when the mint had no value for them.
|
|
329
|
+
*/ export const hasSignedNonce = (authorization)=>{
|
|
330
|
+
return typeof authorization?.nonce === 'string' && authorization.nonce.trim() !== '';
|
|
331
|
+
};
|
|
332
|
+
/**
|
|
333
|
+
* The signed-vs-unsigned BINDING invariant (#2646), co-located with
|
|
334
|
+
* {@link hasSignedNonce} so signer, verifier and the API handler share ONE definition —
|
|
335
|
+
* the same reason {@link isSignedPlanIdConsistent} lives here.
|
|
336
|
+
*
|
|
337
|
+
* For a v3 token every binding member must agree with its unsigned envelope copy:
|
|
338
|
+
* - a signed non-empty value must equal the envelope value EXACTLY. Both are our own
|
|
339
|
+
* mint's output, so exact equality is right; the fuzzier, query-insensitive URL
|
|
340
|
+
* comparison belongs to the demand-match against the SELLER-supplied
|
|
341
|
+
* `paymentRequired`, which is a different question.
|
|
342
|
+
* - a signed `''` means "not bound", so the envelope copy must be absent (or empty)
|
|
343
|
+
* too. This is the half that stops someone ADDING an `agentId` to a token that was
|
|
344
|
+
* signed without one — without it, `''` would be a hole rather than a claim.
|
|
345
|
+
*
|
|
346
|
+
* Returns `true` for v1/v2 tokens: there is no signed binding to be consistent with,
|
|
347
|
+
* and the caller handles those on the legacy path. The API handler maps a `false`
|
|
348
|
+
* result to its forgery-gated rejection, exactly as it does for the planId guard.
|
|
349
|
+
*/ export const isSignedBindingConsistent = (authorization, envelope)=>{
|
|
350
|
+
if (!hasSignedNonce(authorization)) return true;
|
|
351
|
+
const matches = (signed, unsigned)=>{
|
|
352
|
+
const signedValue = signed ?? '';
|
|
353
|
+
// Absence and '' are the same claim on both sides: the mint omits an empty member
|
|
354
|
+
// from the envelope entirely, so an omitted field is what a signed '' looks like.
|
|
355
|
+
if (signedValue === '') return unsigned === undefined || unsigned === null || unsigned === '';
|
|
356
|
+
return unsigned === signedValue;
|
|
357
|
+
};
|
|
358
|
+
return matches(authorization.agentId, envelope.agentId) && matches(authorization.resourceUrl, envelope.resourceUrl) && matches(authorization.httpVerb, envelope.httpVerb);
|
|
359
|
+
};
|
|
241
360
|
/**
|
|
242
361
|
* Shared verifier for both protocols, differing only in the EIP-712 domain name.
|
|
243
362
|
*
|
|
244
|
-
* EIP-712 hashes the type set, so a token signed over
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
* against the matching
|
|
248
|
-
*
|
|
363
|
+
* EIP-712 hashes the type set, so a token signed over one struct cannot verify against
|
|
364
|
+
* another's type list. We therefore detect which shape the token carries — genuine
|
|
365
|
+
* signed nonce → v3 (#2646), else genuine signed planId → v2 (#1678), else v1 — and
|
|
366
|
+
* verify against the matching list. This keeps genuinely-old v1 tokens valid while
|
|
367
|
+
* newer tokens are checked over everything they signed.
|
|
249
368
|
*
|
|
250
|
-
* Downgrade-safe: a v2 token was signed over
|
|
251
|
-
* `authorization.planId` to force the v1 branch
|
|
252
|
-
*
|
|
369
|
+
* Downgrade-safe, and the property extends to v3 unchanged: a v2 token was signed over
|
|
370
|
+
* the with-planId digest, so stripping `authorization.planId` to force the v1 branch
|
|
371
|
+
* changes the digest and the signature fails; a v3 token was signed over the with-nonce
|
|
372
|
+
* digest, so stripping the nonce to escape single-use, or stripping `agentId` /
|
|
373
|
+
* `resourceUrl` to escape the seller binding, fails the same way. An attacker cannot
|
|
374
|
+
* demote a signed token to shed a claim.
|
|
253
375
|
*
|
|
254
376
|
* EIP-712 hashes the DOMAIN separator too, which is what makes `domainName` the protocol
|
|
255
377
|
* boundary (#2640) rather than a label. Not exported: callers go through
|
|
@@ -279,9 +401,12 @@ export const NeverminedTypedDataTypesV1 = {
|
|
|
279
401
|
const isValid = await publicClient.verifyTypedData({
|
|
280
402
|
address: subscriberAddress,
|
|
281
403
|
signature,
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
|
|
404
|
+
// One branch per struct version, selected by GENUINE presence and never by a
|
|
405
|
+
// caller-supplied hint: a nonce (#2646) → v3, else a planId (#1678) → v2, else the
|
|
406
|
+
// original v1 list. The branches are mutually exclusive and no branch falls back to
|
|
407
|
+
// another — trying a second type list on failure would erase exactly the downgrade
|
|
408
|
+
// protection the versioning provides.
|
|
409
|
+
types: hasSignedNonce(authorization) ? NeverminedTypedDataTypesV3 : hasSignedPlanId(authorization) ? NeverminedTypedDataTypes : NeverminedTypedDataTypesV1,
|
|
285
410
|
domain,
|
|
286
411
|
message: authorization,
|
|
287
412
|
primaryType: 'Authorization'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/models/AgentX402AccessToken.ts"],"sourcesContent":["import { keccak256, PublicClient, toBytes, TypedDataDefinition } from 'viem'\nimport { SmartAccount } from 'viem/account-abstraction'\n\n/**\n * x402 Resource information\n */\nexport type X402Resource = {\n url: string\n description?: string\n mimeType?: string\n}\n\n/**\n * Supported x402 payment schemes\n */\nexport type X402Scheme = 'nvm:erc4337' | 'nvm:card-delegation'\n\n/**\n * x402 Accepted scheme fields (common to all schemes)\n */\nexport type X402Accepted = {\n scheme: X402Scheme\n network: string\n planId?: string\n extra: {\n version: string\n agentId?: string\n httpVerb?: string\n }\n}\n\n/**\n * Payload specific to the nvm:erc4337 scheme\n */\nexport type X402Erc4337Payload = {\n signature: `0x${string}`\n authorization: {\n from: `0x${string}`\n sessionKeysProvider: string\n sessionKeys: { id: string; data: string }[]\n /**\n * Signed planId (#1678). Present on v2 tokens (minted after #1678) and\n * covered by the EIP-712 signature, so it is tamper-evident on its own.\n * OPTIONAL on the wire type only for backwards-compat: genuinely-old v1\n * tokens carry no `planId` here and verify against the legacy type list.\n */\n planId?: string\n }\n}\n\n/**\n * Payload specific to the nvm:card-delegation scheme\n */\nexport type X402CardDelegationPayload = {\n token: string\n}\n\n/**\n * x402 Access Token aligned with x402 v2 spec\n *\n * Key changes from previous version:\n * - `subscriberAddress` moved to `payload.authorization.from`\n * - `planId` is now in `accepted` (optional — required for erc4337, optional for card-delegation)\n * - `agentId` moved to `accepted.extra.agentId` (optional)\n * - `scheme` and `network` moved to `accepted`\n * - Added `resource` and `extensions` for x402 v2 alignment\n */\nexport type AgentX402AccessToken = {\n x402Version: 2\n resource?: X402Resource\n accepted: X402Accepted\n payload: X402Erc4337Payload | X402CardDelegationPayload\n validUntil?: number\n extensions: Record<string, unknown>\n}\n\n/**\n * EIP-712 domain name for the x402 protocol — the original, unchanged value.\n */\nexport const NEVERMINED_EIP712_DOMAIN_NAME = 'Nevermined' as const\n\n/**\n * EIP-712 domain name for the MPP protocol (#2640).\n *\n * This constant IS the isolation between the two protocols. EIP-712 hashes the domain separator\n * into the signed digest, so a token minted under `Nevermined-MPP` cannot verify against the x402\n * verifier and vice versa — cross-protocol replay and downgrade are closed by construction.\n *\n * A plaintext `scheme`/`protocol` field would NOT do this for the SECURITY boundary:\n * `accepted.scheme` and `accepted.network` sit OUTSIDE the signature (see {@link X402Accepted}), so\n * they are forgeable. Only the domain is covered by the signature without touching the signed\n * struct.\n *\n * That objection does NOT extend to protocol ROUTING. The two envelopes are byte-identical apart\n * from the signature — `accepted.scheme` is `nvm:erc4337` for both — so a dispatcher holding a\n * decoded token has no signal for which verifier to call. If #2641 needs one, add an UNSIGNED hint\n * (`accepted.extra.protocol`) and document it as **routing only — NEVER the security boundary**:\n * forging it can only select the wrong verifier, and the wrong verifier returns `false`.\n * What is forbidden is trying both verifiers in turn — that collapses this isolation to zero.\n *\n * The signed `Authorization` struct is deliberately IDENTICAL for both protocols — do not add a\n * protocol field to it. Changing the struct would fork the type list and break the v1/v2\n * back-compat that `verifyAgentX402AccessToken` depends on, for no security gain.\n */\nexport const NEVERMINED_MPP_EIP712_DOMAIN_NAME = 'Nevermined-MPP' as const\n\n/** The EIP-712 domain name of a Nevermined access token — one per protocol. */\nexport type NeverminedEip712DomainName =\n | typeof NEVERMINED_EIP712_DOMAIN_NAME\n | typeof NEVERMINED_MPP_EIP712_DOMAIN_NAME\n\n/**\n * Defaults to the x402 domain name so the bare `NeverminedTypedDataDomain` keeps resolving to\n * `name: 'Nevermined'` for existing SDK consumers — widening it to the union would be a\n * source-breaking change to a published package (a `const n: 'Nevermined' = domain.name` stops\n * compiling), and `projectsRelationship: \"fixed\"` makes a core-kit major a workspace-wide major.\n */\nexport type NeverminedTypedDataDomain<\n TName extends NeverminedEip712DomainName = typeof NEVERMINED_EIP712_DOMAIN_NAME,\n> = {\n name: TName\n version: '1'\n chainId: number\n verifyingContract: `0x${string}`\n}\n\n/**\n * EIP-712 `Authorization` struct WITH a signed `planId` (#1678).\n *\n * `planId` is appended LAST so the change is purely additive to the EIP-712\n * encoding of the legacy (v1) struct. Signing the planId makes the token's plan\n * tamper-evident on its own — defense-in-depth atop the resource server's\n * `validateAcceptedMatchesRequirements` demand-match, which remains the primary\n * gate. See `NeverminedTypedDataTypesV1` for the legacy struct that pre-#1678\n * tokens were signed over; both shapes verify against the same signer.\n */\nexport type NeverminedTypedData = {\n SessionKey: [{ name: 'id'; type: 'string' }, { name: 'data'; type: 'string' }]\n SessionKeys: [{ name: 'sessionKeys'; type: 'SessionKey[]' }]\n Authorization: [\n { name: 'from'; type: 'address' },\n { name: 'sessionKeysProvider'; type: 'string' },\n { name: 'sessionKeys'; type: 'SessionKey[]' },\n { name: 'planId'; type: 'string' },\n ]\n}\n\nexport const NeverminedTypedDataTypes: NeverminedTypedData = {\n SessionKey: [\n { name: 'id', type: 'string' },\n { name: 'data', type: 'string' },\n ],\n SessionKeys: [{ name: 'sessionKeys', type: 'SessionKey[]' }],\n Authorization: [\n { name: 'from', type: 'address' },\n { name: 'sessionKeysProvider', type: 'string' },\n { name: 'sessionKeys', type: 'SessionKey[]' },\n { name: 'planId', type: 'string' },\n ],\n}\n\n/**\n * Legacy (v1) EIP-712 `Authorization` struct — WITHOUT a signed `planId`.\n *\n * Retained so `verifyAgentX402AccessToken` can still validate genuinely-old\n * tokens minted before #1678 (their `authorization` carries no `planId`, and\n * EIP-712 hashes the type set, so they only verify against THIS type list).\n * Mint always produces the v2 (with-planId) shape going forward.\n */\nexport type NeverminedTypedDataV1 = {\n SessionKey: [{ name: 'id'; type: 'string' }, { name: 'data'; type: 'string' }]\n SessionKeys: [{ name: 'sessionKeys'; type: 'SessionKey[]' }]\n Authorization: [\n { name: 'from'; type: 'address' },\n { name: 'sessionKeysProvider'; type: 'string' },\n { name: 'sessionKeys'; type: 'SessionKey[]' },\n ]\n}\n\nexport const NeverminedTypedDataTypesV1: NeverminedTypedDataV1 = {\n SessionKey: [\n { name: 'id', type: 'string' },\n { name: 'data', type: 'string' },\n ],\n SessionKeys: [{ name: 'sessionKeys', type: 'SessionKey[]' }],\n Authorization: [\n { name: 'from', type: 'address' },\n { name: 'sessionKeysProvider', type: 'string' },\n { name: 'sessionKeys', type: 'SessionKey[]' },\n ],\n}\n\n/**\n * Legacy (v1) signed message shape — no `planId`. Mirrors `NeverminedTypedDataTypesV1`.\n * Only genuinely-old tokens carry this shape; mint never produces it.\n */\nexport type NeverminedTypedDataMessageV1 = {\n from: `0x${string}`\n sessionKeysProvider: string\n sessionKeys: { id: string; data: string }[]\n}\n\n/**\n * The v2 signed message shape — `planId` is REQUIRED. Mint always produces this\n * shape (#1678), so `planId` is non-optional here; the v1 variant above models\n * the legacy no-planId message. Mirrors `NeverminedTypedDataTypes`.\n */\nexport type NeverminedTypedDataMessage = NeverminedTypedDataMessageV1 & {\n /** Signed planId (#1678) — always present on v2 (minted) tokens. */\n planId: string\n}\n\n/**\n * Options for generating an x402 access token\n */\nexport type GenerateX402TokenOptions = {\n subscriber: SmartAccount\n orderSessionKey: string\n redeemSessionKey: string\n publicClient: PublicClient\n planId: string\n /** Scheme version (e.g., '1') - stored in accepted.extra.version */\n schemeVersion?: string\n agentId?: string\n resource?: X402Resource\n httpVerb?: string\n /** Token expiration timestamp in seconds (Unix epoch) */\n validUntil?: number\n}\n\n/**\n * The domain's `chainId` is a load-bearing part of the separator, and viem drops an `undefined`\n * one from the `EIP712Domain` type list — hashing IDENTICALLY to omitting the field, with no error.\n * A chainless `publicClient` (`createPublicClient({ transport })` with no `chain`, a shape that\n * already exists in this repo) would therefore mint a digest valid on every chain at once. Fail\n * loudly instead: this is a client misconfiguration, never a token defect.\n */\nconst requireChainId = (publicClient: PublicClient, caller: string): number => {\n const chainId = publicClient.chain?.id\n if (typeof chainId !== 'number') {\n throw new Error(`${caller}: publicClient has no chain — the EIP-712 domain needs a chainId`)\n }\n return chainId\n}\n\n/**\n * Shared minter for both protocols. The ONLY difference between an x402 token and an MPP token is\n * `domainName` — same signed struct, same envelope, same session keys. Keeping one implementation\n * is deliberate: two copies would drift, and a drifted MPP minter that happened to reproduce the\n * x402 digest would silently reopen the cross-protocol replay this domain split exists to close.\n *\n * Not exported — callers go through {@link generateAgentX402AccessToken} or\n * {@link generateAgentMppAccessToken}, each with its domain pinned, so a protocol can never be\n * selected by a caller-supplied value.\n */\nconst generateAgentAccessToken = async (\n options: GenerateX402TokenOptions,\n domainName: NeverminedEip712DomainName,\n): Promise<AgentX402AccessToken> => {\n const {\n subscriber,\n orderSessionKey,\n redeemSessionKey,\n publicClient,\n planId,\n schemeVersion = '1',\n agentId,\n resource,\n httpVerb,\n validUntil,\n } = options\n\n // A v2 token MUST carry a non-empty signed planId (#1678) — an empty/blank\n // planId would otherwise be signed into `authorization.planId` and could be\n // misread as a \"present but empty\" signed claim, blurring the v1/v2 boundary\n // that verification keys off genuine presence. erc4337 already requires a\n // planId upstream (`validateX402TokenStructure`), so this is a hard invariant.\n if (typeof planId !== 'string' || planId.trim() === '') {\n throw new Error('Nevermined access token: planId is required and must be a non-empty string')\n }\n\n const chainId = requireChainId(publicClient, 'Nevermined access token mint')\n\n // hash the session keys\n const redeemSessionKeyHash = keccak256(toBytes(redeemSessionKey))\n const orderSessionKeyHash = keccak256(toBytes(orderSessionKey))\n\n // generate the message with `from` address and the SIGNED planId (#1678).\n // Including `planId` here puts it inside the EIP-712 digest, so it cannot be\n // altered without invalidating the signature.\n const message: NeverminedTypedDataMessage = {\n from: subscriber.address,\n sessionKeysProvider: 'zerodev',\n sessionKeys: [\n {\n id: 'order',\n data: orderSessionKeyHash,\n },\n {\n id: 'redeem',\n data: redeemSessionKeyHash,\n },\n ],\n planId,\n }\n\n const domain: NeverminedTypedDataDomain<NeverminedEip712DomainName> = {\n name: domainName,\n version: '1',\n chainId,\n verifyingContract: subscriber.address,\n }\n\n // generate the eip712 data\n const eip712Data = {\n domain,\n\n types: NeverminedTypedDataTypes,\n primaryType: 'Authorization',\n message,\n } satisfies TypedDataDefinition<NeverminedTypedData, 'Authorization'>\n\n const signature = await subscriber.signTypedData(eip712Data)\n\n // Network in CAIP-2 format (eip155:chainId) — always resolvable, `requireChainId` already ran.\n const network = `eip155:${chainId}`\n\n return {\n x402Version: 2,\n ...(resource && { resource }),\n accepted: {\n scheme: 'nvm:erc4337',\n network,\n planId,\n extra: {\n version: schemeVersion,\n ...(agentId && { agentId }),\n ...(httpVerb && { httpVerb }),\n },\n },\n payload: {\n signature,\n authorization: message,\n },\n ...(validUntil !== undefined && { validUntil }),\n extensions: {},\n }\n}\n\n/**\n * Generate an x402 access token aligned with x402 v2 spec.\n *\n * @param options - Token generation options\n * @returns x402 access token with EIP-712 signature\n */\nexport const generateAgentX402AccessToken = (\n options: GenerateX402TokenOptions,\n): Promise<AgentX402AccessToken> => generateAgentAccessToken(options, NEVERMINED_EIP712_DOMAIN_NAME)\n\n/**\n * Generate a NATIVE MPP access token (#2640) — the same token shape signed under the\n * `Nevermined-MPP` EIP-712 domain.\n *\n * Native, not a wrapper: the revised Tier A design (#2024) deliberately does NOT embed an x402\n * token inside an MPP credential, because unwrapping it would hand the holder a token that still\n * settles on the public x402 path.\n *\n * @param options - Token generation options (identical to the x402 minter)\n * @returns MPP access token with EIP-712 signature over the MPP domain. The envelope is IDENTICAL\n * to an x402 token's, `accepted.scheme: 'nvm:erc4337'` included — only the signature differs, so\n * the protocol is fixed by the endpoint, not readable off the wire. See\n * {@link NEVERMINED_MPP_EIP712_DOMAIN_NAME} on routing vs. the security boundary.\n */\nexport const generateAgentMppAccessToken = (\n options: GenerateX402TokenOptions,\n): Promise<AgentX402AccessToken> =>\n generateAgentAccessToken(options, NEVERMINED_MPP_EIP712_DOMAIN_NAME)\n\n/**\n * True when `authorization` carries a GENUINE signed planId (#1678) — a present,\n * non-empty string. This is the single v1/v2 discriminator: verification keys off\n * it to choose the matching EIP-712 type list, the handler keys off it to decide\n * whether the signed-vs-unsigned consistency guard fires. Keying off genuine\n * presence (not just `'planId' in authorization`) ensures a `planId: ''` / nullish\n * value can never be mistaken for a real signed claim.\n */\nexport const hasSignedPlanId = (\n authorization: X402Erc4337Payload['authorization'],\n): authorization is X402Erc4337Payload['authorization'] & { planId: string } => {\n return typeof authorization?.planId === 'string' && authorization.planId.trim() !== ''\n}\n\n/**\n * The signed-vs-unsigned planId consistency invariant (#1678), co-located with\n * `hasSignedPlanId` so signer, verifier, and the API handler share ONE\n * definition (no drift between where the planId is signed and where the guard\n * enforces it).\n *\n * For a v2 token (signed planId present) the tamperable unsigned `accepted.planId`\n * MUST exactly equal the signed `authorization.planId` — anything else is a\n * post-signing edit of the outer JSON. Returns `true` for v1 tokens (no signed\n * planId): there is no signed value to be consistent with, so the guard does not\n * apply (the caller handles the legacy fallback separately). The API handler maps\n * a `false` result to its forgery-gated rejection.\n */\nexport const isSignedPlanIdConsistent = (\n authorization: X402Erc4337Payload['authorization'],\n acceptedPlanId: string | null | undefined,\n): boolean => {\n if (!hasSignedPlanId(authorization)) return true\n return acceptedPlanId === authorization.planId\n}\n\n/**\n * Shared verifier for both protocols, differing only in the EIP-712 domain name.\n *\n * EIP-712 hashes the type set, so a token signed over the v2 (with-planId)\n * struct cannot verify against the v1 type list and vice-versa. We therefore\n * detect which shape the token carries (genuine signed planId → v2) and verify\n * against the matching type list. This keeps genuinely-old v1 tokens valid\n * (#1678 backwards-compat) while v2 tokens are checked over the signed planId.\n *\n * Downgrade-safe: a v2 token was signed over the with-planId digest, so stripping\n * `authorization.planId` to force the v1 branch changes the digest and the\n * signature fails — an attacker cannot demote a signed token to dodge the planId.\n *\n * EIP-712 hashes the DOMAIN separator too, which is what makes `domainName` the protocol\n * boundary (#2640) rather than a label. Not exported: callers go through\n * {@link verifyAgentX402AccessToken} or {@link verifyAgentMppAccessToken}, each with its domain\n * and its `allowLegacyV1` stance pinned, so no caller-supplied value can pick which protocol a\n * token is checked against.\n */\nconst verifyAgentAccessToken = async (\n accessToken: AgentX402AccessToken,\n publicClient: PublicClient,\n domainName: NeverminedEip712DomainName,\n allowLegacyV1: boolean,\n): Promise<boolean> => {\n // `payload` is a union (erc4337 | card-delegation) and this is a public entry point with no\n // guaranteed pre-validation — `validateX402TokenStructure` runs in `apps/api`, not here. A\n // structural precondition, evaluated BEFORE the await: deliberately NOT a try/catch around the\n // verification itself, which must keep letting an RPC fault propagate rather than laundering it\n // into a `false` the caller reads as a forgery.\n const erc4337Payload = accessToken?.payload as X402Erc4337Payload | undefined\n if (!erc4337Payload?.authorization?.from || !erc4337Payload.signature) return false\n\n const { signature, authorization } = erc4337Payload\n const subscriberAddress = authorization.from\n\n // A v1 (unsigned-planId) token leaves `isSignedPlanIdConsistent` inert, so the tamperable\n // `accepted.planId` becomes authoritative. That is a deliberate #1678 back-compat concession on\n // x402 — a protocol with no legacy tokens must not inherit it.\n if (!allowLegacyV1 && !hasSignedPlanId(authorization)) return false\n\n const domain: NeverminedTypedDataDomain<NeverminedEip712DomainName> = {\n name: domainName,\n version: '1',\n chainId: requireChainId(publicClient, 'Nevermined access token verify'),\n verifyingContract: subscriberAddress,\n }\n const isValid = await publicClient.verifyTypedData({\n address: subscriberAddress,\n signature,\n // v2 (signed planId) verifies over the with-planId type list; legacy v1\n // tokens (no signed planId) verify over the original type list.\n types: hasSignedPlanId(authorization) ? NeverminedTypedDataTypes : NeverminedTypedDataTypesV1,\n domain,\n message: authorization,\n primaryType: 'Authorization',\n })\n return isValid\n}\n\n/**\n * Verify an x402 access token signature (see {@link verifyAgentAccessToken} for the v1/v2 type-list\n * detection and its downgrade-safety).\n *\n * Rejects an MPP token: it was signed under a different domain separator, so the digest differs.\n *\n * @param accessToken - The x402 access token to verify\n * @param publicClient - Viem public client\n * @returns true if signature is valid\n */\nexport const verifyAgentX402AccessToken = (\n accessToken: AgentX402AccessToken,\n publicClient: PublicClient,\n): Promise<boolean> =>\n verifyAgentAccessToken(accessToken, publicClient, NEVERMINED_EIP712_DOMAIN_NAME, true)\n\n/**\n * Verify an MPP access token signature (#2640).\n *\n * Rejects an x402 token for the same reason the x402 verifier rejects an MPP one — the domain\n * separator is part of the signed digest. This is the property the whole Tier A isolation rests on:\n * a credential harvested from one protocol is inert on the other, closing cross-protocol replay\n * AND downgrade — WHILE the signing key stays server-side. A subscriber holding it can mint under\n * whichever domain they like, so the split constrains the transport, not that holder.\n *\n * v2-only: unlike the x402 verifier, this rejects a legacy (unsigned-planId) authorization\n * outright. MPP is born with #2640 and has no pre-#1678 tokens to be compatible with.\n *\n * @param accessToken - The MPP access token to verify\n * @param publicClient - Viem public client\n * @returns true if signature is valid\n */\nexport const verifyAgentMppAccessToken = (\n accessToken: AgentX402AccessToken,\n publicClient: PublicClient,\n): Promise<boolean> =>\n verifyAgentAccessToken(accessToken, publicClient, NEVERMINED_MPP_EIP712_DOMAIN_NAME, false)\n\n/**\n * Validation result for x402 token structure\n */\nexport type X402TokenValidationResult = {\n valid: boolean\n errors: string[]\n}\n\n/**\n * Validate the structure of an x402 access token\n *\n * @param token - The token to validate (unknown type for safety)\n * @returns Validation result with errors if any\n */\nconst SUPPORTED_SCHEMES: X402Scheme[] = ['nvm:erc4337', 'nvm:card-delegation']\n\nexport const validateX402TokenStructure = (token: unknown): X402TokenValidationResult => {\n const errors: string[] = []\n\n if (!token || typeof token !== 'object') {\n return { valid: false, errors: ['Token must be an object'] }\n }\n\n const t = token as Record<string, unknown>\n\n // Validate x402Version\n if (t.x402Version !== 2) {\n errors.push('x402Version must be 2')\n }\n\n // Validate accepted object\n if (!t.accepted || typeof t.accepted !== 'object') {\n errors.push('accepted is required')\n } else {\n const accepted = t.accepted as Record<string, unknown>\n const scheme = accepted.scheme as string\n\n if (!SUPPORTED_SCHEMES.includes(scheme as X402Scheme)) {\n errors.push(`scheme must be one of: ${SUPPORTED_SCHEMES.join(', ')}`)\n }\n\n if (!accepted.network || typeof accepted.network !== 'string') {\n errors.push('network is required in accepted')\n }\n\n // Validate extra\n if (!accepted.extra || typeof accepted.extra !== 'object') {\n errors.push('extra is required in accepted')\n } else {\n const extra = accepted.extra as Record<string, unknown>\n if (!extra.version || typeof extra.version !== 'string') {\n errors.push('version is required in accepted.extra')\n }\n }\n\n // Scheme-specific accepted validation\n if (scheme === 'nvm:erc4337') {\n if (!accepted.planId || typeof accepted.planId !== 'string') {\n errors.push('planId is required in accepted for nvm:erc4337')\n }\n }\n }\n\n // Validate payload\n if (!t.payload || typeof t.payload !== 'object') {\n errors.push('payload is required')\n } else {\n const accepted = t.accepted as Record<string, unknown> | undefined\n const scheme = accepted?.scheme as string | undefined\n const payload = t.payload as Record<string, unknown>\n\n if (scheme === 'nvm:erc4337') {\n if (!payload.signature || typeof payload.signature !== 'string') {\n errors.push('signature is required in payload')\n }\n\n if (!payload.authorization || typeof payload.authorization !== 'object') {\n errors.push('authorization is required in payload')\n } else {\n const auth = payload.authorization as Record<string, unknown>\n\n if (!auth.from || typeof auth.from !== 'string') {\n errors.push('from address is required in payload.authorization')\n } else if (!auth.from.startsWith('0x')) {\n errors.push('from must be a valid hex address')\n }\n\n if (!auth.sessionKeysProvider || typeof auth.sessionKeysProvider !== 'string') {\n errors.push('sessionKeysProvider is required in payload.authorization')\n }\n\n if (!Array.isArray(auth.sessionKeys)) {\n errors.push('sessionKeys array is required in payload.authorization')\n }\n }\n } else if (scheme === 'nvm:card-delegation') {\n if (!payload.token || typeof payload.token !== 'string') {\n errors.push('token is required in payload for nvm:card-delegation')\n }\n }\n }\n\n // Validate extensions (must be present, even if empty)\n if (t.extensions === undefined) {\n errors.push('extensions is required (can be empty object)')\n }\n\n return { valid: errors.length === 0, errors }\n}\n\nexport const b64EncodeX402AccessToken = (accessToken: AgentX402AccessToken): string => {\n return Buffer.from(JSON.stringify(accessToken)).toString('base64')\n}\n\nexport const b64DecodeX402AccessToken = (encoded: string): AgentX402AccessToken => {\n return JSON.parse(Buffer.from(encoded, 'base64').toString('utf-8'))\n}\n"],"names":["keccak256","toBytes","NEVERMINED_EIP712_DOMAIN_NAME","NEVERMINED_MPP_EIP712_DOMAIN_NAME","NeverminedTypedDataTypes","SessionKey","name","type","SessionKeys","Authorization","NeverminedTypedDataTypesV1","requireChainId","publicClient","caller","chainId","chain","id","Error","generateAgentAccessToken","options","domainName","subscriber","orderSessionKey","redeemSessionKey","planId","schemeVersion","agentId","resource","httpVerb","validUntil","trim","redeemSessionKeyHash","orderSessionKeyHash","message","from","address","sessionKeysProvider","sessionKeys","data","domain","version","verifyingContract","eip712Data","types","primaryType","signature","signTypedData","network","x402Version","accepted","scheme","extra","payload","authorization","undefined","extensions","generateAgentX402AccessToken","generateAgentMppAccessToken","hasSignedPlanId","isSignedPlanIdConsistent","acceptedPlanId","verifyAgentAccessToken","accessToken","allowLegacyV1","erc4337Payload","subscriberAddress","isValid","verifyTypedData","verifyAgentX402AccessToken","verifyAgentMppAccessToken","SUPPORTED_SCHEMES","validateX402TokenStructure","token","errors","valid","t","push","includes","join","auth","startsWith","Array","isArray","length","b64EncodeX402AccessToken","Buffer","JSON","stringify","toString","b64DecodeX402AccessToken","encoded","parse"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/models/AgentX402AccessToken.ts"],"sourcesContent":["import { keccak256, PublicClient, toBytes, TypedDataDefinition } from 'viem'\nimport { SmartAccount } from 'viem/account-abstraction'\n\n/**\n * x402 Resource information\n */\nexport type X402Resource = {\n url: string\n description?: string\n mimeType?: string\n}\n\n/**\n * Supported x402 payment schemes\n */\nexport type X402Scheme = 'nvm:erc4337' | 'nvm:card-delegation'\n\n/**\n * x402 Accepted scheme fields (common to all schemes)\n */\nexport type X402Accepted = {\n scheme: X402Scheme\n network: string\n planId?: string\n extra: {\n version: string\n agentId?: string\n httpVerb?: string\n }\n}\n\n/**\n * Payload specific to the nvm:erc4337 scheme\n */\nexport type X402Erc4337Payload = {\n signature: `0x${string}`\n authorization: {\n from: `0x${string}`\n sessionKeysProvider: string\n sessionKeys: { id: string; data: string }[]\n /**\n * Signed planId (#1678). Present on v2 tokens (minted after #1678) and\n * covered by the EIP-712 signature, so it is tamper-evident on its own.\n * OPTIONAL on the wire type only for backwards-compat: genuinely-old v1\n * tokens carry no `planId` here and verify against the legacy type list.\n */\n planId?: string\n /**\n * Signed seller/resource binding + one-time nonce (#2646). Present on v3\n * tokens and covered by the EIP-712 signature.\n *\n * `agentId` / `resourceUrl` / `httpVerb` are signed as the EMPTY STRING when\n * the mint had no value for them — EIP-712 has no optional members, so\n * \"absent\" has to be a signed value rather than a missing field. The guard\n * (`isSignedBindingConsistent`) then requires the unsigned envelope copy to\n * be absent too, which is what stops anyone ADDING a binding to a token that\n * was signed without one.\n *\n * `nonce` is always a non-empty random value on a v3 token, and is the\n * single v2/v3 discriminator (see `hasSignedNonce`).\n *\n * All four are OPTIONAL on the wire type only for backwards-compat: v1/v2\n * tokens carry none of them and verify against their own type lists.\n */\n agentId?: string\n resourceUrl?: string\n httpVerb?: string\n nonce?: string\n }\n}\n\n/**\n * Payload specific to the nvm:card-delegation scheme\n */\nexport type X402CardDelegationPayload = {\n token: string\n}\n\n/**\n * x402 Access Token aligned with x402 v2 spec\n *\n * Key changes from previous version:\n * - `subscriberAddress` moved to `payload.authorization.from`\n * - `planId` is now in `accepted` (optional — required for erc4337, optional for card-delegation)\n * - `agentId` moved to `accepted.extra.agentId` (optional)\n * - `scheme` and `network` moved to `accepted`\n * - Added `resource` and `extensions` for x402 v2 alignment\n */\nexport type AgentX402AccessToken = {\n x402Version: 2\n resource?: X402Resource\n accepted: X402Accepted\n payload: X402Erc4337Payload | X402CardDelegationPayload\n validUntil?: number\n extensions: Record<string, unknown>\n}\n\n/**\n * EIP-712 domain name for the x402 protocol — the original, unchanged value.\n */\nexport const NEVERMINED_EIP712_DOMAIN_NAME = 'Nevermined' as const\n\n/**\n * EIP-712 domain name for the MPP protocol (#2640).\n *\n * This constant IS the isolation between the two protocols. EIP-712 hashes the domain separator\n * into the signed digest, so a token minted under `Nevermined-MPP` cannot verify against the x402\n * verifier and vice versa — cross-protocol replay and downgrade are closed by construction.\n *\n * A plaintext `scheme`/`protocol` field would NOT do this for the SECURITY boundary:\n * `accepted.scheme` and `accepted.network` sit OUTSIDE the signature (see {@link X402Accepted}), so\n * they are forgeable. Only the domain is covered by the signature without touching the signed\n * struct.\n *\n * That objection does NOT extend to protocol ROUTING. The two envelopes are byte-identical apart\n * from the signature — `accepted.scheme` is `nvm:erc4337` for both — so a dispatcher holding a\n * decoded token has no signal for which verifier to call. If #2641 needs one, add an UNSIGNED hint\n * (`accepted.extra.protocol`) and document it as **routing only — NEVER the security boundary**:\n * forging it can only select the wrong verifier, and the wrong verifier returns `false`.\n * What is forbidden is trying both verifiers in turn — that collapses this isolation to zero.\n *\n * The signed `Authorization` struct is deliberately IDENTICAL for both protocols — do not add a\n * protocol field to it. Changing the struct would fork the type list and break the v1/v2\n * back-compat that `verifyAgentX402AccessToken` depends on, for no security gain.\n */\nexport const NEVERMINED_MPP_EIP712_DOMAIN_NAME = 'Nevermined-MPP' as const\n\n/** The EIP-712 domain name of a Nevermined access token — one per protocol. */\nexport type NeverminedEip712DomainName =\n | typeof NEVERMINED_EIP712_DOMAIN_NAME\n | typeof NEVERMINED_MPP_EIP712_DOMAIN_NAME\n\n/**\n * Defaults to the x402 domain name so the bare `NeverminedTypedDataDomain` keeps resolving to\n * `name: 'Nevermined'` for existing SDK consumers — widening it to the union would be a\n * source-breaking change to a published package (a `const n: 'Nevermined' = domain.name` stops\n * compiling), and `projectsRelationship: \"fixed\"` makes a core-kit major a workspace-wide major.\n */\nexport type NeverminedTypedDataDomain<\n TName extends NeverminedEip712DomainName = typeof NEVERMINED_EIP712_DOMAIN_NAME,\n> = {\n name: TName\n version: '1'\n chainId: number\n verifyingContract: `0x${string}`\n}\n\n/**\n * EIP-712 `Authorization` struct WITH a signed `planId` (#1678).\n *\n * `planId` is appended LAST so the change is purely additive to the EIP-712\n * encoding of the legacy (v1) struct. Signing the planId makes the token's plan\n * tamper-evident on its own — defense-in-depth atop the resource server's\n * `validateAcceptedMatchesRequirements` demand-match, which remains the primary\n * gate. See `NeverminedTypedDataTypesV1` for the legacy struct that pre-#1678\n * tokens were signed over; both shapes verify against the same signer.\n */\nexport type NeverminedTypedData = {\n SessionKey: [{ name: 'id'; type: 'string' }, { name: 'data'; type: 'string' }]\n SessionKeys: [{ name: 'sessionKeys'; type: 'SessionKey[]' }]\n Authorization: [\n { name: 'from'; type: 'address' },\n { name: 'sessionKeysProvider'; type: 'string' },\n { name: 'sessionKeys'; type: 'SessionKey[]' },\n { name: 'planId'; type: 'string' },\n ]\n}\n\nexport const NeverminedTypedDataTypes: NeverminedTypedData = {\n SessionKey: [\n { name: 'id', type: 'string' },\n { name: 'data', type: 'string' },\n ],\n SessionKeys: [{ name: 'sessionKeys', type: 'SessionKey[]' }],\n Authorization: [\n { name: 'from', type: 'address' },\n { name: 'sessionKeysProvider', type: 'string' },\n { name: 'sessionKeys', type: 'SessionKey[]' },\n { name: 'planId', type: 'string' },\n ],\n}\n\n/**\n * Legacy (v1) EIP-712 `Authorization` struct — WITHOUT a signed `planId`.\n *\n * Retained so `verifyAgentX402AccessToken` can still validate genuinely-old\n * tokens minted before #1678 (their `authorization` carries no `planId`, and\n * EIP-712 hashes the type set, so they only verify against THIS type list).\n * Mint always produces the v2 (with-planId) shape going forward.\n */\nexport type NeverminedTypedDataV1 = {\n SessionKey: [{ name: 'id'; type: 'string' }, { name: 'data'; type: 'string' }]\n SessionKeys: [{ name: 'sessionKeys'; type: 'SessionKey[]' }]\n Authorization: [\n { name: 'from'; type: 'address' },\n { name: 'sessionKeysProvider'; type: 'string' },\n { name: 'sessionKeys'; type: 'SessionKey[]' },\n ]\n}\n\nexport const NeverminedTypedDataTypesV1: NeverminedTypedDataV1 = {\n SessionKey: [\n { name: 'id', type: 'string' },\n { name: 'data', type: 'string' },\n ],\n SessionKeys: [{ name: 'sessionKeys', type: 'SessionKey[]' }],\n Authorization: [\n { name: 'from', type: 'address' },\n { name: 'sessionKeysProvider', type: 'string' },\n { name: 'sessionKeys', type: 'SessionKey[]' },\n ],\n}\n\n/**\n * EIP-712 `Authorization` struct v3 (#2646) — WITH the seller/resource binding and\n * a one-time `nonce`, appended after `planId`.\n *\n * Append-only, exactly as #1678 appended `planId` to the v1 struct: the four new\n * members come LAST, so the change is purely additive to the v2 encoding and the\n * older type lists keep verifying the tokens that were signed over them.\n *\n * What each member buys:\n * - `agentId` + `resourceUrl` + `httpVerb` remove the token's CROSS-SELLER\n * property. Before v3 these lived only in `accepted.extra` / `resource`, which\n * sit outside the signature and are demand-matched against a `paymentRequired`\n * the SELLER supplies — so seller A could present a token minted for seller B\n * on the same plan and nothing in the signature contradicted it.\n * - `nonce` removes the BEARER property: a v3 token is single-use at the\n * signature layer, enforced by the replay store in `apps/api` at settle time,\n * independently of any burn-level dedupe.\n *\n * A member the mint had no value for is signed as `''` — EIP-712 has no optional\n * struct members, so absence must itself be a signed value. `isSignedBindingConsistent`\n * pairs that with \"the unsigned copy must be absent too\", which is what prevents\n * ADDING a binding to a token signed without one.\n */\nexport type NeverminedTypedDataV3 = {\n SessionKey: [{ name: 'id'; type: 'string' }, { name: 'data'; type: 'string' }]\n SessionKeys: [{ name: 'sessionKeys'; type: 'SessionKey[]' }]\n Authorization: [\n { name: 'from'; type: 'address' },\n { name: 'sessionKeysProvider'; type: 'string' },\n { name: 'sessionKeys'; type: 'SessionKey[]' },\n { name: 'planId'; type: 'string' },\n { name: 'agentId'; type: 'string' },\n { name: 'resourceUrl'; type: 'string' },\n { name: 'httpVerb'; type: 'string' },\n { name: 'nonce'; type: 'string' },\n ]\n}\n\nexport const NeverminedTypedDataTypesV3: NeverminedTypedDataV3 = {\n SessionKey: [\n { name: 'id', type: 'string' },\n { name: 'data', type: 'string' },\n ],\n SessionKeys: [{ name: 'sessionKeys', type: 'SessionKey[]' }],\n Authorization: [\n { name: 'from', type: 'address' },\n { name: 'sessionKeysProvider', type: 'string' },\n { name: 'sessionKeys', type: 'SessionKey[]' },\n { name: 'planId', type: 'string' },\n { name: 'agentId', type: 'string' },\n { name: 'resourceUrl', type: 'string' },\n { name: 'httpVerb', type: 'string' },\n { name: 'nonce', type: 'string' },\n ],\n}\n\n/**\n * Legacy (v1) signed message shape — no `planId`. Mirrors `NeverminedTypedDataTypesV1`.\n * Only genuinely-old tokens carry this shape; mint never produces it.\n */\nexport type NeverminedTypedDataMessageV1 = {\n from: `0x${string}`\n sessionKeysProvider: string\n sessionKeys: { id: string; data: string }[]\n}\n\n/**\n * The v2 signed message shape — `planId` is REQUIRED. Mint always produces this\n * shape (#1678), so `planId` is non-optional here; the v1 variant above models\n * the legacy no-planId message. Mirrors `NeverminedTypedDataTypes`.\n */\nexport type NeverminedTypedDataMessage = NeverminedTypedDataMessageV1 & {\n /** Signed planId (#1678) — always present on v2 (minted) tokens. */\n planId: string\n}\n\n/**\n * The v3 signed message shape (#2646) — every binding member is REQUIRED, with\n * `''` standing for \"the mint had no value for this\". Mirrors\n * {@link NeverminedTypedDataTypesV3}.\n */\nexport type NeverminedTypedDataMessageV3 = NeverminedTypedDataMessage & {\n /** Signed agentId, or `''` when the token is not bound to an agent. */\n agentId: string\n /** Signed `resource.url`, or `''` when the token is not bound to a resource. */\n resourceUrl: string\n /** Signed HTTP verb, or `''` when the token is not bound to a verb. */\n httpVerb: string\n /** One-time nonce — always a non-empty random value on a v3 token. */\n nonce: string\n}\n\n/**\n * The EIP-712 struct version a token is signed over.\n *\n * `1` is not mintable — it only exists as a verification branch for tokens minted\n * before #1678. New tokens are v2 (the default, reusable, pre-#2646 semantics) or\n * v3 (seller/resource-bound and single-use).\n */\nexport type NeverminedTokenVersion = 2 | 3\n\n/**\n * Options for generating an x402 access token\n */\nexport type GenerateX402TokenOptions = {\n subscriber: SmartAccount\n orderSessionKey: string\n redeemSessionKey: string\n publicClient: PublicClient\n planId: string\n /** Scheme version (e.g., '1') - stored in accepted.extra.version */\n schemeVersion?: string\n agentId?: string\n resource?: X402Resource\n httpVerb?: string\n /** Token expiration timestamp in seconds (Unix epoch) */\n validUntil?: number\n /**\n * EIP-712 struct version to sign (#2646). Defaults to `2` — the reusable,\n * pre-#2646 shape — so existing callers are untouched and there is no flag day.\n *\n * `3` binds the token to `agentId` / `resource.url` / `httpVerb` and adds a\n * one-time nonce, which the resource server consumes at settle. A v3 token is\n * therefore SINGLE-USE: a caller that opts in must mint one per paid request.\n *\n * Applies identically to BOTH protocols, and MPP does not pin v3 even though it has no\n * pre-#2646 tokens in the wild to stay compatible with. The reason is GRANULARITY, not\n * retry safety (that is #2667's ledger, a separate layer): one MPP access token is\n * reused across MANY challenges, which are genuinely distinct operations rather than\n * retries of one. A per-token nonce would therefore kill every MPP buyer's second\n * challenge. So MPP crosses to v3 on the same schedule as x402 — when the SDKs mint\n * one token per paid request.\n */\n tokenVersion?: NeverminedTokenVersion\n}\n\n/**\n * The domain's `chainId` is a load-bearing part of the separator, and viem drops an `undefined`\n * one from the `EIP712Domain` type list — hashing IDENTICALLY to omitting the field, with no error.\n * A chainless `publicClient` (`createPublicClient({ transport })` with no `chain`, a shape that\n * already exists in this repo) would therefore mint a digest valid on every chain at once. Fail\n * loudly instead: this is a client misconfiguration, never a token defect.\n */\nconst requireChainId = (publicClient: PublicClient, caller: string): number => {\n const chainId = publicClient.chain?.id\n if (typeof chainId !== 'number') {\n throw new Error(`${caller}: publicClient has no chain — the EIP-712 domain needs a chainId`)\n }\n return chainId\n}\n\n/**\n * 32 bytes of CSPRNG output, hex-encoded — the one-time nonce of a v3 token (#2646).\n *\n * `globalThis.crypto` (WebCrypto) rather than node's `crypto` module so core-kit keeps\n * working in the browser, where this package is also consumed. 256 bits makes a\n * collision in the resource server's replay store (a UNIQUE column) unreachable, so a\n * legitimate mint can never be refused as someone else's replay.\n */\nconst generateTokenNonce = (): string => {\n const bytes = new Uint8Array(32)\n globalThis.crypto.getRandomValues(bytes)\n return `0x${Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('')}`\n}\n\n/**\n * Shared minter for both protocols. The ONLY difference between an x402 token and an MPP token is\n * `domainName` — same signed struct, same envelope, same session keys. Keeping one implementation\n * is deliberate: two copies would drift, and a drifted MPP minter that happened to reproduce the\n * x402 digest would silently reopen the cross-protocol replay this domain split exists to close.\n *\n * Not exported — callers go through {@link generateAgentX402AccessToken} or\n * {@link generateAgentMppAccessToken}, each with its domain pinned, so a protocol can never be\n * selected by a caller-supplied value.\n */\nconst generateAgentAccessToken = async (\n options: GenerateX402TokenOptions,\n domainName: NeverminedEip712DomainName,\n): Promise<AgentX402AccessToken> => {\n const {\n subscriber,\n orderSessionKey,\n redeemSessionKey,\n publicClient,\n planId,\n schemeVersion = '1',\n agentId,\n resource,\n httpVerb,\n validUntil,\n } = options\n const tokenVersion: NeverminedTokenVersion = options.tokenVersion ?? 2\n\n // A v2 token MUST carry a non-empty signed planId (#1678) — an empty/blank\n // planId would otherwise be signed into `authorization.planId` and could be\n // misread as a \"present but empty\" signed claim, blurring the v1/v2 boundary\n // that verification keys off genuine presence. erc4337 already requires a\n // planId upstream (`validateX402TokenStructure`), so this is a hard invariant.\n if (typeof planId !== 'string' || planId.trim() === '') {\n throw new Error('Nevermined access token: planId is required and must be a non-empty string')\n }\n\n const chainId = requireChainId(publicClient, 'Nevermined access token mint')\n\n // hash the session keys\n const redeemSessionKeyHash = keccak256(toBytes(redeemSessionKey))\n const orderSessionKeyHash = keccak256(toBytes(orderSessionKey))\n\n // generate the message with `from` address and the SIGNED planId (#1678).\n // Including `planId` here puts it inside the EIP-712 digest, so it cannot be\n // altered without invalidating the signature.\n const messageV2: NeverminedTypedDataMessage = {\n from: subscriber.address,\n sessionKeysProvider: 'zerodev',\n sessionKeys: [\n {\n id: 'order',\n data: orderSessionKeyHash,\n },\n {\n id: 'redeem',\n data: redeemSessionKeyHash,\n },\n ],\n planId,\n }\n\n // v3 (#2646) appends the seller/resource binding and the one-time nonce. A member\n // the caller left undefined is signed as '' — EIP-712 has no optional members, so\n // \"not bound\" must itself be a signed value; `isSignedBindingConsistent` then holds\n // the unsigned envelope copy to the same absence.\n const message: NeverminedTypedDataMessage | NeverminedTypedDataMessageV3 =\n tokenVersion === 3\n ? {\n ...messageV2,\n agentId: agentId ?? '',\n resourceUrl: resource?.url ?? '',\n httpVerb: httpVerb ?? '',\n nonce: generateTokenNonce(),\n }\n : messageV2\n\n const domain: NeverminedTypedDataDomain<NeverminedEip712DomainName> = {\n name: domainName,\n version: '1',\n chainId,\n verifyingContract: subscriber.address,\n }\n\n // generate the eip712 data. EIP-712 hashes the TYPE SET, so the type list and the\n // message shape must be chosen together — a v3 message signed over the v2 list\n // would produce a digest no verifier reproduces.\n const eip712Data = {\n domain,\n\n types: tokenVersion === 3 ? NeverminedTypedDataTypesV3 : NeverminedTypedDataTypes,\n primaryType: 'Authorization',\n message,\n } as TypedDataDefinition<NeverminedTypedData | NeverminedTypedDataV3, 'Authorization'>\n\n const signature = await subscriber.signTypedData(eip712Data)\n\n // Network in CAIP-2 format (eip155:chainId) — always resolvable, `requireChainId` already ran.\n const network = `eip155:${chainId}`\n\n return {\n x402Version: 2,\n ...(resource && { resource }),\n accepted: {\n scheme: 'nvm:erc4337',\n network,\n planId,\n extra: {\n version: schemeVersion,\n ...(agentId && { agentId }),\n ...(httpVerb && { httpVerb }),\n },\n },\n payload: {\n signature,\n authorization: message,\n },\n ...(validUntil !== undefined && { validUntil }),\n extensions: {},\n }\n}\n\n/**\n * Generate an x402 access token aligned with x402 v2 spec.\n *\n * @param options - Token generation options\n * @returns x402 access token with EIP-712 signature\n */\nexport const generateAgentX402AccessToken = (\n options: GenerateX402TokenOptions,\n): Promise<AgentX402AccessToken> => generateAgentAccessToken(options, NEVERMINED_EIP712_DOMAIN_NAME)\n\n/**\n * Generate a NATIVE MPP access token (#2640) — the same token shape signed under the\n * `Nevermined-MPP` EIP-712 domain.\n *\n * Native, not a wrapper: the revised Tier A design (#2024) deliberately does NOT embed an x402\n * token inside an MPP credential, because unwrapping it would hand the holder a token that still\n * settles on the public x402 path.\n *\n * @param options - Token generation options (identical to the x402 minter)\n * @returns MPP access token with EIP-712 signature over the MPP domain. The envelope is IDENTICAL\n * to an x402 token's, `accepted.scheme: 'nvm:erc4337'` included — only the signature differs, so\n * the protocol is fixed by the endpoint, not readable off the wire. See\n * {@link NEVERMINED_MPP_EIP712_DOMAIN_NAME} on routing vs. the security boundary.\n */\nexport const generateAgentMppAccessToken = (\n options: GenerateX402TokenOptions,\n): Promise<AgentX402AccessToken> =>\n generateAgentAccessToken(options, NEVERMINED_MPP_EIP712_DOMAIN_NAME)\n\n/**\n * True when `authorization` carries a GENUINE signed planId (#1678) — a present,\n * non-empty string. This is the single v1/v2 discriminator: verification keys off\n * it to choose the matching EIP-712 type list, the handler keys off it to decide\n * whether the signed-vs-unsigned consistency guard fires. Keying off genuine\n * presence (not just `'planId' in authorization`) ensures a `planId: ''` / nullish\n * value can never be mistaken for a real signed claim.\n */\nexport const hasSignedPlanId = (\n authorization: X402Erc4337Payload['authorization'],\n): authorization is X402Erc4337Payload['authorization'] & { planId: string } => {\n return typeof authorization?.planId === 'string' && authorization.planId.trim() !== ''\n}\n\n/**\n * The signed-vs-unsigned planId consistency invariant (#1678), co-located with\n * `hasSignedPlanId` so signer, verifier, and the API handler share ONE\n * definition (no drift between where the planId is signed and where the guard\n * enforces it).\n *\n * For a v2 token (signed planId present) the tamperable unsigned `accepted.planId`\n * MUST exactly equal the signed `authorization.planId` — anything else is a\n * post-signing edit of the outer JSON. Returns `true` for v1 tokens (no signed\n * planId): there is no signed value to be consistent with, so the guard does not\n * apply (the caller handles the legacy fallback separately). The API handler maps\n * a `false` result to its forgery-gated rejection.\n */\nexport const isSignedPlanIdConsistent = (\n authorization: X402Erc4337Payload['authorization'],\n acceptedPlanId: string | null | undefined,\n): boolean => {\n if (!hasSignedPlanId(authorization)) return true\n return acceptedPlanId === authorization.planId\n}\n\n/**\n * True when `authorization` carries a GENUINE one-time nonce (#2646) — a present,\n * non-empty string. This is the single v2/v3 discriminator, the exact counterpart of\n * {@link hasSignedPlanId} for the v1/v2 boundary: verification keys off it to choose\n * the matching EIP-712 type list, and the resource server keys off it to decide\n * whether the binding guard and the single-use replay store apply.\n *\n * `nonce` is the discriminator rather than one of the binding members because it is\n * the only v3 member that is always non-empty — `agentId` / `resourceUrl` / `httpVerb`\n * are legitimately signed as `''` when the mint had no value for them.\n */\nexport const hasSignedNonce = (\n authorization: X402Erc4337Payload['authorization'],\n): authorization is X402Erc4337Payload['authorization'] & { nonce: string } => {\n return typeof authorization?.nonce === 'string' && authorization.nonce.trim() !== ''\n}\n\n/** The unsigned envelope values a v3 binding is checked against. */\nexport type SignedBindingEnvelope = {\n /** `accepted.extra.agentId` */\n agentId: string | null | undefined\n /** `resource.url` */\n resourceUrl: string | null | undefined\n /** `accepted.extra.httpVerb` */\n httpVerb: string | null | undefined\n}\n\n/**\n * The signed-vs-unsigned BINDING invariant (#2646), co-located with\n * {@link hasSignedNonce} so signer, verifier and the API handler share ONE definition —\n * the same reason {@link isSignedPlanIdConsistent} lives here.\n *\n * For a v3 token every binding member must agree with its unsigned envelope copy:\n * - a signed non-empty value must equal the envelope value EXACTLY. Both are our own\n * mint's output, so exact equality is right; the fuzzier, query-insensitive URL\n * comparison belongs to the demand-match against the SELLER-supplied\n * `paymentRequired`, which is a different question.\n * - a signed `''` means \"not bound\", so the envelope copy must be absent (or empty)\n * too. This is the half that stops someone ADDING an `agentId` to a token that was\n * signed without one — without it, `''` would be a hole rather than a claim.\n *\n * Returns `true` for v1/v2 tokens: there is no signed binding to be consistent with,\n * and the caller handles those on the legacy path. The API handler maps a `false`\n * result to its forgery-gated rejection, exactly as it does for the planId guard.\n */\nexport const isSignedBindingConsistent = (\n authorization: X402Erc4337Payload['authorization'],\n envelope: SignedBindingEnvelope,\n): boolean => {\n if (!hasSignedNonce(authorization)) return true\n\n const matches = (signed: string | undefined, unsigned: string | null | undefined): boolean => {\n const signedValue = signed ?? ''\n // Absence and '' are the same claim on both sides: the mint omits an empty member\n // from the envelope entirely, so an omitted field is what a signed '' looks like.\n if (signedValue === '') return unsigned === undefined || unsigned === null || unsigned === ''\n return unsigned === signedValue\n }\n\n return (\n matches(authorization.agentId, envelope.agentId) &&\n matches(authorization.resourceUrl, envelope.resourceUrl) &&\n matches(authorization.httpVerb, envelope.httpVerb)\n )\n}\n\n/**\n * Shared verifier for both protocols, differing only in the EIP-712 domain name.\n *\n * EIP-712 hashes the type set, so a token signed over one struct cannot verify against\n * another's type list. We therefore detect which shape the token carries — genuine\n * signed nonce → v3 (#2646), else genuine signed planId → v2 (#1678), else v1 — and\n * verify against the matching list. This keeps genuinely-old v1 tokens valid while\n * newer tokens are checked over everything they signed.\n *\n * Downgrade-safe, and the property extends to v3 unchanged: a v2 token was signed over\n * the with-planId digest, so stripping `authorization.planId` to force the v1 branch\n * changes the digest and the signature fails; a v3 token was signed over the with-nonce\n * digest, so stripping the nonce to escape single-use, or stripping `agentId` /\n * `resourceUrl` to escape the seller binding, fails the same way. An attacker cannot\n * demote a signed token to shed a claim.\n *\n * EIP-712 hashes the DOMAIN separator too, which is what makes `domainName` the protocol\n * boundary (#2640) rather than a label. Not exported: callers go through\n * {@link verifyAgentX402AccessToken} or {@link verifyAgentMppAccessToken}, each with its domain\n * and its `allowLegacyV1` stance pinned, so no caller-supplied value can pick which protocol a\n * token is checked against.\n */\nconst verifyAgentAccessToken = async (\n accessToken: AgentX402AccessToken,\n publicClient: PublicClient,\n domainName: NeverminedEip712DomainName,\n allowLegacyV1: boolean,\n): Promise<boolean> => {\n // `payload` is a union (erc4337 | card-delegation) and this is a public entry point with no\n // guaranteed pre-validation — `validateX402TokenStructure` runs in `apps/api`, not here. A\n // structural precondition, evaluated BEFORE the await: deliberately NOT a try/catch around the\n // verification itself, which must keep letting an RPC fault propagate rather than laundering it\n // into a `false` the caller reads as a forgery.\n const erc4337Payload = accessToken?.payload as X402Erc4337Payload | undefined\n if (!erc4337Payload?.authorization?.from || !erc4337Payload.signature) return false\n\n const { signature, authorization } = erc4337Payload\n const subscriberAddress = authorization.from\n\n // A v1 (unsigned-planId) token leaves `isSignedPlanIdConsistent` inert, so the tamperable\n // `accepted.planId` becomes authoritative. That is a deliberate #1678 back-compat concession on\n // x402 — a protocol with no legacy tokens must not inherit it.\n if (!allowLegacyV1 && !hasSignedPlanId(authorization)) return false\n\n const domain: NeverminedTypedDataDomain<NeverminedEip712DomainName> = {\n name: domainName,\n version: '1',\n chainId: requireChainId(publicClient, 'Nevermined access token verify'),\n verifyingContract: subscriberAddress,\n }\n const isValid = await publicClient.verifyTypedData({\n address: subscriberAddress,\n signature,\n // One branch per struct version, selected by GENUINE presence and never by a\n // caller-supplied hint: a nonce (#2646) → v3, else a planId (#1678) → v2, else the\n // original v1 list. The branches are mutually exclusive and no branch falls back to\n // another — trying a second type list on failure would erase exactly the downgrade\n // protection the versioning provides.\n types: hasSignedNonce(authorization)\n ? NeverminedTypedDataTypesV3\n : hasSignedPlanId(authorization)\n ? NeverminedTypedDataTypes\n : NeverminedTypedDataTypesV1,\n domain,\n message: authorization,\n primaryType: 'Authorization',\n })\n return isValid\n}\n\n/**\n * Verify an x402 access token signature (see {@link verifyAgentAccessToken} for the v1/v2 type-list\n * detection and its downgrade-safety).\n *\n * Rejects an MPP token: it was signed under a different domain separator, so the digest differs.\n *\n * @param accessToken - The x402 access token to verify\n * @param publicClient - Viem public client\n * @returns true if signature is valid\n */\nexport const verifyAgentX402AccessToken = (\n accessToken: AgentX402AccessToken,\n publicClient: PublicClient,\n): Promise<boolean> =>\n verifyAgentAccessToken(accessToken, publicClient, NEVERMINED_EIP712_DOMAIN_NAME, true)\n\n/**\n * Verify an MPP access token signature (#2640).\n *\n * Rejects an x402 token for the same reason the x402 verifier rejects an MPP one — the domain\n * separator is part of the signed digest. This is the property the whole Tier A isolation rests on:\n * a credential harvested from one protocol is inert on the other, closing cross-protocol replay\n * AND downgrade — WHILE the signing key stays server-side. A subscriber holding it can mint under\n * whichever domain they like, so the split constrains the transport, not that holder.\n *\n * v2-only: unlike the x402 verifier, this rejects a legacy (unsigned-planId) authorization\n * outright. MPP is born with #2640 and has no pre-#1678 tokens to be compatible with.\n *\n * @param accessToken - The MPP access token to verify\n * @param publicClient - Viem public client\n * @returns true if signature is valid\n */\nexport const verifyAgentMppAccessToken = (\n accessToken: AgentX402AccessToken,\n publicClient: PublicClient,\n): Promise<boolean> =>\n verifyAgentAccessToken(accessToken, publicClient, NEVERMINED_MPP_EIP712_DOMAIN_NAME, false)\n\n/**\n * Validation result for x402 token structure\n */\nexport type X402TokenValidationResult = {\n valid: boolean\n errors: string[]\n}\n\n/**\n * Validate the structure of an x402 access token\n *\n * @param token - The token to validate (unknown type for safety)\n * @returns Validation result with errors if any\n */\nconst SUPPORTED_SCHEMES: X402Scheme[] = ['nvm:erc4337', 'nvm:card-delegation']\n\nexport const validateX402TokenStructure = (token: unknown): X402TokenValidationResult => {\n const errors: string[] = []\n\n if (!token || typeof token !== 'object') {\n return { valid: false, errors: ['Token must be an object'] }\n }\n\n const t = token as Record<string, unknown>\n\n // Validate x402Version\n if (t.x402Version !== 2) {\n errors.push('x402Version must be 2')\n }\n\n // Validate accepted object\n if (!t.accepted || typeof t.accepted !== 'object') {\n errors.push('accepted is required')\n } else {\n const accepted = t.accepted as Record<string, unknown>\n const scheme = accepted.scheme as string\n\n if (!SUPPORTED_SCHEMES.includes(scheme as X402Scheme)) {\n errors.push(`scheme must be one of: ${SUPPORTED_SCHEMES.join(', ')}`)\n }\n\n if (!accepted.network || typeof accepted.network !== 'string') {\n errors.push('network is required in accepted')\n }\n\n // Validate extra\n if (!accepted.extra || typeof accepted.extra !== 'object') {\n errors.push('extra is required in accepted')\n } else {\n const extra = accepted.extra as Record<string, unknown>\n if (!extra.version || typeof extra.version !== 'string') {\n errors.push('version is required in accepted.extra')\n }\n }\n\n // Scheme-specific accepted validation\n if (scheme === 'nvm:erc4337') {\n if (!accepted.planId || typeof accepted.planId !== 'string') {\n errors.push('planId is required in accepted for nvm:erc4337')\n }\n }\n }\n\n // Validate payload\n if (!t.payload || typeof t.payload !== 'object') {\n errors.push('payload is required')\n } else {\n const accepted = t.accepted as Record<string, unknown> | undefined\n const scheme = accepted?.scheme as string | undefined\n const payload = t.payload as Record<string, unknown>\n\n if (scheme === 'nvm:erc4337') {\n if (!payload.signature || typeof payload.signature !== 'string') {\n errors.push('signature is required in payload')\n }\n\n if (!payload.authorization || typeof payload.authorization !== 'object') {\n errors.push('authorization is required in payload')\n } else {\n const auth = payload.authorization as Record<string, unknown>\n\n if (!auth.from || typeof auth.from !== 'string') {\n errors.push('from address is required in payload.authorization')\n } else if (!auth.from.startsWith('0x')) {\n errors.push('from must be a valid hex address')\n }\n\n if (!auth.sessionKeysProvider || typeof auth.sessionKeysProvider !== 'string') {\n errors.push('sessionKeysProvider is required in payload.authorization')\n }\n\n if (!Array.isArray(auth.sessionKeys)) {\n errors.push('sessionKeys array is required in payload.authorization')\n }\n }\n } else if (scheme === 'nvm:card-delegation') {\n if (!payload.token || typeof payload.token !== 'string') {\n errors.push('token is required in payload for nvm:card-delegation')\n }\n }\n }\n\n // Validate extensions (must be present, even if empty)\n if (t.extensions === undefined) {\n errors.push('extensions is required (can be empty object)')\n }\n\n return { valid: errors.length === 0, errors }\n}\n\nexport const b64EncodeX402AccessToken = (accessToken: AgentX402AccessToken): string => {\n return Buffer.from(JSON.stringify(accessToken)).toString('base64')\n}\n\nexport const b64DecodeX402AccessToken = (encoded: string): AgentX402AccessToken => {\n return JSON.parse(Buffer.from(encoded, 'base64').toString('utf-8'))\n}\n"],"names":["keccak256","toBytes","NEVERMINED_EIP712_DOMAIN_NAME","NEVERMINED_MPP_EIP712_DOMAIN_NAME","NeverminedTypedDataTypes","SessionKey","name","type","SessionKeys","Authorization","NeverminedTypedDataTypesV1","NeverminedTypedDataTypesV3","requireChainId","publicClient","caller","chainId","chain","id","Error","generateTokenNonce","bytes","Uint8Array","globalThis","crypto","getRandomValues","Array","from","b","toString","padStart","join","generateAgentAccessToken","options","domainName","subscriber","orderSessionKey","redeemSessionKey","planId","schemeVersion","agentId","resource","httpVerb","validUntil","tokenVersion","trim","redeemSessionKeyHash","orderSessionKeyHash","messageV2","address","sessionKeysProvider","sessionKeys","data","message","resourceUrl","url","nonce","domain","version","verifyingContract","eip712Data","types","primaryType","signature","signTypedData","network","x402Version","accepted","scheme","extra","payload","authorization","undefined","extensions","generateAgentX402AccessToken","generateAgentMppAccessToken","hasSignedPlanId","isSignedPlanIdConsistent","acceptedPlanId","hasSignedNonce","isSignedBindingConsistent","envelope","matches","signed","unsigned","signedValue","verifyAgentAccessToken","accessToken","allowLegacyV1","erc4337Payload","subscriberAddress","isValid","verifyTypedData","verifyAgentX402AccessToken","verifyAgentMppAccessToken","SUPPORTED_SCHEMES","validateX402TokenStructure","token","errors","valid","t","push","includes","auth","startsWith","isArray","length","b64EncodeX402AccessToken","Buffer","JSON","stringify","b64DecodeX402AccessToken","encoded","parse"],"mappings":"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"}
|