@mixrpay/agent-sdk 0.6.0 → 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/README.md +24 -4
- package/dist/index.cjs +269 -3
- package/dist/index.d.cts +133 -2
- package/dist/index.d.ts +133 -2
- package/dist/index.js +268 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -41,9 +41,13 @@ await wallet.fetch(url, options);
|
|
|
41
41
|
// Get wallet balance
|
|
42
42
|
const balance = await wallet.getBalance();
|
|
43
43
|
|
|
44
|
-
//
|
|
45
|
-
const
|
|
46
|
-
|
|
44
|
+
// Pre-flight balance check
|
|
45
|
+
const check = await wallet.canAfford(0.50);
|
|
46
|
+
if (check.canAfford) { /* proceed */ }
|
|
47
|
+
|
|
48
|
+
// Run diagnostics
|
|
49
|
+
const diag = await wallet.runDiagnostics();
|
|
50
|
+
console.log(diag.recommendations);
|
|
47
51
|
```
|
|
48
52
|
|
|
49
53
|
## Configuration
|
|
@@ -63,6 +67,7 @@ import {
|
|
|
63
67
|
AgentWallet,
|
|
64
68
|
InsufficientBalanceError,
|
|
65
69
|
SessionLimitExceededError,
|
|
70
|
+
MixrPayError,
|
|
66
71
|
} from '@mixrpay/agent-sdk';
|
|
67
72
|
|
|
68
73
|
try {
|
|
@@ -71,12 +76,27 @@ try {
|
|
|
71
76
|
if (error instanceof InsufficientBalanceError) {
|
|
72
77
|
console.log(`Need $${error.required}, have $${error.available}`);
|
|
73
78
|
}
|
|
79
|
+
|
|
80
|
+
// Check if error is retryable
|
|
81
|
+
if (error instanceof MixrPayError && error.isRetryable()) {
|
|
82
|
+
const delay = error.retryAfterMs || 1000;
|
|
83
|
+
await sleep(delay);
|
|
84
|
+
return retry();
|
|
85
|
+
}
|
|
74
86
|
}
|
|
75
87
|
```
|
|
76
88
|
|
|
89
|
+
## What's New in v0.6.0
|
|
90
|
+
|
|
91
|
+
- `canAfford(amountUsd)` - Pre-flight balance check
|
|
92
|
+
- `isRetryable()` on all errors - Determine if operation should be retried
|
|
93
|
+
- `retryAfterMs` on errors - Suggested retry delay
|
|
94
|
+
- Enhanced `runDiagnostics()` - Session limits, latency, recommendations
|
|
95
|
+
- `requestId` and `correlationId` in payment events
|
|
96
|
+
|
|
77
97
|
## Documentation
|
|
78
98
|
|
|
79
|
-
[mixrpay.com/
|
|
99
|
+
[mixrpay.com/docs](https://www.mixrpay.com/docs)
|
|
80
100
|
|
|
81
101
|
## License
|
|
82
102
|
|
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
|
}
|
|
@@ -507,7 +525,7 @@ function getAmountUsd(requirements) {
|
|
|
507
525
|
}
|
|
508
526
|
|
|
509
527
|
// src/agent-wallet.ts
|
|
510
|
-
var SDK_VERSION = "0.6.
|
|
528
|
+
var SDK_VERSION = "0.6.1";
|
|
511
529
|
var DEFAULT_BASE_URL = process.env.MIXRPAY_BASE_URL || "https://www.mixrpay.com";
|
|
512
530
|
var DEFAULT_TIMEOUT = 3e4;
|
|
513
531
|
var NETWORKS = {
|
|
@@ -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
|
/**
|
|
@@ -699,7 +840,19 @@ var AgentWallet = class {
|
|
|
699
840
|
});
|
|
700
841
|
if (!registerResponse.ok) {
|
|
701
842
|
const error = await registerResponse.json().catch(() => ({}));
|
|
702
|
-
|
|
843
|
+
const errorMessage = error.error || `Registration failed with status ${registerResponse.status}`;
|
|
844
|
+
const requestId = error.request_id;
|
|
845
|
+
const errorCode = error.code;
|
|
846
|
+
let helpText = "";
|
|
847
|
+
if (registerResponse.status === 503) {
|
|
848
|
+
helpText = " The service may be temporarily unavailable. Please try again later.";
|
|
849
|
+
} else if (registerResponse.status === 500) {
|
|
850
|
+
helpText = " This is a server error. Please contact support with the request ID.";
|
|
851
|
+
} else if (errorCode === "MISSING_CHALLENGE" || errorCode === "MISSING_SIGNATURE") {
|
|
852
|
+
helpText = " This may indicate an SDK bug. Please update to the latest version.";
|
|
853
|
+
}
|
|
854
|
+
const fullMessage = requestId ? `${errorMessage} (request_id: ${requestId})${helpText}` : `${errorMessage}${helpText}`;
|
|
855
|
+
throw new MixrPayError(fullMessage);
|
|
703
856
|
}
|
|
704
857
|
const data = await registerResponse.json();
|
|
705
858
|
return {
|
|
@@ -707,6 +860,52 @@ var AgentWallet = class {
|
|
|
707
860
|
depositAddress: data.deposit_address
|
|
708
861
|
};
|
|
709
862
|
}
|
|
863
|
+
/**
|
|
864
|
+
* Check if the MixrPay server is properly configured for agent registration.
|
|
865
|
+
*
|
|
866
|
+
* Use this to diagnose registration issues before attempting to register.
|
|
867
|
+
*
|
|
868
|
+
* @param baseUrl - MixrPay API base URL (default: https://www.mixrpay.com)
|
|
869
|
+
* @returns Server health status including agent registration availability
|
|
870
|
+
*
|
|
871
|
+
* @example
|
|
872
|
+
* ```typescript
|
|
873
|
+
* const status = await AgentWallet.checkServerHealth();
|
|
874
|
+
* if (!status.agentRegistrationAvailable) {
|
|
875
|
+
* console.error('Agent registration is not available:', status);
|
|
876
|
+
* }
|
|
877
|
+
* ```
|
|
878
|
+
*/
|
|
879
|
+
static async checkServerHealth(baseUrl) {
|
|
880
|
+
const url = (baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
881
|
+
try {
|
|
882
|
+
const response = await fetch(`${url}/api/health/ready?details=true`);
|
|
883
|
+
if (!response.ok) {
|
|
884
|
+
return {
|
|
885
|
+
healthy: false,
|
|
886
|
+
database: "unknown",
|
|
887
|
+
agentRegistrationAvailable: false,
|
|
888
|
+
privyConfigured: false,
|
|
889
|
+
error: `Health check failed with status ${response.status}`
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
const data = await response.json();
|
|
893
|
+
return {
|
|
894
|
+
healthy: data.status === "ready",
|
|
895
|
+
database: data.database || "unknown",
|
|
896
|
+
agentRegistrationAvailable: data.services?.agentRegistration?.available ?? false,
|
|
897
|
+
privyConfigured: data.services?.privy?.configured ?? false
|
|
898
|
+
};
|
|
899
|
+
} catch (error) {
|
|
900
|
+
return {
|
|
901
|
+
healthy: false,
|
|
902
|
+
database: "unreachable",
|
|
903
|
+
agentRegistrationAvailable: false,
|
|
904
|
+
privyConfigured: false,
|
|
905
|
+
error: error instanceof Error ? error.message : "Failed to reach server"
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
}
|
|
710
909
|
/**
|
|
711
910
|
* Get a session key for an already-registered agent.
|
|
712
911
|
*
|
|
@@ -881,6 +1080,67 @@ var AgentWallet = class {
|
|
|
881
1080
|
}
|
|
882
1081
|
return true;
|
|
883
1082
|
}
|
|
1083
|
+
/**
|
|
1084
|
+
* Withdraw USDC from agent's MixrPay wallet to their external wallet.
|
|
1085
|
+
*
|
|
1086
|
+
* SECURITY: Withdrawals can ONLY go to the agent's own registration wallet
|
|
1087
|
+
* (the wallet used during `register()`). This prevents prompt injection
|
|
1088
|
+
* attacks where a compromised agent might be tricked into withdrawing
|
|
1089
|
+
* to an attacker's address.
|
|
1090
|
+
*
|
|
1091
|
+
* @param options - Withdrawal options
|
|
1092
|
+
* @returns Withdrawal result with transaction hash
|
|
1093
|
+
* @throws {MixrPayError} If withdrawal fails
|
|
1094
|
+
*
|
|
1095
|
+
* @example
|
|
1096
|
+
* ```typescript
|
|
1097
|
+
* const result = await AgentWallet.withdraw({
|
|
1098
|
+
* privateKey: process.env.AGENT_WALLET_KEY as `0x${string}`,
|
|
1099
|
+
* amountUsd: 50.00,
|
|
1100
|
+
* });
|
|
1101
|
+
*
|
|
1102
|
+
* console.log(`Withdrew $${result.amountUsd}`);
|
|
1103
|
+
* console.log(`Transaction: ${result.txHash}`);
|
|
1104
|
+
* console.log(`Remaining balance: $${result.remainingBalanceUsd}`);
|
|
1105
|
+
* ```
|
|
1106
|
+
*/
|
|
1107
|
+
static async withdraw(options) {
|
|
1108
|
+
const { privateKey, amountUsd } = options;
|
|
1109
|
+
const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
1110
|
+
const account = (0, import_accounts2.privateKeyToAccount)(privateKey);
|
|
1111
|
+
const walletAddress = account.address;
|
|
1112
|
+
const challengeResponse = await fetch(
|
|
1113
|
+
`${baseUrl}/api/v1/agent/challenge?wallet=${walletAddress}&action=withdraw`
|
|
1114
|
+
);
|
|
1115
|
+
if (!challengeResponse.ok) {
|
|
1116
|
+
const error = await challengeResponse.json().catch(() => ({}));
|
|
1117
|
+
throw new MixrPayError(error.error || `Failed to get challenge: ${challengeResponse.status}`);
|
|
1118
|
+
}
|
|
1119
|
+
const { challenge, message } = await challengeResponse.json();
|
|
1120
|
+
const signature = await (0, import_accounts2.signMessage)({ message, privateKey });
|
|
1121
|
+
const withdrawResponse = await fetch(`${baseUrl}/api/v1/agent/withdraw`, {
|
|
1122
|
+
method: "POST",
|
|
1123
|
+
headers: { "Content-Type": "application/json" },
|
|
1124
|
+
body: JSON.stringify({
|
|
1125
|
+
challenge,
|
|
1126
|
+
external_wallet: walletAddress,
|
|
1127
|
+
signature,
|
|
1128
|
+
to_address: walletAddress,
|
|
1129
|
+
// Always withdraw to self
|
|
1130
|
+
amount_usd: amountUsd
|
|
1131
|
+
})
|
|
1132
|
+
});
|
|
1133
|
+
if (!withdrawResponse.ok) {
|
|
1134
|
+
const error = await withdrawResponse.json().catch(() => ({}));
|
|
1135
|
+
throw new MixrPayError(error.error || `Withdrawal failed: ${withdrawResponse.status}`);
|
|
1136
|
+
}
|
|
1137
|
+
const data = await withdrawResponse.json();
|
|
1138
|
+
return {
|
|
1139
|
+
txHash: data.tx_hash,
|
|
1140
|
+
amountUsd: data.amount_usd,
|
|
1141
|
+
remainingBalanceUsd: data.remaining_balance_usd
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
884
1144
|
// ===========================================================================
|
|
885
1145
|
// Core Methods
|
|
886
1146
|
// ===========================================================================
|
|
@@ -919,6 +1179,7 @@ var AgentWallet = class {
|
|
|
919
1179
|
*/
|
|
920
1180
|
async fetch(url, init) {
|
|
921
1181
|
this.logger.debug(`Fetching ${init?.method || "GET"} ${url}`);
|
|
1182
|
+
await this.validateMerchantAllowed(url, "url");
|
|
922
1183
|
const requestId = crypto.randomUUID();
|
|
923
1184
|
const correlationId = this.extractCorrelationId(init?.headers);
|
|
924
1185
|
const controller = new AbortController();
|
|
@@ -1192,7 +1453,8 @@ var AgentWallet = class {
|
|
|
1192
1453
|
},
|
|
1193
1454
|
expiresAt: data.expires_at ? new Date(data.expires_at) : null,
|
|
1194
1455
|
createdAt: data.created_at ? new Date(data.created_at) : null,
|
|
1195
|
-
name: data.name
|
|
1456
|
+
name: data.name,
|
|
1457
|
+
allowedMerchants: data.allowed_merchants ?? data.allowedMerchants ?? []
|
|
1196
1458
|
};
|
|
1197
1459
|
this.sessionKeyInfoFetchedAt = Date.now();
|
|
1198
1460
|
return this.sessionKeyInfo;
|
|
@@ -1758,6 +2020,7 @@ var AgentWallet = class {
|
|
|
1758
2020
|
feature
|
|
1759
2021
|
} = options;
|
|
1760
2022
|
this.logger.debug("callMerchantApi", { url, method, merchantPublicKey, priceUsd });
|
|
2023
|
+
await this.validateMerchantAllowed(url, "url");
|
|
1761
2024
|
if (priceUsd !== void 0 && this.maxPaymentUsd !== void 0 && priceUsd > this.maxPaymentUsd) {
|
|
1762
2025
|
throw new SpendingLimitExceededError("client_max", this.maxPaymentUsd, priceUsd);
|
|
1763
2026
|
}
|
|
@@ -1965,6 +2228,7 @@ Timestamp: ${timestamp}`;
|
|
|
1965
2228
|
*/
|
|
1966
2229
|
async callMCPTool(toolName, args = {}) {
|
|
1967
2230
|
this.logger.debug("callMCPTool", { toolName, args });
|
|
2231
|
+
await this.validateMerchantAllowed(toolName, "tool");
|
|
1968
2232
|
const authHeaders = await this.getMCPAuthHeaders();
|
|
1969
2233
|
const response = await fetch(`${this.baseUrl}/api/mcp`, {
|
|
1970
2234
|
method: "POST",
|
|
@@ -2354,6 +2618,7 @@ Timestamp: ${timestamp}`;
|
|
|
2354
2618
|
*/
|
|
2355
2619
|
async callMCPToolWithSession(sessionId, toolName, args = {}) {
|
|
2356
2620
|
this.logger.debug("callMCPToolWithSession", { sessionId, toolName, args });
|
|
2621
|
+
await this.validateMerchantAllowed(toolName, "tool");
|
|
2357
2622
|
const response = await fetch(`${this.baseUrl}/api/mcp`, {
|
|
2358
2623
|
method: "POST",
|
|
2359
2624
|
headers: {
|
|
@@ -2404,6 +2669,7 @@ Timestamp: ${timestamp}`;
|
|
|
2404
2669
|
AgentWallet,
|
|
2405
2670
|
InsufficientBalanceError,
|
|
2406
2671
|
InvalidSessionKeyError,
|
|
2672
|
+
MerchantNotAllowedError,
|
|
2407
2673
|
MixrPayError,
|
|
2408
2674
|
PaymentFailedError,
|
|
2409
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.
|
|
@@ -378,7 +380,7 @@ interface SessionStats {
|
|
|
378
380
|
*/
|
|
379
381
|
|
|
380
382
|
/** Current SDK version */
|
|
381
|
-
declare const SDK_VERSION = "0.6.
|
|
383
|
+
declare const SDK_VERSION = "0.6.1";
|
|
382
384
|
/** Supported networks */
|
|
383
385
|
declare const NETWORKS: {
|
|
384
386
|
readonly BASE_MAINNET: {
|
|
@@ -487,6 +489,28 @@ interface AgentRevokeSessionKeyOptions {
|
|
|
487
489
|
/** MixrPay API base URL (default: https://www.mixrpay.com) */
|
|
488
490
|
baseUrl?: string;
|
|
489
491
|
}
|
|
492
|
+
/**
|
|
493
|
+
* Options for withdrawing funds
|
|
494
|
+
*/
|
|
495
|
+
interface AgentWithdrawOptions {
|
|
496
|
+
/** The agent's external wallet private key (used for signing, NOT transmitted) */
|
|
497
|
+
privateKey: `0x${string}`;
|
|
498
|
+
/** Amount to withdraw in USD */
|
|
499
|
+
amountUsd: number;
|
|
500
|
+
/** MixrPay API base URL (default: https://www.mixrpay.com) */
|
|
501
|
+
baseUrl?: string;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Result from withdrawal
|
|
505
|
+
*/
|
|
506
|
+
interface AgentWithdrawResult {
|
|
507
|
+
/** On-chain transaction hash */
|
|
508
|
+
txHash: string;
|
|
509
|
+
/** Amount withdrawn in USD */
|
|
510
|
+
amountUsd: number;
|
|
511
|
+
/** Remaining balance after withdrawal */
|
|
512
|
+
remainingBalanceUsd: number;
|
|
513
|
+
}
|
|
490
514
|
type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'none';
|
|
491
515
|
/**
|
|
492
516
|
* A wallet wrapper for AI agents that handles x402 payments automatically.
|
|
@@ -539,6 +563,8 @@ declare class AgentWallet {
|
|
|
539
563
|
private totalSpentUsd;
|
|
540
564
|
private sessionKeyInfo?;
|
|
541
565
|
private sessionKeyInfoFetchedAt?;
|
|
566
|
+
private allowlist?;
|
|
567
|
+
private allowlistFetchedAt?;
|
|
542
568
|
/**
|
|
543
569
|
* Create a new AgentWallet instance.
|
|
544
570
|
*
|
|
@@ -568,6 +594,35 @@ declare class AgentWallet {
|
|
|
568
594
|
* Validate the configuration before initialization.
|
|
569
595
|
*/
|
|
570
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;
|
|
571
626
|
/**
|
|
572
627
|
* Register a new agent with MixrPay.
|
|
573
628
|
*
|
|
@@ -589,6 +644,29 @@ declare class AgentWallet {
|
|
|
589
644
|
* ```
|
|
590
645
|
*/
|
|
591
646
|
static register(options: AgentRegisterOptions): Promise<AgentRegisterResult>;
|
|
647
|
+
/**
|
|
648
|
+
* Check if the MixrPay server is properly configured for agent registration.
|
|
649
|
+
*
|
|
650
|
+
* Use this to diagnose registration issues before attempting to register.
|
|
651
|
+
*
|
|
652
|
+
* @param baseUrl - MixrPay API base URL (default: https://www.mixrpay.com)
|
|
653
|
+
* @returns Server health status including agent registration availability
|
|
654
|
+
*
|
|
655
|
+
* @example
|
|
656
|
+
* ```typescript
|
|
657
|
+
* const status = await AgentWallet.checkServerHealth();
|
|
658
|
+
* if (!status.agentRegistrationAvailable) {
|
|
659
|
+
* console.error('Agent registration is not available:', status);
|
|
660
|
+
* }
|
|
661
|
+
* ```
|
|
662
|
+
*/
|
|
663
|
+
static checkServerHealth(baseUrl?: string): Promise<{
|
|
664
|
+
healthy: boolean;
|
|
665
|
+
database: string;
|
|
666
|
+
agentRegistrationAvailable: boolean;
|
|
667
|
+
privyConfigured: boolean;
|
|
668
|
+
error?: string;
|
|
669
|
+
}>;
|
|
592
670
|
/**
|
|
593
671
|
* Get a session key for an already-registered agent.
|
|
594
672
|
*
|
|
@@ -657,6 +735,31 @@ declare class AgentWallet {
|
|
|
657
735
|
* ```
|
|
658
736
|
*/
|
|
659
737
|
static revokeSessionKey(options: AgentRevokeSessionKeyOptions): Promise<boolean>;
|
|
738
|
+
/**
|
|
739
|
+
* Withdraw USDC from agent's MixrPay wallet to their external wallet.
|
|
740
|
+
*
|
|
741
|
+
* SECURITY: Withdrawals can ONLY go to the agent's own registration wallet
|
|
742
|
+
* (the wallet used during `register()`). This prevents prompt injection
|
|
743
|
+
* attacks where a compromised agent might be tricked into withdrawing
|
|
744
|
+
* to an attacker's address.
|
|
745
|
+
*
|
|
746
|
+
* @param options - Withdrawal options
|
|
747
|
+
* @returns Withdrawal result with transaction hash
|
|
748
|
+
* @throws {MixrPayError} If withdrawal fails
|
|
749
|
+
*
|
|
750
|
+
* @example
|
|
751
|
+
* ```typescript
|
|
752
|
+
* const result = await AgentWallet.withdraw({
|
|
753
|
+
* privateKey: process.env.AGENT_WALLET_KEY as `0x${string}`,
|
|
754
|
+
* amountUsd: 50.00,
|
|
755
|
+
* });
|
|
756
|
+
*
|
|
757
|
+
* console.log(`Withdrew $${result.amountUsd}`);
|
|
758
|
+
* console.log(`Transaction: ${result.txHash}`);
|
|
759
|
+
* console.log(`Remaining balance: $${result.remainingBalanceUsd}`);
|
|
760
|
+
* ```
|
|
761
|
+
*/
|
|
762
|
+
static withdraw(options: AgentWithdrawOptions): Promise<AgentWithdrawResult>;
|
|
660
763
|
/**
|
|
661
764
|
* Make an HTTP request, automatically handling x402 payment if required.
|
|
662
765
|
*
|
|
@@ -1713,6 +1816,34 @@ declare class SessionRevokedError extends MixrPayError {
|
|
|
1713
1816
|
readonly reason?: string;
|
|
1714
1817
|
constructor(sessionId: string, reason?: string);
|
|
1715
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
|
+
}
|
|
1716
1847
|
/**
|
|
1717
1848
|
* Check if an error is a MixrPay SDK error.
|
|
1718
1849
|
*
|
|
@@ -1749,4 +1880,4 @@ declare function isMixrPayError(error: unknown): error is MixrPayError;
|
|
|
1749
1880
|
*/
|
|
1750
1881
|
declare function getErrorMessage(error: unknown): string;
|
|
1751
1882
|
|
|
1752
|
-
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.
|
|
@@ -378,7 +380,7 @@ interface SessionStats {
|
|
|
378
380
|
*/
|
|
379
381
|
|
|
380
382
|
/** Current SDK version */
|
|
381
|
-
declare const SDK_VERSION = "0.6.
|
|
383
|
+
declare const SDK_VERSION = "0.6.1";
|
|
382
384
|
/** Supported networks */
|
|
383
385
|
declare const NETWORKS: {
|
|
384
386
|
readonly BASE_MAINNET: {
|
|
@@ -487,6 +489,28 @@ interface AgentRevokeSessionKeyOptions {
|
|
|
487
489
|
/** MixrPay API base URL (default: https://www.mixrpay.com) */
|
|
488
490
|
baseUrl?: string;
|
|
489
491
|
}
|
|
492
|
+
/**
|
|
493
|
+
* Options for withdrawing funds
|
|
494
|
+
*/
|
|
495
|
+
interface AgentWithdrawOptions {
|
|
496
|
+
/** The agent's external wallet private key (used for signing, NOT transmitted) */
|
|
497
|
+
privateKey: `0x${string}`;
|
|
498
|
+
/** Amount to withdraw in USD */
|
|
499
|
+
amountUsd: number;
|
|
500
|
+
/** MixrPay API base URL (default: https://www.mixrpay.com) */
|
|
501
|
+
baseUrl?: string;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Result from withdrawal
|
|
505
|
+
*/
|
|
506
|
+
interface AgentWithdrawResult {
|
|
507
|
+
/** On-chain transaction hash */
|
|
508
|
+
txHash: string;
|
|
509
|
+
/** Amount withdrawn in USD */
|
|
510
|
+
amountUsd: number;
|
|
511
|
+
/** Remaining balance after withdrawal */
|
|
512
|
+
remainingBalanceUsd: number;
|
|
513
|
+
}
|
|
490
514
|
type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'none';
|
|
491
515
|
/**
|
|
492
516
|
* A wallet wrapper for AI agents that handles x402 payments automatically.
|
|
@@ -539,6 +563,8 @@ declare class AgentWallet {
|
|
|
539
563
|
private totalSpentUsd;
|
|
540
564
|
private sessionKeyInfo?;
|
|
541
565
|
private sessionKeyInfoFetchedAt?;
|
|
566
|
+
private allowlist?;
|
|
567
|
+
private allowlistFetchedAt?;
|
|
542
568
|
/**
|
|
543
569
|
* Create a new AgentWallet instance.
|
|
544
570
|
*
|
|
@@ -568,6 +594,35 @@ declare class AgentWallet {
|
|
|
568
594
|
* Validate the configuration before initialization.
|
|
569
595
|
*/
|
|
570
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;
|
|
571
626
|
/**
|
|
572
627
|
* Register a new agent with MixrPay.
|
|
573
628
|
*
|
|
@@ -589,6 +644,29 @@ declare class AgentWallet {
|
|
|
589
644
|
* ```
|
|
590
645
|
*/
|
|
591
646
|
static register(options: AgentRegisterOptions): Promise<AgentRegisterResult>;
|
|
647
|
+
/**
|
|
648
|
+
* Check if the MixrPay server is properly configured for agent registration.
|
|
649
|
+
*
|
|
650
|
+
* Use this to diagnose registration issues before attempting to register.
|
|
651
|
+
*
|
|
652
|
+
* @param baseUrl - MixrPay API base URL (default: https://www.mixrpay.com)
|
|
653
|
+
* @returns Server health status including agent registration availability
|
|
654
|
+
*
|
|
655
|
+
* @example
|
|
656
|
+
* ```typescript
|
|
657
|
+
* const status = await AgentWallet.checkServerHealth();
|
|
658
|
+
* if (!status.agentRegistrationAvailable) {
|
|
659
|
+
* console.error('Agent registration is not available:', status);
|
|
660
|
+
* }
|
|
661
|
+
* ```
|
|
662
|
+
*/
|
|
663
|
+
static checkServerHealth(baseUrl?: string): Promise<{
|
|
664
|
+
healthy: boolean;
|
|
665
|
+
database: string;
|
|
666
|
+
agentRegistrationAvailable: boolean;
|
|
667
|
+
privyConfigured: boolean;
|
|
668
|
+
error?: string;
|
|
669
|
+
}>;
|
|
592
670
|
/**
|
|
593
671
|
* Get a session key for an already-registered agent.
|
|
594
672
|
*
|
|
@@ -657,6 +735,31 @@ declare class AgentWallet {
|
|
|
657
735
|
* ```
|
|
658
736
|
*/
|
|
659
737
|
static revokeSessionKey(options: AgentRevokeSessionKeyOptions): Promise<boolean>;
|
|
738
|
+
/**
|
|
739
|
+
* Withdraw USDC from agent's MixrPay wallet to their external wallet.
|
|
740
|
+
*
|
|
741
|
+
* SECURITY: Withdrawals can ONLY go to the agent's own registration wallet
|
|
742
|
+
* (the wallet used during `register()`). This prevents prompt injection
|
|
743
|
+
* attacks where a compromised agent might be tricked into withdrawing
|
|
744
|
+
* to an attacker's address.
|
|
745
|
+
*
|
|
746
|
+
* @param options - Withdrawal options
|
|
747
|
+
* @returns Withdrawal result with transaction hash
|
|
748
|
+
* @throws {MixrPayError} If withdrawal fails
|
|
749
|
+
*
|
|
750
|
+
* @example
|
|
751
|
+
* ```typescript
|
|
752
|
+
* const result = await AgentWallet.withdraw({
|
|
753
|
+
* privateKey: process.env.AGENT_WALLET_KEY as `0x${string}`,
|
|
754
|
+
* amountUsd: 50.00,
|
|
755
|
+
* });
|
|
756
|
+
*
|
|
757
|
+
* console.log(`Withdrew $${result.amountUsd}`);
|
|
758
|
+
* console.log(`Transaction: ${result.txHash}`);
|
|
759
|
+
* console.log(`Remaining balance: $${result.remainingBalanceUsd}`);
|
|
760
|
+
* ```
|
|
761
|
+
*/
|
|
762
|
+
static withdraw(options: AgentWithdrawOptions): Promise<AgentWithdrawResult>;
|
|
660
763
|
/**
|
|
661
764
|
* Make an HTTP request, automatically handling x402 payment if required.
|
|
662
765
|
*
|
|
@@ -1713,6 +1816,34 @@ declare class SessionRevokedError extends MixrPayError {
|
|
|
1713
1816
|
readonly reason?: string;
|
|
1714
1817
|
constructor(sessionId: string, reason?: string);
|
|
1715
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
|
+
}
|
|
1716
1847
|
/**
|
|
1717
1848
|
* Check if an error is a MixrPay SDK error.
|
|
1718
1849
|
*
|
|
@@ -1749,4 +1880,4 @@ declare function isMixrPayError(error: unknown): error is MixrPayError;
|
|
|
1749
1880
|
*/
|
|
1750
1881
|
declare function getErrorMessage(error: unknown): string;
|
|
1751
1882
|
|
|
1752
|
-
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
|
}
|
|
@@ -471,7 +488,7 @@ function getAmountUsd(requirements) {
|
|
|
471
488
|
}
|
|
472
489
|
|
|
473
490
|
// src/agent-wallet.ts
|
|
474
|
-
var SDK_VERSION = "0.6.
|
|
491
|
+
var SDK_VERSION = "0.6.1";
|
|
475
492
|
var DEFAULT_BASE_URL = process.env.MIXRPAY_BASE_URL || "https://www.mixrpay.com";
|
|
476
493
|
var DEFAULT_TIMEOUT = 3e4;
|
|
477
494
|
var NETWORKS = {
|
|
@@ -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
|
/**
|
|
@@ -663,7 +803,19 @@ var AgentWallet = class {
|
|
|
663
803
|
});
|
|
664
804
|
if (!registerResponse.ok) {
|
|
665
805
|
const error = await registerResponse.json().catch(() => ({}));
|
|
666
|
-
|
|
806
|
+
const errorMessage = error.error || `Registration failed with status ${registerResponse.status}`;
|
|
807
|
+
const requestId = error.request_id;
|
|
808
|
+
const errorCode = error.code;
|
|
809
|
+
let helpText = "";
|
|
810
|
+
if (registerResponse.status === 503) {
|
|
811
|
+
helpText = " The service may be temporarily unavailable. Please try again later.";
|
|
812
|
+
} else if (registerResponse.status === 500) {
|
|
813
|
+
helpText = " This is a server error. Please contact support with the request ID.";
|
|
814
|
+
} else if (errorCode === "MISSING_CHALLENGE" || errorCode === "MISSING_SIGNATURE") {
|
|
815
|
+
helpText = " This may indicate an SDK bug. Please update to the latest version.";
|
|
816
|
+
}
|
|
817
|
+
const fullMessage = requestId ? `${errorMessage} (request_id: ${requestId})${helpText}` : `${errorMessage}${helpText}`;
|
|
818
|
+
throw new MixrPayError(fullMessage);
|
|
667
819
|
}
|
|
668
820
|
const data = await registerResponse.json();
|
|
669
821
|
return {
|
|
@@ -671,6 +823,52 @@ var AgentWallet = class {
|
|
|
671
823
|
depositAddress: data.deposit_address
|
|
672
824
|
};
|
|
673
825
|
}
|
|
826
|
+
/**
|
|
827
|
+
* Check if the MixrPay server is properly configured for agent registration.
|
|
828
|
+
*
|
|
829
|
+
* Use this to diagnose registration issues before attempting to register.
|
|
830
|
+
*
|
|
831
|
+
* @param baseUrl - MixrPay API base URL (default: https://www.mixrpay.com)
|
|
832
|
+
* @returns Server health status including agent registration availability
|
|
833
|
+
*
|
|
834
|
+
* @example
|
|
835
|
+
* ```typescript
|
|
836
|
+
* const status = await AgentWallet.checkServerHealth();
|
|
837
|
+
* if (!status.agentRegistrationAvailable) {
|
|
838
|
+
* console.error('Agent registration is not available:', status);
|
|
839
|
+
* }
|
|
840
|
+
* ```
|
|
841
|
+
*/
|
|
842
|
+
static async checkServerHealth(baseUrl) {
|
|
843
|
+
const url = (baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
844
|
+
try {
|
|
845
|
+
const response = await fetch(`${url}/api/health/ready?details=true`);
|
|
846
|
+
if (!response.ok) {
|
|
847
|
+
return {
|
|
848
|
+
healthy: false,
|
|
849
|
+
database: "unknown",
|
|
850
|
+
agentRegistrationAvailable: false,
|
|
851
|
+
privyConfigured: false,
|
|
852
|
+
error: `Health check failed with status ${response.status}`
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
const data = await response.json();
|
|
856
|
+
return {
|
|
857
|
+
healthy: data.status === "ready",
|
|
858
|
+
database: data.database || "unknown",
|
|
859
|
+
agentRegistrationAvailable: data.services?.agentRegistration?.available ?? false,
|
|
860
|
+
privyConfigured: data.services?.privy?.configured ?? false
|
|
861
|
+
};
|
|
862
|
+
} catch (error) {
|
|
863
|
+
return {
|
|
864
|
+
healthy: false,
|
|
865
|
+
database: "unreachable",
|
|
866
|
+
agentRegistrationAvailable: false,
|
|
867
|
+
privyConfigured: false,
|
|
868
|
+
error: error instanceof Error ? error.message : "Failed to reach server"
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
}
|
|
674
872
|
/**
|
|
675
873
|
* Get a session key for an already-registered agent.
|
|
676
874
|
*
|
|
@@ -845,6 +1043,67 @@ var AgentWallet = class {
|
|
|
845
1043
|
}
|
|
846
1044
|
return true;
|
|
847
1045
|
}
|
|
1046
|
+
/**
|
|
1047
|
+
* Withdraw USDC from agent's MixrPay wallet to their external wallet.
|
|
1048
|
+
*
|
|
1049
|
+
* SECURITY: Withdrawals can ONLY go to the agent's own registration wallet
|
|
1050
|
+
* (the wallet used during `register()`). This prevents prompt injection
|
|
1051
|
+
* attacks where a compromised agent might be tricked into withdrawing
|
|
1052
|
+
* to an attacker's address.
|
|
1053
|
+
*
|
|
1054
|
+
* @param options - Withdrawal options
|
|
1055
|
+
* @returns Withdrawal result with transaction hash
|
|
1056
|
+
* @throws {MixrPayError} If withdrawal fails
|
|
1057
|
+
*
|
|
1058
|
+
* @example
|
|
1059
|
+
* ```typescript
|
|
1060
|
+
* const result = await AgentWallet.withdraw({
|
|
1061
|
+
* privateKey: process.env.AGENT_WALLET_KEY as `0x${string}`,
|
|
1062
|
+
* amountUsd: 50.00,
|
|
1063
|
+
* });
|
|
1064
|
+
*
|
|
1065
|
+
* console.log(`Withdrew $${result.amountUsd}`);
|
|
1066
|
+
* console.log(`Transaction: ${result.txHash}`);
|
|
1067
|
+
* console.log(`Remaining balance: $${result.remainingBalanceUsd}`);
|
|
1068
|
+
* ```
|
|
1069
|
+
*/
|
|
1070
|
+
static async withdraw(options) {
|
|
1071
|
+
const { privateKey, amountUsd } = options;
|
|
1072
|
+
const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
1073
|
+
const account = privateKeyToAccount2(privateKey);
|
|
1074
|
+
const walletAddress = account.address;
|
|
1075
|
+
const challengeResponse = await fetch(
|
|
1076
|
+
`${baseUrl}/api/v1/agent/challenge?wallet=${walletAddress}&action=withdraw`
|
|
1077
|
+
);
|
|
1078
|
+
if (!challengeResponse.ok) {
|
|
1079
|
+
const error = await challengeResponse.json().catch(() => ({}));
|
|
1080
|
+
throw new MixrPayError(error.error || `Failed to get challenge: ${challengeResponse.status}`);
|
|
1081
|
+
}
|
|
1082
|
+
const { challenge, message } = await challengeResponse.json();
|
|
1083
|
+
const signature = await signMessage({ message, privateKey });
|
|
1084
|
+
const withdrawResponse = await fetch(`${baseUrl}/api/v1/agent/withdraw`, {
|
|
1085
|
+
method: "POST",
|
|
1086
|
+
headers: { "Content-Type": "application/json" },
|
|
1087
|
+
body: JSON.stringify({
|
|
1088
|
+
challenge,
|
|
1089
|
+
external_wallet: walletAddress,
|
|
1090
|
+
signature,
|
|
1091
|
+
to_address: walletAddress,
|
|
1092
|
+
// Always withdraw to self
|
|
1093
|
+
amount_usd: amountUsd
|
|
1094
|
+
})
|
|
1095
|
+
});
|
|
1096
|
+
if (!withdrawResponse.ok) {
|
|
1097
|
+
const error = await withdrawResponse.json().catch(() => ({}));
|
|
1098
|
+
throw new MixrPayError(error.error || `Withdrawal failed: ${withdrawResponse.status}`);
|
|
1099
|
+
}
|
|
1100
|
+
const data = await withdrawResponse.json();
|
|
1101
|
+
return {
|
|
1102
|
+
txHash: data.tx_hash,
|
|
1103
|
+
amountUsd: data.amount_usd,
|
|
1104
|
+
remainingBalanceUsd: data.remaining_balance_usd
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
848
1107
|
// ===========================================================================
|
|
849
1108
|
// Core Methods
|
|
850
1109
|
// ===========================================================================
|
|
@@ -883,6 +1142,7 @@ var AgentWallet = class {
|
|
|
883
1142
|
*/
|
|
884
1143
|
async fetch(url, init) {
|
|
885
1144
|
this.logger.debug(`Fetching ${init?.method || "GET"} ${url}`);
|
|
1145
|
+
await this.validateMerchantAllowed(url, "url");
|
|
886
1146
|
const requestId = crypto.randomUUID();
|
|
887
1147
|
const correlationId = this.extractCorrelationId(init?.headers);
|
|
888
1148
|
const controller = new AbortController();
|
|
@@ -1156,7 +1416,8 @@ var AgentWallet = class {
|
|
|
1156
1416
|
},
|
|
1157
1417
|
expiresAt: data.expires_at ? new Date(data.expires_at) : null,
|
|
1158
1418
|
createdAt: data.created_at ? new Date(data.created_at) : null,
|
|
1159
|
-
name: data.name
|
|
1419
|
+
name: data.name,
|
|
1420
|
+
allowedMerchants: data.allowed_merchants ?? data.allowedMerchants ?? []
|
|
1160
1421
|
};
|
|
1161
1422
|
this.sessionKeyInfoFetchedAt = Date.now();
|
|
1162
1423
|
return this.sessionKeyInfo;
|
|
@@ -1722,6 +1983,7 @@ var AgentWallet = class {
|
|
|
1722
1983
|
feature
|
|
1723
1984
|
} = options;
|
|
1724
1985
|
this.logger.debug("callMerchantApi", { url, method, merchantPublicKey, priceUsd });
|
|
1986
|
+
await this.validateMerchantAllowed(url, "url");
|
|
1725
1987
|
if (priceUsd !== void 0 && this.maxPaymentUsd !== void 0 && priceUsd > this.maxPaymentUsd) {
|
|
1726
1988
|
throw new SpendingLimitExceededError("client_max", this.maxPaymentUsd, priceUsd);
|
|
1727
1989
|
}
|
|
@@ -1929,6 +2191,7 @@ Timestamp: ${timestamp}`;
|
|
|
1929
2191
|
*/
|
|
1930
2192
|
async callMCPTool(toolName, args = {}) {
|
|
1931
2193
|
this.logger.debug("callMCPTool", { toolName, args });
|
|
2194
|
+
await this.validateMerchantAllowed(toolName, "tool");
|
|
1932
2195
|
const authHeaders = await this.getMCPAuthHeaders();
|
|
1933
2196
|
const response = await fetch(`${this.baseUrl}/api/mcp`, {
|
|
1934
2197
|
method: "POST",
|
|
@@ -2318,6 +2581,7 @@ Timestamp: ${timestamp}`;
|
|
|
2318
2581
|
*/
|
|
2319
2582
|
async callMCPToolWithSession(sessionId, toolName, args = {}) {
|
|
2320
2583
|
this.logger.debug("callMCPToolWithSession", { sessionId, toolName, args });
|
|
2584
|
+
await this.validateMerchantAllowed(toolName, "tool");
|
|
2321
2585
|
const response = await fetch(`${this.baseUrl}/api/mcp`, {
|
|
2322
2586
|
method: "POST",
|
|
2323
2587
|
headers: {
|
|
@@ -2367,6 +2631,7 @@ export {
|
|
|
2367
2631
|
AgentWallet,
|
|
2368
2632
|
InsufficientBalanceError,
|
|
2369
2633
|
InvalidSessionKeyError,
|
|
2634
|
+
MerchantNotAllowedError,
|
|
2370
2635
|
MixrPayError,
|
|
2371
2636
|
PaymentFailedError,
|
|
2372
2637
|
SDK_VERSION,
|