@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 +148 -1
- package/dist/index.d.cts +62 -1
- package/dist/index.d.ts +62 -1
- package/dist/index.js +147 -1
- package/package.json +1 -1
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,
|