@x402/extensions 2.1.0 → 2.3.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 (33) hide show
  1. package/README.md +324 -95
  2. package/dist/cjs/bazaar/index.d.ts +3 -547
  3. package/dist/cjs/bazaar/index.js +26 -3
  4. package/dist/cjs/bazaar/index.js.map +1 -1
  5. package/dist/cjs/index-DvDlinmy.d.ts +575 -0
  6. package/dist/cjs/index.d.ts +4 -1
  7. package/dist/cjs/index.js +1022 -5
  8. package/dist/cjs/index.js.map +1 -1
  9. package/dist/cjs/payment-identifier/index.d.ts +345 -0
  10. package/dist/cjs/payment-identifier/index.js +285 -0
  11. package/dist/cjs/payment-identifier/index.js.map +1 -0
  12. package/dist/cjs/sign-in-with-x/index.d.ts +1054 -1
  13. package/dist/cjs/sign-in-with-x/index.js +766 -0
  14. package/dist/cjs/sign-in-with-x/index.js.map +1 -1
  15. package/dist/esm/bazaar/index.d.mts +3 -547
  16. package/dist/esm/bazaar/index.mjs +1 -1
  17. package/dist/esm/chunk-73HCOE6N.mjs +233 -0
  18. package/dist/esm/chunk-73HCOE6N.mjs.map +1 -0
  19. package/dist/esm/{chunk-STXY3Q5R.mjs → chunk-DFJ4ZQFO.mjs} +27 -4
  20. package/dist/esm/chunk-DFJ4ZQFO.mjs.map +1 -0
  21. package/dist/esm/chunk-E3F2XHTI.mjs +719 -0
  22. package/dist/esm/chunk-E3F2XHTI.mjs.map +1 -0
  23. package/dist/esm/index-DvDlinmy.d.mts +575 -0
  24. package/dist/esm/index.d.mts +4 -1
  25. package/dist/esm/index.mjs +102 -3
  26. package/dist/esm/payment-identifier/index.d.mts +345 -0
  27. package/dist/esm/payment-identifier/index.mjs +39 -0
  28. package/dist/esm/sign-in-with-x/index.d.mts +1054 -1
  29. package/dist/esm/sign-in-with-x/index.mjs +66 -1
  30. package/package.json +16 -2
  31. package/dist/esm/chunk-MKFJ5AA3.mjs +0 -1
  32. package/dist/esm/chunk-STXY3Q5R.mjs.map +0 -1
  33. /package/dist/esm/{chunk-MKFJ5AA3.mjs.map → payment-identifier/index.mjs.map} +0 -0
package/dist/cjs/index.js CHANGED
@@ -31,16 +31,64 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
33
  BAZAAR: () => BAZAAR,
34
+ InMemorySIWxStorage: () => InMemorySIWxStorage,
35
+ PAYMENT_IDENTIFIER: () => PAYMENT_IDENTIFIER,
36
+ PAYMENT_ID_MAX_LENGTH: () => PAYMENT_ID_MAX_LENGTH,
37
+ PAYMENT_ID_MIN_LENGTH: () => PAYMENT_ID_MIN_LENGTH,
38
+ PAYMENT_ID_PATTERN: () => PAYMENT_ID_PATTERN,
39
+ SIGN_IN_WITH_X: () => SIGN_IN_WITH_X,
40
+ SIWxPayloadSchema: () => SIWxPayloadSchema,
41
+ SOLANA_DEVNET: () => SOLANA_DEVNET,
42
+ SOLANA_MAINNET: () => SOLANA_MAINNET,
43
+ SOLANA_TESTNET: () => SOLANA_TESTNET,
44
+ appendPaymentIdentifierToExtensions: () => appendPaymentIdentifierToExtensions,
34
45
  bazaarResourceServerExtension: () => bazaarResourceServerExtension,
46
+ buildSIWxSchema: () => buildSIWxSchema,
47
+ createSIWxClientHook: () => createSIWxClientHook,
48
+ createSIWxMessage: () => createSIWxMessage,
49
+ createSIWxPayload: () => createSIWxPayload,
50
+ createSIWxRequestHook: () => createSIWxRequestHook,
51
+ createSIWxSettleHook: () => createSIWxSettleHook,
35
52
  declareDiscoveryExtension: () => declareDiscoveryExtension,
53
+ declarePaymentIdentifierExtension: () => declarePaymentIdentifierExtension,
54
+ declareSIWxExtension: () => declareSIWxExtension,
55
+ decodeBase58: () => decodeBase58,
56
+ encodeBase58: () => encodeBase58,
57
+ encodeSIWxHeader: () => encodeSIWxHeader,
58
+ extractAndValidatePaymentIdentifier: () => extractAndValidatePaymentIdentifier,
36
59
  extractDiscoveryInfo: () => extractDiscoveryInfo,
37
60
  extractDiscoveryInfoFromExtension: () => extractDiscoveryInfoFromExtension,
38
61
  extractDiscoveryInfoV1: () => extractDiscoveryInfoV1,
62
+ extractEVMChainId: () => extractEVMChainId,
63
+ extractPaymentIdentifier: () => extractPaymentIdentifier,
39
64
  extractResourceMetadataV1: () => extractResourceMetadataV1,
65
+ extractSolanaChainReference: () => extractSolanaChainReference,
66
+ formatSIWEMessage: () => formatSIWEMessage,
67
+ formatSIWSMessage: () => formatSIWSMessage,
68
+ generatePaymentId: () => generatePaymentId,
69
+ getEVMAddress: () => getEVMAddress,
70
+ getSolanaAddress: () => getSolanaAddress,
71
+ hasPaymentIdentifier: () => hasPaymentIdentifier,
40
72
  isDiscoverableV1: () => isDiscoverableV1,
73
+ isPaymentIdentifierExtension: () => isPaymentIdentifierExtension,
74
+ isPaymentIdentifierRequired: () => isPaymentIdentifierRequired,
75
+ isValidPaymentId: () => isValidPaymentId,
76
+ parseSIWxHeader: () => parseSIWxHeader,
77
+ paymentIdentifierResourceServerExtension: () => paymentIdentifierResourceServerExtension,
78
+ paymentIdentifierSchema: () => paymentIdentifierSchema,
79
+ signEVMMessage: () => signEVMMessage,
80
+ signSolanaMessage: () => signSolanaMessage,
81
+ siwxResourceServerExtension: () => siwxResourceServerExtension,
41
82
  validateAndExtract: () => validateAndExtract,
42
83
  validateDiscoveryExtension: () => validateDiscoveryExtension,
43
- withBazaar: () => withBazaar
84
+ validatePaymentIdentifier: () => validatePaymentIdentifier,
85
+ validatePaymentIdentifierRequirement: () => validatePaymentIdentifierRequirement,
86
+ validateSIWxMessage: () => validateSIWxMessage,
87
+ verifyEVMSignature: () => verifyEVMSignature,
88
+ verifySIWxSignature: () => verifySIWxSignature,
89
+ verifySolanaSignature: () => verifySolanaSignature,
90
+ withBazaar: () => withBazaar,
91
+ wrapFetchWithSIWx: () => wrapFetchWithSIWx
44
92
  });
45
93
  module.exports = __toCommonJS(src_exports);
46
94
 
@@ -117,7 +165,7 @@ function createBodyDiscoveryExtension({
117
165
  method,
118
166
  input = {},
119
167
  inputSchema = { properties: {} },
120
- bodyType = "json",
168
+ bodyType,
121
169
  output
122
170
  }) {
123
171
  return {
@@ -198,6 +246,14 @@ var bazaarResourceServerExtension = {
198
246
  }
199
247
  const extension = declaration;
200
248
  const method = transportContext.method;
249
+ const existingInputProps = extension.schema?.properties?.input?.properties || {};
250
+ const updatedInputProps = {
251
+ ...existingInputProps,
252
+ method: {
253
+ type: "string",
254
+ enum: [method]
255
+ }
256
+ };
201
257
  return {
202
258
  ...extension,
203
259
  info: {
@@ -213,6 +269,7 @@ var bazaarResourceServerExtension = {
213
269
  ...extension.schema?.properties || {},
214
270
  input: {
215
271
  ...extension.schema?.properties?.input || {},
272
+ properties: updatedInputProps,
216
273
  required: [
217
274
  ...extension.schema?.properties?.input?.required || [],
218
275
  ...!(extension.schema?.properties?.input?.required || []).includes("method") ? ["method"] : []
@@ -225,7 +282,7 @@ var bazaarResourceServerExtension = {
225
282
  };
226
283
 
227
284
  // src/bazaar/facilitator.ts
228
- var import__ = __toESM(require("ajv/dist/2020"));
285
+ var import__ = __toESM(require("ajv/dist/2020.js"));
229
286
 
230
287
  // src/bazaar/v1/facilitator.ts
231
288
  function hasV1OutputSchema(obj) {
@@ -401,8 +458,22 @@ function extractDiscoveryInfo(paymentPayload, paymentRequirements, validate = tr
401
458
  if (!discoveryInfo) {
402
459
  return null;
403
460
  }
461
+ const url = new URL(resourceUrl);
462
+ const normalizedResourceUrl = `${url.origin}${url.pathname}`;
463
+ let description;
464
+ let mimeType;
465
+ if (paymentPayload.x402Version === 2) {
466
+ description = paymentPayload.resource?.description;
467
+ mimeType = paymentPayload.resource?.mimeType;
468
+ } else if (paymentPayload.x402Version === 1) {
469
+ const requirementsV1 = paymentRequirements;
470
+ description = requirementsV1.description;
471
+ mimeType = requirementsV1.mimeType;
472
+ }
404
473
  return {
405
- resourceUrl,
474
+ resourceUrl: normalizedResourceUrl,
475
+ description,
476
+ mimeType,
406
477
  method: discoveryInfo.input.method,
407
478
  x402Version: paymentPayload.x402Version,
408
479
  discoveryInfo
@@ -474,18 +545,964 @@ function withBazaar(client) {
474
545
  };
475
546
  return extended;
476
547
  }
548
+
549
+ // src/sign-in-with-x/types.ts
550
+ var import_zod = require("zod");
551
+ var SIGN_IN_WITH_X = "sign-in-with-x";
552
+ var SIWxPayloadSchema = import_zod.z.object({
553
+ domain: import_zod.z.string(),
554
+ address: import_zod.z.string(),
555
+ statement: import_zod.z.string().optional(),
556
+ uri: import_zod.z.string(),
557
+ version: import_zod.z.string(),
558
+ chainId: import_zod.z.string(),
559
+ type: import_zod.z.enum(["eip191", "ed25519"]),
560
+ nonce: import_zod.z.string(),
561
+ issuedAt: import_zod.z.string(),
562
+ expirationTime: import_zod.z.string().optional(),
563
+ notBefore: import_zod.z.string().optional(),
564
+ requestId: import_zod.z.string().optional(),
565
+ resources: import_zod.z.array(import_zod.z.string()).optional(),
566
+ signatureScheme: import_zod.z.enum(["eip191", "eip1271", "eip6492", "siws"]).optional(),
567
+ signature: import_zod.z.string()
568
+ });
569
+
570
+ // src/sign-in-with-x/solana.ts
571
+ var import_base = require("@scure/base");
572
+ var import_tweetnacl = __toESM(require("tweetnacl"));
573
+ var SOLANA_MAINNET = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
574
+ var SOLANA_DEVNET = "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1";
575
+ var SOLANA_TESTNET = "solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z";
576
+ function extractSolanaChainReference(chainId) {
577
+ const [, reference] = chainId.split(":");
578
+ return reference;
579
+ }
580
+ function formatSIWSMessage(info, address) {
581
+ const lines = [
582
+ `${info.domain} wants you to sign in with your Solana account:`,
583
+ address,
584
+ ""
585
+ ];
586
+ if (info.statement) {
587
+ lines.push(info.statement, "");
588
+ }
589
+ lines.push(
590
+ `URI: ${info.uri}`,
591
+ `Version: ${info.version}`,
592
+ `Chain ID: ${extractSolanaChainReference(info.chainId)}`,
593
+ `Nonce: ${info.nonce}`,
594
+ `Issued At: ${info.issuedAt}`
595
+ );
596
+ if (info.expirationTime) {
597
+ lines.push(`Expiration Time: ${info.expirationTime}`);
598
+ }
599
+ if (info.notBefore) {
600
+ lines.push(`Not Before: ${info.notBefore}`);
601
+ }
602
+ if (info.requestId) {
603
+ lines.push(`Request ID: ${info.requestId}`);
604
+ }
605
+ if (info.resources && info.resources.length > 0) {
606
+ lines.push("Resources:");
607
+ for (const resource of info.resources) {
608
+ lines.push(`- ${resource}`);
609
+ }
610
+ }
611
+ return lines.join("\n");
612
+ }
613
+ function verifySolanaSignature(message, signature, publicKey) {
614
+ const messageBytes = new TextEncoder().encode(message);
615
+ return import_tweetnacl.default.sign.detached.verify(messageBytes, signature, publicKey);
616
+ }
617
+ function decodeBase58(encoded) {
618
+ return import_base.base58.decode(encoded);
619
+ }
620
+ function encodeBase58(bytes) {
621
+ return import_base.base58.encode(bytes);
622
+ }
623
+
624
+ // src/sign-in-with-x/schema.ts
625
+ function buildSIWxSchema() {
626
+ return {
627
+ $schema: "https://json-schema.org/draft/2020-12/schema",
628
+ type: "object",
629
+ properties: {
630
+ domain: { type: "string" },
631
+ address: { type: "string" },
632
+ statement: { type: "string" },
633
+ uri: { type: "string", format: "uri" },
634
+ version: { type: "string" },
635
+ chainId: { type: "string" },
636
+ type: { type: "string" },
637
+ nonce: { type: "string" },
638
+ issuedAt: { type: "string", format: "date-time" },
639
+ expirationTime: { type: "string", format: "date-time" },
640
+ notBefore: { type: "string", format: "date-time" },
641
+ requestId: { type: "string" },
642
+ resources: { type: "array", items: { type: "string", format: "uri" } },
643
+ signature: { type: "string" }
644
+ },
645
+ required: [
646
+ "domain",
647
+ "address",
648
+ "uri",
649
+ "version",
650
+ "chainId",
651
+ "type",
652
+ "nonce",
653
+ "issuedAt",
654
+ "signature"
655
+ ]
656
+ };
657
+ }
658
+
659
+ // src/sign-in-with-x/declare.ts
660
+ function getSignatureType(network) {
661
+ return network.startsWith("solana:") ? "ed25519" : "eip191";
662
+ }
663
+ function declareSIWxExtension(options = {}) {
664
+ const info = {
665
+ version: options.version ?? "1"
666
+ };
667
+ if (options.domain) {
668
+ info.domain = options.domain;
669
+ }
670
+ if (options.resourceUri) {
671
+ info.uri = options.resourceUri;
672
+ info.resources = [options.resourceUri];
673
+ }
674
+ if (options.statement) {
675
+ info.statement = options.statement;
676
+ }
677
+ let supportedChains = [];
678
+ if (options.network) {
679
+ const networks = Array.isArray(options.network) ? options.network : [options.network];
680
+ supportedChains = networks.map((network) => ({
681
+ chainId: network,
682
+ type: getSignatureType(network)
683
+ }));
684
+ }
685
+ const declaration = {
686
+ info,
687
+ supportedChains,
688
+ schema: buildSIWxSchema(),
689
+ _options: options
690
+ };
691
+ return { [SIGN_IN_WITH_X]: declaration };
692
+ }
693
+
694
+ // src/sign-in-with-x/server.ts
695
+ var import_crypto = require("crypto");
696
+ var siwxResourceServerExtension = {
697
+ key: SIGN_IN_WITH_X,
698
+ enrichPaymentRequiredResponse: async (declaration, context) => {
699
+ const decl = declaration;
700
+ const opts = decl._options ?? {};
701
+ const resourceUri = opts.resourceUri ?? context.resourceInfo.url;
702
+ let domain = opts.domain;
703
+ if (!domain && resourceUri) {
704
+ try {
705
+ domain = new URL(resourceUri).hostname;
706
+ } catch {
707
+ }
708
+ }
709
+ let networks;
710
+ if (opts.network) {
711
+ networks = Array.isArray(opts.network) ? opts.network : [opts.network];
712
+ } else {
713
+ networks = [...new Set(context.requirements.map((r) => r.network))];
714
+ }
715
+ const nonce = (0, import_crypto.randomBytes)(16).toString("hex");
716
+ const issuedAt = (/* @__PURE__ */ new Date()).toISOString();
717
+ const expirationSeconds = opts.expirationSeconds;
718
+ const expirationTime = expirationSeconds !== void 0 ? new Date(Date.now() + expirationSeconds * 1e3).toISOString() : void 0;
719
+ const info = {
720
+ domain: domain ?? "",
721
+ uri: resourceUri,
722
+ version: opts.version ?? "1",
723
+ nonce,
724
+ issuedAt,
725
+ resources: [resourceUri]
726
+ };
727
+ if (expirationTime) {
728
+ info.expirationTime = expirationTime;
729
+ }
730
+ if (opts.statement) {
731
+ info.statement = opts.statement;
732
+ }
733
+ const supportedChains = networks.map((network) => ({
734
+ chainId: network,
735
+ type: getSignatureType(network)
736
+ }));
737
+ return {
738
+ info,
739
+ supportedChains,
740
+ schema: buildSIWxSchema()
741
+ };
742
+ }
743
+ };
744
+
745
+ // src/sign-in-with-x/parse.ts
746
+ var import_utils = require("@x402/core/utils");
747
+ function parseSIWxHeader(header) {
748
+ if (!import_utils.Base64EncodedRegex.test(header)) {
749
+ throw new Error("Invalid SIWX header: not valid base64");
750
+ }
751
+ const jsonStr = (0, import_utils.safeBase64Decode)(header);
752
+ let rawPayload;
753
+ try {
754
+ rawPayload = JSON.parse(jsonStr);
755
+ } catch (error) {
756
+ if (error instanceof SyntaxError) {
757
+ throw new Error("Invalid SIWX header: not valid JSON");
758
+ }
759
+ throw error;
760
+ }
761
+ const parsed = SIWxPayloadSchema.safeParse(rawPayload);
762
+ if (!parsed.success) {
763
+ const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
764
+ throw new Error(`Invalid SIWX header: ${issues}`);
765
+ }
766
+ return parsed.data;
767
+ }
768
+
769
+ // src/sign-in-with-x/validate.ts
770
+ var DEFAULT_MAX_AGE_MS = 5 * 60 * 1e3;
771
+ async function validateSIWxMessage(message, expectedResourceUri, options = {}) {
772
+ const expectedUrl = new URL(expectedResourceUri);
773
+ const maxAge = options.maxAge ?? DEFAULT_MAX_AGE_MS;
774
+ if (message.domain !== expectedUrl.hostname) {
775
+ return {
776
+ valid: false,
777
+ error: `Domain mismatch: expected "${expectedUrl.hostname}", got "${message.domain}"`
778
+ };
779
+ }
780
+ if (!message.uri.startsWith(expectedUrl.origin)) {
781
+ return {
782
+ valid: false,
783
+ error: `URI mismatch: expected origin "${expectedUrl.origin}", got "${message.uri}"`
784
+ };
785
+ }
786
+ const issuedAt = new Date(message.issuedAt);
787
+ if (isNaN(issuedAt.getTime())) {
788
+ return {
789
+ valid: false,
790
+ error: "Invalid issuedAt timestamp"
791
+ };
792
+ }
793
+ const age = Date.now() - issuedAt.getTime();
794
+ if (age > maxAge) {
795
+ return {
796
+ valid: false,
797
+ error: `Message too old: ${Math.round(age / 1e3)}s exceeds ${maxAge / 1e3}s limit`
798
+ };
799
+ }
800
+ if (age < 0) {
801
+ return {
802
+ valid: false,
803
+ error: "issuedAt is in the future"
804
+ };
805
+ }
806
+ if (message.expirationTime) {
807
+ const expiration = new Date(message.expirationTime);
808
+ if (isNaN(expiration.getTime())) {
809
+ return {
810
+ valid: false,
811
+ error: "Invalid expirationTime timestamp"
812
+ };
813
+ }
814
+ if (expiration < /* @__PURE__ */ new Date()) {
815
+ return {
816
+ valid: false,
817
+ error: "Message expired"
818
+ };
819
+ }
820
+ }
821
+ if (message.notBefore) {
822
+ const notBefore = new Date(message.notBefore);
823
+ if (isNaN(notBefore.getTime())) {
824
+ return {
825
+ valid: false,
826
+ error: "Invalid notBefore timestamp"
827
+ };
828
+ }
829
+ if (/* @__PURE__ */ new Date() < notBefore) {
830
+ return {
831
+ valid: false,
832
+ error: "Message not yet valid (notBefore is in the future)"
833
+ };
834
+ }
835
+ }
836
+ if (options.checkNonce) {
837
+ const nonceValid = await options.checkNonce(message.nonce);
838
+ if (!nonceValid) {
839
+ return {
840
+ valid: false,
841
+ error: "Nonce validation failed (possible replay attack)"
842
+ };
843
+ }
844
+ }
845
+ return { valid: true };
846
+ }
847
+
848
+ // src/sign-in-with-x/evm.ts
849
+ var import_viem = require("viem");
850
+ var import_siwe = require("siwe");
851
+ function extractEVMChainId(chainId) {
852
+ const match = /^eip155:(\d+)$/.exec(chainId);
853
+ if (!match) {
854
+ throw new Error(`Invalid EVM chainId format: ${chainId}. Expected eip155:<number>`);
855
+ }
856
+ return parseInt(match[1], 10);
857
+ }
858
+ function formatSIWEMessage(info, address) {
859
+ const numericChainId = extractEVMChainId(info.chainId);
860
+ const siweMessage = new import_siwe.SiweMessage({
861
+ domain: info.domain,
862
+ address,
863
+ statement: info.statement,
864
+ uri: info.uri,
865
+ version: info.version,
866
+ chainId: numericChainId,
867
+ nonce: info.nonce,
868
+ issuedAt: info.issuedAt,
869
+ expirationTime: info.expirationTime,
870
+ notBefore: info.notBefore,
871
+ requestId: info.requestId,
872
+ resources: info.resources
873
+ });
874
+ return siweMessage.prepareMessage();
875
+ }
876
+ async function verifyEVMSignature(message, address, signature, verifier) {
877
+ const args = {
878
+ address,
879
+ message,
880
+ signature
881
+ };
882
+ if (verifier) {
883
+ return verifier(args);
884
+ }
885
+ return (0, import_viem.verifyMessage)(args);
886
+ }
887
+
888
+ // src/sign-in-with-x/verify.ts
889
+ async function verifySIWxSignature(payload, options) {
890
+ try {
891
+ if (payload.chainId.startsWith("eip155:")) {
892
+ return verifyEVMPayload(payload, options?.evmVerifier);
893
+ }
894
+ if (payload.chainId.startsWith("solana:")) {
895
+ return verifySolanaPayload(payload);
896
+ }
897
+ return {
898
+ valid: false,
899
+ error: `Unsupported chain namespace: ${payload.chainId}. Supported: eip155:* (EVM), solana:* (Solana)`
900
+ };
901
+ } catch (error) {
902
+ return {
903
+ valid: false,
904
+ error: error instanceof Error ? error.message : "Verification failed"
905
+ };
906
+ }
907
+ }
908
+ async function verifyEVMPayload(payload, verifier) {
909
+ const message = formatSIWEMessage(
910
+ {
911
+ domain: payload.domain,
912
+ uri: payload.uri,
913
+ statement: payload.statement,
914
+ version: payload.version,
915
+ chainId: payload.chainId,
916
+ type: payload.type,
917
+ nonce: payload.nonce,
918
+ issuedAt: payload.issuedAt,
919
+ expirationTime: payload.expirationTime,
920
+ notBefore: payload.notBefore,
921
+ requestId: payload.requestId,
922
+ resources: payload.resources
923
+ },
924
+ payload.address
925
+ );
926
+ try {
927
+ const valid = await verifyEVMSignature(message, payload.address, payload.signature, verifier);
928
+ if (!valid) {
929
+ return {
930
+ valid: false,
931
+ error: "Signature verification failed"
932
+ };
933
+ }
934
+ return {
935
+ valid: true,
936
+ address: payload.address
937
+ };
938
+ } catch (error) {
939
+ return {
940
+ valid: false,
941
+ error: error instanceof Error ? error.message : "Signature verification failed"
942
+ };
943
+ }
944
+ }
945
+ function verifySolanaPayload(payload) {
946
+ const message = formatSIWSMessage(
947
+ {
948
+ domain: payload.domain,
949
+ uri: payload.uri,
950
+ statement: payload.statement,
951
+ version: payload.version,
952
+ chainId: payload.chainId,
953
+ type: payload.type,
954
+ nonce: payload.nonce,
955
+ issuedAt: payload.issuedAt,
956
+ expirationTime: payload.expirationTime,
957
+ notBefore: payload.notBefore,
958
+ requestId: payload.requestId,
959
+ resources: payload.resources
960
+ },
961
+ payload.address
962
+ );
963
+ let signature;
964
+ let publicKey;
965
+ try {
966
+ signature = decodeBase58(payload.signature);
967
+ publicKey = decodeBase58(payload.address);
968
+ } catch (error) {
969
+ return {
970
+ valid: false,
971
+ error: `Invalid Base58 encoding: ${error instanceof Error ? error.message : "decode failed"}`
972
+ };
973
+ }
974
+ if (signature.length !== 64) {
975
+ return {
976
+ valid: false,
977
+ error: `Invalid signature length: expected 64 bytes, got ${signature.length}`
978
+ };
979
+ }
980
+ if (publicKey.length !== 32) {
981
+ return {
982
+ valid: false,
983
+ error: `Invalid public key length: expected 32 bytes, got ${publicKey.length}`
984
+ };
985
+ }
986
+ const valid = verifySolanaSignature(message, signature, publicKey);
987
+ if (!valid) {
988
+ return {
989
+ valid: false,
990
+ error: "Solana signature verification failed"
991
+ };
992
+ }
993
+ return {
994
+ valid: true,
995
+ address: payload.address
996
+ };
997
+ }
998
+
999
+ // src/sign-in-with-x/message.ts
1000
+ function createSIWxMessage(serverInfo, address) {
1001
+ if (serverInfo.chainId.startsWith("eip155:")) {
1002
+ return formatSIWEMessage(serverInfo, address);
1003
+ }
1004
+ if (serverInfo.chainId.startsWith("solana:")) {
1005
+ return formatSIWSMessage(serverInfo, address);
1006
+ }
1007
+ throw new Error(
1008
+ `Unsupported chain namespace: ${serverInfo.chainId}. Supported: eip155:* (EVM), solana:* (Solana)`
1009
+ );
1010
+ }
1011
+
1012
+ // src/sign-in-with-x/sign.ts
1013
+ function getEVMAddress(signer) {
1014
+ if (signer.account?.address) {
1015
+ return signer.account.address;
1016
+ }
1017
+ if (signer.address) {
1018
+ return signer.address;
1019
+ }
1020
+ throw new Error("EVM signer missing address");
1021
+ }
1022
+ function getSolanaAddress(signer) {
1023
+ const pk = signer.publicKey;
1024
+ return typeof pk === "string" ? pk : pk.toBase58();
1025
+ }
1026
+ async function signEVMMessage(message, signer) {
1027
+ if (signer.account) {
1028
+ return signer.signMessage({ message, account: signer.account });
1029
+ }
1030
+ return signer.signMessage({ message });
1031
+ }
1032
+ async function signSolanaMessage(message, signer) {
1033
+ const messageBytes = new TextEncoder().encode(message);
1034
+ const signatureBytes = await signer.signMessage(messageBytes);
1035
+ return encodeBase58(signatureBytes);
1036
+ }
1037
+
1038
+ // src/sign-in-with-x/client.ts
1039
+ async function createSIWxPayload(serverExtension, signer) {
1040
+ const isSolana = serverExtension.chainId.startsWith("solana:");
1041
+ const address = isSolana ? getSolanaAddress(signer) : getEVMAddress(signer);
1042
+ const message = createSIWxMessage(serverExtension, address);
1043
+ const signature = isSolana ? await signSolanaMessage(message, signer) : await signEVMMessage(message, signer);
1044
+ return {
1045
+ domain: serverExtension.domain,
1046
+ address,
1047
+ statement: serverExtension.statement,
1048
+ uri: serverExtension.uri,
1049
+ version: serverExtension.version,
1050
+ chainId: serverExtension.chainId,
1051
+ type: serverExtension.type,
1052
+ nonce: serverExtension.nonce,
1053
+ issuedAt: serverExtension.issuedAt,
1054
+ expirationTime: serverExtension.expirationTime,
1055
+ notBefore: serverExtension.notBefore,
1056
+ requestId: serverExtension.requestId,
1057
+ resources: serverExtension.resources,
1058
+ signatureScheme: serverExtension.signatureScheme,
1059
+ signature
1060
+ };
1061
+ }
1062
+
1063
+ // src/sign-in-with-x/encode.ts
1064
+ var import_utils2 = require("@x402/core/utils");
1065
+ function encodeSIWxHeader(payload) {
1066
+ return (0, import_utils2.safeBase64Encode)(JSON.stringify(payload));
1067
+ }
1068
+
1069
+ // src/sign-in-with-x/fetch.ts
1070
+ var import_http = require("@x402/core/http");
1071
+ function wrapFetchWithSIWx(fetch2, signer) {
1072
+ return async (input, init) => {
1073
+ const request = new Request(input, init);
1074
+ const clonedRequest = request.clone();
1075
+ const response = await fetch2(request);
1076
+ if (response.status !== 402) {
1077
+ return response;
1078
+ }
1079
+ const paymentRequiredHeader = response.headers.get("PAYMENT-REQUIRED");
1080
+ if (!paymentRequiredHeader) {
1081
+ return response;
1082
+ }
1083
+ const paymentRequired = (0, import_http.decodePaymentRequiredHeader)(paymentRequiredHeader);
1084
+ const siwxExtension = paymentRequired.extensions?.[SIGN_IN_WITH_X];
1085
+ if (!siwxExtension?.supportedChains) {
1086
+ return response;
1087
+ }
1088
+ if (clonedRequest.headers.has(SIGN_IN_WITH_X)) {
1089
+ throw new Error("SIWX authentication already attempted");
1090
+ }
1091
+ const paymentNetwork = paymentRequired.accepts?.[0]?.network;
1092
+ if (!paymentNetwork) {
1093
+ return response;
1094
+ }
1095
+ const matchingChain = siwxExtension.supportedChains.find(
1096
+ (chain) => chain.chainId === paymentNetwork
1097
+ );
1098
+ if (!matchingChain) {
1099
+ return response;
1100
+ }
1101
+ const completeInfo = {
1102
+ ...siwxExtension.info,
1103
+ chainId: matchingChain.chainId,
1104
+ type: matchingChain.type
1105
+ };
1106
+ const payload = await createSIWxPayload(completeInfo, signer);
1107
+ const siwxHeader = encodeSIWxHeader(payload);
1108
+ clonedRequest.headers.set(SIGN_IN_WITH_X, siwxHeader);
1109
+ return fetch2(clonedRequest);
1110
+ };
1111
+ }
1112
+
1113
+ // src/sign-in-with-x/storage.ts
1114
+ var InMemorySIWxStorage = class {
1115
+ constructor() {
1116
+ this.paidAddresses = /* @__PURE__ */ new Map();
1117
+ }
1118
+ /**
1119
+ * Check if an address has paid for a resource.
1120
+ *
1121
+ * @param resource - The resource path
1122
+ * @param address - The wallet address to check
1123
+ * @returns True if the address has paid
1124
+ */
1125
+ hasPaid(resource, address) {
1126
+ return this.paidAddresses.get(resource)?.has(address.toLowerCase()) ?? false;
1127
+ }
1128
+ /**
1129
+ * Record that an address has paid for a resource.
1130
+ *
1131
+ * @param resource - The resource path
1132
+ * @param address - The wallet address that paid
1133
+ */
1134
+ recordPayment(resource, address) {
1135
+ if (!this.paidAddresses.has(resource)) {
1136
+ this.paidAddresses.set(resource, /* @__PURE__ */ new Set());
1137
+ }
1138
+ this.paidAddresses.get(resource).add(address.toLowerCase());
1139
+ }
1140
+ };
1141
+
1142
+ // src/sign-in-with-x/hooks.ts
1143
+ function createSIWxSettleHook(options) {
1144
+ const { storage, onEvent } = options;
1145
+ return async (ctx) => {
1146
+ if (!ctx.result.success) return;
1147
+ const address = ctx.result.payer;
1148
+ if (!address) return;
1149
+ const resource = new URL(ctx.paymentPayload.resource.url).pathname;
1150
+ await storage.recordPayment(resource, address);
1151
+ onEvent?.({ type: "payment_recorded", resource, address });
1152
+ };
1153
+ }
1154
+ function createSIWxRequestHook(options) {
1155
+ const { storage, verifyOptions, onEvent } = options;
1156
+ const hasUsedNonce = typeof storage.hasUsedNonce === "function";
1157
+ const hasRecordNonce = typeof storage.recordNonce === "function";
1158
+ if (hasUsedNonce !== hasRecordNonce) {
1159
+ throw new Error(
1160
+ "SIWxStorage nonce tracking requires both hasUsedNonce and recordNonce to be implemented"
1161
+ );
1162
+ }
1163
+ return async (context) => {
1164
+ const header = context.adapter.getHeader(SIGN_IN_WITH_X) || context.adapter.getHeader(SIGN_IN_WITH_X.toLowerCase());
1165
+ if (!header) return;
1166
+ try {
1167
+ const payload = parseSIWxHeader(header);
1168
+ const resourceUri = context.adapter.getUrl();
1169
+ const validation = await validateSIWxMessage(payload, resourceUri);
1170
+ if (!validation.valid) {
1171
+ onEvent?.({ type: "validation_failed", resource: context.path, error: validation.error });
1172
+ return;
1173
+ }
1174
+ const verification = await verifySIWxSignature(payload, verifyOptions);
1175
+ if (!verification.valid || !verification.address) {
1176
+ onEvent?.({ type: "validation_failed", resource: context.path, error: verification.error });
1177
+ return;
1178
+ }
1179
+ if (storage.hasUsedNonce) {
1180
+ const nonceUsed = await storage.hasUsedNonce(payload.nonce);
1181
+ if (nonceUsed) {
1182
+ onEvent?.({ type: "nonce_reused", resource: context.path, nonce: payload.nonce });
1183
+ return;
1184
+ }
1185
+ }
1186
+ const hasPaid = await storage.hasPaid(context.path, verification.address);
1187
+ if (hasPaid) {
1188
+ if (storage.recordNonce) {
1189
+ await storage.recordNonce(payload.nonce);
1190
+ }
1191
+ onEvent?.({
1192
+ type: "access_granted",
1193
+ resource: context.path,
1194
+ address: verification.address
1195
+ });
1196
+ return { grantAccess: true };
1197
+ }
1198
+ } catch (err) {
1199
+ onEvent?.({
1200
+ type: "validation_failed",
1201
+ resource: context.path,
1202
+ error: err instanceof Error ? err.message : "Unknown error"
1203
+ });
1204
+ }
1205
+ };
1206
+ }
1207
+ function createSIWxClientHook(signer) {
1208
+ return async (context) => {
1209
+ const extensions = context.paymentRequired.extensions ?? {};
1210
+ const siwxExtension = extensions[SIGN_IN_WITH_X];
1211
+ if (!siwxExtension?.supportedChains) return;
1212
+ try {
1213
+ const paymentNetwork = context.paymentRequired.accepts?.[0]?.network;
1214
+ if (!paymentNetwork) return;
1215
+ const matchingChain = siwxExtension.supportedChains.find(
1216
+ (chain) => chain.chainId === paymentNetwork
1217
+ );
1218
+ if (!matchingChain) {
1219
+ return;
1220
+ }
1221
+ const completeInfo = {
1222
+ ...siwxExtension.info,
1223
+ chainId: matchingChain.chainId,
1224
+ type: matchingChain.type
1225
+ };
1226
+ const payload = await createSIWxPayload(completeInfo, signer);
1227
+ const header = encodeSIWxHeader(payload);
1228
+ return { headers: { [SIGN_IN_WITH_X]: header } };
1229
+ } catch {
1230
+ }
1231
+ };
1232
+ }
1233
+
1234
+ // src/payment-identifier/types.ts
1235
+ var PAYMENT_IDENTIFIER = "payment-identifier";
1236
+ var PAYMENT_ID_MIN_LENGTH = 16;
1237
+ var PAYMENT_ID_MAX_LENGTH = 128;
1238
+ var PAYMENT_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
1239
+
1240
+ // src/payment-identifier/schema.ts
1241
+ var paymentIdentifierSchema = {
1242
+ $schema: "https://json-schema.org/draft/2020-12/schema",
1243
+ type: "object",
1244
+ properties: {
1245
+ required: {
1246
+ type: "boolean"
1247
+ },
1248
+ id: {
1249
+ type: "string",
1250
+ minLength: PAYMENT_ID_MIN_LENGTH,
1251
+ maxLength: PAYMENT_ID_MAX_LENGTH,
1252
+ pattern: "^[a-zA-Z0-9_-]+$"
1253
+ }
1254
+ },
1255
+ required: ["required"]
1256
+ };
1257
+
1258
+ // src/payment-identifier/utils.ts
1259
+ function generatePaymentId(prefix = "pay_") {
1260
+ const uuid = crypto.randomUUID().replace(/-/g, "");
1261
+ return `${prefix}${uuid}`;
1262
+ }
1263
+ function isValidPaymentId(id) {
1264
+ if (typeof id !== "string") {
1265
+ return false;
1266
+ }
1267
+ if (id.length < PAYMENT_ID_MIN_LENGTH || id.length > PAYMENT_ID_MAX_LENGTH) {
1268
+ return false;
1269
+ }
1270
+ return PAYMENT_ID_PATTERN.test(id);
1271
+ }
1272
+
1273
+ // src/payment-identifier/validation.ts
1274
+ var import__2 = __toESM(require("ajv/dist/2020.js"));
1275
+ function isPaymentIdentifierExtension(extension) {
1276
+ if (!extension || typeof extension !== "object") {
1277
+ return false;
1278
+ }
1279
+ const ext = extension;
1280
+ if (!ext.info || typeof ext.info !== "object") {
1281
+ return false;
1282
+ }
1283
+ const info = ext.info;
1284
+ if (typeof info.required !== "boolean") {
1285
+ return false;
1286
+ }
1287
+ return true;
1288
+ }
1289
+ function validatePaymentIdentifier(extension) {
1290
+ if (!extension || typeof extension !== "object") {
1291
+ return {
1292
+ valid: false,
1293
+ errors: ["Extension must be an object"]
1294
+ };
1295
+ }
1296
+ const ext = extension;
1297
+ if (!ext.info || typeof ext.info !== "object") {
1298
+ return {
1299
+ valid: false,
1300
+ errors: ["Extension must have an 'info' property"]
1301
+ };
1302
+ }
1303
+ const info = ext.info;
1304
+ if (typeof info.required !== "boolean") {
1305
+ return {
1306
+ valid: false,
1307
+ errors: ["Extension info must have a 'required' boolean property"]
1308
+ };
1309
+ }
1310
+ if (info.id !== void 0 && typeof info.id !== "string") {
1311
+ return {
1312
+ valid: false,
1313
+ errors: ["Extension info 'id' must be a string if provided"]
1314
+ };
1315
+ }
1316
+ if (info.id !== void 0 && !isValidPaymentId(info.id)) {
1317
+ return {
1318
+ valid: false,
1319
+ errors: [
1320
+ `Invalid payment ID format. ID must be 16-128 characters and contain only alphanumeric characters, hyphens, and underscores.`
1321
+ ]
1322
+ };
1323
+ }
1324
+ if (ext.schema) {
1325
+ try {
1326
+ const ajv = new import__2.default({ strict: false, allErrors: true });
1327
+ const validate = ajv.compile(ext.schema);
1328
+ const valid = validate(ext.info);
1329
+ if (!valid && validate.errors) {
1330
+ const errors = validate.errors?.map((err) => {
1331
+ const path = err.instancePath || "(root)";
1332
+ return `${path}: ${err.message}`;
1333
+ }) || ["Unknown validation error"];
1334
+ return { valid: false, errors };
1335
+ }
1336
+ } catch (error) {
1337
+ return {
1338
+ valid: false,
1339
+ errors: [
1340
+ `Schema validation failed: ${error instanceof Error ? error.message : String(error)}`
1341
+ ]
1342
+ };
1343
+ }
1344
+ }
1345
+ return { valid: true };
1346
+ }
1347
+ function extractPaymentIdentifier(paymentPayload, validate = true) {
1348
+ if (!paymentPayload.extensions) {
1349
+ return null;
1350
+ }
1351
+ const extension = paymentPayload.extensions[PAYMENT_IDENTIFIER];
1352
+ if (!extension || typeof extension !== "object") {
1353
+ return null;
1354
+ }
1355
+ const ext = extension;
1356
+ if (!ext.info || typeof ext.info !== "object") {
1357
+ return null;
1358
+ }
1359
+ const info = ext.info;
1360
+ if (typeof info.id !== "string") {
1361
+ return null;
1362
+ }
1363
+ if (validate && !isValidPaymentId(info.id)) {
1364
+ return null;
1365
+ }
1366
+ return info.id;
1367
+ }
1368
+ function extractAndValidatePaymentIdentifier(paymentPayload) {
1369
+ if (!paymentPayload.extensions) {
1370
+ return { id: null, validation: { valid: true } };
1371
+ }
1372
+ const extension = paymentPayload.extensions[PAYMENT_IDENTIFIER];
1373
+ if (!extension) {
1374
+ return { id: null, validation: { valid: true } };
1375
+ }
1376
+ const validation = validatePaymentIdentifier(extension);
1377
+ if (!validation.valid) {
1378
+ return { id: null, validation };
1379
+ }
1380
+ const ext = extension;
1381
+ return { id: ext.info.id ?? null, validation: { valid: true } };
1382
+ }
1383
+ function hasPaymentIdentifier(paymentPayload) {
1384
+ return !!(paymentPayload.extensions && paymentPayload.extensions[PAYMENT_IDENTIFIER]);
1385
+ }
1386
+ function isPaymentIdentifierRequired(extension) {
1387
+ if (!extension || typeof extension !== "object") {
1388
+ return false;
1389
+ }
1390
+ const ext = extension;
1391
+ if (!ext.info || typeof ext.info !== "object") {
1392
+ return false;
1393
+ }
1394
+ return ext.info.required === true;
1395
+ }
1396
+ function validatePaymentIdentifierRequirement(paymentPayload, serverRequired) {
1397
+ if (!serverRequired) {
1398
+ return { valid: true };
1399
+ }
1400
+ const id = extractPaymentIdentifier(paymentPayload, false);
1401
+ if (!id) {
1402
+ return {
1403
+ valid: false,
1404
+ errors: ["Server requires a payment identifier but none was provided"]
1405
+ };
1406
+ }
1407
+ if (!isValidPaymentId(id)) {
1408
+ return {
1409
+ valid: false,
1410
+ errors: [
1411
+ `Invalid payment ID format. ID must be 16-128 characters and contain only alphanumeric characters, hyphens, and underscores.`
1412
+ ]
1413
+ };
1414
+ }
1415
+ return { valid: true };
1416
+ }
1417
+
1418
+ // src/payment-identifier/client.ts
1419
+ function appendPaymentIdentifierToExtensions(extensions, id) {
1420
+ const extension = extensions[PAYMENT_IDENTIFIER];
1421
+ if (!isPaymentIdentifierExtension(extension)) {
1422
+ return extensions;
1423
+ }
1424
+ const paymentId = id ?? generatePaymentId();
1425
+ if (!isValidPaymentId(paymentId)) {
1426
+ throw new Error(
1427
+ `Invalid payment ID: "${paymentId}". ID must be 16-128 characters and contain only alphanumeric characters, hyphens, and underscores.`
1428
+ );
1429
+ }
1430
+ extension.info.id = paymentId;
1431
+ return extensions;
1432
+ }
1433
+
1434
+ // src/payment-identifier/resourceServer.ts
1435
+ function declarePaymentIdentifierExtension(required = false) {
1436
+ return {
1437
+ info: { required },
1438
+ schema: paymentIdentifierSchema
1439
+ };
1440
+ }
1441
+ var paymentIdentifierResourceServerExtension = {
1442
+ key: PAYMENT_IDENTIFIER
1443
+ // No enrichment needed - the declaration is static
1444
+ // Future hooks for idempotency could be added here if needed
1445
+ };
477
1446
  // Annotate the CommonJS export names for ESM import in node:
478
1447
  0 && (module.exports = {
479
1448
  BAZAAR,
1449
+ InMemorySIWxStorage,
1450
+ PAYMENT_IDENTIFIER,
1451
+ PAYMENT_ID_MAX_LENGTH,
1452
+ PAYMENT_ID_MIN_LENGTH,
1453
+ PAYMENT_ID_PATTERN,
1454
+ SIGN_IN_WITH_X,
1455
+ SIWxPayloadSchema,
1456
+ SOLANA_DEVNET,
1457
+ SOLANA_MAINNET,
1458
+ SOLANA_TESTNET,
1459
+ appendPaymentIdentifierToExtensions,
480
1460
  bazaarResourceServerExtension,
1461
+ buildSIWxSchema,
1462
+ createSIWxClientHook,
1463
+ createSIWxMessage,
1464
+ createSIWxPayload,
1465
+ createSIWxRequestHook,
1466
+ createSIWxSettleHook,
481
1467
  declareDiscoveryExtension,
1468
+ declarePaymentIdentifierExtension,
1469
+ declareSIWxExtension,
1470
+ decodeBase58,
1471
+ encodeBase58,
1472
+ encodeSIWxHeader,
1473
+ extractAndValidatePaymentIdentifier,
482
1474
  extractDiscoveryInfo,
483
1475
  extractDiscoveryInfoFromExtension,
484
1476
  extractDiscoveryInfoV1,
1477
+ extractEVMChainId,
1478
+ extractPaymentIdentifier,
485
1479
  extractResourceMetadataV1,
1480
+ extractSolanaChainReference,
1481
+ formatSIWEMessage,
1482
+ formatSIWSMessage,
1483
+ generatePaymentId,
1484
+ getEVMAddress,
1485
+ getSolanaAddress,
1486
+ hasPaymentIdentifier,
486
1487
  isDiscoverableV1,
1488
+ isPaymentIdentifierExtension,
1489
+ isPaymentIdentifierRequired,
1490
+ isValidPaymentId,
1491
+ parseSIWxHeader,
1492
+ paymentIdentifierResourceServerExtension,
1493
+ paymentIdentifierSchema,
1494
+ signEVMMessage,
1495
+ signSolanaMessage,
1496
+ siwxResourceServerExtension,
487
1497
  validateAndExtract,
488
1498
  validateDiscoveryExtension,
489
- withBazaar
1499
+ validatePaymentIdentifier,
1500
+ validatePaymentIdentifierRequirement,
1501
+ validateSIWxMessage,
1502
+ verifyEVMSignature,
1503
+ verifySIWxSignature,
1504
+ verifySolanaSignature,
1505
+ withBazaar,
1506
+ wrapFetchWithSIWx
490
1507
  });
491
1508
  //# sourceMappingURL=index.js.map