@mixrpay/agent-sdk 0.5.0 → 0.6.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 +181 -16
- package/dist/index.d.cts +140 -3
- package/dist/index.d.ts +140 -3
- package/dist/index.js +179 -16
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -32,6 +32,8 @@ __export(index_exports, {
|
|
|
32
32
|
SessionNotFoundError: () => SessionNotFoundError,
|
|
33
33
|
SessionRevokedError: () => SessionRevokedError,
|
|
34
34
|
SpendingLimitExceededError: () => SpendingLimitExceededError,
|
|
35
|
+
X402ProtocolError: () => X402ProtocolError,
|
|
36
|
+
getErrorMessage: () => getErrorMessage,
|
|
35
37
|
isMixrPayError: () => isMixrPayError
|
|
36
38
|
});
|
|
37
39
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -43,14 +45,43 @@ var import_accounts = require("viem/accounts");
|
|
|
43
45
|
var MixrPayError = class extends Error {
|
|
44
46
|
/** Error code for programmatic handling */
|
|
45
47
|
code;
|
|
46
|
-
|
|
48
|
+
/** Optional hint for how long to wait before retrying (in milliseconds) */
|
|
49
|
+
retryAfterMs;
|
|
50
|
+
constructor(message, code = "MIXRPAY_ERROR", retryAfterMs) {
|
|
47
51
|
super(message);
|
|
48
52
|
this.name = "MixrPayError";
|
|
49
53
|
this.code = code;
|
|
54
|
+
this.retryAfterMs = retryAfterMs;
|
|
50
55
|
if (Error.captureStackTrace) {
|
|
51
56
|
Error.captureStackTrace(this, this.constructor);
|
|
52
57
|
}
|
|
53
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Check if this error is retryable.
|
|
61
|
+
*
|
|
62
|
+
* Returns true if the operation might succeed on retry (e.g., transient network issues).
|
|
63
|
+
* Returns false if the error requires user action to resolve (e.g., insufficient balance).
|
|
64
|
+
*
|
|
65
|
+
* @returns true if the operation should be retried, false otherwise
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```typescript
|
|
69
|
+
* try {
|
|
70
|
+
* await wallet.fetch(...);
|
|
71
|
+
* } catch (error) {
|
|
72
|
+
* if (error instanceof MixrPayError && error.isRetryable()) {
|
|
73
|
+
* // Retry the operation
|
|
74
|
+
* await retry();
|
|
75
|
+
* } else {
|
|
76
|
+
* // Handle permanent failure
|
|
77
|
+
* throw error;
|
|
78
|
+
* }
|
|
79
|
+
* }
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
isRetryable() {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
54
85
|
};
|
|
55
86
|
var InsufficientBalanceError = class extends MixrPayError {
|
|
56
87
|
/** Amount required for the payment in USD */
|
|
@@ -108,6 +139,14 @@ var SpendingLimitExceededError = class extends MixrPayError {
|
|
|
108
139
|
this.limit = limit;
|
|
109
140
|
this.attempted = attempted;
|
|
110
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Daily limits reset at midnight, so those are retryable (after waiting).
|
|
144
|
+
* Other limit types require user action (new session key, config change).
|
|
145
|
+
* @returns true only for daily limits
|
|
146
|
+
*/
|
|
147
|
+
isRetryable() {
|
|
148
|
+
return this.limitType === "daily";
|
|
149
|
+
}
|
|
111
150
|
};
|
|
112
151
|
var PaymentFailedError = class extends MixrPayError {
|
|
113
152
|
/** Detailed reason for the failure */
|
|
@@ -124,6 +163,13 @@ var PaymentFailedError = class extends MixrPayError {
|
|
|
124
163
|
this.reason = reason;
|
|
125
164
|
this.txHash = txHash;
|
|
126
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Payment failures are often transient (network issues, temporary server errors).
|
|
168
|
+
* @returns true - payment failures should generally be retried
|
|
169
|
+
*/
|
|
170
|
+
isRetryable() {
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
127
173
|
};
|
|
128
174
|
var InvalidSessionKeyError = class extends MixrPayError {
|
|
129
175
|
/** Detailed reason why the key is invalid */
|
|
@@ -148,6 +194,13 @@ var X402ProtocolError = class extends MixrPayError {
|
|
|
148
194
|
this.name = "X402ProtocolError";
|
|
149
195
|
this.reason = reason;
|
|
150
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Protocol errors may be caused by temporary server issues.
|
|
199
|
+
* @returns true - worth retrying in case server recovers
|
|
200
|
+
*/
|
|
201
|
+
isRetryable() {
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
151
204
|
};
|
|
152
205
|
var SessionExpiredError = class extends MixrPayError {
|
|
153
206
|
/** ID of the expired session */
|
|
@@ -215,6 +268,15 @@ var SessionRevokedError = class extends MixrPayError {
|
|
|
215
268
|
function isMixrPayError(error) {
|
|
216
269
|
return error instanceof MixrPayError;
|
|
217
270
|
}
|
|
271
|
+
function getErrorMessage(error) {
|
|
272
|
+
if (error instanceof MixrPayError) {
|
|
273
|
+
return error.message;
|
|
274
|
+
}
|
|
275
|
+
if (error instanceof Error) {
|
|
276
|
+
return error.message;
|
|
277
|
+
}
|
|
278
|
+
return String(error);
|
|
279
|
+
}
|
|
218
280
|
|
|
219
281
|
// src/session-key.ts
|
|
220
282
|
var USDC_ADDRESSES = {
|
|
@@ -445,7 +507,7 @@ function getAmountUsd(requirements) {
|
|
|
445
507
|
}
|
|
446
508
|
|
|
447
509
|
// src/agent-wallet.ts
|
|
448
|
-
var SDK_VERSION = "0.
|
|
510
|
+
var SDK_VERSION = "0.6.0";
|
|
449
511
|
var DEFAULT_BASE_URL = process.env.MIXRPAY_BASE_URL || "https://www.mixrpay.com";
|
|
450
512
|
var DEFAULT_TIMEOUT = 3e4;
|
|
451
513
|
var NETWORKS = {
|
|
@@ -857,6 +919,8 @@ var AgentWallet = class {
|
|
|
857
919
|
*/
|
|
858
920
|
async fetch(url, init) {
|
|
859
921
|
this.logger.debug(`Fetching ${init?.method || "GET"} ${url}`);
|
|
922
|
+
const requestId = crypto.randomUUID();
|
|
923
|
+
const correlationId = this.extractCorrelationId(init?.headers);
|
|
860
924
|
const controller = new AbortController();
|
|
861
925
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
862
926
|
try {
|
|
@@ -867,7 +931,7 @@ var AgentWallet = class {
|
|
|
867
931
|
this.logger.debug(`Initial response: ${response.status}`);
|
|
868
932
|
if (response.status === 402) {
|
|
869
933
|
this.logger.info(`Payment required for ${url}`);
|
|
870
|
-
response = await this.handlePaymentRequired(url, init, response);
|
|
934
|
+
response = await this.handlePaymentRequired(url, init, response, requestId, correlationId);
|
|
871
935
|
}
|
|
872
936
|
return response;
|
|
873
937
|
} catch (error) {
|
|
@@ -879,10 +943,27 @@ var AgentWallet = class {
|
|
|
879
943
|
clearTimeout(timeoutId);
|
|
880
944
|
}
|
|
881
945
|
}
|
|
946
|
+
/**
|
|
947
|
+
* Extract correlation ID from request headers.
|
|
948
|
+
*/
|
|
949
|
+
extractCorrelationId(headers) {
|
|
950
|
+
if (!headers) return void 0;
|
|
951
|
+
if (headers instanceof Headers) {
|
|
952
|
+
return headers.get("X-Correlation-Id") || headers.get("x-correlation-id") || void 0;
|
|
953
|
+
}
|
|
954
|
+
if (Array.isArray(headers)) {
|
|
955
|
+
const entry = headers.find(
|
|
956
|
+
([key]) => key.toLowerCase() === "x-correlation-id"
|
|
957
|
+
);
|
|
958
|
+
return entry ? entry[1] : void 0;
|
|
959
|
+
}
|
|
960
|
+
const record = headers;
|
|
961
|
+
return record["X-Correlation-Id"] || record["x-correlation-id"] || void 0;
|
|
962
|
+
}
|
|
882
963
|
/**
|
|
883
964
|
* Handle a 402 Payment Required response.
|
|
884
965
|
*/
|
|
885
|
-
async handlePaymentRequired(url, init, response) {
|
|
966
|
+
async handlePaymentRequired(url, init, response, requestId, correlationId) {
|
|
886
967
|
let requirements;
|
|
887
968
|
try {
|
|
888
969
|
requirements = await parse402Response(response);
|
|
@@ -938,7 +1019,9 @@ var AgentWallet = class {
|
|
|
938
1019
|
txHash: retryResponse.headers.get("X-Payment-TxHash"),
|
|
939
1020
|
timestamp: /* @__PURE__ */ new Date(),
|
|
940
1021
|
description: requirements.description,
|
|
941
|
-
url
|
|
1022
|
+
url,
|
|
1023
|
+
requestId,
|
|
1024
|
+
correlationId
|
|
942
1025
|
};
|
|
943
1026
|
this.payments.push(payment);
|
|
944
1027
|
this.totalSpentUsd += amountUsd;
|
|
@@ -1037,6 +1120,36 @@ var AgentWallet = class {
|
|
|
1037
1120
|
this.logger.debug("Using estimated balance based on tracking");
|
|
1038
1121
|
return Math.max(0, 100 - this.totalSpentUsd);
|
|
1039
1122
|
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Check if the wallet can afford a specific amount.
|
|
1125
|
+
*
|
|
1126
|
+
* This is a convenience method to check balance before making a request
|
|
1127
|
+
* when you know the expected cost.
|
|
1128
|
+
*
|
|
1129
|
+
* @param amountUsd - Amount to check in USD
|
|
1130
|
+
* @returns Object with affordability information
|
|
1131
|
+
*
|
|
1132
|
+
* @example
|
|
1133
|
+
* ```typescript
|
|
1134
|
+
* const check = await wallet.canAfford(5.00);
|
|
1135
|
+
* if (check.canAfford) {
|
|
1136
|
+
* console.log(`Can afford! Will have $${check.remainingAfter.toFixed(2)} left`);
|
|
1137
|
+
* await wallet.fetch(url);
|
|
1138
|
+
* } else {
|
|
1139
|
+
* console.log(`Need $${check.shortfall.toFixed(2)} more`);
|
|
1140
|
+
* }
|
|
1141
|
+
* ```
|
|
1142
|
+
*/
|
|
1143
|
+
async canAfford(amountUsd) {
|
|
1144
|
+
const balance = await this.getBalance();
|
|
1145
|
+
const canAfford = balance >= amountUsd;
|
|
1146
|
+
return {
|
|
1147
|
+
canAfford,
|
|
1148
|
+
balance,
|
|
1149
|
+
shortfall: canAfford ? 0 : amountUsd - balance,
|
|
1150
|
+
remainingAfter: canAfford ? balance - amountUsd : 0
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1040
1153
|
/**
|
|
1041
1154
|
* Get information about the session key.
|
|
1042
1155
|
*
|
|
@@ -1177,56 +1290,100 @@ var AgentWallet = class {
|
|
|
1177
1290
|
async runDiagnostics() {
|
|
1178
1291
|
this.logger.info("Running diagnostics...");
|
|
1179
1292
|
const issues = [];
|
|
1293
|
+
const recommendations = [];
|
|
1180
1294
|
const checks = {};
|
|
1295
|
+
let latencyMs;
|
|
1296
|
+
let sessionLimits;
|
|
1181
1297
|
checks.sessionKeyFormat = true;
|
|
1182
1298
|
try {
|
|
1299
|
+
const startTime = Date.now();
|
|
1183
1300
|
const response = await fetch(`${this.baseUrl}/health`, {
|
|
1184
1301
|
method: "GET",
|
|
1185
1302
|
signal: AbortSignal.timeout(5e3)
|
|
1186
1303
|
});
|
|
1304
|
+
latencyMs = Date.now() - startTime;
|
|
1187
1305
|
checks.apiConnectivity = response.ok;
|
|
1188
1306
|
if (!response.ok) {
|
|
1189
1307
|
issues.push(`API server returned ${response.status}. Check baseUrl configuration.`);
|
|
1308
|
+
recommendations.push("Verify the baseUrl configuration points to a valid MixrPay server.");
|
|
1309
|
+
}
|
|
1310
|
+
if (latencyMs > 2e3) {
|
|
1311
|
+
issues.push(`High API latency: ${latencyMs}ms. This may cause timeouts.`);
|
|
1312
|
+
recommendations.push("Consider using a server closer to your region or check network connectivity.");
|
|
1190
1313
|
}
|
|
1191
1314
|
} catch {
|
|
1192
1315
|
checks.apiConnectivity = false;
|
|
1193
1316
|
issues.push("Cannot connect to MixrPay API. Check your network connection and baseUrl.");
|
|
1317
|
+
recommendations.push("Verify network connectivity and that the MixrPay server is running.");
|
|
1194
1318
|
}
|
|
1195
1319
|
try {
|
|
1196
1320
|
const info = await this.getSessionKeyInfo(true);
|
|
1197
1321
|
checks.sessionKeyValid = info.isValid;
|
|
1198
1322
|
if (!info.isValid) {
|
|
1199
1323
|
issues.push("Session key is invalid or has been revoked.");
|
|
1324
|
+
recommendations.push("Request a new session key from the wallet owner or create one at /wallet/sessions.");
|
|
1325
|
+
}
|
|
1326
|
+
const now = /* @__PURE__ */ new Date();
|
|
1327
|
+
let expiresInHours = null;
|
|
1328
|
+
if (info.expiresAt) {
|
|
1329
|
+
expiresInHours = (info.expiresAt.getTime() - now.getTime()) / (1e3 * 60 * 60);
|
|
1330
|
+
if (info.expiresAt < now) {
|
|
1331
|
+
checks.sessionKeyValid = false;
|
|
1332
|
+
issues.push(`Session key expired on ${info.expiresAt.toISOString()}`);
|
|
1333
|
+
recommendations.push("Create a new session key to continue making payments.");
|
|
1334
|
+
} else if (expiresInHours < 24) {
|
|
1335
|
+
issues.push(`Session key expires in ${expiresInHours.toFixed(1)} hours.`);
|
|
1336
|
+
recommendations.push("Consider creating a new session key before the current one expires.");
|
|
1337
|
+
}
|
|
1200
1338
|
}
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1339
|
+
const stats = await this.getSpendingStats();
|
|
1340
|
+
sessionLimits = {
|
|
1341
|
+
remainingDailyUsd: stats.remainingDailyUsd,
|
|
1342
|
+
remainingTotalUsd: stats.remainingTotalUsd,
|
|
1343
|
+
expiresAt: info.expiresAt,
|
|
1344
|
+
expiresInHours
|
|
1345
|
+
};
|
|
1346
|
+
if (stats.remainingDailyUsd !== null && stats.remainingDailyUsd < 1) {
|
|
1347
|
+
issues.push(`Daily limit nearly exhausted: $${stats.remainingDailyUsd.toFixed(2)} remaining.`);
|
|
1348
|
+
recommendations.push("Wait until tomorrow for daily limit to reset, or request a higher daily limit.");
|
|
1349
|
+
}
|
|
1350
|
+
if (stats.remainingTotalUsd !== null && stats.remainingTotalUsd < 1) {
|
|
1351
|
+
issues.push(`Total limit nearly exhausted: $${stats.remainingTotalUsd.toFixed(2)} remaining.`);
|
|
1352
|
+
recommendations.push("Request a new session key with a higher total limit.");
|
|
1204
1353
|
}
|
|
1205
1354
|
} catch {
|
|
1206
1355
|
checks.sessionKeyValid = false;
|
|
1207
1356
|
issues.push("Could not verify session key validity.");
|
|
1357
|
+
recommendations.push("Check network connectivity and try again.");
|
|
1208
1358
|
}
|
|
1359
|
+
let balance = 0;
|
|
1209
1360
|
try {
|
|
1210
|
-
|
|
1361
|
+
balance = await this.getBalance();
|
|
1211
1362
|
checks.hasBalance = balance > 0;
|
|
1212
1363
|
if (balance <= 0) {
|
|
1213
1364
|
issues.push("Wallet has no USDC balance. Top up at your MixrPay server /wallet");
|
|
1365
|
+
recommendations.push("Deposit USDC to your wallet address to enable payments.");
|
|
1214
1366
|
} else if (balance < 1) {
|
|
1215
1367
|
issues.push(`Low balance: $${balance.toFixed(2)}. Consider topping up.`);
|
|
1368
|
+
recommendations.push("Top up your wallet balance to avoid payment failures.");
|
|
1216
1369
|
}
|
|
1217
1370
|
} catch {
|
|
1218
1371
|
checks.hasBalance = false;
|
|
1219
1372
|
issues.push("Could not fetch wallet balance.");
|
|
1373
|
+
recommendations.push("Check network connectivity and try again.");
|
|
1220
1374
|
}
|
|
1221
1375
|
const healthy = issues.length === 0;
|
|
1222
|
-
this.logger.info("Diagnostics complete:", { healthy, issues });
|
|
1376
|
+
this.logger.info("Diagnostics complete:", { healthy, issues, latencyMs });
|
|
1223
1377
|
return {
|
|
1224
1378
|
healthy,
|
|
1225
1379
|
issues,
|
|
1226
1380
|
checks,
|
|
1227
1381
|
sdkVersion: SDK_VERSION,
|
|
1228
1382
|
network: this.getNetwork().name,
|
|
1229
|
-
walletAddress: this.walletAddress
|
|
1383
|
+
walletAddress: this.walletAddress,
|
|
1384
|
+
sessionLimits,
|
|
1385
|
+
latencyMs,
|
|
1386
|
+
recommendations
|
|
1230
1387
|
};
|
|
1231
1388
|
}
|
|
1232
1389
|
/**
|
|
@@ -1635,7 +1792,9 @@ var AgentWallet = class {
|
|
|
1635
1792
|
txHash: response.headers.get("X-Payment-TxHash"),
|
|
1636
1793
|
timestamp: /* @__PURE__ */ new Date(),
|
|
1637
1794
|
description: feature || "API call",
|
|
1638
|
-
url
|
|
1795
|
+
url,
|
|
1796
|
+
requestId: crypto.randomUUID(),
|
|
1797
|
+
correlationId: this.extractCorrelationId(customHeaders)
|
|
1639
1798
|
};
|
|
1640
1799
|
this.payments.push(payment);
|
|
1641
1800
|
this.totalSpentUsd += amountUsd;
|
|
@@ -1834,7 +1993,8 @@ Timestamp: ${timestamp}`;
|
|
|
1834
1993
|
txHash: mixrpay.txHash,
|
|
1835
1994
|
timestamp: /* @__PURE__ */ new Date(),
|
|
1836
1995
|
description: `MCP: ${toolName}`,
|
|
1837
|
-
url: `${this.baseUrl}/api/mcp
|
|
1996
|
+
url: `${this.baseUrl}/api/mcp`,
|
|
1997
|
+
requestId: crypto.randomUUID()
|
|
1838
1998
|
};
|
|
1839
1999
|
this.payments.push(payment);
|
|
1840
2000
|
this.totalSpentUsd += mixrpay.chargedUsd;
|
|
@@ -1951,7 +2111,8 @@ Timestamp: ${timestamp}`;
|
|
|
1951
2111
|
txHash: data.tx_hash,
|
|
1952
2112
|
timestamp: /* @__PURE__ */ new Date(),
|
|
1953
2113
|
description: `Agent run: ${data.run_id}`,
|
|
1954
|
-
url: `${this.baseUrl}/api/v2/agent/run
|
|
2114
|
+
url: `${this.baseUrl}/api/v2/agent/run`,
|
|
2115
|
+
requestId: idempotencyKey || crypto.randomUUID()
|
|
1955
2116
|
};
|
|
1956
2117
|
this.payments.push(payment);
|
|
1957
2118
|
this.totalSpentUsd += data.cost.total_usd;
|
|
@@ -2043,7 +2204,8 @@ Timestamp: ${timestamp}`;
|
|
|
2043
2204
|
txHash: data.tx_hash,
|
|
2044
2205
|
timestamp: /* @__PURE__ */ new Date(),
|
|
2045
2206
|
description: `Agent run: ${data.run_id}`,
|
|
2046
|
-
url: `${this.baseUrl}/api/v2/agent/run
|
|
2207
|
+
url: `${this.baseUrl}/api/v2/agent/run`,
|
|
2208
|
+
requestId: body.idempotency_key || crypto.randomUUID()
|
|
2047
2209
|
};
|
|
2048
2210
|
this.payments.push(payment);
|
|
2049
2211
|
this.totalSpentUsd += data.total_cost_usd;
|
|
@@ -2219,7 +2381,8 @@ Timestamp: ${timestamp}`;
|
|
|
2219
2381
|
txHash: mixrpay.txHash,
|
|
2220
2382
|
timestamp: /* @__PURE__ */ new Date(),
|
|
2221
2383
|
description: `MCP: ${toolName}`,
|
|
2222
|
-
url: `${this.baseUrl}/api/mcp
|
|
2384
|
+
url: `${this.baseUrl}/api/mcp`,
|
|
2385
|
+
requestId: crypto.randomUUID()
|
|
2223
2386
|
};
|
|
2224
2387
|
this.payments.push(payment);
|
|
2225
2388
|
this.totalSpentUsd += mixrpay.chargedUsd;
|
|
@@ -2250,5 +2413,7 @@ Timestamp: ${timestamp}`;
|
|
|
2250
2413
|
SessionNotFoundError,
|
|
2251
2414
|
SessionRevokedError,
|
|
2252
2415
|
SpendingLimitExceededError,
|
|
2416
|
+
X402ProtocolError,
|
|
2417
|
+
getErrorMessage,
|
|
2253
2418
|
isMixrPayError
|
|
2254
2419
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -121,6 +121,10 @@ interface PaymentEvent {
|
|
|
121
121
|
description?: string;
|
|
122
122
|
/** URL that triggered the payment */
|
|
123
123
|
url?: string;
|
|
124
|
+
/** Auto-generated unique ID for this request */
|
|
125
|
+
requestId: string;
|
|
126
|
+
/** User-provided correlation ID (from X-Correlation-Id header if provided) */
|
|
127
|
+
correlationId?: string;
|
|
124
128
|
}
|
|
125
129
|
/**
|
|
126
130
|
* Information about a session key.
|
|
@@ -195,6 +199,21 @@ interface DiagnosticsResult {
|
|
|
195
199
|
network: string;
|
|
196
200
|
/** Wallet address */
|
|
197
201
|
walletAddress: string;
|
|
202
|
+
/** Session key limit info (if available) */
|
|
203
|
+
sessionLimits?: {
|
|
204
|
+
/** Remaining daily limit in USD (null if no limit) */
|
|
205
|
+
remainingDailyUsd: number | null;
|
|
206
|
+
/** Remaining total limit in USD (null if no limit) */
|
|
207
|
+
remainingTotalUsd: number | null;
|
|
208
|
+
/** When the session key expires (null if never) */
|
|
209
|
+
expiresAt: Date | null;
|
|
210
|
+
/** Hours until expiration (null if never) */
|
|
211
|
+
expiresInHours: number | null;
|
|
212
|
+
};
|
|
213
|
+
/** Network latency to API in milliseconds */
|
|
214
|
+
latencyMs?: number;
|
|
215
|
+
/** Actionable recommendations based on issues found */
|
|
216
|
+
recommendations: string[];
|
|
198
217
|
}
|
|
199
218
|
/**
|
|
200
219
|
* Options for creating a session authorization with a MixrPay merchant.
|
|
@@ -359,7 +378,7 @@ interface SessionStats {
|
|
|
359
378
|
*/
|
|
360
379
|
|
|
361
380
|
/** Current SDK version */
|
|
362
|
-
declare const SDK_VERSION = "0.
|
|
381
|
+
declare const SDK_VERSION = "0.6.0";
|
|
363
382
|
/** Supported networks */
|
|
364
383
|
declare const NETWORKS: {
|
|
365
384
|
readonly BASE_MAINNET: {
|
|
@@ -672,6 +691,10 @@ declare class AgentWallet {
|
|
|
672
691
|
* ```
|
|
673
692
|
*/
|
|
674
693
|
fetch(url: string, init?: RequestInit): Promise<Response>;
|
|
694
|
+
/**
|
|
695
|
+
* Extract correlation ID from request headers.
|
|
696
|
+
*/
|
|
697
|
+
private extractCorrelationId;
|
|
675
698
|
/**
|
|
676
699
|
* Handle a 402 Payment Required response.
|
|
677
700
|
*/
|
|
@@ -710,6 +733,36 @@ declare class AgentWallet {
|
|
|
710
733
|
* ```
|
|
711
734
|
*/
|
|
712
735
|
getBalance(): Promise<number>;
|
|
736
|
+
/**
|
|
737
|
+
* Check if the wallet can afford a specific amount.
|
|
738
|
+
*
|
|
739
|
+
* This is a convenience method to check balance before making a request
|
|
740
|
+
* when you know the expected cost.
|
|
741
|
+
*
|
|
742
|
+
* @param amountUsd - Amount to check in USD
|
|
743
|
+
* @returns Object with affordability information
|
|
744
|
+
*
|
|
745
|
+
* @example
|
|
746
|
+
* ```typescript
|
|
747
|
+
* const check = await wallet.canAfford(5.00);
|
|
748
|
+
* if (check.canAfford) {
|
|
749
|
+
* console.log(`Can afford! Will have $${check.remainingAfter.toFixed(2)} left`);
|
|
750
|
+
* await wallet.fetch(url);
|
|
751
|
+
* } else {
|
|
752
|
+
* console.log(`Need $${check.shortfall.toFixed(2)} more`);
|
|
753
|
+
* }
|
|
754
|
+
* ```
|
|
755
|
+
*/
|
|
756
|
+
canAfford(amountUsd: number): Promise<{
|
|
757
|
+
/** Whether the wallet can afford this amount */
|
|
758
|
+
canAfford: boolean;
|
|
759
|
+
/** Current wallet balance in USD */
|
|
760
|
+
balance: number;
|
|
761
|
+
/** Amount short (0 if can afford) */
|
|
762
|
+
shortfall: number;
|
|
763
|
+
/** Remaining after payment (0 if can't afford) */
|
|
764
|
+
remainingAfter: number;
|
|
765
|
+
}>;
|
|
713
766
|
/**
|
|
714
767
|
* Get information about the session key.
|
|
715
768
|
*
|
|
@@ -1326,7 +1379,33 @@ interface AgentRunStatusResult {
|
|
|
1326
1379
|
declare class MixrPayError extends Error {
|
|
1327
1380
|
/** Error code for programmatic handling */
|
|
1328
1381
|
readonly code: string;
|
|
1329
|
-
|
|
1382
|
+
/** Optional hint for how long to wait before retrying (in milliseconds) */
|
|
1383
|
+
readonly retryAfterMs?: number;
|
|
1384
|
+
constructor(message: string, code?: string, retryAfterMs?: number);
|
|
1385
|
+
/**
|
|
1386
|
+
* Check if this error is retryable.
|
|
1387
|
+
*
|
|
1388
|
+
* Returns true if the operation might succeed on retry (e.g., transient network issues).
|
|
1389
|
+
* Returns false if the error requires user action to resolve (e.g., insufficient balance).
|
|
1390
|
+
*
|
|
1391
|
+
* @returns true if the operation should be retried, false otherwise
|
|
1392
|
+
*
|
|
1393
|
+
* @example
|
|
1394
|
+
* ```typescript
|
|
1395
|
+
* try {
|
|
1396
|
+
* await wallet.fetch(...);
|
|
1397
|
+
* } catch (error) {
|
|
1398
|
+
* if (error instanceof MixrPayError && error.isRetryable()) {
|
|
1399
|
+
* // Retry the operation
|
|
1400
|
+
* await retry();
|
|
1401
|
+
* } else {
|
|
1402
|
+
* // Handle permanent failure
|
|
1403
|
+
* throw error;
|
|
1404
|
+
* }
|
|
1405
|
+
* }
|
|
1406
|
+
* ```
|
|
1407
|
+
*/
|
|
1408
|
+
isRetryable(): boolean;
|
|
1330
1409
|
}
|
|
1331
1410
|
/**
|
|
1332
1411
|
* Thrown when the wallet doesn't have enough USDC for the payment.
|
|
@@ -1418,6 +1497,12 @@ declare class SpendingLimitExceededError extends MixrPayError {
|
|
|
1418
1497
|
/** The amount that was attempted in USD */
|
|
1419
1498
|
readonly attempted: number;
|
|
1420
1499
|
constructor(limitType: string, limit: number, attempted: number);
|
|
1500
|
+
/**
|
|
1501
|
+
* Daily limits reset at midnight, so those are retryable (after waiting).
|
|
1502
|
+
* Other limit types require user action (new session key, config change).
|
|
1503
|
+
* @returns true only for daily limits
|
|
1504
|
+
*/
|
|
1505
|
+
isRetryable(): boolean;
|
|
1421
1506
|
}
|
|
1422
1507
|
/**
|
|
1423
1508
|
* Thrown when a payment transaction fails.
|
|
@@ -1449,6 +1534,11 @@ declare class PaymentFailedError extends MixrPayError {
|
|
|
1449
1534
|
/** Transaction hash if the tx was submitted (for debugging) */
|
|
1450
1535
|
readonly txHash?: string;
|
|
1451
1536
|
constructor(reason: string, txHash?: string);
|
|
1537
|
+
/**
|
|
1538
|
+
* Payment failures are often transient (network issues, temporary server errors).
|
|
1539
|
+
* @returns true - payment failures should generally be retried
|
|
1540
|
+
*/
|
|
1541
|
+
isRetryable(): boolean;
|
|
1452
1542
|
}
|
|
1453
1543
|
/**
|
|
1454
1544
|
* Thrown when the session key format is invalid.
|
|
@@ -1479,6 +1569,38 @@ declare class InvalidSessionKeyError extends MixrPayError {
|
|
|
1479
1569
|
readonly reason: string;
|
|
1480
1570
|
constructor(reason?: string);
|
|
1481
1571
|
}
|
|
1572
|
+
/**
|
|
1573
|
+
* Thrown when there's an error in x402 protocol handling.
|
|
1574
|
+
*
|
|
1575
|
+
* This indicates a problem with the payment protocol, usually due to:
|
|
1576
|
+
* - Malformed 402 response from server
|
|
1577
|
+
* - Missing required payment fields
|
|
1578
|
+
* - Invalid payment parameters
|
|
1579
|
+
* - Protocol version mismatch
|
|
1580
|
+
*
|
|
1581
|
+
* ## Resolution
|
|
1582
|
+
* This is usually a server-side issue. Contact the API provider if the error persists.
|
|
1583
|
+
*
|
|
1584
|
+
* @example
|
|
1585
|
+
* ```typescript
|
|
1586
|
+
* catch (error) {
|
|
1587
|
+
* if (error instanceof X402ProtocolError) {
|
|
1588
|
+
* console.log(`Protocol error: ${error.reason}`);
|
|
1589
|
+
* // Contact API provider or check server configuration
|
|
1590
|
+
* }
|
|
1591
|
+
* }
|
|
1592
|
+
* ```
|
|
1593
|
+
*/
|
|
1594
|
+
declare class X402ProtocolError extends MixrPayError {
|
|
1595
|
+
/** Detailed reason for the protocol error */
|
|
1596
|
+
readonly reason: string;
|
|
1597
|
+
constructor(reason: string);
|
|
1598
|
+
/**
|
|
1599
|
+
* Protocol errors may be caused by temporary server issues.
|
|
1600
|
+
* @returns true - worth retrying in case server recovers
|
|
1601
|
+
*/
|
|
1602
|
+
isRetryable(): boolean;
|
|
1603
|
+
}
|
|
1482
1604
|
/**
|
|
1483
1605
|
* Thrown when a session authorization has expired.
|
|
1484
1606
|
*
|
|
@@ -1611,5 +1733,20 @@ declare class SessionRevokedError extends MixrPayError {
|
|
|
1611
1733
|
* ```
|
|
1612
1734
|
*/
|
|
1613
1735
|
declare function isMixrPayError(error: unknown): error is MixrPayError;
|
|
1736
|
+
/**
|
|
1737
|
+
* Get a user-friendly error message from any error.
|
|
1738
|
+
*
|
|
1739
|
+
* @param error - The error to get a message from
|
|
1740
|
+
* @returns A user-friendly error message
|
|
1741
|
+
*
|
|
1742
|
+
* @example
|
|
1743
|
+
* ```typescript
|
|
1744
|
+
* catch (error) {
|
|
1745
|
+
* const message = getErrorMessage(error);
|
|
1746
|
+
* showToast(message);
|
|
1747
|
+
* }
|
|
1748
|
+
* ```
|
|
1749
|
+
*/
|
|
1750
|
+
declare function getErrorMessage(error: unknown): string;
|
|
1614
1751
|
|
|
1615
|
-
export { type AgentMessage, type AgentRunConfig, type AgentRunEvent, type AgentRunOptions, type AgentRunResult, type AgentRunStatusResult, AgentWallet, type AgentWalletConfig, type CallMerchantApiOptions, type ChargeResult, InsufficientBalanceError, InvalidSessionKeyError, MixrPayError, type PaymentEvent, PaymentFailedError, SDK_VERSION, type SessionAuthorization, SessionExpiredError, SessionKeyExpiredError, SessionLimitExceededError, SessionNotFoundError, SessionRevokedError, type SessionStats, SpendingLimitExceededError, type SpendingStats, isMixrPayError };
|
|
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -121,6 +121,10 @@ interface PaymentEvent {
|
|
|
121
121
|
description?: string;
|
|
122
122
|
/** URL that triggered the payment */
|
|
123
123
|
url?: string;
|
|
124
|
+
/** Auto-generated unique ID for this request */
|
|
125
|
+
requestId: string;
|
|
126
|
+
/** User-provided correlation ID (from X-Correlation-Id header if provided) */
|
|
127
|
+
correlationId?: string;
|
|
124
128
|
}
|
|
125
129
|
/**
|
|
126
130
|
* Information about a session key.
|
|
@@ -195,6 +199,21 @@ interface DiagnosticsResult {
|
|
|
195
199
|
network: string;
|
|
196
200
|
/** Wallet address */
|
|
197
201
|
walletAddress: string;
|
|
202
|
+
/** Session key limit info (if available) */
|
|
203
|
+
sessionLimits?: {
|
|
204
|
+
/** Remaining daily limit in USD (null if no limit) */
|
|
205
|
+
remainingDailyUsd: number | null;
|
|
206
|
+
/** Remaining total limit in USD (null if no limit) */
|
|
207
|
+
remainingTotalUsd: number | null;
|
|
208
|
+
/** When the session key expires (null if never) */
|
|
209
|
+
expiresAt: Date | null;
|
|
210
|
+
/** Hours until expiration (null if never) */
|
|
211
|
+
expiresInHours: number | null;
|
|
212
|
+
};
|
|
213
|
+
/** Network latency to API in milliseconds */
|
|
214
|
+
latencyMs?: number;
|
|
215
|
+
/** Actionable recommendations based on issues found */
|
|
216
|
+
recommendations: string[];
|
|
198
217
|
}
|
|
199
218
|
/**
|
|
200
219
|
* Options for creating a session authorization with a MixrPay merchant.
|
|
@@ -359,7 +378,7 @@ interface SessionStats {
|
|
|
359
378
|
*/
|
|
360
379
|
|
|
361
380
|
/** Current SDK version */
|
|
362
|
-
declare const SDK_VERSION = "0.
|
|
381
|
+
declare const SDK_VERSION = "0.6.0";
|
|
363
382
|
/** Supported networks */
|
|
364
383
|
declare const NETWORKS: {
|
|
365
384
|
readonly BASE_MAINNET: {
|
|
@@ -672,6 +691,10 @@ declare class AgentWallet {
|
|
|
672
691
|
* ```
|
|
673
692
|
*/
|
|
674
693
|
fetch(url: string, init?: RequestInit): Promise<Response>;
|
|
694
|
+
/**
|
|
695
|
+
* Extract correlation ID from request headers.
|
|
696
|
+
*/
|
|
697
|
+
private extractCorrelationId;
|
|
675
698
|
/**
|
|
676
699
|
* Handle a 402 Payment Required response.
|
|
677
700
|
*/
|
|
@@ -710,6 +733,36 @@ declare class AgentWallet {
|
|
|
710
733
|
* ```
|
|
711
734
|
*/
|
|
712
735
|
getBalance(): Promise<number>;
|
|
736
|
+
/**
|
|
737
|
+
* Check if the wallet can afford a specific amount.
|
|
738
|
+
*
|
|
739
|
+
* This is a convenience method to check balance before making a request
|
|
740
|
+
* when you know the expected cost.
|
|
741
|
+
*
|
|
742
|
+
* @param amountUsd - Amount to check in USD
|
|
743
|
+
* @returns Object with affordability information
|
|
744
|
+
*
|
|
745
|
+
* @example
|
|
746
|
+
* ```typescript
|
|
747
|
+
* const check = await wallet.canAfford(5.00);
|
|
748
|
+
* if (check.canAfford) {
|
|
749
|
+
* console.log(`Can afford! Will have $${check.remainingAfter.toFixed(2)} left`);
|
|
750
|
+
* await wallet.fetch(url);
|
|
751
|
+
* } else {
|
|
752
|
+
* console.log(`Need $${check.shortfall.toFixed(2)} more`);
|
|
753
|
+
* }
|
|
754
|
+
* ```
|
|
755
|
+
*/
|
|
756
|
+
canAfford(amountUsd: number): Promise<{
|
|
757
|
+
/** Whether the wallet can afford this amount */
|
|
758
|
+
canAfford: boolean;
|
|
759
|
+
/** Current wallet balance in USD */
|
|
760
|
+
balance: number;
|
|
761
|
+
/** Amount short (0 if can afford) */
|
|
762
|
+
shortfall: number;
|
|
763
|
+
/** Remaining after payment (0 if can't afford) */
|
|
764
|
+
remainingAfter: number;
|
|
765
|
+
}>;
|
|
713
766
|
/**
|
|
714
767
|
* Get information about the session key.
|
|
715
768
|
*
|
|
@@ -1326,7 +1379,33 @@ interface AgentRunStatusResult {
|
|
|
1326
1379
|
declare class MixrPayError extends Error {
|
|
1327
1380
|
/** Error code for programmatic handling */
|
|
1328
1381
|
readonly code: string;
|
|
1329
|
-
|
|
1382
|
+
/** Optional hint for how long to wait before retrying (in milliseconds) */
|
|
1383
|
+
readonly retryAfterMs?: number;
|
|
1384
|
+
constructor(message: string, code?: string, retryAfterMs?: number);
|
|
1385
|
+
/**
|
|
1386
|
+
* Check if this error is retryable.
|
|
1387
|
+
*
|
|
1388
|
+
* Returns true if the operation might succeed on retry (e.g., transient network issues).
|
|
1389
|
+
* Returns false if the error requires user action to resolve (e.g., insufficient balance).
|
|
1390
|
+
*
|
|
1391
|
+
* @returns true if the operation should be retried, false otherwise
|
|
1392
|
+
*
|
|
1393
|
+
* @example
|
|
1394
|
+
* ```typescript
|
|
1395
|
+
* try {
|
|
1396
|
+
* await wallet.fetch(...);
|
|
1397
|
+
* } catch (error) {
|
|
1398
|
+
* if (error instanceof MixrPayError && error.isRetryable()) {
|
|
1399
|
+
* // Retry the operation
|
|
1400
|
+
* await retry();
|
|
1401
|
+
* } else {
|
|
1402
|
+
* // Handle permanent failure
|
|
1403
|
+
* throw error;
|
|
1404
|
+
* }
|
|
1405
|
+
* }
|
|
1406
|
+
* ```
|
|
1407
|
+
*/
|
|
1408
|
+
isRetryable(): boolean;
|
|
1330
1409
|
}
|
|
1331
1410
|
/**
|
|
1332
1411
|
* Thrown when the wallet doesn't have enough USDC for the payment.
|
|
@@ -1418,6 +1497,12 @@ declare class SpendingLimitExceededError extends MixrPayError {
|
|
|
1418
1497
|
/** The amount that was attempted in USD */
|
|
1419
1498
|
readonly attempted: number;
|
|
1420
1499
|
constructor(limitType: string, limit: number, attempted: number);
|
|
1500
|
+
/**
|
|
1501
|
+
* Daily limits reset at midnight, so those are retryable (after waiting).
|
|
1502
|
+
* Other limit types require user action (new session key, config change).
|
|
1503
|
+
* @returns true only for daily limits
|
|
1504
|
+
*/
|
|
1505
|
+
isRetryable(): boolean;
|
|
1421
1506
|
}
|
|
1422
1507
|
/**
|
|
1423
1508
|
* Thrown when a payment transaction fails.
|
|
@@ -1449,6 +1534,11 @@ declare class PaymentFailedError extends MixrPayError {
|
|
|
1449
1534
|
/** Transaction hash if the tx was submitted (for debugging) */
|
|
1450
1535
|
readonly txHash?: string;
|
|
1451
1536
|
constructor(reason: string, txHash?: string);
|
|
1537
|
+
/**
|
|
1538
|
+
* Payment failures are often transient (network issues, temporary server errors).
|
|
1539
|
+
* @returns true - payment failures should generally be retried
|
|
1540
|
+
*/
|
|
1541
|
+
isRetryable(): boolean;
|
|
1452
1542
|
}
|
|
1453
1543
|
/**
|
|
1454
1544
|
* Thrown when the session key format is invalid.
|
|
@@ -1479,6 +1569,38 @@ declare class InvalidSessionKeyError extends MixrPayError {
|
|
|
1479
1569
|
readonly reason: string;
|
|
1480
1570
|
constructor(reason?: string);
|
|
1481
1571
|
}
|
|
1572
|
+
/**
|
|
1573
|
+
* Thrown when there's an error in x402 protocol handling.
|
|
1574
|
+
*
|
|
1575
|
+
* This indicates a problem with the payment protocol, usually due to:
|
|
1576
|
+
* - Malformed 402 response from server
|
|
1577
|
+
* - Missing required payment fields
|
|
1578
|
+
* - Invalid payment parameters
|
|
1579
|
+
* - Protocol version mismatch
|
|
1580
|
+
*
|
|
1581
|
+
* ## Resolution
|
|
1582
|
+
* This is usually a server-side issue. Contact the API provider if the error persists.
|
|
1583
|
+
*
|
|
1584
|
+
* @example
|
|
1585
|
+
* ```typescript
|
|
1586
|
+
* catch (error) {
|
|
1587
|
+
* if (error instanceof X402ProtocolError) {
|
|
1588
|
+
* console.log(`Protocol error: ${error.reason}`);
|
|
1589
|
+
* // Contact API provider or check server configuration
|
|
1590
|
+
* }
|
|
1591
|
+
* }
|
|
1592
|
+
* ```
|
|
1593
|
+
*/
|
|
1594
|
+
declare class X402ProtocolError extends MixrPayError {
|
|
1595
|
+
/** Detailed reason for the protocol error */
|
|
1596
|
+
readonly reason: string;
|
|
1597
|
+
constructor(reason: string);
|
|
1598
|
+
/**
|
|
1599
|
+
* Protocol errors may be caused by temporary server issues.
|
|
1600
|
+
* @returns true - worth retrying in case server recovers
|
|
1601
|
+
*/
|
|
1602
|
+
isRetryable(): boolean;
|
|
1603
|
+
}
|
|
1482
1604
|
/**
|
|
1483
1605
|
* Thrown when a session authorization has expired.
|
|
1484
1606
|
*
|
|
@@ -1611,5 +1733,20 @@ declare class SessionRevokedError extends MixrPayError {
|
|
|
1611
1733
|
* ```
|
|
1612
1734
|
*/
|
|
1613
1735
|
declare function isMixrPayError(error: unknown): error is MixrPayError;
|
|
1736
|
+
/**
|
|
1737
|
+
* Get a user-friendly error message from any error.
|
|
1738
|
+
*
|
|
1739
|
+
* @param error - The error to get a message from
|
|
1740
|
+
* @returns A user-friendly error message
|
|
1741
|
+
*
|
|
1742
|
+
* @example
|
|
1743
|
+
* ```typescript
|
|
1744
|
+
* catch (error) {
|
|
1745
|
+
* const message = getErrorMessage(error);
|
|
1746
|
+
* showToast(message);
|
|
1747
|
+
* }
|
|
1748
|
+
* ```
|
|
1749
|
+
*/
|
|
1750
|
+
declare function getErrorMessage(error: unknown): string;
|
|
1614
1751
|
|
|
1615
|
-
export { type AgentMessage, type AgentRunConfig, type AgentRunEvent, type AgentRunOptions, type AgentRunResult, type AgentRunStatusResult, AgentWallet, type AgentWalletConfig, type CallMerchantApiOptions, type ChargeResult, InsufficientBalanceError, InvalidSessionKeyError, MixrPayError, type PaymentEvent, PaymentFailedError, SDK_VERSION, type SessionAuthorization, SessionExpiredError, SessionKeyExpiredError, SessionLimitExceededError, SessionNotFoundError, SessionRevokedError, type SessionStats, SpendingLimitExceededError, type SpendingStats, isMixrPayError };
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -9,14 +9,43 @@ import {
|
|
|
9
9
|
var MixrPayError = class extends Error {
|
|
10
10
|
/** Error code for programmatic handling */
|
|
11
11
|
code;
|
|
12
|
-
|
|
12
|
+
/** Optional hint for how long to wait before retrying (in milliseconds) */
|
|
13
|
+
retryAfterMs;
|
|
14
|
+
constructor(message, code = "MIXRPAY_ERROR", retryAfterMs) {
|
|
13
15
|
super(message);
|
|
14
16
|
this.name = "MixrPayError";
|
|
15
17
|
this.code = code;
|
|
18
|
+
this.retryAfterMs = retryAfterMs;
|
|
16
19
|
if (Error.captureStackTrace) {
|
|
17
20
|
Error.captureStackTrace(this, this.constructor);
|
|
18
21
|
}
|
|
19
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Check if this error is retryable.
|
|
25
|
+
*
|
|
26
|
+
* Returns true if the operation might succeed on retry (e.g., transient network issues).
|
|
27
|
+
* Returns false if the error requires user action to resolve (e.g., insufficient balance).
|
|
28
|
+
*
|
|
29
|
+
* @returns true if the operation should be retried, false otherwise
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* try {
|
|
34
|
+
* await wallet.fetch(...);
|
|
35
|
+
* } catch (error) {
|
|
36
|
+
* if (error instanceof MixrPayError && error.isRetryable()) {
|
|
37
|
+
* // Retry the operation
|
|
38
|
+
* await retry();
|
|
39
|
+
* } else {
|
|
40
|
+
* // Handle permanent failure
|
|
41
|
+
* throw error;
|
|
42
|
+
* }
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
isRetryable() {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
20
49
|
};
|
|
21
50
|
var InsufficientBalanceError = class extends MixrPayError {
|
|
22
51
|
/** Amount required for the payment in USD */
|
|
@@ -74,6 +103,14 @@ var SpendingLimitExceededError = class extends MixrPayError {
|
|
|
74
103
|
this.limit = limit;
|
|
75
104
|
this.attempted = attempted;
|
|
76
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Daily limits reset at midnight, so those are retryable (after waiting).
|
|
108
|
+
* Other limit types require user action (new session key, config change).
|
|
109
|
+
* @returns true only for daily limits
|
|
110
|
+
*/
|
|
111
|
+
isRetryable() {
|
|
112
|
+
return this.limitType === "daily";
|
|
113
|
+
}
|
|
77
114
|
};
|
|
78
115
|
var PaymentFailedError = class extends MixrPayError {
|
|
79
116
|
/** Detailed reason for the failure */
|
|
@@ -90,6 +127,13 @@ var PaymentFailedError = class extends MixrPayError {
|
|
|
90
127
|
this.reason = reason;
|
|
91
128
|
this.txHash = txHash;
|
|
92
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Payment failures are often transient (network issues, temporary server errors).
|
|
132
|
+
* @returns true - payment failures should generally be retried
|
|
133
|
+
*/
|
|
134
|
+
isRetryable() {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
93
137
|
};
|
|
94
138
|
var InvalidSessionKeyError = class extends MixrPayError {
|
|
95
139
|
/** Detailed reason why the key is invalid */
|
|
@@ -114,6 +158,13 @@ var X402ProtocolError = class extends MixrPayError {
|
|
|
114
158
|
this.name = "X402ProtocolError";
|
|
115
159
|
this.reason = reason;
|
|
116
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Protocol errors may be caused by temporary server issues.
|
|
163
|
+
* @returns true - worth retrying in case server recovers
|
|
164
|
+
*/
|
|
165
|
+
isRetryable() {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
117
168
|
};
|
|
118
169
|
var SessionExpiredError = class extends MixrPayError {
|
|
119
170
|
/** ID of the expired session */
|
|
@@ -181,6 +232,15 @@ var SessionRevokedError = class extends MixrPayError {
|
|
|
181
232
|
function isMixrPayError(error) {
|
|
182
233
|
return error instanceof MixrPayError;
|
|
183
234
|
}
|
|
235
|
+
function getErrorMessage(error) {
|
|
236
|
+
if (error instanceof MixrPayError) {
|
|
237
|
+
return error.message;
|
|
238
|
+
}
|
|
239
|
+
if (error instanceof Error) {
|
|
240
|
+
return error.message;
|
|
241
|
+
}
|
|
242
|
+
return String(error);
|
|
243
|
+
}
|
|
184
244
|
|
|
185
245
|
// src/session-key.ts
|
|
186
246
|
var USDC_ADDRESSES = {
|
|
@@ -411,7 +471,7 @@ function getAmountUsd(requirements) {
|
|
|
411
471
|
}
|
|
412
472
|
|
|
413
473
|
// src/agent-wallet.ts
|
|
414
|
-
var SDK_VERSION = "0.
|
|
474
|
+
var SDK_VERSION = "0.6.0";
|
|
415
475
|
var DEFAULT_BASE_URL = process.env.MIXRPAY_BASE_URL || "https://www.mixrpay.com";
|
|
416
476
|
var DEFAULT_TIMEOUT = 3e4;
|
|
417
477
|
var NETWORKS = {
|
|
@@ -823,6 +883,8 @@ var AgentWallet = class {
|
|
|
823
883
|
*/
|
|
824
884
|
async fetch(url, init) {
|
|
825
885
|
this.logger.debug(`Fetching ${init?.method || "GET"} ${url}`);
|
|
886
|
+
const requestId = crypto.randomUUID();
|
|
887
|
+
const correlationId = this.extractCorrelationId(init?.headers);
|
|
826
888
|
const controller = new AbortController();
|
|
827
889
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
828
890
|
try {
|
|
@@ -833,7 +895,7 @@ var AgentWallet = class {
|
|
|
833
895
|
this.logger.debug(`Initial response: ${response.status}`);
|
|
834
896
|
if (response.status === 402) {
|
|
835
897
|
this.logger.info(`Payment required for ${url}`);
|
|
836
|
-
response = await this.handlePaymentRequired(url, init, response);
|
|
898
|
+
response = await this.handlePaymentRequired(url, init, response, requestId, correlationId);
|
|
837
899
|
}
|
|
838
900
|
return response;
|
|
839
901
|
} catch (error) {
|
|
@@ -845,10 +907,27 @@ var AgentWallet = class {
|
|
|
845
907
|
clearTimeout(timeoutId);
|
|
846
908
|
}
|
|
847
909
|
}
|
|
910
|
+
/**
|
|
911
|
+
* Extract correlation ID from request headers.
|
|
912
|
+
*/
|
|
913
|
+
extractCorrelationId(headers) {
|
|
914
|
+
if (!headers) return void 0;
|
|
915
|
+
if (headers instanceof Headers) {
|
|
916
|
+
return headers.get("X-Correlation-Id") || headers.get("x-correlation-id") || void 0;
|
|
917
|
+
}
|
|
918
|
+
if (Array.isArray(headers)) {
|
|
919
|
+
const entry = headers.find(
|
|
920
|
+
([key]) => key.toLowerCase() === "x-correlation-id"
|
|
921
|
+
);
|
|
922
|
+
return entry ? entry[1] : void 0;
|
|
923
|
+
}
|
|
924
|
+
const record = headers;
|
|
925
|
+
return record["X-Correlation-Id"] || record["x-correlation-id"] || void 0;
|
|
926
|
+
}
|
|
848
927
|
/**
|
|
849
928
|
* Handle a 402 Payment Required response.
|
|
850
929
|
*/
|
|
851
|
-
async handlePaymentRequired(url, init, response) {
|
|
930
|
+
async handlePaymentRequired(url, init, response, requestId, correlationId) {
|
|
852
931
|
let requirements;
|
|
853
932
|
try {
|
|
854
933
|
requirements = await parse402Response(response);
|
|
@@ -904,7 +983,9 @@ var AgentWallet = class {
|
|
|
904
983
|
txHash: retryResponse.headers.get("X-Payment-TxHash"),
|
|
905
984
|
timestamp: /* @__PURE__ */ new Date(),
|
|
906
985
|
description: requirements.description,
|
|
907
|
-
url
|
|
986
|
+
url,
|
|
987
|
+
requestId,
|
|
988
|
+
correlationId
|
|
908
989
|
};
|
|
909
990
|
this.payments.push(payment);
|
|
910
991
|
this.totalSpentUsd += amountUsd;
|
|
@@ -1003,6 +1084,36 @@ var AgentWallet = class {
|
|
|
1003
1084
|
this.logger.debug("Using estimated balance based on tracking");
|
|
1004
1085
|
return Math.max(0, 100 - this.totalSpentUsd);
|
|
1005
1086
|
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Check if the wallet can afford a specific amount.
|
|
1089
|
+
*
|
|
1090
|
+
* This is a convenience method to check balance before making a request
|
|
1091
|
+
* when you know the expected cost.
|
|
1092
|
+
*
|
|
1093
|
+
* @param amountUsd - Amount to check in USD
|
|
1094
|
+
* @returns Object with affordability information
|
|
1095
|
+
*
|
|
1096
|
+
* @example
|
|
1097
|
+
* ```typescript
|
|
1098
|
+
* const check = await wallet.canAfford(5.00);
|
|
1099
|
+
* if (check.canAfford) {
|
|
1100
|
+
* console.log(`Can afford! Will have $${check.remainingAfter.toFixed(2)} left`);
|
|
1101
|
+
* await wallet.fetch(url);
|
|
1102
|
+
* } else {
|
|
1103
|
+
* console.log(`Need $${check.shortfall.toFixed(2)} more`);
|
|
1104
|
+
* }
|
|
1105
|
+
* ```
|
|
1106
|
+
*/
|
|
1107
|
+
async canAfford(amountUsd) {
|
|
1108
|
+
const balance = await this.getBalance();
|
|
1109
|
+
const canAfford = balance >= amountUsd;
|
|
1110
|
+
return {
|
|
1111
|
+
canAfford,
|
|
1112
|
+
balance,
|
|
1113
|
+
shortfall: canAfford ? 0 : amountUsd - balance,
|
|
1114
|
+
remainingAfter: canAfford ? balance - amountUsd : 0
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1006
1117
|
/**
|
|
1007
1118
|
* Get information about the session key.
|
|
1008
1119
|
*
|
|
@@ -1143,56 +1254,100 @@ var AgentWallet = class {
|
|
|
1143
1254
|
async runDiagnostics() {
|
|
1144
1255
|
this.logger.info("Running diagnostics...");
|
|
1145
1256
|
const issues = [];
|
|
1257
|
+
const recommendations = [];
|
|
1146
1258
|
const checks = {};
|
|
1259
|
+
let latencyMs;
|
|
1260
|
+
let sessionLimits;
|
|
1147
1261
|
checks.sessionKeyFormat = true;
|
|
1148
1262
|
try {
|
|
1263
|
+
const startTime = Date.now();
|
|
1149
1264
|
const response = await fetch(`${this.baseUrl}/health`, {
|
|
1150
1265
|
method: "GET",
|
|
1151
1266
|
signal: AbortSignal.timeout(5e3)
|
|
1152
1267
|
});
|
|
1268
|
+
latencyMs = Date.now() - startTime;
|
|
1153
1269
|
checks.apiConnectivity = response.ok;
|
|
1154
1270
|
if (!response.ok) {
|
|
1155
1271
|
issues.push(`API server returned ${response.status}. Check baseUrl configuration.`);
|
|
1272
|
+
recommendations.push("Verify the baseUrl configuration points to a valid MixrPay server.");
|
|
1273
|
+
}
|
|
1274
|
+
if (latencyMs > 2e3) {
|
|
1275
|
+
issues.push(`High API latency: ${latencyMs}ms. This may cause timeouts.`);
|
|
1276
|
+
recommendations.push("Consider using a server closer to your region or check network connectivity.");
|
|
1156
1277
|
}
|
|
1157
1278
|
} catch {
|
|
1158
1279
|
checks.apiConnectivity = false;
|
|
1159
1280
|
issues.push("Cannot connect to MixrPay API. Check your network connection and baseUrl.");
|
|
1281
|
+
recommendations.push("Verify network connectivity and that the MixrPay server is running.");
|
|
1160
1282
|
}
|
|
1161
1283
|
try {
|
|
1162
1284
|
const info = await this.getSessionKeyInfo(true);
|
|
1163
1285
|
checks.sessionKeyValid = info.isValid;
|
|
1164
1286
|
if (!info.isValid) {
|
|
1165
1287
|
issues.push("Session key is invalid or has been revoked.");
|
|
1288
|
+
recommendations.push("Request a new session key from the wallet owner or create one at /wallet/sessions.");
|
|
1289
|
+
}
|
|
1290
|
+
const now = /* @__PURE__ */ new Date();
|
|
1291
|
+
let expiresInHours = null;
|
|
1292
|
+
if (info.expiresAt) {
|
|
1293
|
+
expiresInHours = (info.expiresAt.getTime() - now.getTime()) / (1e3 * 60 * 60);
|
|
1294
|
+
if (info.expiresAt < now) {
|
|
1295
|
+
checks.sessionKeyValid = false;
|
|
1296
|
+
issues.push(`Session key expired on ${info.expiresAt.toISOString()}`);
|
|
1297
|
+
recommendations.push("Create a new session key to continue making payments.");
|
|
1298
|
+
} else if (expiresInHours < 24) {
|
|
1299
|
+
issues.push(`Session key expires in ${expiresInHours.toFixed(1)} hours.`);
|
|
1300
|
+
recommendations.push("Consider creating a new session key before the current one expires.");
|
|
1301
|
+
}
|
|
1166
1302
|
}
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1303
|
+
const stats = await this.getSpendingStats();
|
|
1304
|
+
sessionLimits = {
|
|
1305
|
+
remainingDailyUsd: stats.remainingDailyUsd,
|
|
1306
|
+
remainingTotalUsd: stats.remainingTotalUsd,
|
|
1307
|
+
expiresAt: info.expiresAt,
|
|
1308
|
+
expiresInHours
|
|
1309
|
+
};
|
|
1310
|
+
if (stats.remainingDailyUsd !== null && stats.remainingDailyUsd < 1) {
|
|
1311
|
+
issues.push(`Daily limit nearly exhausted: $${stats.remainingDailyUsd.toFixed(2)} remaining.`);
|
|
1312
|
+
recommendations.push("Wait until tomorrow for daily limit to reset, or request a higher daily limit.");
|
|
1313
|
+
}
|
|
1314
|
+
if (stats.remainingTotalUsd !== null && stats.remainingTotalUsd < 1) {
|
|
1315
|
+
issues.push(`Total limit nearly exhausted: $${stats.remainingTotalUsd.toFixed(2)} remaining.`);
|
|
1316
|
+
recommendations.push("Request a new session key with a higher total limit.");
|
|
1170
1317
|
}
|
|
1171
1318
|
} catch {
|
|
1172
1319
|
checks.sessionKeyValid = false;
|
|
1173
1320
|
issues.push("Could not verify session key validity.");
|
|
1321
|
+
recommendations.push("Check network connectivity and try again.");
|
|
1174
1322
|
}
|
|
1323
|
+
let balance = 0;
|
|
1175
1324
|
try {
|
|
1176
|
-
|
|
1325
|
+
balance = await this.getBalance();
|
|
1177
1326
|
checks.hasBalance = balance > 0;
|
|
1178
1327
|
if (balance <= 0) {
|
|
1179
1328
|
issues.push("Wallet has no USDC balance. Top up at your MixrPay server /wallet");
|
|
1329
|
+
recommendations.push("Deposit USDC to your wallet address to enable payments.");
|
|
1180
1330
|
} else if (balance < 1) {
|
|
1181
1331
|
issues.push(`Low balance: $${balance.toFixed(2)}. Consider topping up.`);
|
|
1332
|
+
recommendations.push("Top up your wallet balance to avoid payment failures.");
|
|
1182
1333
|
}
|
|
1183
1334
|
} catch {
|
|
1184
1335
|
checks.hasBalance = false;
|
|
1185
1336
|
issues.push("Could not fetch wallet balance.");
|
|
1337
|
+
recommendations.push("Check network connectivity and try again.");
|
|
1186
1338
|
}
|
|
1187
1339
|
const healthy = issues.length === 0;
|
|
1188
|
-
this.logger.info("Diagnostics complete:", { healthy, issues });
|
|
1340
|
+
this.logger.info("Diagnostics complete:", { healthy, issues, latencyMs });
|
|
1189
1341
|
return {
|
|
1190
1342
|
healthy,
|
|
1191
1343
|
issues,
|
|
1192
1344
|
checks,
|
|
1193
1345
|
sdkVersion: SDK_VERSION,
|
|
1194
1346
|
network: this.getNetwork().name,
|
|
1195
|
-
walletAddress: this.walletAddress
|
|
1347
|
+
walletAddress: this.walletAddress,
|
|
1348
|
+
sessionLimits,
|
|
1349
|
+
latencyMs,
|
|
1350
|
+
recommendations
|
|
1196
1351
|
};
|
|
1197
1352
|
}
|
|
1198
1353
|
/**
|
|
@@ -1601,7 +1756,9 @@ var AgentWallet = class {
|
|
|
1601
1756
|
txHash: response.headers.get("X-Payment-TxHash"),
|
|
1602
1757
|
timestamp: /* @__PURE__ */ new Date(),
|
|
1603
1758
|
description: feature || "API call",
|
|
1604
|
-
url
|
|
1759
|
+
url,
|
|
1760
|
+
requestId: crypto.randomUUID(),
|
|
1761
|
+
correlationId: this.extractCorrelationId(customHeaders)
|
|
1605
1762
|
};
|
|
1606
1763
|
this.payments.push(payment);
|
|
1607
1764
|
this.totalSpentUsd += amountUsd;
|
|
@@ -1800,7 +1957,8 @@ Timestamp: ${timestamp}`;
|
|
|
1800
1957
|
txHash: mixrpay.txHash,
|
|
1801
1958
|
timestamp: /* @__PURE__ */ new Date(),
|
|
1802
1959
|
description: `MCP: ${toolName}`,
|
|
1803
|
-
url: `${this.baseUrl}/api/mcp
|
|
1960
|
+
url: `${this.baseUrl}/api/mcp`,
|
|
1961
|
+
requestId: crypto.randomUUID()
|
|
1804
1962
|
};
|
|
1805
1963
|
this.payments.push(payment);
|
|
1806
1964
|
this.totalSpentUsd += mixrpay.chargedUsd;
|
|
@@ -1917,7 +2075,8 @@ Timestamp: ${timestamp}`;
|
|
|
1917
2075
|
txHash: data.tx_hash,
|
|
1918
2076
|
timestamp: /* @__PURE__ */ new Date(),
|
|
1919
2077
|
description: `Agent run: ${data.run_id}`,
|
|
1920
|
-
url: `${this.baseUrl}/api/v2/agent/run
|
|
2078
|
+
url: `${this.baseUrl}/api/v2/agent/run`,
|
|
2079
|
+
requestId: idempotencyKey || crypto.randomUUID()
|
|
1921
2080
|
};
|
|
1922
2081
|
this.payments.push(payment);
|
|
1923
2082
|
this.totalSpentUsd += data.cost.total_usd;
|
|
@@ -2009,7 +2168,8 @@ Timestamp: ${timestamp}`;
|
|
|
2009
2168
|
txHash: data.tx_hash,
|
|
2010
2169
|
timestamp: /* @__PURE__ */ new Date(),
|
|
2011
2170
|
description: `Agent run: ${data.run_id}`,
|
|
2012
|
-
url: `${this.baseUrl}/api/v2/agent/run
|
|
2171
|
+
url: `${this.baseUrl}/api/v2/agent/run`,
|
|
2172
|
+
requestId: body.idempotency_key || crypto.randomUUID()
|
|
2013
2173
|
};
|
|
2014
2174
|
this.payments.push(payment);
|
|
2015
2175
|
this.totalSpentUsd += data.total_cost_usd;
|
|
@@ -2185,7 +2345,8 @@ Timestamp: ${timestamp}`;
|
|
|
2185
2345
|
txHash: mixrpay.txHash,
|
|
2186
2346
|
timestamp: /* @__PURE__ */ new Date(),
|
|
2187
2347
|
description: `MCP: ${toolName}`,
|
|
2188
|
-
url: `${this.baseUrl}/api/mcp
|
|
2348
|
+
url: `${this.baseUrl}/api/mcp`,
|
|
2349
|
+
requestId: crypto.randomUUID()
|
|
2189
2350
|
};
|
|
2190
2351
|
this.payments.push(payment);
|
|
2191
2352
|
this.totalSpentUsd += mixrpay.chargedUsd;
|
|
@@ -2215,5 +2376,7 @@ export {
|
|
|
2215
2376
|
SessionNotFoundError,
|
|
2216
2377
|
SessionRevokedError,
|
|
2217
2378
|
SpendingLimitExceededError,
|
|
2379
|
+
X402ProtocolError,
|
|
2380
|
+
getErrorMessage,
|
|
2218
2381
|
isMixrPayError
|
|
2219
2382
|
};
|