@mixrpay/agent-sdk 0.6.1 → 0.8.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 +234 -2
- package/dist/index.d.cts +130 -2
- package/dist/index.d.ts +130 -2
- package/dist/index.js +233 -2
- 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
|
}
|
|
@@ -507,7 +525,7 @@ function getAmountUsd(requirements) {
|
|
|
507
525
|
}
|
|
508
526
|
|
|
509
527
|
// src/agent-wallet.ts
|
|
510
|
-
var SDK_VERSION = "0.
|
|
528
|
+
var SDK_VERSION = "0.8.0";
|
|
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
|
/**
|
|
@@ -1000,6 +1141,91 @@ var AgentWallet = class {
|
|
|
1000
1141
|
remainingBalanceUsd: data.remaining_balance_usd
|
|
1001
1142
|
};
|
|
1002
1143
|
}
|
|
1144
|
+
/**
|
|
1145
|
+
* Claim an agent invite code to receive a session key for the inviter's wallet.
|
|
1146
|
+
*
|
|
1147
|
+
* This allows an agent to get a pre-configured session key from a human wallet owner
|
|
1148
|
+
* without needing to register their own wallet or fund it. The human sets the budget
|
|
1149
|
+
* limits and merchant whitelist when creating the invite.
|
|
1150
|
+
*
|
|
1151
|
+
* @param options - Claim invite options including the invite code and agent's private key
|
|
1152
|
+
* @returns Claim result with the new session key
|
|
1153
|
+
* @throws {MixrPayError} If claiming fails (e.g., invalid code, already claimed, expired)
|
|
1154
|
+
*
|
|
1155
|
+
* @example
|
|
1156
|
+
* ```typescript
|
|
1157
|
+
* // Human creates invite at their dashboard, shares code "mixr-abc123"
|
|
1158
|
+
*
|
|
1159
|
+
* const result = await AgentWallet.claimInvite({
|
|
1160
|
+
* inviteCode: 'mixr-abc123',
|
|
1161
|
+
* privateKey: process.env.AGENT_WALLET_KEY as `0x${string}`,
|
|
1162
|
+
* });
|
|
1163
|
+
*
|
|
1164
|
+
* console.log(`Got session key: ${result.sessionKey}`);
|
|
1165
|
+
* console.log(`Budget: $${result.limits.budgetUsd}/${result.limits.budgetPeriod}`);
|
|
1166
|
+
* console.log(`Invited by: ${result.inviterName}`);
|
|
1167
|
+
*
|
|
1168
|
+
* // Use the session key to make payments
|
|
1169
|
+
* const wallet = new AgentWallet({ sessionKey: result.sessionKey });
|
|
1170
|
+
* const response = await wallet.fetch('https://api.example.com/endpoint');
|
|
1171
|
+
* ```
|
|
1172
|
+
*/
|
|
1173
|
+
static async claimInvite(options) {
|
|
1174
|
+
const { inviteCode, privateKey } = options;
|
|
1175
|
+
const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
1176
|
+
const account = (0, import_accounts2.privateKeyToAccount)(privateKey);
|
|
1177
|
+
const walletAddress = account.address;
|
|
1178
|
+
const challengeResponse = await fetch(
|
|
1179
|
+
`${baseUrl}/api/v1/agent/challenge?wallet=${walletAddress}&action=claim-invite`
|
|
1180
|
+
);
|
|
1181
|
+
if (!challengeResponse.ok) {
|
|
1182
|
+
const error = await challengeResponse.json().catch(() => ({}));
|
|
1183
|
+
throw new MixrPayError(error.error || `Failed to get challenge: ${challengeResponse.status}`);
|
|
1184
|
+
}
|
|
1185
|
+
const { challenge, message } = await challengeResponse.json();
|
|
1186
|
+
const signature = await (0, import_accounts2.signMessage)({ message, privateKey });
|
|
1187
|
+
const claimResponse = await fetch(`${baseUrl}/api/v1/agent/claim-invite`, {
|
|
1188
|
+
method: "POST",
|
|
1189
|
+
headers: { "Content-Type": "application/json" },
|
|
1190
|
+
body: JSON.stringify({
|
|
1191
|
+
invite_code: inviteCode,
|
|
1192
|
+
challenge,
|
|
1193
|
+
agent_wallet_address: walletAddress,
|
|
1194
|
+
signature
|
|
1195
|
+
})
|
|
1196
|
+
});
|
|
1197
|
+
if (!claimResponse.ok) {
|
|
1198
|
+
const error = await claimResponse.json().catch(() => ({}));
|
|
1199
|
+
const errorMessage = error.error || `Failed to claim invite: ${claimResponse.status}`;
|
|
1200
|
+
let helpText = "";
|
|
1201
|
+
if (claimResponse.status === 404) {
|
|
1202
|
+
helpText = " The invite code may be invalid or misspelled.";
|
|
1203
|
+
} else if (claimResponse.status === 400) {
|
|
1204
|
+
if (errorMessage.includes("already claimed")) {
|
|
1205
|
+
helpText = " This invite has already been used by another agent.";
|
|
1206
|
+
} else if (errorMessage.includes("expired")) {
|
|
1207
|
+
helpText = " Ask the wallet owner to create a new invite.";
|
|
1208
|
+
} else if (errorMessage.includes("revoked")) {
|
|
1209
|
+
helpText = " The wallet owner has revoked this invite.";
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
throw new MixrPayError(`${errorMessage}${helpText}`);
|
|
1213
|
+
}
|
|
1214
|
+
const data = await claimResponse.json();
|
|
1215
|
+
return {
|
|
1216
|
+
sessionKey: data.session_key,
|
|
1217
|
+
address: data.address,
|
|
1218
|
+
sessionKeyId: data.session_key_id,
|
|
1219
|
+
expiresAt: new Date(data.expires_at),
|
|
1220
|
+
limits: {
|
|
1221
|
+
budgetUsd: data.limits.budget_usd,
|
|
1222
|
+
budgetPeriod: data.limits.budget_period,
|
|
1223
|
+
maxPerTxUsd: data.limits.max_per_tx_usd
|
|
1224
|
+
},
|
|
1225
|
+
allowedMerchants: data.allowed_merchants || [],
|
|
1226
|
+
inviterName: data.inviter_name || "Anonymous"
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1003
1229
|
// ===========================================================================
|
|
1004
1230
|
// Core Methods
|
|
1005
1231
|
// ===========================================================================
|
|
@@ -1038,6 +1264,7 @@ var AgentWallet = class {
|
|
|
1038
1264
|
*/
|
|
1039
1265
|
async fetch(url, init) {
|
|
1040
1266
|
this.logger.debug(`Fetching ${init?.method || "GET"} ${url}`);
|
|
1267
|
+
await this.validateMerchantAllowed(url, "url");
|
|
1041
1268
|
const requestId = crypto.randomUUID();
|
|
1042
1269
|
const correlationId = this.extractCorrelationId(init?.headers);
|
|
1043
1270
|
const controller = new AbortController();
|
|
@@ -1311,7 +1538,8 @@ var AgentWallet = class {
|
|
|
1311
1538
|
},
|
|
1312
1539
|
expiresAt: data.expires_at ? new Date(data.expires_at) : null,
|
|
1313
1540
|
createdAt: data.created_at ? new Date(data.created_at) : null,
|
|
1314
|
-
name: data.name
|
|
1541
|
+
name: data.name,
|
|
1542
|
+
allowedMerchants: data.allowed_merchants ?? data.allowedMerchants ?? []
|
|
1315
1543
|
};
|
|
1316
1544
|
this.sessionKeyInfoFetchedAt = Date.now();
|
|
1317
1545
|
return this.sessionKeyInfo;
|
|
@@ -1877,6 +2105,7 @@ var AgentWallet = class {
|
|
|
1877
2105
|
feature
|
|
1878
2106
|
} = options;
|
|
1879
2107
|
this.logger.debug("callMerchantApi", { url, method, merchantPublicKey, priceUsd });
|
|
2108
|
+
await this.validateMerchantAllowed(url, "url");
|
|
1880
2109
|
if (priceUsd !== void 0 && this.maxPaymentUsd !== void 0 && priceUsd > this.maxPaymentUsd) {
|
|
1881
2110
|
throw new SpendingLimitExceededError("client_max", this.maxPaymentUsd, priceUsd);
|
|
1882
2111
|
}
|
|
@@ -2084,6 +2313,7 @@ Timestamp: ${timestamp}`;
|
|
|
2084
2313
|
*/
|
|
2085
2314
|
async callMCPTool(toolName, args = {}) {
|
|
2086
2315
|
this.logger.debug("callMCPTool", { toolName, args });
|
|
2316
|
+
await this.validateMerchantAllowed(toolName, "tool");
|
|
2087
2317
|
const authHeaders = await this.getMCPAuthHeaders();
|
|
2088
2318
|
const response = await fetch(`${this.baseUrl}/api/mcp`, {
|
|
2089
2319
|
method: "POST",
|
|
@@ -2473,6 +2703,7 @@ Timestamp: ${timestamp}`;
|
|
|
2473
2703
|
*/
|
|
2474
2704
|
async callMCPToolWithSession(sessionId, toolName, args = {}) {
|
|
2475
2705
|
this.logger.debug("callMCPToolWithSession", { sessionId, toolName, args });
|
|
2706
|
+
await this.validateMerchantAllowed(toolName, "tool");
|
|
2476
2707
|
const response = await fetch(`${this.baseUrl}/api/mcp`, {
|
|
2477
2708
|
method: "POST",
|
|
2478
2709
|
headers: {
|
|
@@ -2523,6 +2754,7 @@ Timestamp: ${timestamp}`;
|
|
|
2523
2754
|
AgentWallet,
|
|
2524
2755
|
InsufficientBalanceError,
|
|
2525
2756
|
InvalidSessionKeyError,
|
|
2757
|
+
MerchantNotAllowedError,
|
|
2526
2758
|
MixrPayError,
|
|
2527
2759
|
PaymentFailedError,
|
|
2528
2760
|
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.
|
|
383
|
+
declare const SDK_VERSION = "0.8.0";
|
|
382
384
|
/** Supported networks */
|
|
383
385
|
declare const NETWORKS: {
|
|
384
386
|
readonly BASE_MAINNET: {
|
|
@@ -509,6 +511,43 @@ interface AgentWithdrawResult {
|
|
|
509
511
|
/** Remaining balance after withdrawal */
|
|
510
512
|
remainingBalanceUsd: number;
|
|
511
513
|
}
|
|
514
|
+
/**
|
|
515
|
+
* Options for claiming an agent invite
|
|
516
|
+
*/
|
|
517
|
+
interface AgentClaimInviteOptions {
|
|
518
|
+
/** The invite code provided by the human wallet owner (e.g., "mixr-abc123") */
|
|
519
|
+
inviteCode: string;
|
|
520
|
+
/** The agent's external wallet private key (used for signing, NOT transmitted) */
|
|
521
|
+
privateKey: `0x${string}`;
|
|
522
|
+
/** MixrPay API base URL (default: https://www.mixrpay.com) */
|
|
523
|
+
baseUrl?: string;
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Result from claiming an agent invite
|
|
527
|
+
*/
|
|
528
|
+
interface AgentClaimInviteResult {
|
|
529
|
+
/** The session key in sk_live_{hex} format - STORE SECURELY! */
|
|
530
|
+
sessionKey: string;
|
|
531
|
+
/** The derived public address of the session key */
|
|
532
|
+
address: string;
|
|
533
|
+
/** Session key database ID */
|
|
534
|
+
sessionKeyId: string;
|
|
535
|
+
/** When the session key expires */
|
|
536
|
+
expiresAt: Date;
|
|
537
|
+
/** Budget limits set by the inviter */
|
|
538
|
+
limits: {
|
|
539
|
+
/** Total budget in USD */
|
|
540
|
+
budgetUsd: number;
|
|
541
|
+
/** Budget period: 'daily', 'monthly', or 'total' */
|
|
542
|
+
budgetPeriod: string;
|
|
543
|
+
/** Maximum per transaction in USD (null = no limit) */
|
|
544
|
+
maxPerTxUsd: number | null;
|
|
545
|
+
};
|
|
546
|
+
/** Whitelisted merchants/tools (empty = allow all) */
|
|
547
|
+
allowedMerchants: string[];
|
|
548
|
+
/** Name of the person who created the invite */
|
|
549
|
+
inviterName: string;
|
|
550
|
+
}
|
|
512
551
|
type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'none';
|
|
513
552
|
/**
|
|
514
553
|
* A wallet wrapper for AI agents that handles x402 payments automatically.
|
|
@@ -561,6 +600,8 @@ declare class AgentWallet {
|
|
|
561
600
|
private totalSpentUsd;
|
|
562
601
|
private sessionKeyInfo?;
|
|
563
602
|
private sessionKeyInfoFetchedAt?;
|
|
603
|
+
private allowlist?;
|
|
604
|
+
private allowlistFetchedAt?;
|
|
564
605
|
/**
|
|
565
606
|
* Create a new AgentWallet instance.
|
|
566
607
|
*
|
|
@@ -590,6 +631,35 @@ declare class AgentWallet {
|
|
|
590
631
|
* Validate the configuration before initialization.
|
|
591
632
|
*/
|
|
592
633
|
private validateConfig;
|
|
634
|
+
/**
|
|
635
|
+
* Fetch the merchant allowlist from the server.
|
|
636
|
+
* Caches the result for 5 minutes to avoid excessive API calls.
|
|
637
|
+
*/
|
|
638
|
+
private fetchAllowlist;
|
|
639
|
+
/**
|
|
640
|
+
* Validate that a target is allowed by the session's allowlist.
|
|
641
|
+
*
|
|
642
|
+
* @param target - URL, domain, or tool name to check
|
|
643
|
+
* @param type - Type of target: 'url' for HTTP requests, 'tool' for MCP tools
|
|
644
|
+
* @throws {MerchantNotAllowedError} If target is not in allowlist
|
|
645
|
+
*/
|
|
646
|
+
private validateMerchantAllowed;
|
|
647
|
+
/**
|
|
648
|
+
* Check if a target matches any pattern in the allowlist.
|
|
649
|
+
*/
|
|
650
|
+
private matchesAllowlist;
|
|
651
|
+
/**
|
|
652
|
+
* Check if a target matches a single pattern.
|
|
653
|
+
*/
|
|
654
|
+
private matchPattern;
|
|
655
|
+
/**
|
|
656
|
+
* Match a URL or domain against a pattern.
|
|
657
|
+
*/
|
|
658
|
+
private matchUrlPattern;
|
|
659
|
+
/**
|
|
660
|
+
* Match an MCP tool name against a pattern.
|
|
661
|
+
*/
|
|
662
|
+
private matchToolPattern;
|
|
593
663
|
/**
|
|
594
664
|
* Register a new agent with MixrPay.
|
|
595
665
|
*
|
|
@@ -727,6 +797,36 @@ declare class AgentWallet {
|
|
|
727
797
|
* ```
|
|
728
798
|
*/
|
|
729
799
|
static withdraw(options: AgentWithdrawOptions): Promise<AgentWithdrawResult>;
|
|
800
|
+
/**
|
|
801
|
+
* Claim an agent invite code to receive a session key for the inviter's wallet.
|
|
802
|
+
*
|
|
803
|
+
* This allows an agent to get a pre-configured session key from a human wallet owner
|
|
804
|
+
* without needing to register their own wallet or fund it. The human sets the budget
|
|
805
|
+
* limits and merchant whitelist when creating the invite.
|
|
806
|
+
*
|
|
807
|
+
* @param options - Claim invite options including the invite code and agent's private key
|
|
808
|
+
* @returns Claim result with the new session key
|
|
809
|
+
* @throws {MixrPayError} If claiming fails (e.g., invalid code, already claimed, expired)
|
|
810
|
+
*
|
|
811
|
+
* @example
|
|
812
|
+
* ```typescript
|
|
813
|
+
* // Human creates invite at their dashboard, shares code "mixr-abc123"
|
|
814
|
+
*
|
|
815
|
+
* const result = await AgentWallet.claimInvite({
|
|
816
|
+
* inviteCode: 'mixr-abc123',
|
|
817
|
+
* privateKey: process.env.AGENT_WALLET_KEY as `0x${string}`,
|
|
818
|
+
* });
|
|
819
|
+
*
|
|
820
|
+
* console.log(`Got session key: ${result.sessionKey}`);
|
|
821
|
+
* console.log(`Budget: $${result.limits.budgetUsd}/${result.limits.budgetPeriod}`);
|
|
822
|
+
* console.log(`Invited by: ${result.inviterName}`);
|
|
823
|
+
*
|
|
824
|
+
* // Use the session key to make payments
|
|
825
|
+
* const wallet = new AgentWallet({ sessionKey: result.sessionKey });
|
|
826
|
+
* const response = await wallet.fetch('https://api.example.com/endpoint');
|
|
827
|
+
* ```
|
|
828
|
+
*/
|
|
829
|
+
static claimInvite(options: AgentClaimInviteOptions): Promise<AgentClaimInviteResult>;
|
|
730
830
|
/**
|
|
731
831
|
* Make an HTTP request, automatically handling x402 payment if required.
|
|
732
832
|
*
|
|
@@ -1783,6 +1883,34 @@ declare class SessionRevokedError extends MixrPayError {
|
|
|
1783
1883
|
readonly reason?: string;
|
|
1784
1884
|
constructor(sessionId: string, reason?: string);
|
|
1785
1885
|
}
|
|
1886
|
+
/**
|
|
1887
|
+
* Thrown when a payment is attempted to a merchant/tool not in the session's allowlist.
|
|
1888
|
+
*
|
|
1889
|
+
* This error indicates the session key has a configured allowlist that doesn't
|
|
1890
|
+
* include the target merchant or tool.
|
|
1891
|
+
*
|
|
1892
|
+
* ## Resolution
|
|
1893
|
+
* 1. Update the session's allowlist to include the target
|
|
1894
|
+
* 2. Create a new session with the correct allowlist
|
|
1895
|
+
* 3. Use a session without allowlist restrictions
|
|
1896
|
+
*
|
|
1897
|
+
* @example
|
|
1898
|
+
* ```typescript
|
|
1899
|
+
* catch (error) {
|
|
1900
|
+
* if (error instanceof MerchantNotAllowedError) {
|
|
1901
|
+
* console.log(`Cannot pay "${error.attempted}"`);
|
|
1902
|
+
* console.log(`Allowed patterns: ${error.allowedPatterns.join(', ')}`);
|
|
1903
|
+
* }
|
|
1904
|
+
* }
|
|
1905
|
+
* ```
|
|
1906
|
+
*/
|
|
1907
|
+
declare class MerchantNotAllowedError extends MixrPayError {
|
|
1908
|
+
/** The merchant/tool that was attempted */
|
|
1909
|
+
readonly attempted: string;
|
|
1910
|
+
/** The patterns that are allowed by the session */
|
|
1911
|
+
readonly allowedPatterns: string[];
|
|
1912
|
+
constructor(attempted: string, allowedPatterns: string[]);
|
|
1913
|
+
}
|
|
1786
1914
|
/**
|
|
1787
1915
|
* Check if an error is a MixrPay SDK error.
|
|
1788
1916
|
*
|
|
@@ -1819,4 +1947,4 @@ declare function isMixrPayError(error: unknown): error is MixrPayError;
|
|
|
1819
1947
|
*/
|
|
1820
1948
|
declare function getErrorMessage(error: unknown): string;
|
|
1821
1949
|
|
|
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 };
|
|
1950
|
+
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.
|
|
383
|
+
declare const SDK_VERSION = "0.8.0";
|
|
382
384
|
/** Supported networks */
|
|
383
385
|
declare const NETWORKS: {
|
|
384
386
|
readonly BASE_MAINNET: {
|
|
@@ -509,6 +511,43 @@ interface AgentWithdrawResult {
|
|
|
509
511
|
/** Remaining balance after withdrawal */
|
|
510
512
|
remainingBalanceUsd: number;
|
|
511
513
|
}
|
|
514
|
+
/**
|
|
515
|
+
* Options for claiming an agent invite
|
|
516
|
+
*/
|
|
517
|
+
interface AgentClaimInviteOptions {
|
|
518
|
+
/** The invite code provided by the human wallet owner (e.g., "mixr-abc123") */
|
|
519
|
+
inviteCode: string;
|
|
520
|
+
/** The agent's external wallet private key (used for signing, NOT transmitted) */
|
|
521
|
+
privateKey: `0x${string}`;
|
|
522
|
+
/** MixrPay API base URL (default: https://www.mixrpay.com) */
|
|
523
|
+
baseUrl?: string;
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Result from claiming an agent invite
|
|
527
|
+
*/
|
|
528
|
+
interface AgentClaimInviteResult {
|
|
529
|
+
/** The session key in sk_live_{hex} format - STORE SECURELY! */
|
|
530
|
+
sessionKey: string;
|
|
531
|
+
/** The derived public address of the session key */
|
|
532
|
+
address: string;
|
|
533
|
+
/** Session key database ID */
|
|
534
|
+
sessionKeyId: string;
|
|
535
|
+
/** When the session key expires */
|
|
536
|
+
expiresAt: Date;
|
|
537
|
+
/** Budget limits set by the inviter */
|
|
538
|
+
limits: {
|
|
539
|
+
/** Total budget in USD */
|
|
540
|
+
budgetUsd: number;
|
|
541
|
+
/** Budget period: 'daily', 'monthly', or 'total' */
|
|
542
|
+
budgetPeriod: string;
|
|
543
|
+
/** Maximum per transaction in USD (null = no limit) */
|
|
544
|
+
maxPerTxUsd: number | null;
|
|
545
|
+
};
|
|
546
|
+
/** Whitelisted merchants/tools (empty = allow all) */
|
|
547
|
+
allowedMerchants: string[];
|
|
548
|
+
/** Name of the person who created the invite */
|
|
549
|
+
inviterName: string;
|
|
550
|
+
}
|
|
512
551
|
type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'none';
|
|
513
552
|
/**
|
|
514
553
|
* A wallet wrapper for AI agents that handles x402 payments automatically.
|
|
@@ -561,6 +600,8 @@ declare class AgentWallet {
|
|
|
561
600
|
private totalSpentUsd;
|
|
562
601
|
private sessionKeyInfo?;
|
|
563
602
|
private sessionKeyInfoFetchedAt?;
|
|
603
|
+
private allowlist?;
|
|
604
|
+
private allowlistFetchedAt?;
|
|
564
605
|
/**
|
|
565
606
|
* Create a new AgentWallet instance.
|
|
566
607
|
*
|
|
@@ -590,6 +631,35 @@ declare class AgentWallet {
|
|
|
590
631
|
* Validate the configuration before initialization.
|
|
591
632
|
*/
|
|
592
633
|
private validateConfig;
|
|
634
|
+
/**
|
|
635
|
+
* Fetch the merchant allowlist from the server.
|
|
636
|
+
* Caches the result for 5 minutes to avoid excessive API calls.
|
|
637
|
+
*/
|
|
638
|
+
private fetchAllowlist;
|
|
639
|
+
/**
|
|
640
|
+
* Validate that a target is allowed by the session's allowlist.
|
|
641
|
+
*
|
|
642
|
+
* @param target - URL, domain, or tool name to check
|
|
643
|
+
* @param type - Type of target: 'url' for HTTP requests, 'tool' for MCP tools
|
|
644
|
+
* @throws {MerchantNotAllowedError} If target is not in allowlist
|
|
645
|
+
*/
|
|
646
|
+
private validateMerchantAllowed;
|
|
647
|
+
/**
|
|
648
|
+
* Check if a target matches any pattern in the allowlist.
|
|
649
|
+
*/
|
|
650
|
+
private matchesAllowlist;
|
|
651
|
+
/**
|
|
652
|
+
* Check if a target matches a single pattern.
|
|
653
|
+
*/
|
|
654
|
+
private matchPattern;
|
|
655
|
+
/**
|
|
656
|
+
* Match a URL or domain against a pattern.
|
|
657
|
+
*/
|
|
658
|
+
private matchUrlPattern;
|
|
659
|
+
/**
|
|
660
|
+
* Match an MCP tool name against a pattern.
|
|
661
|
+
*/
|
|
662
|
+
private matchToolPattern;
|
|
593
663
|
/**
|
|
594
664
|
* Register a new agent with MixrPay.
|
|
595
665
|
*
|
|
@@ -727,6 +797,36 @@ declare class AgentWallet {
|
|
|
727
797
|
* ```
|
|
728
798
|
*/
|
|
729
799
|
static withdraw(options: AgentWithdrawOptions): Promise<AgentWithdrawResult>;
|
|
800
|
+
/**
|
|
801
|
+
* Claim an agent invite code to receive a session key for the inviter's wallet.
|
|
802
|
+
*
|
|
803
|
+
* This allows an agent to get a pre-configured session key from a human wallet owner
|
|
804
|
+
* without needing to register their own wallet or fund it. The human sets the budget
|
|
805
|
+
* limits and merchant whitelist when creating the invite.
|
|
806
|
+
*
|
|
807
|
+
* @param options - Claim invite options including the invite code and agent's private key
|
|
808
|
+
* @returns Claim result with the new session key
|
|
809
|
+
* @throws {MixrPayError} If claiming fails (e.g., invalid code, already claimed, expired)
|
|
810
|
+
*
|
|
811
|
+
* @example
|
|
812
|
+
* ```typescript
|
|
813
|
+
* // Human creates invite at their dashboard, shares code "mixr-abc123"
|
|
814
|
+
*
|
|
815
|
+
* const result = await AgentWallet.claimInvite({
|
|
816
|
+
* inviteCode: 'mixr-abc123',
|
|
817
|
+
* privateKey: process.env.AGENT_WALLET_KEY as `0x${string}`,
|
|
818
|
+
* });
|
|
819
|
+
*
|
|
820
|
+
* console.log(`Got session key: ${result.sessionKey}`);
|
|
821
|
+
* console.log(`Budget: $${result.limits.budgetUsd}/${result.limits.budgetPeriod}`);
|
|
822
|
+
* console.log(`Invited by: ${result.inviterName}`);
|
|
823
|
+
*
|
|
824
|
+
* // Use the session key to make payments
|
|
825
|
+
* const wallet = new AgentWallet({ sessionKey: result.sessionKey });
|
|
826
|
+
* const response = await wallet.fetch('https://api.example.com/endpoint');
|
|
827
|
+
* ```
|
|
828
|
+
*/
|
|
829
|
+
static claimInvite(options: AgentClaimInviteOptions): Promise<AgentClaimInviteResult>;
|
|
730
830
|
/**
|
|
731
831
|
* Make an HTTP request, automatically handling x402 payment if required.
|
|
732
832
|
*
|
|
@@ -1783,6 +1883,34 @@ declare class SessionRevokedError extends MixrPayError {
|
|
|
1783
1883
|
readonly reason?: string;
|
|
1784
1884
|
constructor(sessionId: string, reason?: string);
|
|
1785
1885
|
}
|
|
1886
|
+
/**
|
|
1887
|
+
* Thrown when a payment is attempted to a merchant/tool not in the session's allowlist.
|
|
1888
|
+
*
|
|
1889
|
+
* This error indicates the session key has a configured allowlist that doesn't
|
|
1890
|
+
* include the target merchant or tool.
|
|
1891
|
+
*
|
|
1892
|
+
* ## Resolution
|
|
1893
|
+
* 1. Update the session's allowlist to include the target
|
|
1894
|
+
* 2. Create a new session with the correct allowlist
|
|
1895
|
+
* 3. Use a session without allowlist restrictions
|
|
1896
|
+
*
|
|
1897
|
+
* @example
|
|
1898
|
+
* ```typescript
|
|
1899
|
+
* catch (error) {
|
|
1900
|
+
* if (error instanceof MerchantNotAllowedError) {
|
|
1901
|
+
* console.log(`Cannot pay "${error.attempted}"`);
|
|
1902
|
+
* console.log(`Allowed patterns: ${error.allowedPatterns.join(', ')}`);
|
|
1903
|
+
* }
|
|
1904
|
+
* }
|
|
1905
|
+
* ```
|
|
1906
|
+
*/
|
|
1907
|
+
declare class MerchantNotAllowedError extends MixrPayError {
|
|
1908
|
+
/** The merchant/tool that was attempted */
|
|
1909
|
+
readonly attempted: string;
|
|
1910
|
+
/** The patterns that are allowed by the session */
|
|
1911
|
+
readonly allowedPatterns: string[];
|
|
1912
|
+
constructor(attempted: string, allowedPatterns: string[]);
|
|
1913
|
+
}
|
|
1786
1914
|
/**
|
|
1787
1915
|
* Check if an error is a MixrPay SDK error.
|
|
1788
1916
|
*
|
|
@@ -1819,4 +1947,4 @@ declare function isMixrPayError(error: unknown): error is MixrPayError;
|
|
|
1819
1947
|
*/
|
|
1820
1948
|
declare function getErrorMessage(error: unknown): string;
|
|
1821
1949
|
|
|
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 };
|
|
1950
|
+
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.
|
|
491
|
+
var SDK_VERSION = "0.8.0";
|
|
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
|
/**
|
|
@@ -964,6 +1104,91 @@ var AgentWallet = class {
|
|
|
964
1104
|
remainingBalanceUsd: data.remaining_balance_usd
|
|
965
1105
|
};
|
|
966
1106
|
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Claim an agent invite code to receive a session key for the inviter's wallet.
|
|
1109
|
+
*
|
|
1110
|
+
* This allows an agent to get a pre-configured session key from a human wallet owner
|
|
1111
|
+
* without needing to register their own wallet or fund it. The human sets the budget
|
|
1112
|
+
* limits and merchant whitelist when creating the invite.
|
|
1113
|
+
*
|
|
1114
|
+
* @param options - Claim invite options including the invite code and agent's private key
|
|
1115
|
+
* @returns Claim result with the new session key
|
|
1116
|
+
* @throws {MixrPayError} If claiming fails (e.g., invalid code, already claimed, expired)
|
|
1117
|
+
*
|
|
1118
|
+
* @example
|
|
1119
|
+
* ```typescript
|
|
1120
|
+
* // Human creates invite at their dashboard, shares code "mixr-abc123"
|
|
1121
|
+
*
|
|
1122
|
+
* const result = await AgentWallet.claimInvite({
|
|
1123
|
+
* inviteCode: 'mixr-abc123',
|
|
1124
|
+
* privateKey: process.env.AGENT_WALLET_KEY as `0x${string}`,
|
|
1125
|
+
* });
|
|
1126
|
+
*
|
|
1127
|
+
* console.log(`Got session key: ${result.sessionKey}`);
|
|
1128
|
+
* console.log(`Budget: $${result.limits.budgetUsd}/${result.limits.budgetPeriod}`);
|
|
1129
|
+
* console.log(`Invited by: ${result.inviterName}`);
|
|
1130
|
+
*
|
|
1131
|
+
* // Use the session key to make payments
|
|
1132
|
+
* const wallet = new AgentWallet({ sessionKey: result.sessionKey });
|
|
1133
|
+
* const response = await wallet.fetch('https://api.example.com/endpoint');
|
|
1134
|
+
* ```
|
|
1135
|
+
*/
|
|
1136
|
+
static async claimInvite(options) {
|
|
1137
|
+
const { inviteCode, privateKey } = options;
|
|
1138
|
+
const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
1139
|
+
const account = privateKeyToAccount2(privateKey);
|
|
1140
|
+
const walletAddress = account.address;
|
|
1141
|
+
const challengeResponse = await fetch(
|
|
1142
|
+
`${baseUrl}/api/v1/agent/challenge?wallet=${walletAddress}&action=claim-invite`
|
|
1143
|
+
);
|
|
1144
|
+
if (!challengeResponse.ok) {
|
|
1145
|
+
const error = await challengeResponse.json().catch(() => ({}));
|
|
1146
|
+
throw new MixrPayError(error.error || `Failed to get challenge: ${challengeResponse.status}`);
|
|
1147
|
+
}
|
|
1148
|
+
const { challenge, message } = await challengeResponse.json();
|
|
1149
|
+
const signature = await signMessage({ message, privateKey });
|
|
1150
|
+
const claimResponse = await fetch(`${baseUrl}/api/v1/agent/claim-invite`, {
|
|
1151
|
+
method: "POST",
|
|
1152
|
+
headers: { "Content-Type": "application/json" },
|
|
1153
|
+
body: JSON.stringify({
|
|
1154
|
+
invite_code: inviteCode,
|
|
1155
|
+
challenge,
|
|
1156
|
+
agent_wallet_address: walletAddress,
|
|
1157
|
+
signature
|
|
1158
|
+
})
|
|
1159
|
+
});
|
|
1160
|
+
if (!claimResponse.ok) {
|
|
1161
|
+
const error = await claimResponse.json().catch(() => ({}));
|
|
1162
|
+
const errorMessage = error.error || `Failed to claim invite: ${claimResponse.status}`;
|
|
1163
|
+
let helpText = "";
|
|
1164
|
+
if (claimResponse.status === 404) {
|
|
1165
|
+
helpText = " The invite code may be invalid or misspelled.";
|
|
1166
|
+
} else if (claimResponse.status === 400) {
|
|
1167
|
+
if (errorMessage.includes("already claimed")) {
|
|
1168
|
+
helpText = " This invite has already been used by another agent.";
|
|
1169
|
+
} else if (errorMessage.includes("expired")) {
|
|
1170
|
+
helpText = " Ask the wallet owner to create a new invite.";
|
|
1171
|
+
} else if (errorMessage.includes("revoked")) {
|
|
1172
|
+
helpText = " The wallet owner has revoked this invite.";
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
throw new MixrPayError(`${errorMessage}${helpText}`);
|
|
1176
|
+
}
|
|
1177
|
+
const data = await claimResponse.json();
|
|
1178
|
+
return {
|
|
1179
|
+
sessionKey: data.session_key,
|
|
1180
|
+
address: data.address,
|
|
1181
|
+
sessionKeyId: data.session_key_id,
|
|
1182
|
+
expiresAt: new Date(data.expires_at),
|
|
1183
|
+
limits: {
|
|
1184
|
+
budgetUsd: data.limits.budget_usd,
|
|
1185
|
+
budgetPeriod: data.limits.budget_period,
|
|
1186
|
+
maxPerTxUsd: data.limits.max_per_tx_usd
|
|
1187
|
+
},
|
|
1188
|
+
allowedMerchants: data.allowed_merchants || [],
|
|
1189
|
+
inviterName: data.inviter_name || "Anonymous"
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
967
1192
|
// ===========================================================================
|
|
968
1193
|
// Core Methods
|
|
969
1194
|
// ===========================================================================
|
|
@@ -1002,6 +1227,7 @@ var AgentWallet = class {
|
|
|
1002
1227
|
*/
|
|
1003
1228
|
async fetch(url, init) {
|
|
1004
1229
|
this.logger.debug(`Fetching ${init?.method || "GET"} ${url}`);
|
|
1230
|
+
await this.validateMerchantAllowed(url, "url");
|
|
1005
1231
|
const requestId = crypto.randomUUID();
|
|
1006
1232
|
const correlationId = this.extractCorrelationId(init?.headers);
|
|
1007
1233
|
const controller = new AbortController();
|
|
@@ -1275,7 +1501,8 @@ var AgentWallet = class {
|
|
|
1275
1501
|
},
|
|
1276
1502
|
expiresAt: data.expires_at ? new Date(data.expires_at) : null,
|
|
1277
1503
|
createdAt: data.created_at ? new Date(data.created_at) : null,
|
|
1278
|
-
name: data.name
|
|
1504
|
+
name: data.name,
|
|
1505
|
+
allowedMerchants: data.allowed_merchants ?? data.allowedMerchants ?? []
|
|
1279
1506
|
};
|
|
1280
1507
|
this.sessionKeyInfoFetchedAt = Date.now();
|
|
1281
1508
|
return this.sessionKeyInfo;
|
|
@@ -1841,6 +2068,7 @@ var AgentWallet = class {
|
|
|
1841
2068
|
feature
|
|
1842
2069
|
} = options;
|
|
1843
2070
|
this.logger.debug("callMerchantApi", { url, method, merchantPublicKey, priceUsd });
|
|
2071
|
+
await this.validateMerchantAllowed(url, "url");
|
|
1844
2072
|
if (priceUsd !== void 0 && this.maxPaymentUsd !== void 0 && priceUsd > this.maxPaymentUsd) {
|
|
1845
2073
|
throw new SpendingLimitExceededError("client_max", this.maxPaymentUsd, priceUsd);
|
|
1846
2074
|
}
|
|
@@ -2048,6 +2276,7 @@ Timestamp: ${timestamp}`;
|
|
|
2048
2276
|
*/
|
|
2049
2277
|
async callMCPTool(toolName, args = {}) {
|
|
2050
2278
|
this.logger.debug("callMCPTool", { toolName, args });
|
|
2279
|
+
await this.validateMerchantAllowed(toolName, "tool");
|
|
2051
2280
|
const authHeaders = await this.getMCPAuthHeaders();
|
|
2052
2281
|
const response = await fetch(`${this.baseUrl}/api/mcp`, {
|
|
2053
2282
|
method: "POST",
|
|
@@ -2437,6 +2666,7 @@ Timestamp: ${timestamp}`;
|
|
|
2437
2666
|
*/
|
|
2438
2667
|
async callMCPToolWithSession(sessionId, toolName, args = {}) {
|
|
2439
2668
|
this.logger.debug("callMCPToolWithSession", { sessionId, toolName, args });
|
|
2669
|
+
await this.validateMerchantAllowed(toolName, "tool");
|
|
2440
2670
|
const response = await fetch(`${this.baseUrl}/api/mcp`, {
|
|
2441
2671
|
method: "POST",
|
|
2442
2672
|
headers: {
|
|
@@ -2486,6 +2716,7 @@ export {
|
|
|
2486
2716
|
AgentWallet,
|
|
2487
2717
|
InsufficientBalanceError,
|
|
2488
2718
|
InvalidSessionKeyError,
|
|
2719
|
+
MerchantNotAllowedError,
|
|
2489
2720
|
MixrPayError,
|
|
2490
2721
|
PaymentFailedError,
|
|
2491
2722
|
SDK_VERSION,
|