@bankofai/x402-extensions 1.0.0-beta.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.
Files changed (52) hide show
  1. package/README.md +28 -0
  2. package/dist/cjs/bazaar/index.d.ts +4 -0
  3. package/dist/cjs/bazaar/index.js +1048 -0
  4. package/dist/cjs/bazaar/index.js.map +1 -0
  5. package/dist/cjs/builder-code/index.d.ts +225 -0
  6. package/dist/cjs/builder-code/index.js +369 -0
  7. package/dist/cjs/builder-code/index.js.map +1 -0
  8. package/dist/cjs/index-B8S563vv.d.ts +929 -0
  9. package/dist/cjs/index-BUV9B9f8.d.ts +929 -0
  10. package/dist/cjs/index.d.ts +373 -0
  11. package/dist/cjs/index.js +3536 -0
  12. package/dist/cjs/index.js.map +1 -0
  13. package/dist/cjs/offer-receipt/index.d.ts +702 -0
  14. package/dist/cjs/offer-receipt/index.js +909 -0
  15. package/dist/cjs/offer-receipt/index.js.map +1 -0
  16. package/dist/cjs/payment-identifier/index.d.ts +345 -0
  17. package/dist/cjs/payment-identifier/index.js +285 -0
  18. package/dist/cjs/payment-identifier/index.js.map +1 -0
  19. package/dist/cjs/sign-in-with-x/index.d.ts +1117 -0
  20. package/dist/cjs/sign-in-with-x/index.js +876 -0
  21. package/dist/cjs/sign-in-with-x/index.js.map +1 -0
  22. package/dist/esm/bazaar/index.d.mts +4 -0
  23. package/dist/esm/bazaar/index.mjs +51 -0
  24. package/dist/esm/bazaar/index.mjs.map +1 -0
  25. package/dist/esm/builder-code/index.d.mts +225 -0
  26. package/dist/esm/builder-code/index.mjs +27 -0
  27. package/dist/esm/builder-code/index.mjs.map +1 -0
  28. package/dist/esm/chunk-4IPDE3NS.mjs +990 -0
  29. package/dist/esm/chunk-4IPDE3NS.mjs.map +1 -0
  30. package/dist/esm/chunk-N74HQTNO.mjs +807 -0
  31. package/dist/esm/chunk-N74HQTNO.mjs.map +1 -0
  32. package/dist/esm/chunk-OWZP4CUR.mjs +333 -0
  33. package/dist/esm/chunk-OWZP4CUR.mjs.map +1 -0
  34. package/dist/esm/chunk-RERA4OZZ.mjs +233 -0
  35. package/dist/esm/chunk-RERA4OZZ.mjs.map +1 -0
  36. package/dist/esm/chunk-TIVMC3ZS.mjs +828 -0
  37. package/dist/esm/chunk-TIVMC3ZS.mjs.map +1 -0
  38. package/dist/esm/index-B8S563vv.d.mts +929 -0
  39. package/dist/esm/index-BUV9B9f8.d.mts +929 -0
  40. package/dist/esm/index.d.mts +373 -0
  41. package/dist/esm/index.mjs +455 -0
  42. package/dist/esm/index.mjs.map +1 -0
  43. package/dist/esm/offer-receipt/index.d.mts +702 -0
  44. package/dist/esm/offer-receipt/index.mjs +97 -0
  45. package/dist/esm/offer-receipt/index.mjs.map +1 -0
  46. package/dist/esm/payment-identifier/index.d.mts +345 -0
  47. package/dist/esm/payment-identifier/index.mjs +39 -0
  48. package/dist/esm/payment-identifier/index.mjs.map +1 -0
  49. package/dist/esm/sign-in-with-x/index.d.mts +1117 -0
  50. package/dist/esm/sign-in-with-x/index.mjs +73 -0
  51. package/dist/esm/sign-in-with-x/index.mjs.map +1 -0
  52. package/package.json +124 -0
@@ -0,0 +1,807 @@
1
+ // src/sign-in-with-x/types.ts
2
+ import { z } from "zod";
3
+ var SIGN_IN_WITH_X = "sign-in-with-x";
4
+ var SIWxPayloadSchema = z.object({
5
+ domain: z.string(),
6
+ address: z.string(),
7
+ statement: z.string().optional(),
8
+ uri: z.string(),
9
+ version: z.string(),
10
+ chainId: z.string(),
11
+ type: z.enum(["eip191", "ed25519"]),
12
+ nonce: z.string(),
13
+ issuedAt: z.string(),
14
+ expirationTime: z.string().optional(),
15
+ notBefore: z.string().optional(),
16
+ requestId: z.string().optional(),
17
+ resources: z.array(z.string()).optional(),
18
+ signatureScheme: z.enum(["eip191", "eip1271", "eip6492", "siws"]).optional(),
19
+ signature: z.string()
20
+ });
21
+
22
+ // src/sign-in-with-x/solana.ts
23
+ import { base58 } from "@scure/base";
24
+ import nacl from "tweetnacl";
25
+ var SOLANA_MAINNET = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
26
+ var SOLANA_DEVNET = "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1";
27
+ var SOLANA_TESTNET = "solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z";
28
+ function extractSolanaChainReference(chainId) {
29
+ const [, reference] = chainId.split(":");
30
+ return reference;
31
+ }
32
+ function formatSIWSMessage(info, address) {
33
+ const lines = [
34
+ `${info.domain} wants you to sign in with your Solana account:`,
35
+ address,
36
+ ""
37
+ ];
38
+ if (info.statement) {
39
+ lines.push(info.statement, "");
40
+ }
41
+ lines.push(
42
+ `URI: ${info.uri}`,
43
+ `Version: ${info.version}`,
44
+ `Chain ID: ${extractSolanaChainReference(info.chainId)}`,
45
+ `Nonce: ${info.nonce}`,
46
+ `Issued At: ${info.issuedAt}`
47
+ );
48
+ if (info.expirationTime) {
49
+ lines.push(`Expiration Time: ${info.expirationTime}`);
50
+ }
51
+ if (info.notBefore) {
52
+ lines.push(`Not Before: ${info.notBefore}`);
53
+ }
54
+ if (info.requestId) {
55
+ lines.push(`Request ID: ${info.requestId}`);
56
+ }
57
+ if (info.resources && info.resources.length > 0) {
58
+ lines.push("Resources:");
59
+ for (const resource of info.resources) {
60
+ lines.push(`- ${resource}`);
61
+ }
62
+ }
63
+ return lines.join("\n");
64
+ }
65
+ function verifySolanaSignature(message, signature, publicKey) {
66
+ const messageBytes = new TextEncoder().encode(message);
67
+ return nacl.sign.detached.verify(messageBytes, signature, publicKey);
68
+ }
69
+ function decodeBase58(encoded) {
70
+ return base58.decode(encoded);
71
+ }
72
+ function encodeBase58(bytes) {
73
+ return base58.encode(bytes);
74
+ }
75
+ function isSolanaSigner(signer) {
76
+ if ("signMessages" in signer && typeof signer.signMessages === "function") {
77
+ return true;
78
+ }
79
+ if ("publicKey" in signer && signer.publicKey) {
80
+ const pk = signer.publicKey;
81
+ if (typeof pk === "object" && pk !== null && "toBase58" in pk) {
82
+ return true;
83
+ }
84
+ if (typeof pk === "string" && !pk.startsWith("0x")) {
85
+ return true;
86
+ }
87
+ }
88
+ return false;
89
+ }
90
+
91
+ // src/sign-in-with-x/schema.ts
92
+ function buildSIWxSchema() {
93
+ return {
94
+ $schema: "https://json-schema.org/draft/2020-12/schema",
95
+ type: "object",
96
+ properties: {
97
+ domain: { type: "string" },
98
+ address: { type: "string" },
99
+ statement: { type: "string" },
100
+ uri: { type: "string", format: "uri" },
101
+ version: { type: "string" },
102
+ chainId: { type: "string" },
103
+ type: { type: "string" },
104
+ nonce: { type: "string" },
105
+ issuedAt: { type: "string", format: "date-time" },
106
+ expirationTime: { type: "string", format: "date-time" },
107
+ notBefore: { type: "string", format: "date-time" },
108
+ requestId: { type: "string" },
109
+ resources: { type: "array", items: { type: "string", format: "uri" } },
110
+ signature: { type: "string" }
111
+ },
112
+ required: [
113
+ "domain",
114
+ "address",
115
+ "uri",
116
+ "version",
117
+ "chainId",
118
+ "type",
119
+ "nonce",
120
+ "issuedAt",
121
+ "signature"
122
+ ]
123
+ };
124
+ }
125
+
126
+ // src/sign-in-with-x/declare.ts
127
+ function getSignatureType(network) {
128
+ return network.startsWith("solana:") ? "ed25519" : "eip191";
129
+ }
130
+ function declareSIWxExtension(options = {}) {
131
+ const info = {
132
+ version: options.version ?? "1"
133
+ };
134
+ if (options.domain) {
135
+ info.domain = options.domain;
136
+ }
137
+ if (options.resourceUri) {
138
+ info.uri = options.resourceUri;
139
+ info.resources = [options.resourceUri];
140
+ }
141
+ if (options.statement) {
142
+ info.statement = options.statement;
143
+ }
144
+ let supportedChains = [];
145
+ if (options.network) {
146
+ const networks = Array.isArray(options.network) ? options.network : [options.network];
147
+ supportedChains = networks.map((network) => ({
148
+ chainId: network,
149
+ type: getSignatureType(network)
150
+ }));
151
+ }
152
+ const declaration = {
153
+ info,
154
+ supportedChains,
155
+ schema: buildSIWxSchema(),
156
+ _options: options
157
+ };
158
+ return { [SIGN_IN_WITH_X]: declaration };
159
+ }
160
+
161
+ // src/sign-in-with-x/parse.ts
162
+ import { Base64EncodedRegex, safeBase64Decode } from "@bankofai/x402-core/utils";
163
+ function parseSIWxHeader(header) {
164
+ if (!Base64EncodedRegex.test(header)) {
165
+ throw new Error("Invalid SIWX header: not valid base64");
166
+ }
167
+ const jsonStr = safeBase64Decode(header);
168
+ let rawPayload;
169
+ try {
170
+ rawPayload = JSON.parse(jsonStr);
171
+ } catch (error) {
172
+ if (error instanceof SyntaxError) {
173
+ throw new Error("Invalid SIWX header: not valid JSON");
174
+ }
175
+ throw error;
176
+ }
177
+ const parsed = SIWxPayloadSchema.safeParse(rawPayload);
178
+ if (!parsed.success) {
179
+ const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
180
+ throw new Error(`Invalid SIWX header: ${issues}`);
181
+ }
182
+ return parsed.data;
183
+ }
184
+
185
+ // src/sign-in-with-x/validate.ts
186
+ var DEFAULT_MAX_AGE_MS = 5 * 60 * 1e3;
187
+ async function validateSIWxMessage(message, expectedResourceUri, options = {}) {
188
+ const expectedUrl = new URL(expectedResourceUri);
189
+ const maxAge = options.maxAge ?? DEFAULT_MAX_AGE_MS;
190
+ if (message.domain !== expectedUrl.hostname) {
191
+ return {
192
+ valid: false,
193
+ error: `Domain mismatch: expected "${expectedUrl.hostname}", got "${message.domain}"`
194
+ };
195
+ }
196
+ if (!message.uri.startsWith(expectedUrl.origin)) {
197
+ return {
198
+ valid: false,
199
+ error: `URI mismatch: expected origin "${expectedUrl.origin}", got "${message.uri}"`
200
+ };
201
+ }
202
+ const issuedAt = new Date(message.issuedAt);
203
+ if (isNaN(issuedAt.getTime())) {
204
+ return {
205
+ valid: false,
206
+ error: "Invalid issuedAt timestamp"
207
+ };
208
+ }
209
+ const age = Date.now() - issuedAt.getTime();
210
+ if (age > maxAge) {
211
+ return {
212
+ valid: false,
213
+ error: `Message too old: ${Math.round(age / 1e3)}s exceeds ${maxAge / 1e3}s limit`
214
+ };
215
+ }
216
+ if (age < 0) {
217
+ return {
218
+ valid: false,
219
+ error: "issuedAt is in the future"
220
+ };
221
+ }
222
+ if (message.expirationTime) {
223
+ const expiration = new Date(message.expirationTime);
224
+ if (isNaN(expiration.getTime())) {
225
+ return {
226
+ valid: false,
227
+ error: "Invalid expirationTime timestamp"
228
+ };
229
+ }
230
+ if (expiration < /* @__PURE__ */ new Date()) {
231
+ return {
232
+ valid: false,
233
+ error: "Message expired"
234
+ };
235
+ }
236
+ }
237
+ if (message.notBefore) {
238
+ const notBefore = new Date(message.notBefore);
239
+ if (isNaN(notBefore.getTime())) {
240
+ return {
241
+ valid: false,
242
+ error: "Invalid notBefore timestamp"
243
+ };
244
+ }
245
+ if (/* @__PURE__ */ new Date() < notBefore) {
246
+ return {
247
+ valid: false,
248
+ error: "Message not yet valid (notBefore is in the future)"
249
+ };
250
+ }
251
+ }
252
+ if (options.checkNonce) {
253
+ const nonceValid = await options.checkNonce(message.nonce);
254
+ if (!nonceValid) {
255
+ return {
256
+ valid: false,
257
+ error: "Nonce validation failed (possible replay attack)"
258
+ };
259
+ }
260
+ }
261
+ return { valid: true };
262
+ }
263
+
264
+ // src/sign-in-with-x/evm.ts
265
+ import { verifyMessage } from "viem";
266
+ import { SiweMessage } from "@signinwithethereum/siwe";
267
+ function extractEVMChainId(chainId) {
268
+ const match = /^eip155:(\d+)$/.exec(chainId);
269
+ if (!match) {
270
+ throw new Error(`Invalid EVM chainId format: ${chainId}. Expected eip155:<number>`);
271
+ }
272
+ return parseInt(match[1], 10);
273
+ }
274
+ function formatSIWEMessage(info, address) {
275
+ const numericChainId = extractEVMChainId(info.chainId);
276
+ const siweMessage = new SiweMessage({
277
+ domain: info.domain,
278
+ address,
279
+ statement: info.statement,
280
+ uri: info.uri,
281
+ version: info.version,
282
+ chainId: numericChainId,
283
+ nonce: info.nonce,
284
+ issuedAt: info.issuedAt,
285
+ expirationTime: info.expirationTime,
286
+ notBefore: info.notBefore,
287
+ requestId: info.requestId,
288
+ resources: info.resources
289
+ });
290
+ return siweMessage.prepareMessage();
291
+ }
292
+ async function verifyEVMSignature(message, address, signature, verifier) {
293
+ const args = {
294
+ address,
295
+ message,
296
+ signature
297
+ };
298
+ if (verifier) {
299
+ return verifier(args);
300
+ }
301
+ return verifyMessage(args);
302
+ }
303
+ function isEVMSigner(signer) {
304
+ if ("signMessages" in signer && typeof signer.signMessages === "function") {
305
+ return false;
306
+ }
307
+ if ("publicKey" in signer && signer.publicKey) {
308
+ const pk = signer.publicKey;
309
+ if (typeof pk === "object" && pk !== null && "toBase58" in pk) {
310
+ return false;
311
+ }
312
+ if (typeof pk === "string" && !pk.startsWith("0x")) {
313
+ return false;
314
+ }
315
+ }
316
+ if ("account" in signer && signer.account && typeof signer.account === "object") {
317
+ const account = signer.account;
318
+ if (account.address && account.address.startsWith("0x")) {
319
+ return true;
320
+ }
321
+ }
322
+ if ("address" in signer && typeof signer.address === "string" && signer.address.startsWith("0x")) {
323
+ return true;
324
+ }
325
+ return false;
326
+ }
327
+
328
+ // src/sign-in-with-x/verify.ts
329
+ async function verifySIWxSignature(payload, options) {
330
+ try {
331
+ if (payload.chainId.startsWith("eip155:")) {
332
+ return verifyEVMPayload(payload, options?.evmVerifier);
333
+ }
334
+ if (payload.chainId.startsWith("solana:")) {
335
+ return verifySolanaPayload(payload);
336
+ }
337
+ return {
338
+ valid: false,
339
+ error: `Unsupported chain namespace: ${payload.chainId}. Supported: eip155:* (EVM), solana:* (Solana)`
340
+ };
341
+ } catch (error) {
342
+ return {
343
+ valid: false,
344
+ error: error instanceof Error ? error.message : "Verification failed"
345
+ };
346
+ }
347
+ }
348
+ async function verifyEVMPayload(payload, verifier) {
349
+ const message = formatSIWEMessage(
350
+ {
351
+ domain: payload.domain,
352
+ uri: payload.uri,
353
+ statement: payload.statement,
354
+ version: payload.version,
355
+ chainId: payload.chainId,
356
+ type: payload.type,
357
+ nonce: payload.nonce,
358
+ issuedAt: payload.issuedAt,
359
+ expirationTime: payload.expirationTime,
360
+ notBefore: payload.notBefore,
361
+ requestId: payload.requestId,
362
+ resources: payload.resources
363
+ },
364
+ payload.address
365
+ );
366
+ try {
367
+ const valid = await verifyEVMSignature(message, payload.address, payload.signature, verifier);
368
+ if (!valid) {
369
+ return {
370
+ valid: false,
371
+ error: "Signature verification failed"
372
+ };
373
+ }
374
+ return {
375
+ valid: true,
376
+ address: payload.address
377
+ };
378
+ } catch (error) {
379
+ return {
380
+ valid: false,
381
+ error: error instanceof Error ? error.message : "Signature verification failed"
382
+ };
383
+ }
384
+ }
385
+ function verifySolanaPayload(payload) {
386
+ const message = formatSIWSMessage(
387
+ {
388
+ domain: payload.domain,
389
+ uri: payload.uri,
390
+ statement: payload.statement,
391
+ version: payload.version,
392
+ chainId: payload.chainId,
393
+ type: payload.type,
394
+ nonce: payload.nonce,
395
+ issuedAt: payload.issuedAt,
396
+ expirationTime: payload.expirationTime,
397
+ notBefore: payload.notBefore,
398
+ requestId: payload.requestId,
399
+ resources: payload.resources
400
+ },
401
+ payload.address
402
+ );
403
+ let signature;
404
+ let publicKey;
405
+ try {
406
+ signature = decodeBase58(payload.signature);
407
+ publicKey = decodeBase58(payload.address);
408
+ } catch (error) {
409
+ return {
410
+ valid: false,
411
+ error: `Invalid Base58 encoding: ${error instanceof Error ? error.message : "decode failed"}`
412
+ };
413
+ }
414
+ if (signature.length !== 64) {
415
+ return {
416
+ valid: false,
417
+ error: `Invalid signature length: expected 64 bytes, got ${signature.length}`
418
+ };
419
+ }
420
+ if (publicKey.length !== 32) {
421
+ return {
422
+ valid: false,
423
+ error: `Invalid public key length: expected 32 bytes, got ${publicKey.length}`
424
+ };
425
+ }
426
+ const valid = verifySolanaSignature(message, signature, publicKey);
427
+ if (!valid) {
428
+ return {
429
+ valid: false,
430
+ error: "Solana signature verification failed"
431
+ };
432
+ }
433
+ return {
434
+ valid: true,
435
+ address: payload.address
436
+ };
437
+ }
438
+
439
+ // src/sign-in-with-x/sign.ts
440
+ function getEVMAddress(signer) {
441
+ if (signer.account?.address) {
442
+ return signer.account.address;
443
+ }
444
+ if (signer.address) {
445
+ return signer.address;
446
+ }
447
+ throw new Error("EVM signer missing address");
448
+ }
449
+ function getSolanaAddress(signer) {
450
+ if ("address" in signer && signer.address) {
451
+ return signer.address;
452
+ }
453
+ if ("publicKey" in signer) {
454
+ const pk = signer.publicKey;
455
+ return typeof pk === "string" ? pk : pk.toBase58();
456
+ }
457
+ throw new Error("Solana signer missing address or publicKey");
458
+ }
459
+ async function signEVMMessage(message, signer) {
460
+ if (signer.account) {
461
+ return signer.signMessage({ message, account: signer.account });
462
+ }
463
+ return signer.signMessage({ message });
464
+ }
465
+ async function signSolanaMessage(message, signer) {
466
+ const messageBytes = new TextEncoder().encode(message);
467
+ if ("signMessages" in signer) {
468
+ const results = await signer.signMessages([{ content: messageBytes, signatures: {} }]);
469
+ const sigDict = results[0];
470
+ const signatureBytes = Object.values(sigDict)[0];
471
+ return encodeBase58(signatureBytes);
472
+ }
473
+ if ("signMessage" in signer) {
474
+ const signatureBytes = await signer.signMessage(messageBytes);
475
+ return encodeBase58(signatureBytes);
476
+ }
477
+ throw new Error("Solana signer missing signMessage or signMessages method");
478
+ }
479
+
480
+ // src/sign-in-with-x/message.ts
481
+ function createSIWxMessage(serverInfo, address) {
482
+ if (serverInfo.chainId.startsWith("eip155:")) {
483
+ return formatSIWEMessage(serverInfo, address);
484
+ }
485
+ if (serverInfo.chainId.startsWith("solana:")) {
486
+ return formatSIWSMessage(serverInfo, address);
487
+ }
488
+ throw new Error(
489
+ `Unsupported chain namespace: ${serverInfo.chainId}. Supported: eip155:* (EVM), solana:* (Solana)`
490
+ );
491
+ }
492
+
493
+ // src/sign-in-with-x/client.ts
494
+ async function createSIWxPayload(serverExtension, signer) {
495
+ const isSolana = serverExtension.chainId.startsWith("solana:");
496
+ const address = isSolana ? getSolanaAddress(signer) : getEVMAddress(signer);
497
+ const message = createSIWxMessage(serverExtension, address);
498
+ const signature = isSolana ? await signSolanaMessage(message, signer) : await signEVMMessage(message, signer);
499
+ return {
500
+ domain: serverExtension.domain,
501
+ address,
502
+ statement: serverExtension.statement,
503
+ uri: serverExtension.uri,
504
+ version: serverExtension.version,
505
+ chainId: serverExtension.chainId,
506
+ type: serverExtension.type,
507
+ nonce: serverExtension.nonce,
508
+ issuedAt: serverExtension.issuedAt,
509
+ expirationTime: serverExtension.expirationTime,
510
+ notBefore: serverExtension.notBefore,
511
+ requestId: serverExtension.requestId,
512
+ resources: serverExtension.resources,
513
+ signatureScheme: serverExtension.signatureScheme,
514
+ signature
515
+ };
516
+ }
517
+
518
+ // src/sign-in-with-x/encode.ts
519
+ import { safeBase64Encode } from "@bankofai/x402-core/utils";
520
+ function encodeSIWxHeader(payload) {
521
+ return safeBase64Encode(JSON.stringify(payload));
522
+ }
523
+
524
+ // src/sign-in-with-x/hooks.ts
525
+ function createSIWxSettleHook(options) {
526
+ const { storage, onEvent } = options;
527
+ return async (ctx) => {
528
+ if (!ctx.result.success) return;
529
+ const address = ctx.result.payer;
530
+ if (!address) return;
531
+ const resourceUrl = ctx.paymentPayload.resource?.url;
532
+ if (!resourceUrl) return;
533
+ const resource = new URL(resourceUrl).pathname;
534
+ await storage.recordPayment(resource, address);
535
+ onEvent?.({ type: "payment_recorded", resource, address });
536
+ };
537
+ }
538
+ function createSIWxRequestHook(options) {
539
+ const { storage, verifyOptions, onEvent } = options;
540
+ const hasUsedNonce = typeof storage.hasUsedNonce === "function";
541
+ const hasRecordNonce = typeof storage.recordNonce === "function";
542
+ if (hasUsedNonce !== hasRecordNonce) {
543
+ throw new Error(
544
+ "SIWxStorage nonce tracking requires both hasUsedNonce and recordNonce to be implemented"
545
+ );
546
+ }
547
+ return async (context, routeConfig) => {
548
+ const header = context.adapter.getHeader(SIGN_IN_WITH_X) || context.adapter.getHeader(SIGN_IN_WITH_X.toLowerCase());
549
+ if (!header) return;
550
+ try {
551
+ const payload = parseSIWxHeader(header);
552
+ const resourceUri = context.adapter.getUrl();
553
+ const validation = await validateSIWxMessage(payload, resourceUri);
554
+ if (!validation.valid) {
555
+ onEvent?.({ type: "validation_failed", resource: context.path, error: validation.error });
556
+ return;
557
+ }
558
+ const verification = await verifySIWxSignature(payload, verifyOptions);
559
+ if (!verification.valid || !verification.address) {
560
+ onEvent?.({ type: "validation_failed", resource: context.path, error: verification.error });
561
+ return;
562
+ }
563
+ if (storage.hasUsedNonce) {
564
+ const nonceUsed = await storage.hasUsedNonce(payload.nonce);
565
+ if (nonceUsed) {
566
+ onEvent?.({ type: "nonce_reused", resource: context.path, nonce: payload.nonce });
567
+ return;
568
+ }
569
+ }
570
+ const isAuthOnly = Array.isArray(routeConfig?.accepts) && routeConfig.accepts.length === 0;
571
+ const shouldGrant = isAuthOnly || await storage.hasPaid(context.path, verification.address);
572
+ if (shouldGrant) {
573
+ if (storage.recordNonce) {
574
+ await storage.recordNonce(payload.nonce);
575
+ }
576
+ onEvent?.({
577
+ type: "access_granted",
578
+ resource: context.path,
579
+ address: verification.address
580
+ });
581
+ return { grantAccess: true };
582
+ }
583
+ } catch (err) {
584
+ onEvent?.({
585
+ type: "validation_failed",
586
+ resource: context.path,
587
+ error: err instanceof Error ? err.message : "Unknown error"
588
+ });
589
+ }
590
+ };
591
+ }
592
+ function createSIWxClientHook(signer) {
593
+ const signerIsSolana = isSolanaSigner(signer);
594
+ const expectedSignatureType = signerIsSolana ? "ed25519" : "eip191";
595
+ return async (context) => {
596
+ const extensions = context.paymentRequired.extensions ?? {};
597
+ const siwxExtension = extensions[SIGN_IN_WITH_X];
598
+ if (!siwxExtension?.supportedChains) return;
599
+ try {
600
+ const matchingChain = siwxExtension.supportedChains.find(
601
+ (chain) => chain.type === expectedSignatureType
602
+ );
603
+ if (!matchingChain) {
604
+ return;
605
+ }
606
+ const completeInfo = {
607
+ ...siwxExtension.info,
608
+ chainId: matchingChain.chainId,
609
+ type: matchingChain.type
610
+ };
611
+ const payload = await createSIWxPayload(completeInfo, signer);
612
+ const header = encodeSIWxHeader(payload);
613
+ return { headers: { [SIGN_IN_WITH_X]: header } };
614
+ } catch {
615
+ }
616
+ };
617
+ }
618
+ function createSIWxClientExtension(options) {
619
+ const hooks = options.signers.map(createSIWxClientHook);
620
+ return {
621
+ key: SIGN_IN_WITH_X,
622
+ transportHooks: {
623
+ http: {
624
+ onPaymentRequired: async (_declaration, context) => {
625
+ for (const hook of hooks) {
626
+ const result = await hook(context);
627
+ if (result?.headers) return result;
628
+ }
629
+ }
630
+ }
631
+ }
632
+ };
633
+ }
634
+
635
+ // src/sign-in-with-x/server.ts
636
+ async function enrichSIWxPaymentRequiredResponse(declaration, context) {
637
+ const decl = declaration;
638
+ const opts = decl._options ?? {};
639
+ const resourceUri = opts.resourceUri ?? context.resourceInfo.url;
640
+ let domain = opts.domain;
641
+ if (!domain && resourceUri) {
642
+ try {
643
+ domain = new URL(resourceUri).hostname;
644
+ } catch {
645
+ domain = void 0;
646
+ }
647
+ }
648
+ let supportedNetworks;
649
+ if (opts.network) {
650
+ supportedNetworks = Array.isArray(opts.network) ? opts.network : [opts.network];
651
+ } else {
652
+ supportedNetworks = [...new Set(context.requirements.map((r) => r.network))];
653
+ }
654
+ const nonce = Array.from(globalThis.crypto.getRandomValues(new Uint8Array(16))).map((b) => b.toString(16).padStart(2, "0")).join("");
655
+ const issuedAt = (/* @__PURE__ */ new Date()).toISOString();
656
+ const expirationTime = opts.expirationSeconds !== void 0 ? new Date(Date.now() + opts.expirationSeconds * 1e3).toISOString() : void 0;
657
+ const info = {
658
+ domain: domain ?? "",
659
+ uri: resourceUri,
660
+ version: opts.version ?? "1",
661
+ nonce,
662
+ issuedAt,
663
+ resources: [resourceUri]
664
+ };
665
+ if (expirationTime) {
666
+ info.expirationTime = expirationTime;
667
+ }
668
+ if (opts.statement) {
669
+ info.statement = opts.statement;
670
+ }
671
+ const supportedChains = supportedNetworks.map((network) => ({
672
+ chainId: network,
673
+ type: getSignatureType(network)
674
+ }));
675
+ return {
676
+ info,
677
+ supportedChains,
678
+ schema: buildSIWxSchema()
679
+ };
680
+ }
681
+ function createSIWxResourceServerExtension(options) {
682
+ const settleHook = createSIWxSettleHook(options);
683
+ const requestHook = createSIWxRequestHook(options);
684
+ return {
685
+ key: SIGN_IN_WITH_X,
686
+ enrichPaymentRequiredResponse: enrichSIWxPaymentRequiredResponse,
687
+ transportHooks: {
688
+ http: {
689
+ onProtectedRequest: async (_declaration, context, routeConfig) => requestHook(context, routeConfig)
690
+ }
691
+ },
692
+ hooks: {
693
+ onAfterSettle: async (_declaration, context) => settleHook(context)
694
+ }
695
+ };
696
+ }
697
+
698
+ // src/sign-in-with-x/fetch.ts
699
+ import { decodePaymentRequiredHeader } from "@bankofai/x402-core/http";
700
+ function wrapFetchWithSIWx(fetch, signer) {
701
+ return async (input, init) => {
702
+ const request = new Request(input, init);
703
+ const clonedRequest = request.clone();
704
+ const response = await fetch(request);
705
+ if (response.status !== 402) {
706
+ return response;
707
+ }
708
+ const paymentRequiredHeader = response.headers.get("PAYMENT-REQUIRED");
709
+ if (!paymentRequiredHeader) {
710
+ return response;
711
+ }
712
+ const paymentRequired = decodePaymentRequiredHeader(paymentRequiredHeader);
713
+ const siwxExtension = paymentRequired.extensions?.[SIGN_IN_WITH_X];
714
+ if (!siwxExtension?.supportedChains) {
715
+ return response;
716
+ }
717
+ if (clonedRequest.headers.has(SIGN_IN_WITH_X)) {
718
+ throw new Error("SIWX authentication already attempted");
719
+ }
720
+ const paymentNetwork = paymentRequired.accepts?.[0]?.network;
721
+ if (!paymentNetwork) {
722
+ return response;
723
+ }
724
+ const matchingChain = siwxExtension.supportedChains.find(
725
+ (chain) => chain.chainId === paymentNetwork
726
+ );
727
+ if (!matchingChain) {
728
+ return response;
729
+ }
730
+ const completeInfo = {
731
+ ...siwxExtension.info,
732
+ chainId: matchingChain.chainId,
733
+ type: matchingChain.type
734
+ };
735
+ const payload = await createSIWxPayload(completeInfo, signer);
736
+ const siwxHeader = encodeSIWxHeader(payload);
737
+ clonedRequest.headers.set(SIGN_IN_WITH_X, siwxHeader);
738
+ return fetch(clonedRequest);
739
+ };
740
+ }
741
+
742
+ // src/sign-in-with-x/storage.ts
743
+ var InMemorySIWxStorage = class {
744
+ constructor() {
745
+ this.paidAddresses = /* @__PURE__ */ new Map();
746
+ }
747
+ /**
748
+ * Check if an address has paid for a resource.
749
+ *
750
+ * @param resource - The resource path
751
+ * @param address - The wallet address to check
752
+ * @returns True if the address has paid
753
+ */
754
+ hasPaid(resource, address) {
755
+ return this.paidAddresses.get(resource)?.has(address.toLowerCase()) ?? false;
756
+ }
757
+ /**
758
+ * Record that an address has paid for a resource.
759
+ *
760
+ * @param resource - The resource path
761
+ * @param address - The wallet address that paid
762
+ */
763
+ recordPayment(resource, address) {
764
+ if (!this.paidAddresses.has(resource)) {
765
+ this.paidAddresses.set(resource, /* @__PURE__ */ new Set());
766
+ }
767
+ this.paidAddresses.get(resource).add(address.toLowerCase());
768
+ }
769
+ };
770
+
771
+ export {
772
+ SIGN_IN_WITH_X,
773
+ SIWxPayloadSchema,
774
+ SOLANA_MAINNET,
775
+ SOLANA_DEVNET,
776
+ SOLANA_TESTNET,
777
+ extractSolanaChainReference,
778
+ formatSIWSMessage,
779
+ verifySolanaSignature,
780
+ decodeBase58,
781
+ encodeBase58,
782
+ isSolanaSigner,
783
+ buildSIWxSchema,
784
+ declareSIWxExtension,
785
+ parseSIWxHeader,
786
+ validateSIWxMessage,
787
+ extractEVMChainId,
788
+ formatSIWEMessage,
789
+ verifyEVMSignature,
790
+ isEVMSigner,
791
+ verifySIWxSignature,
792
+ getEVMAddress,
793
+ getSolanaAddress,
794
+ signEVMMessage,
795
+ signSolanaMessage,
796
+ createSIWxMessage,
797
+ createSIWxPayload,
798
+ encodeSIWxHeader,
799
+ createSIWxSettleHook,
800
+ createSIWxRequestHook,
801
+ createSIWxClientHook,
802
+ createSIWxClientExtension,
803
+ createSIWxResourceServerExtension,
804
+ wrapFetchWithSIWx,
805
+ InMemorySIWxStorage
806
+ };
807
+ //# sourceMappingURL=chunk-N74HQTNO.mjs.map