@x402/extensions 2.5.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/bazaar/index.d.ts +1 -1
- package/dist/cjs/{index-G8RNfr6X.d.ts → index-CtOzXcjN.d.ts} +2 -2
- package/dist/cjs/index.d.ts +33 -23
- package/dist/cjs/index.js +874 -10
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/offer-receipt/index.d.ts +702 -0
- package/dist/cjs/offer-receipt/index.js +909 -0
- package/dist/cjs/offer-receipt/index.js.map +1 -0
- package/dist/cjs/sign-in-with-x/index.js +1 -2
- package/dist/cjs/sign-in-with-x/index.js.map +1 -1
- package/dist/esm/bazaar/index.d.mts +1 -1
- package/dist/esm/{chunk-O34SGKEP.mjs → chunk-QVNCC7CH.mjs} +2 -3
- package/dist/esm/chunk-QVNCC7CH.mjs.map +1 -0
- package/dist/esm/chunk-TYR4QHVX.mjs +828 -0
- package/dist/esm/chunk-TYR4QHVX.mjs.map +1 -0
- package/dist/esm/{index-G8RNfr6X.d.mts → index-CtOzXcjN.d.mts} +2 -2
- package/dist/esm/index.d.mts +33 -23
- package/dist/esm/index.mjs +97 -9
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/offer-receipt/index.d.mts +702 -0
- package/dist/esm/offer-receipt/index.mjs +97 -0
- package/dist/esm/offer-receipt/index.mjs.map +1 -0
- package/dist/esm/sign-in-with-x/index.mjs +1 -1
- package/package.json +14 -2
- package/dist/esm/chunk-O34SGKEP.mjs.map +0 -1
|
@@ -0,0 +1,828 @@
|
|
|
1
|
+
// src/offer-receipt/types.ts
|
|
2
|
+
var OFFER_RECEIPT = "offer-receipt";
|
|
3
|
+
function isJWSSignedOffer(offer) {
|
|
4
|
+
return offer.format === "jws";
|
|
5
|
+
}
|
|
6
|
+
function isEIP712SignedOffer(offer) {
|
|
7
|
+
return offer.format === "eip712";
|
|
8
|
+
}
|
|
9
|
+
function isJWSSignedReceipt(receipt) {
|
|
10
|
+
return receipt.format === "jws";
|
|
11
|
+
}
|
|
12
|
+
function isEIP712SignedReceipt(receipt) {
|
|
13
|
+
return receipt.format === "eip712";
|
|
14
|
+
}
|
|
15
|
+
function isJWSSigner(signer) {
|
|
16
|
+
return signer.format === "jws";
|
|
17
|
+
}
|
|
18
|
+
function isEIP712Signer(signer) {
|
|
19
|
+
return signer.format === "eip712";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/offer-receipt/signing.ts
|
|
23
|
+
import * as jose2 from "jose";
|
|
24
|
+
import { hashTypedData, recoverTypedDataAddress } from "viem";
|
|
25
|
+
|
|
26
|
+
// src/offer-receipt/did.ts
|
|
27
|
+
import * as jose from "jose";
|
|
28
|
+
import { base58 } from "@scure/base";
|
|
29
|
+
import { secp256k1 } from "@noble/curves/secp256k1";
|
|
30
|
+
import { p256 } from "@noble/curves/nist";
|
|
31
|
+
var MULTICODEC_ED25519_PUB = 237;
|
|
32
|
+
var MULTICODEC_SECP256K1_PUB = 231;
|
|
33
|
+
var MULTICODEC_P256_PUB = 4608;
|
|
34
|
+
async function extractPublicKeyFromKid(kid) {
|
|
35
|
+
const [didPart, fragment] = kid.split("#");
|
|
36
|
+
const parts = didPart.split(":");
|
|
37
|
+
if (parts.length < 3 || parts[0] !== "did") {
|
|
38
|
+
throw new Error(`Invalid DID format: ${kid}`);
|
|
39
|
+
}
|
|
40
|
+
const method = parts[1];
|
|
41
|
+
const identifier = parts.slice(2).join(":");
|
|
42
|
+
switch (method) {
|
|
43
|
+
case "key":
|
|
44
|
+
return extractKeyFromDidKey(identifier);
|
|
45
|
+
case "jwk":
|
|
46
|
+
return extractKeyFromDidJwk(identifier);
|
|
47
|
+
case "web":
|
|
48
|
+
return resolveDidWeb(identifier, fragment);
|
|
49
|
+
default:
|
|
50
|
+
throw new Error(
|
|
51
|
+
`Unsupported DID method "${method}". Supported: did:key, did:jwk, did:web. Provide the public key directly for other methods.`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function extractKeyFromDidKey(identifier) {
|
|
56
|
+
if (!identifier.startsWith("z")) {
|
|
57
|
+
throw new Error(`Unsupported multibase encoding. Expected 'z' (base58-btc).`);
|
|
58
|
+
}
|
|
59
|
+
const decoded = base58.decode(identifier.slice(1));
|
|
60
|
+
const { codec, keyBytes } = readMulticodec(decoded);
|
|
61
|
+
switch (codec) {
|
|
62
|
+
case MULTICODEC_ED25519_PUB:
|
|
63
|
+
return importAsymmetricJWK({
|
|
64
|
+
kty: "OKP",
|
|
65
|
+
crv: "Ed25519",
|
|
66
|
+
x: jose.base64url.encode(keyBytes)
|
|
67
|
+
});
|
|
68
|
+
case MULTICODEC_SECP256K1_PUB: {
|
|
69
|
+
const point = secp256k1.Point.fromHex(keyBytes);
|
|
70
|
+
const uncompressed = point.toBytes(false);
|
|
71
|
+
return importAsymmetricJWK({
|
|
72
|
+
kty: "EC",
|
|
73
|
+
crv: "secp256k1",
|
|
74
|
+
x: jose.base64url.encode(uncompressed.slice(1, 33)),
|
|
75
|
+
y: jose.base64url.encode(uncompressed.slice(33, 65))
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
case MULTICODEC_P256_PUB: {
|
|
79
|
+
const point = p256.Point.fromHex(keyBytes);
|
|
80
|
+
const uncompressed = point.toBytes(false);
|
|
81
|
+
return importAsymmetricJWK({
|
|
82
|
+
kty: "EC",
|
|
83
|
+
crv: "P-256",
|
|
84
|
+
x: jose.base64url.encode(uncompressed.slice(1, 33)),
|
|
85
|
+
y: jose.base64url.encode(uncompressed.slice(33, 65))
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
default:
|
|
89
|
+
throw new Error(
|
|
90
|
+
`Unsupported key type in did:key (multicodec: 0x${codec.toString(16)}). Supported: Ed25519, secp256k1, P-256.`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async function extractKeyFromDidJwk(identifier) {
|
|
95
|
+
const jwkJson = new TextDecoder().decode(jose.base64url.decode(identifier));
|
|
96
|
+
const jwk = JSON.parse(jwkJson);
|
|
97
|
+
return importAsymmetricJWK(jwk);
|
|
98
|
+
}
|
|
99
|
+
async function resolveDidWeb(identifier, fragment) {
|
|
100
|
+
const parts = identifier.split(":");
|
|
101
|
+
const domain = decodeURIComponent(parts[0]);
|
|
102
|
+
const path = parts.slice(1).map(decodeURIComponent).join("/");
|
|
103
|
+
const host = domain.split(":")[0];
|
|
104
|
+
const scheme = host === "localhost" || host === "127.0.0.1" ? "http" : "https";
|
|
105
|
+
const url = path ? `${scheme}://${domain}/${path}/did.json` : `${scheme}://${domain}/.well-known/did.json`;
|
|
106
|
+
let didDocument;
|
|
107
|
+
try {
|
|
108
|
+
const response = await fetch(url, {
|
|
109
|
+
headers: { Accept: "application/did+json, application/json" }
|
|
110
|
+
});
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
throw new Error(`HTTP ${response.status}`);
|
|
113
|
+
}
|
|
114
|
+
didDocument = await response.json();
|
|
115
|
+
} catch (error) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`Failed to resolve did:web:${identifier}: ${error instanceof Error ? error.message : error}`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
const fullDid = `did:web:${identifier}`;
|
|
121
|
+
const keyId = fragment ? `${fullDid}#${fragment}` : void 0;
|
|
122
|
+
const method = findVerificationMethod(didDocument, keyId);
|
|
123
|
+
if (!method) {
|
|
124
|
+
throw new Error(`No verification method found for ${keyId || fullDid}`);
|
|
125
|
+
}
|
|
126
|
+
if (method.publicKeyJwk) {
|
|
127
|
+
return importAsymmetricJWK(method.publicKeyJwk);
|
|
128
|
+
}
|
|
129
|
+
if (method.publicKeyMultibase) {
|
|
130
|
+
return extractKeyFromDidKey(method.publicKeyMultibase);
|
|
131
|
+
}
|
|
132
|
+
throw new Error(`Verification method ${method.id} has no supported key format`);
|
|
133
|
+
}
|
|
134
|
+
function readMulticodec(bytes) {
|
|
135
|
+
let codec = 0;
|
|
136
|
+
let shift = 0;
|
|
137
|
+
let offset = 0;
|
|
138
|
+
for (const byte of bytes) {
|
|
139
|
+
codec |= (byte & 127) << shift;
|
|
140
|
+
offset++;
|
|
141
|
+
if ((byte & 128) === 0) break;
|
|
142
|
+
shift += 7;
|
|
143
|
+
}
|
|
144
|
+
return { codec, keyBytes: bytes.slice(offset) };
|
|
145
|
+
}
|
|
146
|
+
async function importAsymmetricJWK(jwk) {
|
|
147
|
+
const key = await jose.importJWK(jwk);
|
|
148
|
+
if (key instanceof Uint8Array) {
|
|
149
|
+
throw new Error("Symmetric keys are not supported");
|
|
150
|
+
}
|
|
151
|
+
return key;
|
|
152
|
+
}
|
|
153
|
+
function findVerificationMethod(doc, keyId) {
|
|
154
|
+
const methods = doc.verificationMethod || [];
|
|
155
|
+
if (keyId) {
|
|
156
|
+
return methods.find((m) => m.id === keyId);
|
|
157
|
+
}
|
|
158
|
+
for (const ref of doc.assertionMethod || []) {
|
|
159
|
+
if (typeof ref === "string") {
|
|
160
|
+
const m = methods.find((m2) => m2.id === ref);
|
|
161
|
+
if (m) return m;
|
|
162
|
+
} else {
|
|
163
|
+
return ref;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
for (const ref of doc.authentication || []) {
|
|
167
|
+
if (typeof ref === "string") {
|
|
168
|
+
const m = methods.find((m2) => m2.id === ref);
|
|
169
|
+
if (m) return m;
|
|
170
|
+
} else {
|
|
171
|
+
return ref;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return methods[0];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// src/offer-receipt/signing.ts
|
|
178
|
+
function canonicalize(value) {
|
|
179
|
+
return serializeValue(value);
|
|
180
|
+
}
|
|
181
|
+
function serializeValue(value) {
|
|
182
|
+
if (value === null) return "null";
|
|
183
|
+
if (value === void 0) return "null";
|
|
184
|
+
const type = typeof value;
|
|
185
|
+
if (type === "boolean") return value ? "true" : "false";
|
|
186
|
+
if (type === "number") return serializeNumber(value);
|
|
187
|
+
if (type === "string") return serializeString(value);
|
|
188
|
+
if (Array.isArray(value)) return serializeArray(value);
|
|
189
|
+
if (type === "object") return serializeObject(value);
|
|
190
|
+
throw new Error(`Cannot canonicalize value of type ${type}`);
|
|
191
|
+
}
|
|
192
|
+
function serializeNumber(num) {
|
|
193
|
+
if (!Number.isFinite(num)) throw new Error("Cannot canonicalize Infinity or NaN");
|
|
194
|
+
if (Object.is(num, -0)) return "0";
|
|
195
|
+
return String(num);
|
|
196
|
+
}
|
|
197
|
+
function serializeString(str) {
|
|
198
|
+
let result = '"';
|
|
199
|
+
for (let i = 0; i < str.length; i++) {
|
|
200
|
+
const char = str[i];
|
|
201
|
+
const code = str.charCodeAt(i);
|
|
202
|
+
if (code < 32) {
|
|
203
|
+
result += "\\u" + code.toString(16).padStart(4, "0");
|
|
204
|
+
} else if (char === '"') {
|
|
205
|
+
result += '\\"';
|
|
206
|
+
} else if (char === "\\") {
|
|
207
|
+
result += "\\\\";
|
|
208
|
+
} else {
|
|
209
|
+
result += char;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return result + '"';
|
|
213
|
+
}
|
|
214
|
+
function serializeArray(arr) {
|
|
215
|
+
return "[" + arr.map(serializeValue).join(",") + "]";
|
|
216
|
+
}
|
|
217
|
+
function serializeObject(obj) {
|
|
218
|
+
const keys = Object.keys(obj).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
219
|
+
const pairs = [];
|
|
220
|
+
for (const key of keys) {
|
|
221
|
+
const value = obj[key];
|
|
222
|
+
if (value !== void 0) {
|
|
223
|
+
pairs.push(serializeString(key) + ":" + serializeValue(value));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return "{" + pairs.join(",") + "}";
|
|
227
|
+
}
|
|
228
|
+
async function hashCanonical(obj) {
|
|
229
|
+
const canonical = canonicalize(obj);
|
|
230
|
+
const data = new TextEncoder().encode(canonical);
|
|
231
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
232
|
+
return new Uint8Array(hashBuffer);
|
|
233
|
+
}
|
|
234
|
+
function getCanonicalBytes(obj) {
|
|
235
|
+
return new TextEncoder().encode(canonicalize(obj));
|
|
236
|
+
}
|
|
237
|
+
async function createJWS(payload, signer) {
|
|
238
|
+
const headerObj = { alg: signer.algorithm, kid: signer.kid };
|
|
239
|
+
const headerB64 = jose2.base64url.encode(new TextEncoder().encode(JSON.stringify(headerObj)));
|
|
240
|
+
const canonical = canonicalize(payload);
|
|
241
|
+
const payloadB64 = jose2.base64url.encode(new TextEncoder().encode(canonical));
|
|
242
|
+
const signingInput = new TextEncoder().encode(`${headerB64}.${payloadB64}`);
|
|
243
|
+
const signatureB64 = await signer.sign(signingInput);
|
|
244
|
+
return `${headerB64}.${payloadB64}.${signatureB64}`;
|
|
245
|
+
}
|
|
246
|
+
function extractJWSHeader(jws) {
|
|
247
|
+
const parts = jws.split(".");
|
|
248
|
+
if (parts.length !== 3) throw new Error("Invalid JWS format");
|
|
249
|
+
const headerJson = jose2.base64url.decode(parts[0]);
|
|
250
|
+
return JSON.parse(new TextDecoder().decode(headerJson));
|
|
251
|
+
}
|
|
252
|
+
function extractJWSPayload(jws) {
|
|
253
|
+
const parts = jws.split(".");
|
|
254
|
+
if (parts.length !== 3) throw new Error("Invalid JWS format");
|
|
255
|
+
const payloadJson = jose2.base64url.decode(parts[1]);
|
|
256
|
+
return JSON.parse(new TextDecoder().decode(payloadJson));
|
|
257
|
+
}
|
|
258
|
+
function createOfferDomain() {
|
|
259
|
+
return { name: "x402 offer", version: "1", chainId: 1 };
|
|
260
|
+
}
|
|
261
|
+
function createReceiptDomain() {
|
|
262
|
+
return { name: "x402 receipt", version: "1", chainId: 1 };
|
|
263
|
+
}
|
|
264
|
+
var OFFER_TYPES = {
|
|
265
|
+
Offer: [
|
|
266
|
+
{ name: "version", type: "uint256" },
|
|
267
|
+
{ name: "resourceUrl", type: "string" },
|
|
268
|
+
{ name: "scheme", type: "string" },
|
|
269
|
+
{ name: "network", type: "string" },
|
|
270
|
+
{ name: "asset", type: "string" },
|
|
271
|
+
{ name: "payTo", type: "string" },
|
|
272
|
+
{ name: "amount", type: "string" },
|
|
273
|
+
{ name: "validUntil", type: "uint256" }
|
|
274
|
+
]
|
|
275
|
+
};
|
|
276
|
+
var RECEIPT_TYPES = {
|
|
277
|
+
Receipt: [
|
|
278
|
+
{ name: "version", type: "uint256" },
|
|
279
|
+
{ name: "network", type: "string" },
|
|
280
|
+
{ name: "resourceUrl", type: "string" },
|
|
281
|
+
{ name: "payer", type: "string" },
|
|
282
|
+
{ name: "issuedAt", type: "uint256" },
|
|
283
|
+
{ name: "transaction", type: "string" }
|
|
284
|
+
]
|
|
285
|
+
};
|
|
286
|
+
function prepareOfferForEIP712(payload) {
|
|
287
|
+
return {
|
|
288
|
+
version: BigInt(payload.version),
|
|
289
|
+
resourceUrl: payload.resourceUrl,
|
|
290
|
+
scheme: payload.scheme,
|
|
291
|
+
network: payload.network,
|
|
292
|
+
asset: payload.asset,
|
|
293
|
+
payTo: payload.payTo,
|
|
294
|
+
amount: payload.amount,
|
|
295
|
+
validUntil: BigInt(payload.validUntil)
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function prepareReceiptForEIP712(payload) {
|
|
299
|
+
return {
|
|
300
|
+
version: BigInt(payload.version),
|
|
301
|
+
network: payload.network,
|
|
302
|
+
resourceUrl: payload.resourceUrl,
|
|
303
|
+
payer: payload.payer,
|
|
304
|
+
issuedAt: BigInt(payload.issuedAt),
|
|
305
|
+
transaction: payload.transaction
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
function hashOfferTypedData(payload) {
|
|
309
|
+
return hashTypedData({
|
|
310
|
+
domain: createOfferDomain(),
|
|
311
|
+
types: OFFER_TYPES,
|
|
312
|
+
primaryType: "Offer",
|
|
313
|
+
message: prepareOfferForEIP712(payload)
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
function hashReceiptTypedData(payload) {
|
|
317
|
+
return hashTypedData({
|
|
318
|
+
domain: createReceiptDomain(),
|
|
319
|
+
types: RECEIPT_TYPES,
|
|
320
|
+
primaryType: "Receipt",
|
|
321
|
+
message: prepareReceiptForEIP712(payload)
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
async function signOfferEIP712(payload, signTypedData) {
|
|
325
|
+
return signTypedData({
|
|
326
|
+
domain: createOfferDomain(),
|
|
327
|
+
types: OFFER_TYPES,
|
|
328
|
+
primaryType: "Offer",
|
|
329
|
+
message: prepareOfferForEIP712(payload)
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
async function signReceiptEIP712(payload, signTypedData) {
|
|
333
|
+
return signTypedData({
|
|
334
|
+
domain: createReceiptDomain(),
|
|
335
|
+
types: RECEIPT_TYPES,
|
|
336
|
+
primaryType: "Receipt",
|
|
337
|
+
message: prepareReceiptForEIP712(payload)
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
function extractEIP155ChainId(network) {
|
|
341
|
+
const match = network.match(/^eip155:(\d+)$/);
|
|
342
|
+
if (!match) {
|
|
343
|
+
throw new Error(`Invalid network format: ${network}. Expected "eip155:<chainId>"`);
|
|
344
|
+
}
|
|
345
|
+
return parseInt(match[1], 10);
|
|
346
|
+
}
|
|
347
|
+
var V1_EVM_NETWORK_CHAIN_IDS = {
|
|
348
|
+
ethereum: 1,
|
|
349
|
+
sepolia: 11155111,
|
|
350
|
+
abstract: 2741,
|
|
351
|
+
"abstract-testnet": 11124,
|
|
352
|
+
"base-sepolia": 84532,
|
|
353
|
+
base: 8453,
|
|
354
|
+
"avalanche-fuji": 43113,
|
|
355
|
+
avalanche: 43114,
|
|
356
|
+
iotex: 4689,
|
|
357
|
+
sei: 1329,
|
|
358
|
+
"sei-testnet": 1328,
|
|
359
|
+
polygon: 137,
|
|
360
|
+
"polygon-amoy": 80002,
|
|
361
|
+
peaq: 3338,
|
|
362
|
+
story: 1514,
|
|
363
|
+
educhain: 41923,
|
|
364
|
+
"skale-base-sepolia": 324705682
|
|
365
|
+
};
|
|
366
|
+
var V1_SOLANA_NETWORKS = {
|
|
367
|
+
solana: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
|
|
368
|
+
"solana-devnet": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
|
|
369
|
+
"solana-testnet": "solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z"
|
|
370
|
+
};
|
|
371
|
+
function convertNetworkStringToCAIP2(network) {
|
|
372
|
+
if (network.includes(":")) return network;
|
|
373
|
+
const chainId = V1_EVM_NETWORK_CHAIN_IDS[network.toLowerCase()];
|
|
374
|
+
if (chainId !== void 0) {
|
|
375
|
+
return `eip155:${chainId}`;
|
|
376
|
+
}
|
|
377
|
+
const solanaNetwork = V1_SOLANA_NETWORKS[network.toLowerCase()];
|
|
378
|
+
if (solanaNetwork) {
|
|
379
|
+
return solanaNetwork;
|
|
380
|
+
}
|
|
381
|
+
throw new Error(
|
|
382
|
+
`Unknown network identifier: "${network}". Expected CAIP-2 format (e.g., "eip155:8453") or v1 name (e.g., "base", "solana").`
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
function extractChainIdFromCAIP2(network) {
|
|
386
|
+
const [namespace, reference] = network.split(":");
|
|
387
|
+
if (namespace === "eip155" && reference) {
|
|
388
|
+
const chainId = parseInt(reference, 10);
|
|
389
|
+
return isNaN(chainId) ? void 0 : chainId;
|
|
390
|
+
}
|
|
391
|
+
return void 0;
|
|
392
|
+
}
|
|
393
|
+
var DEFAULT_MAX_TIMEOUT_SECONDS = 300;
|
|
394
|
+
var EXTENSION_VERSION = 1;
|
|
395
|
+
function createOfferPayload(resourceUrl, input) {
|
|
396
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
397
|
+
const offerValiditySeconds = input.offerValiditySeconds ?? DEFAULT_MAX_TIMEOUT_SECONDS;
|
|
398
|
+
return {
|
|
399
|
+
version: EXTENSION_VERSION,
|
|
400
|
+
resourceUrl,
|
|
401
|
+
scheme: input.scheme,
|
|
402
|
+
network: input.network,
|
|
403
|
+
asset: input.asset,
|
|
404
|
+
payTo: input.payTo,
|
|
405
|
+
amount: input.amount,
|
|
406
|
+
validUntil: now + offerValiditySeconds
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
async function createOfferJWS(resourceUrl, input, signer) {
|
|
410
|
+
const payload = createOfferPayload(resourceUrl, input);
|
|
411
|
+
const jws = await createJWS(payload, signer);
|
|
412
|
+
return {
|
|
413
|
+
format: "jws",
|
|
414
|
+
acceptIndex: input.acceptIndex,
|
|
415
|
+
signature: jws
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
async function createOfferEIP712(resourceUrl, input, signTypedData) {
|
|
419
|
+
const payload = createOfferPayload(resourceUrl, input);
|
|
420
|
+
const signature = await signOfferEIP712(payload, signTypedData);
|
|
421
|
+
return {
|
|
422
|
+
format: "eip712",
|
|
423
|
+
acceptIndex: input.acceptIndex,
|
|
424
|
+
payload,
|
|
425
|
+
signature
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
function extractOfferPayload(offer) {
|
|
429
|
+
if (isJWSSignedOffer(offer)) {
|
|
430
|
+
return extractJWSPayload(offer.signature);
|
|
431
|
+
} else if (isEIP712SignedOffer(offer)) {
|
|
432
|
+
return offer.payload;
|
|
433
|
+
}
|
|
434
|
+
throw new Error(`Unknown offer format: ${offer.format}`);
|
|
435
|
+
}
|
|
436
|
+
function createReceiptPayloadForEIP712(input) {
|
|
437
|
+
return {
|
|
438
|
+
version: EXTENSION_VERSION,
|
|
439
|
+
network: input.network,
|
|
440
|
+
resourceUrl: input.resourceUrl,
|
|
441
|
+
payer: input.payer,
|
|
442
|
+
issuedAt: Math.floor(Date.now() / 1e3),
|
|
443
|
+
transaction: input.transaction ?? ""
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
function createReceiptPayloadForJWS(input) {
|
|
447
|
+
const payload = {
|
|
448
|
+
version: EXTENSION_VERSION,
|
|
449
|
+
network: input.network,
|
|
450
|
+
resourceUrl: input.resourceUrl,
|
|
451
|
+
payer: input.payer,
|
|
452
|
+
issuedAt: Math.floor(Date.now() / 1e3)
|
|
453
|
+
};
|
|
454
|
+
if (input.transaction) {
|
|
455
|
+
payload.transaction = input.transaction;
|
|
456
|
+
}
|
|
457
|
+
return payload;
|
|
458
|
+
}
|
|
459
|
+
async function createReceiptJWS(input, signer) {
|
|
460
|
+
const payload = createReceiptPayloadForJWS(input);
|
|
461
|
+
const jws = await createJWS(payload, signer);
|
|
462
|
+
return { format: "jws", signature: jws };
|
|
463
|
+
}
|
|
464
|
+
async function createReceiptEIP712(input, signTypedData) {
|
|
465
|
+
const payload = createReceiptPayloadForEIP712(input);
|
|
466
|
+
const signature = await signReceiptEIP712(payload, signTypedData);
|
|
467
|
+
return { format: "eip712", payload, signature };
|
|
468
|
+
}
|
|
469
|
+
function extractReceiptPayload(receipt) {
|
|
470
|
+
if (isJWSSignedReceipt(receipt)) {
|
|
471
|
+
return extractJWSPayload(receipt.signature);
|
|
472
|
+
} else if (isEIP712SignedReceipt(receipt)) {
|
|
473
|
+
return receipt.payload;
|
|
474
|
+
}
|
|
475
|
+
throw new Error(`Unknown receipt format: ${receipt.format}`);
|
|
476
|
+
}
|
|
477
|
+
async function verifyOfferSignatureEIP712(offer) {
|
|
478
|
+
if (offer.format !== "eip712") {
|
|
479
|
+
throw new Error(`Expected eip712 format, got ${offer.format}`);
|
|
480
|
+
}
|
|
481
|
+
if (!offer.payload || !("scheme" in offer.payload)) {
|
|
482
|
+
throw new Error("Invalid offer: missing or malformed payload");
|
|
483
|
+
}
|
|
484
|
+
const signer = await recoverTypedDataAddress({
|
|
485
|
+
domain: createOfferDomain(),
|
|
486
|
+
types: OFFER_TYPES,
|
|
487
|
+
primaryType: "Offer",
|
|
488
|
+
message: prepareOfferForEIP712(offer.payload),
|
|
489
|
+
signature: offer.signature
|
|
490
|
+
});
|
|
491
|
+
return { signer, payload: offer.payload };
|
|
492
|
+
}
|
|
493
|
+
async function verifyReceiptSignatureEIP712(receipt) {
|
|
494
|
+
if (receipt.format !== "eip712") {
|
|
495
|
+
throw new Error(`Expected eip712 format, got ${receipt.format}`);
|
|
496
|
+
}
|
|
497
|
+
if (!receipt.payload || !("payer" in receipt.payload)) {
|
|
498
|
+
throw new Error("Invalid receipt: missing or malformed payload");
|
|
499
|
+
}
|
|
500
|
+
const signer = await recoverTypedDataAddress({
|
|
501
|
+
domain: createReceiptDomain(),
|
|
502
|
+
types: RECEIPT_TYPES,
|
|
503
|
+
primaryType: "Receipt",
|
|
504
|
+
message: prepareReceiptForEIP712(receipt.payload),
|
|
505
|
+
signature: receipt.signature
|
|
506
|
+
});
|
|
507
|
+
return { signer, payload: receipt.payload };
|
|
508
|
+
}
|
|
509
|
+
async function verifyOfferSignatureJWS(offer, publicKey) {
|
|
510
|
+
if (offer.format !== "jws") {
|
|
511
|
+
throw new Error(`Expected jws format, got ${offer.format}`);
|
|
512
|
+
}
|
|
513
|
+
const key = await resolveVerificationKey(offer.signature, publicKey);
|
|
514
|
+
const { payload } = await jose2.compactVerify(offer.signature, key);
|
|
515
|
+
return JSON.parse(new TextDecoder().decode(payload));
|
|
516
|
+
}
|
|
517
|
+
async function verifyReceiptSignatureJWS(receipt, publicKey) {
|
|
518
|
+
if (receipt.format !== "jws") {
|
|
519
|
+
throw new Error(`Expected jws format, got ${receipt.format}`);
|
|
520
|
+
}
|
|
521
|
+
const key = await resolveVerificationKey(receipt.signature, publicKey);
|
|
522
|
+
const { payload } = await jose2.compactVerify(receipt.signature, key);
|
|
523
|
+
return JSON.parse(new TextDecoder().decode(payload));
|
|
524
|
+
}
|
|
525
|
+
async function resolveVerificationKey(jws, providedKey) {
|
|
526
|
+
if (providedKey) {
|
|
527
|
+
if ("kty" in providedKey) {
|
|
528
|
+
const key = await jose2.importJWK(providedKey);
|
|
529
|
+
if (key instanceof Uint8Array) {
|
|
530
|
+
throw new Error("Symmetric keys are not supported for JWS verification");
|
|
531
|
+
}
|
|
532
|
+
return key;
|
|
533
|
+
}
|
|
534
|
+
return providedKey;
|
|
535
|
+
}
|
|
536
|
+
const header = extractJWSHeader(jws);
|
|
537
|
+
if (!header.kid) {
|
|
538
|
+
throw new Error("No public key provided and JWS header missing kid");
|
|
539
|
+
}
|
|
540
|
+
return extractPublicKeyFromKid(header.kid);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// src/offer-receipt/server.ts
|
|
544
|
+
var OFFER_SCHEMA = {
|
|
545
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
546
|
+
type: "object",
|
|
547
|
+
properties: {
|
|
548
|
+
offers: {
|
|
549
|
+
type: "array",
|
|
550
|
+
items: {
|
|
551
|
+
type: "object",
|
|
552
|
+
properties: {
|
|
553
|
+
format: { type: "string" },
|
|
554
|
+
acceptIndex: { type: "integer" },
|
|
555
|
+
payload: {
|
|
556
|
+
type: "object",
|
|
557
|
+
properties: {
|
|
558
|
+
version: { type: "integer" },
|
|
559
|
+
resourceUrl: { type: "string" },
|
|
560
|
+
scheme: { type: "string" },
|
|
561
|
+
network: { type: "string" },
|
|
562
|
+
asset: { type: "string" },
|
|
563
|
+
payTo: { type: "string" },
|
|
564
|
+
amount: { type: "string" },
|
|
565
|
+
validUntil: { type: "integer" }
|
|
566
|
+
},
|
|
567
|
+
required: ["version", "resourceUrl", "scheme", "network", "asset", "payTo", "amount"]
|
|
568
|
+
},
|
|
569
|
+
signature: { type: "string" }
|
|
570
|
+
},
|
|
571
|
+
required: ["format", "signature"]
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
},
|
|
575
|
+
required: ["offers"]
|
|
576
|
+
};
|
|
577
|
+
var RECEIPT_SCHEMA = {
|
|
578
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
579
|
+
type: "object",
|
|
580
|
+
properties: {
|
|
581
|
+
receipt: {
|
|
582
|
+
type: "object",
|
|
583
|
+
properties: {
|
|
584
|
+
format: { type: "string" },
|
|
585
|
+
payload: {
|
|
586
|
+
type: "object",
|
|
587
|
+
properties: {
|
|
588
|
+
version: { type: "integer" },
|
|
589
|
+
network: { type: "string" },
|
|
590
|
+
resourceUrl: { type: "string" },
|
|
591
|
+
payer: { type: "string" },
|
|
592
|
+
issuedAt: { type: "integer" },
|
|
593
|
+
transaction: { type: "string" }
|
|
594
|
+
},
|
|
595
|
+
required: ["version", "network", "resourceUrl", "payer", "issuedAt"]
|
|
596
|
+
},
|
|
597
|
+
signature: { type: "string" }
|
|
598
|
+
},
|
|
599
|
+
required: ["format", "signature"]
|
|
600
|
+
}
|
|
601
|
+
},
|
|
602
|
+
required: ["receipt"]
|
|
603
|
+
};
|
|
604
|
+
function requirementsToOfferInput(requirements, acceptIndex, offerValiditySeconds) {
|
|
605
|
+
return {
|
|
606
|
+
acceptIndex,
|
|
607
|
+
scheme: requirements.scheme,
|
|
608
|
+
network: requirements.network,
|
|
609
|
+
asset: requirements.asset,
|
|
610
|
+
payTo: requirements.payTo,
|
|
611
|
+
amount: requirements.amount,
|
|
612
|
+
offerValiditySeconds: offerValiditySeconds ?? requirements.maxTimeoutSeconds
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
function createOfferReceiptExtension(issuer) {
|
|
616
|
+
return {
|
|
617
|
+
key: OFFER_RECEIPT,
|
|
618
|
+
// Add signed offers to 402 PaymentRequired response
|
|
619
|
+
enrichPaymentRequiredResponse: async (declaration, context) => {
|
|
620
|
+
const config = declaration;
|
|
621
|
+
const resourceUrl = context.paymentRequiredResponse.resource?.url || context.transportContext?.request?.adapter?.getUrl?.();
|
|
622
|
+
if (!resourceUrl) {
|
|
623
|
+
console.warn("[offer-receipt] No resource URL available for signing offers");
|
|
624
|
+
return void 0;
|
|
625
|
+
}
|
|
626
|
+
const offers = [];
|
|
627
|
+
for (let i = 0; i < context.requirements.length; i++) {
|
|
628
|
+
const requirement = context.requirements[i];
|
|
629
|
+
try {
|
|
630
|
+
const offerInput = requirementsToOfferInput(requirement, i, config?.offerValiditySeconds);
|
|
631
|
+
const signedOffer = await issuer.issueOffer(resourceUrl, offerInput);
|
|
632
|
+
offers.push(signedOffer);
|
|
633
|
+
} catch (error) {
|
|
634
|
+
console.error(`[offer-receipt] Failed to sign offer for requirement ${i}:`, error);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
if (offers.length === 0) {
|
|
638
|
+
return void 0;
|
|
639
|
+
}
|
|
640
|
+
return {
|
|
641
|
+
info: {
|
|
642
|
+
offers
|
|
643
|
+
},
|
|
644
|
+
schema: OFFER_SCHEMA
|
|
645
|
+
};
|
|
646
|
+
},
|
|
647
|
+
// Add signed receipt to settlement response
|
|
648
|
+
enrichSettlementResponse: async (declaration, context) => {
|
|
649
|
+
const config = declaration;
|
|
650
|
+
if (!context.result.success) {
|
|
651
|
+
return void 0;
|
|
652
|
+
}
|
|
653
|
+
const payer = context.result.payer;
|
|
654
|
+
if (!payer) {
|
|
655
|
+
console.warn("[offer-receipt] No payer available for signing receipt");
|
|
656
|
+
return void 0;
|
|
657
|
+
}
|
|
658
|
+
const network = context.result.network;
|
|
659
|
+
if (!network) {
|
|
660
|
+
console.warn("[offer-receipt] No network available for signing receipt");
|
|
661
|
+
return void 0;
|
|
662
|
+
}
|
|
663
|
+
const transaction = context.result.transaction;
|
|
664
|
+
const resourceUrl = context.transportContext?.request?.adapter?.getUrl?.();
|
|
665
|
+
if (!resourceUrl) {
|
|
666
|
+
console.warn("[offer-receipt] No resource URL available for signing receipt");
|
|
667
|
+
return void 0;
|
|
668
|
+
}
|
|
669
|
+
const includeTxHash = config?.includeTxHash === true;
|
|
670
|
+
try {
|
|
671
|
+
const signedReceipt = await issuer.issueReceipt(
|
|
672
|
+
resourceUrl,
|
|
673
|
+
payer,
|
|
674
|
+
network,
|
|
675
|
+
includeTxHash ? transaction || void 0 : void 0
|
|
676
|
+
);
|
|
677
|
+
return {
|
|
678
|
+
info: {
|
|
679
|
+
receipt: signedReceipt
|
|
680
|
+
},
|
|
681
|
+
schema: RECEIPT_SCHEMA
|
|
682
|
+
};
|
|
683
|
+
} catch (error) {
|
|
684
|
+
console.error("[offer-receipt] Failed to sign receipt:", error);
|
|
685
|
+
return void 0;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
function declareOfferReceiptExtension(config) {
|
|
691
|
+
return {
|
|
692
|
+
[OFFER_RECEIPT]: {
|
|
693
|
+
includeTxHash: config?.includeTxHash,
|
|
694
|
+
offerValiditySeconds: config?.offerValiditySeconds
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
function createJWSOfferReceiptIssuer(kid, jwsSigner) {
|
|
699
|
+
return {
|
|
700
|
+
kid,
|
|
701
|
+
format: "jws",
|
|
702
|
+
async issueOffer(resourceUrl, input) {
|
|
703
|
+
return createOfferJWS(resourceUrl, input, jwsSigner);
|
|
704
|
+
},
|
|
705
|
+
async issueReceipt(resourceUrl, payer, network, transaction) {
|
|
706
|
+
return createReceiptJWS({ resourceUrl, payer, network, transaction }, jwsSigner);
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
function createEIP712OfferReceiptIssuer(kid, signTypedData) {
|
|
711
|
+
return {
|
|
712
|
+
kid,
|
|
713
|
+
format: "eip712",
|
|
714
|
+
async issueOffer(resourceUrl, input) {
|
|
715
|
+
return createOfferEIP712(resourceUrl, input, signTypedData);
|
|
716
|
+
},
|
|
717
|
+
async issueReceipt(resourceUrl, payer, network, transaction) {
|
|
718
|
+
return createReceiptEIP712({ resourceUrl, payer, network, transaction }, signTypedData);
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// src/offer-receipt/client.ts
|
|
724
|
+
import { decodePaymentResponseHeader } from "@x402/core/http";
|
|
725
|
+
function verifyReceiptMatchesOffer(receipt, offer, payerAddresses, maxAgeSeconds = 3600) {
|
|
726
|
+
const payload = extractReceiptPayload(receipt);
|
|
727
|
+
const resourceUrlMatch = payload.resourceUrl === offer.resourceUrl;
|
|
728
|
+
const networkMatch = payload.network === offer.network;
|
|
729
|
+
const payerMatch = payerAddresses.some(
|
|
730
|
+
(addr) => payload.payer.toLowerCase() === addr.toLowerCase()
|
|
731
|
+
);
|
|
732
|
+
const issuedRecently = Math.floor(Date.now() / 1e3) - payload.issuedAt < maxAgeSeconds;
|
|
733
|
+
return resourceUrlMatch && networkMatch && payerMatch && issuedRecently;
|
|
734
|
+
}
|
|
735
|
+
function extractOffersFromPaymentRequired(paymentRequired) {
|
|
736
|
+
const extData = paymentRequired.extensions?.[OFFER_RECEIPT];
|
|
737
|
+
return extData?.info?.offers ?? [];
|
|
738
|
+
}
|
|
739
|
+
function decodeSignedOffers(offers) {
|
|
740
|
+
return offers.map((offer) => {
|
|
741
|
+
const payload = extractOfferPayload(offer);
|
|
742
|
+
return {
|
|
743
|
+
// Spread payload fields at top level
|
|
744
|
+
...payload,
|
|
745
|
+
// Include metadata
|
|
746
|
+
signedOffer: offer,
|
|
747
|
+
format: offer.format,
|
|
748
|
+
acceptIndex: offer.acceptIndex
|
|
749
|
+
};
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
function findAcceptsObjectFromSignedOffer(offer, accepts) {
|
|
753
|
+
const isDecoded = "signedOffer" in offer;
|
|
754
|
+
const payload = isDecoded ? offer : extractOfferPayload(offer);
|
|
755
|
+
const acceptIndex = isDecoded ? offer.acceptIndex : offer.acceptIndex;
|
|
756
|
+
if (acceptIndex !== void 0 && acceptIndex < accepts.length) {
|
|
757
|
+
const hinted = accepts[acceptIndex];
|
|
758
|
+
if (hinted.network === payload.network && hinted.scheme === payload.scheme && hinted.asset === payload.asset && hinted.payTo === payload.payTo && hinted.amount === payload.amount) {
|
|
759
|
+
return hinted;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
return accepts.find(
|
|
763
|
+
(req) => req.network === payload.network && req.scheme === payload.scheme && req.asset === payload.asset && req.payTo === payload.payTo && req.amount === payload.amount
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
function extractReceiptFromResponse(response) {
|
|
767
|
+
const paymentResponseHeader = response.headers.get("PAYMENT-RESPONSE") || response.headers.get("X-PAYMENT-RESPONSE");
|
|
768
|
+
if (!paymentResponseHeader) {
|
|
769
|
+
return void 0;
|
|
770
|
+
}
|
|
771
|
+
try {
|
|
772
|
+
const settlementResponse = decodePaymentResponseHeader(paymentResponseHeader);
|
|
773
|
+
const receiptExtData = settlementResponse.extensions?.[OFFER_RECEIPT];
|
|
774
|
+
return receiptExtData?.info?.receipt;
|
|
775
|
+
} catch {
|
|
776
|
+
return void 0;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
export {
|
|
781
|
+
OFFER_RECEIPT,
|
|
782
|
+
isJWSSignedOffer,
|
|
783
|
+
isEIP712SignedOffer,
|
|
784
|
+
isJWSSignedReceipt,
|
|
785
|
+
isEIP712SignedReceipt,
|
|
786
|
+
isJWSSigner,
|
|
787
|
+
isEIP712Signer,
|
|
788
|
+
extractPublicKeyFromKid,
|
|
789
|
+
canonicalize,
|
|
790
|
+
hashCanonical,
|
|
791
|
+
getCanonicalBytes,
|
|
792
|
+
createJWS,
|
|
793
|
+
extractJWSHeader,
|
|
794
|
+
extractJWSPayload,
|
|
795
|
+
createOfferDomain,
|
|
796
|
+
createReceiptDomain,
|
|
797
|
+
OFFER_TYPES,
|
|
798
|
+
RECEIPT_TYPES,
|
|
799
|
+
prepareOfferForEIP712,
|
|
800
|
+
prepareReceiptForEIP712,
|
|
801
|
+
hashOfferTypedData,
|
|
802
|
+
hashReceiptTypedData,
|
|
803
|
+
signOfferEIP712,
|
|
804
|
+
signReceiptEIP712,
|
|
805
|
+
extractEIP155ChainId,
|
|
806
|
+
convertNetworkStringToCAIP2,
|
|
807
|
+
extractChainIdFromCAIP2,
|
|
808
|
+
createOfferJWS,
|
|
809
|
+
createOfferEIP712,
|
|
810
|
+
extractOfferPayload,
|
|
811
|
+
createReceiptJWS,
|
|
812
|
+
createReceiptEIP712,
|
|
813
|
+
extractReceiptPayload,
|
|
814
|
+
verifyOfferSignatureEIP712,
|
|
815
|
+
verifyReceiptSignatureEIP712,
|
|
816
|
+
verifyOfferSignatureJWS,
|
|
817
|
+
verifyReceiptSignatureJWS,
|
|
818
|
+
createOfferReceiptExtension,
|
|
819
|
+
declareOfferReceiptExtension,
|
|
820
|
+
createJWSOfferReceiptIssuer,
|
|
821
|
+
createEIP712OfferReceiptIssuer,
|
|
822
|
+
verifyReceiptMatchesOffer,
|
|
823
|
+
extractOffersFromPaymentRequired,
|
|
824
|
+
decodeSignedOffers,
|
|
825
|
+
findAcceptsObjectFromSignedOffer,
|
|
826
|
+
extractReceiptFromResponse
|
|
827
|
+
};
|
|
828
|
+
//# sourceMappingURL=chunk-TYR4QHVX.mjs.map
|