@mixrpay/agent-sdk 0.6.1 → 0.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/index.cjs CHANGED
@@ -23,6 +23,7 @@ __export(index_exports, {
23
23
  AgentWallet: () => AgentWallet,
24
24
  InsufficientBalanceError: () => InsufficientBalanceError,
25
25
  InvalidSessionKeyError: () => InvalidSessionKeyError,
26
+ MerchantNotAllowedError: () => MerchantNotAllowedError,
26
27
  MixrPayError: () => MixrPayError,
27
28
  PaymentFailedError: () => PaymentFailedError,
28
29
  SDK_VERSION: () => SDK_VERSION,
@@ -265,6 +266,23 @@ var SessionRevokedError = class extends MixrPayError {
265
266
  this.reason = reason;
266
267
  }
267
268
  };
269
+ var MerchantNotAllowedError = class extends MixrPayError {
270
+ /** The merchant/tool that was attempted */
271
+ attempted;
272
+ /** The patterns that are allowed by the session */
273
+ allowedPatterns;
274
+ constructor(attempted, allowedPatterns) {
275
+ const patternsPreview = allowedPatterns.slice(0, 3).join(", ");
276
+ const suffix = allowedPatterns.length > 3 ? "..." : "";
277
+ super(
278
+ `Payment to "${attempted}" not allowed. Session allowlist: ${patternsPreview}${suffix}. Update the session allowlist or create a new session.`,
279
+ "MERCHANT_NOT_ALLOWED"
280
+ );
281
+ this.name = "MerchantNotAllowedError";
282
+ this.attempted = attempted;
283
+ this.allowedPatterns = allowedPatterns;
284
+ }
285
+ };
268
286
  function isMixrPayError(error) {
269
287
  return error instanceof MixrPayError;
270
288
  }
@@ -575,6 +593,9 @@ var AgentWallet = class {
575
593
  // Session key info cache
576
594
  sessionKeyInfo;
577
595
  sessionKeyInfoFetchedAt;
596
+ // Merchant allowlist (fetched from server)
597
+ allowlist;
598
+ allowlistFetchedAt;
578
599
  /**
579
600
  * Create a new AgentWallet instance.
580
601
  *
@@ -651,6 +672,126 @@ var AgentWallet = class {
651
672
  }
652
673
  }
653
674
  // ===========================================================================
675
+ // Merchant Allowlist Validation
676
+ // ===========================================================================
677
+ /**
678
+ * Fetch the merchant allowlist from the server.
679
+ * Caches the result for 5 minutes to avoid excessive API calls.
680
+ */
681
+ async fetchAllowlist() {
682
+ const CACHE_TTL_MS = 5 * 60 * 1e3;
683
+ if (this.allowlist !== void 0 && this.allowlistFetchedAt) {
684
+ const age = Date.now() - this.allowlistFetchedAt;
685
+ if (age < CACHE_TTL_MS) {
686
+ return this.allowlist;
687
+ }
688
+ }
689
+ try {
690
+ const info = await this.getSessionKeyInfo();
691
+ this.allowlist = info.allowedMerchants || [];
692
+ this.allowlistFetchedAt = Date.now();
693
+ this.logger.debug("Fetched allowlist", {
694
+ patterns: this.allowlist.length,
695
+ allowAll: this.allowlist.length === 0
696
+ });
697
+ return this.allowlist;
698
+ } catch (error) {
699
+ this.logger.warn("Failed to fetch allowlist, allowing all merchants", { error });
700
+ this.allowlist = [];
701
+ this.allowlistFetchedAt = Date.now();
702
+ return this.allowlist;
703
+ }
704
+ }
705
+ /**
706
+ * Validate that a target is allowed by the session's allowlist.
707
+ *
708
+ * @param target - URL, domain, or tool name to check
709
+ * @param type - Type of target: 'url' for HTTP requests, 'tool' for MCP tools
710
+ * @throws {MerchantNotAllowedError} If target is not in allowlist
711
+ */
712
+ async validateMerchantAllowed(target, type) {
713
+ const allowlist = await this.fetchAllowlist();
714
+ if (allowlist.length === 0) {
715
+ return;
716
+ }
717
+ const isAllowed = this.matchesAllowlist(target, allowlist, type);
718
+ if (!isAllowed) {
719
+ throw new MerchantNotAllowedError(target, allowlist);
720
+ }
721
+ }
722
+ /**
723
+ * Check if a target matches any pattern in the allowlist.
724
+ */
725
+ matchesAllowlist(target, allowlist, type) {
726
+ for (const pattern of allowlist) {
727
+ if (this.matchPattern(target, pattern, type)) {
728
+ return true;
729
+ }
730
+ }
731
+ return false;
732
+ }
733
+ /**
734
+ * Check if a target matches a single pattern.
735
+ */
736
+ matchPattern(target, pattern, type) {
737
+ if (type === "url") {
738
+ return this.matchUrlPattern(target, pattern);
739
+ } else {
740
+ return this.matchToolPattern(target, pattern);
741
+ }
742
+ }
743
+ /**
744
+ * Match a URL or domain against a pattern.
745
+ */
746
+ matchUrlPattern(target, pattern) {
747
+ let hostname;
748
+ try {
749
+ if (target.includes("://")) {
750
+ const url = new URL(target);
751
+ hostname = url.hostname.toLowerCase();
752
+ } else {
753
+ hostname = target.toLowerCase();
754
+ }
755
+ } catch {
756
+ return false;
757
+ }
758
+ const normalizedPattern = pattern.toLowerCase().trim();
759
+ let patternDomain = normalizedPattern;
760
+ if (patternDomain.includes("://")) {
761
+ try {
762
+ patternDomain = new URL(patternDomain).hostname;
763
+ } catch {
764
+ }
765
+ }
766
+ if (hostname === patternDomain) {
767
+ return true;
768
+ }
769
+ if (patternDomain.startsWith("*.")) {
770
+ const baseDomain = patternDomain.substring(2);
771
+ if (hostname.endsWith(`.${baseDomain}`) && hostname !== baseDomain) {
772
+ return true;
773
+ }
774
+ }
775
+ return false;
776
+ }
777
+ /**
778
+ * Match an MCP tool name against a pattern.
779
+ */
780
+ matchToolPattern(toolName, pattern) {
781
+ const normalizedTool = toolName.toLowerCase().trim();
782
+ const normalizedPattern = pattern.toLowerCase().trim();
783
+ if (normalizedTool === normalizedPattern) {
784
+ return true;
785
+ }
786
+ if (normalizedPattern.endsWith("/*")) {
787
+ const baseProvider = normalizedPattern.substring(0, normalizedPattern.length - 2);
788
+ if (normalizedTool.startsWith(`${baseProvider}/`)) {
789
+ return true;
790
+ }
791
+ }
792
+ return false;
793
+ }
794
+ // ===========================================================================
654
795
  // Static Agent Registration Methods
655
796
  // ===========================================================================
656
797
  /**
@@ -1038,6 +1179,7 @@ var AgentWallet = class {
1038
1179
  */
1039
1180
  async fetch(url, init) {
1040
1181
  this.logger.debug(`Fetching ${init?.method || "GET"} ${url}`);
1182
+ await this.validateMerchantAllowed(url, "url");
1041
1183
  const requestId = crypto.randomUUID();
1042
1184
  const correlationId = this.extractCorrelationId(init?.headers);
1043
1185
  const controller = new AbortController();
@@ -1311,7 +1453,8 @@ var AgentWallet = class {
1311
1453
  },
1312
1454
  expiresAt: data.expires_at ? new Date(data.expires_at) : null,
1313
1455
  createdAt: data.created_at ? new Date(data.created_at) : null,
1314
- name: data.name
1456
+ name: data.name,
1457
+ allowedMerchants: data.allowed_merchants ?? data.allowedMerchants ?? []
1315
1458
  };
1316
1459
  this.sessionKeyInfoFetchedAt = Date.now();
1317
1460
  return this.sessionKeyInfo;
@@ -1877,6 +2020,7 @@ var AgentWallet = class {
1877
2020
  feature
1878
2021
  } = options;
1879
2022
  this.logger.debug("callMerchantApi", { url, method, merchantPublicKey, priceUsd });
2023
+ await this.validateMerchantAllowed(url, "url");
1880
2024
  if (priceUsd !== void 0 && this.maxPaymentUsd !== void 0 && priceUsd > this.maxPaymentUsd) {
1881
2025
  throw new SpendingLimitExceededError("client_max", this.maxPaymentUsd, priceUsd);
1882
2026
  }
@@ -2084,6 +2228,7 @@ Timestamp: ${timestamp}`;
2084
2228
  */
2085
2229
  async callMCPTool(toolName, args = {}) {
2086
2230
  this.logger.debug("callMCPTool", { toolName, args });
2231
+ await this.validateMerchantAllowed(toolName, "tool");
2087
2232
  const authHeaders = await this.getMCPAuthHeaders();
2088
2233
  const response = await fetch(`${this.baseUrl}/api/mcp`, {
2089
2234
  method: "POST",
@@ -2473,6 +2618,7 @@ Timestamp: ${timestamp}`;
2473
2618
  */
2474
2619
  async callMCPToolWithSession(sessionId, toolName, args = {}) {
2475
2620
  this.logger.debug("callMCPToolWithSession", { sessionId, toolName, args });
2621
+ await this.validateMerchantAllowed(toolName, "tool");
2476
2622
  const response = await fetch(`${this.baseUrl}/api/mcp`, {
2477
2623
  method: "POST",
2478
2624
  headers: {
@@ -2523,6 +2669,7 @@ Timestamp: ${timestamp}`;
2523
2669
  AgentWallet,
2524
2670
  InsufficientBalanceError,
2525
2671
  InvalidSessionKeyError,
2672
+ MerchantNotAllowedError,
2526
2673
  MixrPayError,
2527
2674
  PaymentFailedError,
2528
2675
  SDK_VERSION,
package/dist/index.d.cts CHANGED
@@ -158,6 +158,8 @@ interface SessionKeyInfo {
158
158
  createdAt: Date | null;
159
159
  /** Optional name given to this session key */
160
160
  name?: string;
161
+ /** Allowed merchants/tools (empty = allow all) */
162
+ allowedMerchants?: string[];
161
163
  }
162
164
  /**
163
165
  * Spending statistics for the current session.
@@ -561,6 +563,8 @@ declare class AgentWallet {
561
563
  private totalSpentUsd;
562
564
  private sessionKeyInfo?;
563
565
  private sessionKeyInfoFetchedAt?;
566
+ private allowlist?;
567
+ private allowlistFetchedAt?;
564
568
  /**
565
569
  * Create a new AgentWallet instance.
566
570
  *
@@ -590,6 +594,35 @@ declare class AgentWallet {
590
594
  * Validate the configuration before initialization.
591
595
  */
592
596
  private validateConfig;
597
+ /**
598
+ * Fetch the merchant allowlist from the server.
599
+ * Caches the result for 5 minutes to avoid excessive API calls.
600
+ */
601
+ private fetchAllowlist;
602
+ /**
603
+ * Validate that a target is allowed by the session's allowlist.
604
+ *
605
+ * @param target - URL, domain, or tool name to check
606
+ * @param type - Type of target: 'url' for HTTP requests, 'tool' for MCP tools
607
+ * @throws {MerchantNotAllowedError} If target is not in allowlist
608
+ */
609
+ private validateMerchantAllowed;
610
+ /**
611
+ * Check if a target matches any pattern in the allowlist.
612
+ */
613
+ private matchesAllowlist;
614
+ /**
615
+ * Check if a target matches a single pattern.
616
+ */
617
+ private matchPattern;
618
+ /**
619
+ * Match a URL or domain against a pattern.
620
+ */
621
+ private matchUrlPattern;
622
+ /**
623
+ * Match an MCP tool name against a pattern.
624
+ */
625
+ private matchToolPattern;
593
626
  /**
594
627
  * Register a new agent with MixrPay.
595
628
  *
@@ -1783,6 +1816,34 @@ declare class SessionRevokedError extends MixrPayError {
1783
1816
  readonly reason?: string;
1784
1817
  constructor(sessionId: string, reason?: string);
1785
1818
  }
1819
+ /**
1820
+ * Thrown when a payment is attempted to a merchant/tool not in the session's allowlist.
1821
+ *
1822
+ * This error indicates the session key has a configured allowlist that doesn't
1823
+ * include the target merchant or tool.
1824
+ *
1825
+ * ## Resolution
1826
+ * 1. Update the session's allowlist to include the target
1827
+ * 2. Create a new session with the correct allowlist
1828
+ * 3. Use a session without allowlist restrictions
1829
+ *
1830
+ * @example
1831
+ * ```typescript
1832
+ * catch (error) {
1833
+ * if (error instanceof MerchantNotAllowedError) {
1834
+ * console.log(`Cannot pay "${error.attempted}"`);
1835
+ * console.log(`Allowed patterns: ${error.allowedPatterns.join(', ')}`);
1836
+ * }
1837
+ * }
1838
+ * ```
1839
+ */
1840
+ declare class MerchantNotAllowedError extends MixrPayError {
1841
+ /** The merchant/tool that was attempted */
1842
+ readonly attempted: string;
1843
+ /** The patterns that are allowed by the session */
1844
+ readonly allowedPatterns: string[];
1845
+ constructor(attempted: string, allowedPatterns: string[]);
1846
+ }
1786
1847
  /**
1787
1848
  * Check if an error is a MixrPay SDK error.
1788
1849
  *
@@ -1819,4 +1880,4 @@ declare function isMixrPayError(error: unknown): error is MixrPayError;
1819
1880
  */
1820
1881
  declare function getErrorMessage(error: unknown): string;
1821
1882
 
1822
- export { type AgentMessage, type AgentRunConfig, type AgentRunEvent, type AgentRunOptions, type AgentRunResult, type AgentRunStatusResult, AgentWallet, type AgentWalletConfig, type CallMerchantApiOptions, type ChargeResult, type DiagnosticsResult, InsufficientBalanceError, InvalidSessionKeyError, MixrPayError, type PaymentEvent, PaymentFailedError, SDK_VERSION, type SessionAuthorization, SessionExpiredError, SessionKeyExpiredError, type SessionKeyInfo, SessionLimitExceededError, SessionNotFoundError, SessionRevokedError, type SessionStats, SpendingLimitExceededError, type SpendingStats, X402ProtocolError, getErrorMessage, isMixrPayError };
1883
+ export { type AgentMessage, type AgentRunConfig, type AgentRunEvent, type AgentRunOptions, type AgentRunResult, type AgentRunStatusResult, AgentWallet, type AgentWalletConfig, type CallMerchantApiOptions, type ChargeResult, type DiagnosticsResult, InsufficientBalanceError, InvalidSessionKeyError, MerchantNotAllowedError, MixrPayError, type PaymentEvent, PaymentFailedError, SDK_VERSION, type SessionAuthorization, SessionExpiredError, SessionKeyExpiredError, type SessionKeyInfo, SessionLimitExceededError, SessionNotFoundError, SessionRevokedError, type SessionStats, SpendingLimitExceededError, type SpendingStats, X402ProtocolError, getErrorMessage, isMixrPayError };
package/dist/index.d.ts CHANGED
@@ -158,6 +158,8 @@ interface SessionKeyInfo {
158
158
  createdAt: Date | null;
159
159
  /** Optional name given to this session key */
160
160
  name?: string;
161
+ /** Allowed merchants/tools (empty = allow all) */
162
+ allowedMerchants?: string[];
161
163
  }
162
164
  /**
163
165
  * Spending statistics for the current session.
@@ -561,6 +563,8 @@ declare class AgentWallet {
561
563
  private totalSpentUsd;
562
564
  private sessionKeyInfo?;
563
565
  private sessionKeyInfoFetchedAt?;
566
+ private allowlist?;
567
+ private allowlistFetchedAt?;
564
568
  /**
565
569
  * Create a new AgentWallet instance.
566
570
  *
@@ -590,6 +594,35 @@ declare class AgentWallet {
590
594
  * Validate the configuration before initialization.
591
595
  */
592
596
  private validateConfig;
597
+ /**
598
+ * Fetch the merchant allowlist from the server.
599
+ * Caches the result for 5 minutes to avoid excessive API calls.
600
+ */
601
+ private fetchAllowlist;
602
+ /**
603
+ * Validate that a target is allowed by the session's allowlist.
604
+ *
605
+ * @param target - URL, domain, or tool name to check
606
+ * @param type - Type of target: 'url' for HTTP requests, 'tool' for MCP tools
607
+ * @throws {MerchantNotAllowedError} If target is not in allowlist
608
+ */
609
+ private validateMerchantAllowed;
610
+ /**
611
+ * Check if a target matches any pattern in the allowlist.
612
+ */
613
+ private matchesAllowlist;
614
+ /**
615
+ * Check if a target matches a single pattern.
616
+ */
617
+ private matchPattern;
618
+ /**
619
+ * Match a URL or domain against a pattern.
620
+ */
621
+ private matchUrlPattern;
622
+ /**
623
+ * Match an MCP tool name against a pattern.
624
+ */
625
+ private matchToolPattern;
593
626
  /**
594
627
  * Register a new agent with MixrPay.
595
628
  *
@@ -1783,6 +1816,34 @@ declare class SessionRevokedError extends MixrPayError {
1783
1816
  readonly reason?: string;
1784
1817
  constructor(sessionId: string, reason?: string);
1785
1818
  }
1819
+ /**
1820
+ * Thrown when a payment is attempted to a merchant/tool not in the session's allowlist.
1821
+ *
1822
+ * This error indicates the session key has a configured allowlist that doesn't
1823
+ * include the target merchant or tool.
1824
+ *
1825
+ * ## Resolution
1826
+ * 1. Update the session's allowlist to include the target
1827
+ * 2. Create a new session with the correct allowlist
1828
+ * 3. Use a session without allowlist restrictions
1829
+ *
1830
+ * @example
1831
+ * ```typescript
1832
+ * catch (error) {
1833
+ * if (error instanceof MerchantNotAllowedError) {
1834
+ * console.log(`Cannot pay "${error.attempted}"`);
1835
+ * console.log(`Allowed patterns: ${error.allowedPatterns.join(', ')}`);
1836
+ * }
1837
+ * }
1838
+ * ```
1839
+ */
1840
+ declare class MerchantNotAllowedError extends MixrPayError {
1841
+ /** The merchant/tool that was attempted */
1842
+ readonly attempted: string;
1843
+ /** The patterns that are allowed by the session */
1844
+ readonly allowedPatterns: string[];
1845
+ constructor(attempted: string, allowedPatterns: string[]);
1846
+ }
1786
1847
  /**
1787
1848
  * Check if an error is a MixrPay SDK error.
1788
1849
  *
@@ -1819,4 +1880,4 @@ declare function isMixrPayError(error: unknown): error is MixrPayError;
1819
1880
  */
1820
1881
  declare function getErrorMessage(error: unknown): string;
1821
1882
 
1822
- export { type AgentMessage, type AgentRunConfig, type AgentRunEvent, type AgentRunOptions, type AgentRunResult, type AgentRunStatusResult, AgentWallet, type AgentWalletConfig, type CallMerchantApiOptions, type ChargeResult, type DiagnosticsResult, InsufficientBalanceError, InvalidSessionKeyError, MixrPayError, type PaymentEvent, PaymentFailedError, SDK_VERSION, type SessionAuthorization, SessionExpiredError, SessionKeyExpiredError, type SessionKeyInfo, SessionLimitExceededError, SessionNotFoundError, SessionRevokedError, type SessionStats, SpendingLimitExceededError, type SpendingStats, X402ProtocolError, getErrorMessage, isMixrPayError };
1883
+ export { type AgentMessage, type AgentRunConfig, type AgentRunEvent, type AgentRunOptions, type AgentRunResult, type AgentRunStatusResult, AgentWallet, type AgentWalletConfig, type CallMerchantApiOptions, type ChargeResult, type DiagnosticsResult, InsufficientBalanceError, InvalidSessionKeyError, MerchantNotAllowedError, MixrPayError, type PaymentEvent, PaymentFailedError, SDK_VERSION, type SessionAuthorization, SessionExpiredError, SessionKeyExpiredError, type SessionKeyInfo, SessionLimitExceededError, SessionNotFoundError, SessionRevokedError, type SessionStats, SpendingLimitExceededError, type SpendingStats, X402ProtocolError, getErrorMessage, isMixrPayError };
package/dist/index.js CHANGED
@@ -229,6 +229,23 @@ var SessionRevokedError = class extends MixrPayError {
229
229
  this.reason = reason;
230
230
  }
231
231
  };
232
+ var MerchantNotAllowedError = class extends MixrPayError {
233
+ /** The merchant/tool that was attempted */
234
+ attempted;
235
+ /** The patterns that are allowed by the session */
236
+ allowedPatterns;
237
+ constructor(attempted, allowedPatterns) {
238
+ const patternsPreview = allowedPatterns.slice(0, 3).join(", ");
239
+ const suffix = allowedPatterns.length > 3 ? "..." : "";
240
+ super(
241
+ `Payment to "${attempted}" not allowed. Session allowlist: ${patternsPreview}${suffix}. Update the session allowlist or create a new session.`,
242
+ "MERCHANT_NOT_ALLOWED"
243
+ );
244
+ this.name = "MerchantNotAllowedError";
245
+ this.attempted = attempted;
246
+ this.allowedPatterns = allowedPatterns;
247
+ }
248
+ };
232
249
  function isMixrPayError(error) {
233
250
  return error instanceof MixrPayError;
234
251
  }
@@ -539,6 +556,9 @@ var AgentWallet = class {
539
556
  // Session key info cache
540
557
  sessionKeyInfo;
541
558
  sessionKeyInfoFetchedAt;
559
+ // Merchant allowlist (fetched from server)
560
+ allowlist;
561
+ allowlistFetchedAt;
542
562
  /**
543
563
  * Create a new AgentWallet instance.
544
564
  *
@@ -615,6 +635,126 @@ var AgentWallet = class {
615
635
  }
616
636
  }
617
637
  // ===========================================================================
638
+ // Merchant Allowlist Validation
639
+ // ===========================================================================
640
+ /**
641
+ * Fetch the merchant allowlist from the server.
642
+ * Caches the result for 5 minutes to avoid excessive API calls.
643
+ */
644
+ async fetchAllowlist() {
645
+ const CACHE_TTL_MS = 5 * 60 * 1e3;
646
+ if (this.allowlist !== void 0 && this.allowlistFetchedAt) {
647
+ const age = Date.now() - this.allowlistFetchedAt;
648
+ if (age < CACHE_TTL_MS) {
649
+ return this.allowlist;
650
+ }
651
+ }
652
+ try {
653
+ const info = await this.getSessionKeyInfo();
654
+ this.allowlist = info.allowedMerchants || [];
655
+ this.allowlistFetchedAt = Date.now();
656
+ this.logger.debug("Fetched allowlist", {
657
+ patterns: this.allowlist.length,
658
+ allowAll: this.allowlist.length === 0
659
+ });
660
+ return this.allowlist;
661
+ } catch (error) {
662
+ this.logger.warn("Failed to fetch allowlist, allowing all merchants", { error });
663
+ this.allowlist = [];
664
+ this.allowlistFetchedAt = Date.now();
665
+ return this.allowlist;
666
+ }
667
+ }
668
+ /**
669
+ * Validate that a target is allowed by the session's allowlist.
670
+ *
671
+ * @param target - URL, domain, or tool name to check
672
+ * @param type - Type of target: 'url' for HTTP requests, 'tool' for MCP tools
673
+ * @throws {MerchantNotAllowedError} If target is not in allowlist
674
+ */
675
+ async validateMerchantAllowed(target, type) {
676
+ const allowlist = await this.fetchAllowlist();
677
+ if (allowlist.length === 0) {
678
+ return;
679
+ }
680
+ const isAllowed = this.matchesAllowlist(target, allowlist, type);
681
+ if (!isAllowed) {
682
+ throw new MerchantNotAllowedError(target, allowlist);
683
+ }
684
+ }
685
+ /**
686
+ * Check if a target matches any pattern in the allowlist.
687
+ */
688
+ matchesAllowlist(target, allowlist, type) {
689
+ for (const pattern of allowlist) {
690
+ if (this.matchPattern(target, pattern, type)) {
691
+ return true;
692
+ }
693
+ }
694
+ return false;
695
+ }
696
+ /**
697
+ * Check if a target matches a single pattern.
698
+ */
699
+ matchPattern(target, pattern, type) {
700
+ if (type === "url") {
701
+ return this.matchUrlPattern(target, pattern);
702
+ } else {
703
+ return this.matchToolPattern(target, pattern);
704
+ }
705
+ }
706
+ /**
707
+ * Match a URL or domain against a pattern.
708
+ */
709
+ matchUrlPattern(target, pattern) {
710
+ let hostname;
711
+ try {
712
+ if (target.includes("://")) {
713
+ const url = new URL(target);
714
+ hostname = url.hostname.toLowerCase();
715
+ } else {
716
+ hostname = target.toLowerCase();
717
+ }
718
+ } catch {
719
+ return false;
720
+ }
721
+ const normalizedPattern = pattern.toLowerCase().trim();
722
+ let patternDomain = normalizedPattern;
723
+ if (patternDomain.includes("://")) {
724
+ try {
725
+ patternDomain = new URL(patternDomain).hostname;
726
+ } catch {
727
+ }
728
+ }
729
+ if (hostname === patternDomain) {
730
+ return true;
731
+ }
732
+ if (patternDomain.startsWith("*.")) {
733
+ const baseDomain = patternDomain.substring(2);
734
+ if (hostname.endsWith(`.${baseDomain}`) && hostname !== baseDomain) {
735
+ return true;
736
+ }
737
+ }
738
+ return false;
739
+ }
740
+ /**
741
+ * Match an MCP tool name against a pattern.
742
+ */
743
+ matchToolPattern(toolName, pattern) {
744
+ const normalizedTool = toolName.toLowerCase().trim();
745
+ const normalizedPattern = pattern.toLowerCase().trim();
746
+ if (normalizedTool === normalizedPattern) {
747
+ return true;
748
+ }
749
+ if (normalizedPattern.endsWith("/*")) {
750
+ const baseProvider = normalizedPattern.substring(0, normalizedPattern.length - 2);
751
+ if (normalizedTool.startsWith(`${baseProvider}/`)) {
752
+ return true;
753
+ }
754
+ }
755
+ return false;
756
+ }
757
+ // ===========================================================================
618
758
  // Static Agent Registration Methods
619
759
  // ===========================================================================
620
760
  /**
@@ -1002,6 +1142,7 @@ var AgentWallet = class {
1002
1142
  */
1003
1143
  async fetch(url, init) {
1004
1144
  this.logger.debug(`Fetching ${init?.method || "GET"} ${url}`);
1145
+ await this.validateMerchantAllowed(url, "url");
1005
1146
  const requestId = crypto.randomUUID();
1006
1147
  const correlationId = this.extractCorrelationId(init?.headers);
1007
1148
  const controller = new AbortController();
@@ -1275,7 +1416,8 @@ var AgentWallet = class {
1275
1416
  },
1276
1417
  expiresAt: data.expires_at ? new Date(data.expires_at) : null,
1277
1418
  createdAt: data.created_at ? new Date(data.created_at) : null,
1278
- name: data.name
1419
+ name: data.name,
1420
+ allowedMerchants: data.allowed_merchants ?? data.allowedMerchants ?? []
1279
1421
  };
1280
1422
  this.sessionKeyInfoFetchedAt = Date.now();
1281
1423
  return this.sessionKeyInfo;
@@ -1841,6 +1983,7 @@ var AgentWallet = class {
1841
1983
  feature
1842
1984
  } = options;
1843
1985
  this.logger.debug("callMerchantApi", { url, method, merchantPublicKey, priceUsd });
1986
+ await this.validateMerchantAllowed(url, "url");
1844
1987
  if (priceUsd !== void 0 && this.maxPaymentUsd !== void 0 && priceUsd > this.maxPaymentUsd) {
1845
1988
  throw new SpendingLimitExceededError("client_max", this.maxPaymentUsd, priceUsd);
1846
1989
  }
@@ -2048,6 +2191,7 @@ Timestamp: ${timestamp}`;
2048
2191
  */
2049
2192
  async callMCPTool(toolName, args = {}) {
2050
2193
  this.logger.debug("callMCPTool", { toolName, args });
2194
+ await this.validateMerchantAllowed(toolName, "tool");
2051
2195
  const authHeaders = await this.getMCPAuthHeaders();
2052
2196
  const response = await fetch(`${this.baseUrl}/api/mcp`, {
2053
2197
  method: "POST",
@@ -2437,6 +2581,7 @@ Timestamp: ${timestamp}`;
2437
2581
  */
2438
2582
  async callMCPToolWithSession(sessionId, toolName, args = {}) {
2439
2583
  this.logger.debug("callMCPToolWithSession", { sessionId, toolName, args });
2584
+ await this.validateMerchantAllowed(toolName, "tool");
2440
2585
  const response = await fetch(`${this.baseUrl}/api/mcp`, {
2441
2586
  method: "POST",
2442
2587
  headers: {
@@ -2486,6 +2631,7 @@ export {
2486
2631
  AgentWallet,
2487
2632
  InsufficientBalanceError,
2488
2633
  InvalidSessionKeyError,
2634
+ MerchantNotAllowedError,
2489
2635
  MixrPayError,
2490
2636
  PaymentFailedError,
2491
2637
  SDK_VERSION,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mixrpay/agent-sdk",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "MixrPay Agent SDK - Enable AI agents to make x402 payments with session keys",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",