@nevermined-io/core-kit 0.1.21 → 0.1.22
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.
|
@@ -1,14 +1,44 @@
|
|
|
1
1
|
import { PublicClient } from 'viem';
|
|
2
2
|
import { SmartAccount } from 'viem/account-abstraction';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
/**
|
|
4
|
+
* x402 Resource information
|
|
5
|
+
*/
|
|
6
|
+
export type X402Resource = {
|
|
7
|
+
url: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
mimeType?: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* x402 Accepted scheme - nvm:erc4337 specific fields
|
|
13
|
+
*/
|
|
14
|
+
export type X402Accepted = {
|
|
15
|
+
scheme: 'nvm:erc4337';
|
|
6
16
|
network: string;
|
|
7
|
-
|
|
8
|
-
|
|
17
|
+
planId: string;
|
|
18
|
+
extra: {
|
|
19
|
+
version: string;
|
|
20
|
+
agentId?: string;
|
|
21
|
+
httpVerb?: string;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* x402 Access Token aligned with x402 v2 spec
|
|
26
|
+
*
|
|
27
|
+
* Key changes from previous version:
|
|
28
|
+
* - `subscriberAddress` moved to `payload.authorization.from`
|
|
29
|
+
* - `planId` is now in `accepted` (required)
|
|
30
|
+
* - `agentId` moved to `accepted.extra.agentId` (optional)
|
|
31
|
+
* - `scheme` and `network` moved to `accepted`
|
|
32
|
+
* - Added `resource` and `extensions` for x402 v2 alignment
|
|
33
|
+
*/
|
|
34
|
+
export type AgentX402AccessToken = {
|
|
35
|
+
x402Version: 2;
|
|
36
|
+
resource?: X402Resource;
|
|
37
|
+
accepted: X402Accepted;
|
|
9
38
|
payload: {
|
|
10
39
|
signature: `0x${string}`;
|
|
11
40
|
authorization: {
|
|
41
|
+
from: `0x${string}`;
|
|
12
42
|
sessionKeysProvider: string;
|
|
13
43
|
sessionKeys: {
|
|
14
44
|
id: string;
|
|
@@ -16,6 +46,7 @@ export type AgentX402AccessToken = {
|
|
|
16
46
|
}[];
|
|
17
47
|
};
|
|
18
48
|
};
|
|
49
|
+
extensions: Record<string, unknown>;
|
|
19
50
|
};
|
|
20
51
|
export type NeverminedTypedDataDomain = {
|
|
21
52
|
name: 'Nevermined';
|
|
@@ -36,6 +67,10 @@ export type NeverminedTypedData = {
|
|
|
36
67
|
type: 'SessionKey[]';
|
|
37
68
|
}];
|
|
38
69
|
Authorization: [
|
|
70
|
+
{
|
|
71
|
+
name: 'from';
|
|
72
|
+
type: 'address';
|
|
73
|
+
},
|
|
39
74
|
{
|
|
40
75
|
name: 'sessionKeysProvider';
|
|
41
76
|
type: 'string';
|
|
@@ -48,14 +83,57 @@ export type NeverminedTypedData = {
|
|
|
48
83
|
};
|
|
49
84
|
export declare const NeverminedTypedDataTypes: NeverminedTypedData;
|
|
50
85
|
export type NeverminedTypedDataMessage = {
|
|
86
|
+
from: `0x${string}`;
|
|
51
87
|
sessionKeysProvider: string;
|
|
52
88
|
sessionKeys: {
|
|
53
89
|
id: string;
|
|
54
90
|
data: string;
|
|
55
91
|
}[];
|
|
56
92
|
};
|
|
57
|
-
|
|
58
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Options for generating an x402 access token
|
|
95
|
+
*/
|
|
96
|
+
export type GenerateX402TokenOptions = {
|
|
97
|
+
subscriber: SmartAccount;
|
|
98
|
+
orderSessionKey: string;
|
|
99
|
+
redeemSessionKey: string;
|
|
100
|
+
publicClient: PublicClient;
|
|
101
|
+
planId: string;
|
|
102
|
+
/** Scheme version (e.g., '1') - stored in accepted.extra.version */
|
|
103
|
+
schemeVersion?: string;
|
|
104
|
+
agentId?: string;
|
|
105
|
+
resource?: X402Resource;
|
|
106
|
+
httpVerb?: string;
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Generate an x402 access token aligned with x402 v2 spec
|
|
110
|
+
*
|
|
111
|
+
* @param options - Token generation options
|
|
112
|
+
* @returns x402 access token with EIP-712 signature
|
|
113
|
+
*/
|
|
114
|
+
export declare const generateAgentX402AccessToken: (options: GenerateX402TokenOptions) => Promise<AgentX402AccessToken>;
|
|
115
|
+
/**
|
|
116
|
+
* Verify an x402 access token signature
|
|
117
|
+
*
|
|
118
|
+
* @param accessToken - The x402 access token to verify
|
|
119
|
+
* @param publicClient - Viem public client
|
|
120
|
+
* @returns true if signature is valid
|
|
121
|
+
*/
|
|
122
|
+
export declare const verifyAgentX402AccessToken: (accessToken: AgentX402AccessToken, publicClient: PublicClient) => Promise<boolean>;
|
|
123
|
+
/**
|
|
124
|
+
* Validation result for x402 token structure
|
|
125
|
+
*/
|
|
126
|
+
export type X402TokenValidationResult = {
|
|
127
|
+
valid: boolean;
|
|
128
|
+
errors: string[];
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* Validate the structure of an x402 access token
|
|
132
|
+
*
|
|
133
|
+
* @param token - The token to validate (unknown type for safety)
|
|
134
|
+
* @returns Validation result with errors if any
|
|
135
|
+
*/
|
|
136
|
+
export declare const validateX402TokenStructure: (token: unknown) => X402TokenValidationResult;
|
|
59
137
|
export declare const b64EncodeX402AccessToken: (accessToken: AgentX402AccessToken) => string;
|
|
60
138
|
export declare const b64DecodeX402AccessToken: (encoded: string) => AgentX402AccessToken;
|
|
61
139
|
//# sourceMappingURL=AgentX402AccessToken.d.ts.map
|
|
@@ -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,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"AgentX402AccessToken.d.ts","sourceRoot":"","sources":["../../src/models/AgentX402AccessToken.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,16 +6,25 @@ export const NeverminedTypedDataTypes = {
|
|
|
6
6
|
],
|
|
7
7
|
SessionKeys: [{ name: 'sessionKeys', type: 'SessionKey[]' }],
|
|
8
8
|
Authorization: [
|
|
9
|
+
{ name: 'from', type: 'address' },
|
|
9
10
|
{ name: 'sessionKeysProvider', type: 'string' },
|
|
10
11
|
{ name: 'sessionKeys', type: 'SessionKey[]' },
|
|
11
12
|
],
|
|
12
13
|
};
|
|
13
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Generate an x402 access token aligned with x402 v2 spec
|
|
16
|
+
*
|
|
17
|
+
* @param options - Token generation options
|
|
18
|
+
* @returns x402 access token with EIP-712 signature
|
|
19
|
+
*/
|
|
20
|
+
export const generateAgentX402AccessToken = async (options) => {
|
|
21
|
+
const { subscriber, orderSessionKey, redeemSessionKey, publicClient, planId, schemeVersion = '1', agentId, resource, httpVerb, } = options;
|
|
14
22
|
// hash the session keys
|
|
15
|
-
const
|
|
23
|
+
const redeemSessionKeyHash = keccak256(toBytes(redeemSessionKey));
|
|
16
24
|
const orderSessionKeyHash = keccak256(toBytes(orderSessionKey));
|
|
17
|
-
// generate the message
|
|
25
|
+
// generate the message with `from` address
|
|
18
26
|
const message = {
|
|
27
|
+
from: subscriber.address,
|
|
19
28
|
sessionKeysProvider: 'zerodev',
|
|
20
29
|
sessionKeys: [
|
|
21
30
|
{
|
|
@@ -24,7 +33,7 @@ export const generateAgentX402AccessToken = async (subscriber, orderSessionKey,
|
|
|
24
33
|
},
|
|
25
34
|
{
|
|
26
35
|
id: 'redeem',
|
|
27
|
-
data:
|
|
36
|
+
data: redeemSessionKeyHash,
|
|
28
37
|
},
|
|
29
38
|
],
|
|
30
39
|
};
|
|
@@ -42,19 +51,39 @@ export const generateAgentX402AccessToken = async (subscriber, orderSessionKey,
|
|
|
42
51
|
message,
|
|
43
52
|
};
|
|
44
53
|
const signature = await subscriber.signTypedData(eip712Data);
|
|
54
|
+
// Get network in CAIP-2 format (eip155:chainId)
|
|
55
|
+
const chainId = publicClient.chain?.id;
|
|
56
|
+
const network = chainId ? `eip155:${chainId}` : publicClient.chain?.name;
|
|
45
57
|
return {
|
|
46
58
|
x402Version: 2,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
59
|
+
...(resource && { resource }),
|
|
60
|
+
accepted: {
|
|
61
|
+
scheme: 'nvm:erc4337',
|
|
62
|
+
network,
|
|
63
|
+
planId,
|
|
64
|
+
extra: {
|
|
65
|
+
version: schemeVersion,
|
|
66
|
+
...(agentId && { agentId }),
|
|
67
|
+
...(httpVerb && { httpVerb }),
|
|
68
|
+
},
|
|
69
|
+
},
|
|
51
70
|
payload: {
|
|
52
71
|
signature,
|
|
53
72
|
authorization: message,
|
|
54
73
|
},
|
|
74
|
+
extensions: {},
|
|
55
75
|
};
|
|
56
76
|
};
|
|
57
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Verify an x402 access token signature
|
|
79
|
+
*
|
|
80
|
+
* @param accessToken - The x402 access token to verify
|
|
81
|
+
* @param publicClient - Viem public client
|
|
82
|
+
* @returns true if signature is valid
|
|
83
|
+
*/
|
|
84
|
+
export const verifyAgentX402AccessToken = async (accessToken, publicClient) => {
|
|
85
|
+
// Extract subscriber address from payload.authorization.from
|
|
86
|
+
const subscriberAddress = accessToken.payload.authorization.from;
|
|
58
87
|
const domain = {
|
|
59
88
|
name: 'Nevermined',
|
|
60
89
|
version: '1',
|
|
@@ -72,6 +101,82 @@ export const verifyAgentX402AccessToken = async (accessToken, publicClient, subs
|
|
|
72
101
|
});
|
|
73
102
|
return isValid;
|
|
74
103
|
};
|
|
104
|
+
/**
|
|
105
|
+
* Validate the structure of an x402 access token
|
|
106
|
+
*
|
|
107
|
+
* @param token - The token to validate (unknown type for safety)
|
|
108
|
+
* @returns Validation result with errors if any
|
|
109
|
+
*/
|
|
110
|
+
export const validateX402TokenStructure = (token) => {
|
|
111
|
+
const errors = [];
|
|
112
|
+
if (!token || typeof token !== 'object') {
|
|
113
|
+
return { valid: false, errors: ['Token must be an object'] };
|
|
114
|
+
}
|
|
115
|
+
const t = token;
|
|
116
|
+
// Validate x402Version
|
|
117
|
+
if (t.x402Version !== 2) {
|
|
118
|
+
errors.push('x402Version must be 2');
|
|
119
|
+
}
|
|
120
|
+
// Validate accepted object
|
|
121
|
+
if (!t.accepted || typeof t.accepted !== 'object') {
|
|
122
|
+
errors.push('accepted is required');
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
const accepted = t.accepted;
|
|
126
|
+
if (accepted.scheme !== 'nvm:erc4337') {
|
|
127
|
+
errors.push('scheme must be nvm:erc4337');
|
|
128
|
+
}
|
|
129
|
+
if (!accepted.network || typeof accepted.network !== 'string') {
|
|
130
|
+
errors.push('network is required in accepted');
|
|
131
|
+
}
|
|
132
|
+
if (!accepted.planId || typeof accepted.planId !== 'string') {
|
|
133
|
+
errors.push('planId is required in accepted');
|
|
134
|
+
}
|
|
135
|
+
// Validate extra
|
|
136
|
+
if (!accepted.extra || typeof accepted.extra !== 'object') {
|
|
137
|
+
errors.push('extra is required in accepted');
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
const extra = accepted.extra;
|
|
141
|
+
if (!extra.version || typeof extra.version !== 'string') {
|
|
142
|
+
errors.push('version is required in accepted.extra');
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Validate payload
|
|
147
|
+
if (!t.payload || typeof t.payload !== 'object') {
|
|
148
|
+
errors.push('payload is required');
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
const payload = t.payload;
|
|
152
|
+
if (!payload.signature || typeof payload.signature !== 'string') {
|
|
153
|
+
errors.push('signature is required in payload');
|
|
154
|
+
}
|
|
155
|
+
if (!payload.authorization || typeof payload.authorization !== 'object') {
|
|
156
|
+
errors.push('authorization is required in payload');
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
const auth = payload.authorization;
|
|
160
|
+
if (!auth.from || typeof auth.from !== 'string') {
|
|
161
|
+
errors.push('from address is required in payload.authorization');
|
|
162
|
+
}
|
|
163
|
+
else if (!auth.from.startsWith('0x')) {
|
|
164
|
+
errors.push('from must be a valid hex address');
|
|
165
|
+
}
|
|
166
|
+
if (!auth.sessionKeysProvider || typeof auth.sessionKeysProvider !== 'string') {
|
|
167
|
+
errors.push('sessionKeysProvider is required in payload.authorization');
|
|
168
|
+
}
|
|
169
|
+
if (!Array.isArray(auth.sessionKeys)) {
|
|
170
|
+
errors.push('sessionKeys array is required in payload.authorization');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Validate extensions (must be present, even if empty)
|
|
175
|
+
if (t.extensions === undefined) {
|
|
176
|
+
errors.push('extensions is required (can be empty object)');
|
|
177
|
+
}
|
|
178
|
+
return { valid: errors.length === 0, errors };
|
|
179
|
+
};
|
|
75
180
|
export const b64EncodeX402AccessToken = (accessToken) => {
|
|
76
181
|
return Buffer.from(JSON.stringify(accessToken)).toString('base64');
|
|
77
182
|
};
|