@mixrpay/agent-sdk 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -12
- package/dist/index.cjs +160 -67
- package/dist/index.d.cts +142 -268
- package/dist/index.d.ts +142 -268
- package/dist/index.js +155 -52
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -127,7 +127,7 @@ const session = await wallet.getOrCreateSession({
|
|
|
127
127
|
});
|
|
128
128
|
|
|
129
129
|
console.log('Session ID:', session.id);
|
|
130
|
-
console.log('Remaining:', session.
|
|
130
|
+
console.log('Remaining:', session.remainingLimitUsd);
|
|
131
131
|
console.log('Expires:', session.expiresAt);
|
|
132
132
|
|
|
133
133
|
// Charge manually
|
|
@@ -154,7 +154,7 @@ const response = await fetch('https://api.merchant.com/generate', {
|
|
|
154
154
|
// List all active sessions
|
|
155
155
|
const sessions = await wallet.listSessions();
|
|
156
156
|
for (const session of sessions) {
|
|
157
|
-
console.log(`${session.merchantName}: $${session.
|
|
157
|
+
console.log(`${session.merchantName}: $${session.remainingLimitUsd} remaining`);
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
// Revoke a session (e.g., when done with a merchant)
|
|
@@ -197,8 +197,11 @@ import {
|
|
|
197
197
|
InsufficientBalanceError,
|
|
198
198
|
SpendingLimitExceededError,
|
|
199
199
|
SessionKeyExpiredError,
|
|
200
|
+
// Session Authorization Errors
|
|
200
201
|
SessionExpiredError,
|
|
201
202
|
SessionLimitExceededError,
|
|
203
|
+
SessionNotFoundError,
|
|
204
|
+
SessionRevokedError,
|
|
202
205
|
} from '@mixrpay/agent-sdk';
|
|
203
206
|
|
|
204
207
|
const wallet = new AgentWallet({ sessionKey: 'sk_live_...' });
|
|
@@ -210,11 +213,15 @@ try {
|
|
|
210
213
|
console.log(`❌ Not enough funds: need $${error.required}, have $${error.available}`);
|
|
211
214
|
console.log(` Top up at: ${error.topUpUrl}`);
|
|
212
215
|
} else if (error instanceof SessionExpiredError) {
|
|
213
|
-
console.log(
|
|
216
|
+
console.log(`❌ Session ${error.sessionId} expired`);
|
|
214
217
|
// SDK will auto-create on next callMerchantApi()
|
|
215
218
|
} else if (error instanceof SessionLimitExceededError) {
|
|
216
|
-
console.log(`❌ Session limit exceeded
|
|
217
|
-
console.log(` Limit: $${error.limit}, Requested: $${error.requested}`);
|
|
219
|
+
console.log(`❌ Session limit exceeded`);
|
|
220
|
+
console.log(` Limit: $${error.limit}, Requested: $${error.requested}, Remaining: $${error.remaining}`);
|
|
221
|
+
} else if (error instanceof SessionNotFoundError) {
|
|
222
|
+
console.log(`❌ Session ${error.sessionId} not found`);
|
|
223
|
+
} else if (error instanceof SessionRevokedError) {
|
|
224
|
+
console.log(`❌ Session ${error.sessionId} was revoked: ${error.reason || 'no reason given'}`);
|
|
218
225
|
} else if (error instanceof SpendingLimitExceededError) {
|
|
219
226
|
console.log(`❌ Session key limit exceeded: ${error.limitType}`);
|
|
220
227
|
} else if (error instanceof SessionKeyExpiredError) {
|
|
@@ -372,24 +379,41 @@ new AgentWallet({
|
|
|
372
379
|
### Types
|
|
373
380
|
|
|
374
381
|
```typescript
|
|
375
|
-
interface
|
|
382
|
+
interface SessionAuthorization {
|
|
376
383
|
id: string;
|
|
377
|
-
|
|
378
|
-
merchantName
|
|
384
|
+
merchantId: string;
|
|
385
|
+
merchantName: string;
|
|
386
|
+
status: 'pending' | 'active' | 'expired' | 'revoked';
|
|
379
387
|
spendingLimitUsd: number;
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
status: 'active' | 'expired' | 'revoked';
|
|
388
|
+
amountUsedUsd: number;
|
|
389
|
+
remainingLimitUsd: number;
|
|
383
390
|
expiresAt: Date;
|
|
384
391
|
createdAt: Date;
|
|
385
392
|
}
|
|
386
393
|
|
|
394
|
+
interface SessionStats {
|
|
395
|
+
activeCount: number;
|
|
396
|
+
expiredCount: number;
|
|
397
|
+
revokedCount: number;
|
|
398
|
+
totalAuthorizedUsd: number;
|
|
399
|
+
totalSpentUsd: number;
|
|
400
|
+
totalRemainingUsd: number;
|
|
401
|
+
activeSessions: Array<{
|
|
402
|
+
id: string;
|
|
403
|
+
merchantName: string;
|
|
404
|
+
merchantPublicKey: string;
|
|
405
|
+
spendingLimitUsd: number;
|
|
406
|
+
remainingUsd: number;
|
|
407
|
+
expiresAt: Date;
|
|
408
|
+
}>;
|
|
409
|
+
}
|
|
410
|
+
|
|
387
411
|
interface ChargeResult {
|
|
388
412
|
success: boolean;
|
|
389
413
|
chargeId: string;
|
|
390
414
|
amountUsd: number;
|
|
391
415
|
txHash?: string;
|
|
392
|
-
|
|
416
|
+
remainingSessionBalanceUsd: number;
|
|
393
417
|
}
|
|
394
418
|
|
|
395
419
|
interface PaymentEvent {
|
package/dist/index.cjs
CHANGED
|
@@ -21,28 +21,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
AgentWallet: () => AgentWallet,
|
|
24
|
-
DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
|
|
25
|
-
DEFAULT_FACILITATOR_URL: () => DEFAULT_FACILITATOR_URL,
|
|
26
|
-
DEFAULT_TIMEOUT: () => DEFAULT_TIMEOUT,
|
|
27
24
|
InsufficientBalanceError: () => InsufficientBalanceError,
|
|
28
25
|
InvalidSessionKeyError: () => InvalidSessionKeyError,
|
|
29
26
|
MixrPayError: () => MixrPayError,
|
|
30
|
-
NETWORKS: () => NETWORKS,
|
|
31
27
|
PaymentFailedError: () => PaymentFailedError,
|
|
32
28
|
SDK_VERSION: () => SDK_VERSION,
|
|
33
|
-
|
|
29
|
+
SessionExpiredError: () => SessionExpiredError,
|
|
34
30
|
SessionKeyExpiredError: () => SessionKeyExpiredError,
|
|
31
|
+
SessionLimitExceededError: () => SessionLimitExceededError,
|
|
32
|
+
SessionNotFoundError: () => SessionNotFoundError,
|
|
33
|
+
SessionRevokedError: () => SessionRevokedError,
|
|
35
34
|
SpendingLimitExceededError: () => SpendingLimitExceededError,
|
|
36
|
-
|
|
37
|
-
buildTransferAuthorizationData: () => buildTransferAuthorizationData,
|
|
38
|
-
buildXPaymentHeader: () => buildXPaymentHeader,
|
|
39
|
-
generateNonce: () => generateNonce,
|
|
40
|
-
getAmountUsd: () => getAmountUsd,
|
|
41
|
-
getErrorMessage: () => getErrorMessage,
|
|
42
|
-
isMixrPayError: () => isMixrPayError,
|
|
43
|
-
isPaymentExpired: () => isPaymentExpired,
|
|
44
|
-
parse402Response: () => parse402Response,
|
|
45
|
-
validatePaymentAmount: () => validatePaymentAmount
|
|
35
|
+
isMixrPayError: () => isMixrPayError
|
|
46
36
|
});
|
|
47
37
|
module.exports = __toCommonJS(index_exports);
|
|
48
38
|
|
|
@@ -159,17 +149,71 @@ var X402ProtocolError = class extends MixrPayError {
|
|
|
159
149
|
this.reason = reason;
|
|
160
150
|
}
|
|
161
151
|
};
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
152
|
+
var SessionExpiredError = class extends MixrPayError {
|
|
153
|
+
/** ID of the expired session */
|
|
154
|
+
sessionId;
|
|
155
|
+
/** When the session expired (ISO string or Date) */
|
|
156
|
+
expiredAt;
|
|
157
|
+
constructor(sessionId, expiredAt) {
|
|
158
|
+
super(
|
|
159
|
+
`Session ${sessionId} has expired${expiredAt ? ` at ${expiredAt}` : ""}. A new session will be created automatically on your next request.`,
|
|
160
|
+
"SESSION_EXPIRED"
|
|
161
|
+
);
|
|
162
|
+
this.name = "SessionExpiredError";
|
|
163
|
+
this.sessionId = sessionId;
|
|
164
|
+
this.expiredAt = expiredAt;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
var SessionLimitExceededError = class extends MixrPayError {
|
|
168
|
+
/** ID of the session */
|
|
169
|
+
sessionId;
|
|
170
|
+
/** The session's spending limit in USD */
|
|
171
|
+
limit;
|
|
172
|
+
/** The amount requested in USD */
|
|
173
|
+
requested;
|
|
174
|
+
/** Remaining balance in the session (limit - used) */
|
|
175
|
+
remaining;
|
|
176
|
+
constructor(limit, requested, remaining, sessionId) {
|
|
177
|
+
super(
|
|
178
|
+
`Session spending limit exceeded: limit is $${limit.toFixed(2)}, requested $${requested.toFixed(2)}, remaining $${remaining.toFixed(2)}. Create a new session with a higher limit to continue.`,
|
|
179
|
+
"SESSION_LIMIT_EXCEEDED"
|
|
180
|
+
);
|
|
181
|
+
this.name = "SessionLimitExceededError";
|
|
182
|
+
this.sessionId = sessionId;
|
|
183
|
+
this.limit = limit;
|
|
184
|
+
this.requested = requested;
|
|
185
|
+
this.remaining = remaining;
|
|
168
186
|
}
|
|
169
|
-
|
|
170
|
-
|
|
187
|
+
};
|
|
188
|
+
var SessionNotFoundError = class extends MixrPayError {
|
|
189
|
+
/** The session ID that was not found */
|
|
190
|
+
sessionId;
|
|
191
|
+
constructor(sessionId) {
|
|
192
|
+
super(
|
|
193
|
+
`Session ${sessionId} not found. It may have been deleted or never existed. Create a new session with getOrCreateSession().`,
|
|
194
|
+
"SESSION_NOT_FOUND"
|
|
195
|
+
);
|
|
196
|
+
this.name = "SessionNotFoundError";
|
|
197
|
+
this.sessionId = sessionId;
|
|
171
198
|
}
|
|
172
|
-
|
|
199
|
+
};
|
|
200
|
+
var SessionRevokedError = class extends MixrPayError {
|
|
201
|
+
/** The session ID that was revoked */
|
|
202
|
+
sessionId;
|
|
203
|
+
/** Optional reason for revocation */
|
|
204
|
+
reason;
|
|
205
|
+
constructor(sessionId, reason) {
|
|
206
|
+
super(
|
|
207
|
+
`Session ${sessionId} has been revoked${reason ? `: ${reason}` : ""}. Create a new session with getOrCreateSession().`,
|
|
208
|
+
"SESSION_REVOKED"
|
|
209
|
+
);
|
|
210
|
+
this.name = "SessionRevokedError";
|
|
211
|
+
this.sessionId = sessionId;
|
|
212
|
+
this.reason = reason;
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
function isMixrPayError(error) {
|
|
216
|
+
return error instanceof MixrPayError;
|
|
173
217
|
}
|
|
174
218
|
|
|
175
219
|
// src/session-key.ts
|
|
@@ -396,21 +440,10 @@ function isPaymentExpired(requirements) {
|
|
|
396
440
|
function getAmountUsd(requirements) {
|
|
397
441
|
return Number(requirements.amount) / 1e6;
|
|
398
442
|
}
|
|
399
|
-
function validatePaymentAmount(amountUsd, maxPaymentUsd) {
|
|
400
|
-
if (amountUsd <= 0) {
|
|
401
|
-
throw new X402ProtocolError(`Invalid payment amount: $${amountUsd.toFixed(2)}`);
|
|
402
|
-
}
|
|
403
|
-
if (maxPaymentUsd !== void 0 && amountUsd > maxPaymentUsd) {
|
|
404
|
-
throw new X402ProtocolError(
|
|
405
|
-
`Payment amount $${amountUsd.toFixed(2)} exceeds client limit $${maxPaymentUsd.toFixed(2)}`
|
|
406
|
-
);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
443
|
|
|
410
444
|
// src/agent-wallet.ts
|
|
411
|
-
var SDK_VERSION = "0.
|
|
445
|
+
var SDK_VERSION = "0.3.0";
|
|
412
446
|
var DEFAULT_BASE_URL = process.env.MIXRPAY_BASE_URL || "https://mixrpay.com";
|
|
413
|
-
var DEFAULT_FACILITATOR_URL = "https://x402.org/facilitator";
|
|
414
447
|
var DEFAULT_TIMEOUT = 3e4;
|
|
415
448
|
var NETWORKS = {
|
|
416
449
|
BASE_MAINNET: { chainId: 8453, name: "Base", isTestnet: false },
|
|
@@ -1166,6 +1199,63 @@ var AgentWallet = class {
|
|
|
1166
1199
|
this.logger.info(`Session ${sessionId} revoked`);
|
|
1167
1200
|
return true;
|
|
1168
1201
|
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Get statistics about all session authorizations.
|
|
1204
|
+
*
|
|
1205
|
+
* This provides an overview of active, expired, and revoked sessions,
|
|
1206
|
+
* along with aggregate spending information.
|
|
1207
|
+
*
|
|
1208
|
+
* @returns Session statistics
|
|
1209
|
+
*
|
|
1210
|
+
* @example
|
|
1211
|
+
* ```typescript
|
|
1212
|
+
* const stats = await wallet.getSessionStats();
|
|
1213
|
+
* console.log(`Active sessions: ${stats.activeCount}`);
|
|
1214
|
+
* console.log(`Total authorized: $${stats.totalAuthorizedUsd.toFixed(2)}`);
|
|
1215
|
+
* console.log(`Total remaining: $${stats.totalRemainingUsd.toFixed(2)}`);
|
|
1216
|
+
*
|
|
1217
|
+
* for (const session of stats.activeSessions) {
|
|
1218
|
+
* console.log(`${session.merchantName}: $${session.remainingUsd} remaining`);
|
|
1219
|
+
* }
|
|
1220
|
+
* ```
|
|
1221
|
+
*/
|
|
1222
|
+
async getSessionStats() {
|
|
1223
|
+
this.logger.debug("getSessionStats");
|
|
1224
|
+
const sessions = await this.listSessions();
|
|
1225
|
+
const now = /* @__PURE__ */ new Date();
|
|
1226
|
+
const active = [];
|
|
1227
|
+
const expired = [];
|
|
1228
|
+
const revoked = [];
|
|
1229
|
+
for (const session of sessions) {
|
|
1230
|
+
if (session.status === "revoked") {
|
|
1231
|
+
revoked.push(session);
|
|
1232
|
+
} else if (session.status === "expired" || session.expiresAt && session.expiresAt < now) {
|
|
1233
|
+
expired.push(session);
|
|
1234
|
+
} else if (session.status === "active") {
|
|
1235
|
+
active.push(session);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
const totalAuthorizedUsd = active.reduce((sum, s) => sum + s.spendingLimitUsd, 0);
|
|
1239
|
+
const totalSpentUsd = sessions.reduce((sum, s) => sum + s.amountUsedUsd, 0);
|
|
1240
|
+
const totalRemainingUsd = active.reduce((sum, s) => sum + s.remainingLimitUsd, 0);
|
|
1241
|
+
return {
|
|
1242
|
+
activeCount: active.length,
|
|
1243
|
+
expiredCount: expired.length,
|
|
1244
|
+
revokedCount: revoked.length,
|
|
1245
|
+
totalAuthorizedUsd,
|
|
1246
|
+
totalSpentUsd,
|
|
1247
|
+
totalRemainingUsd,
|
|
1248
|
+
activeSessions: active.map((s) => ({
|
|
1249
|
+
id: s.id,
|
|
1250
|
+
merchantName: s.merchantName,
|
|
1251
|
+
merchantPublicKey: s.merchantId,
|
|
1252
|
+
// merchantId is the public key in this context
|
|
1253
|
+
spendingLimitUsd: s.spendingLimitUsd,
|
|
1254
|
+
remainingUsd: s.remainingLimitUsd,
|
|
1255
|
+
expiresAt: s.expiresAt
|
|
1256
|
+
}))
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1169
1259
|
/**
|
|
1170
1260
|
* Charge against an active session authorization.
|
|
1171
1261
|
*
|
|
@@ -1206,21 +1296,29 @@ var AgentWallet = class {
|
|
|
1206
1296
|
});
|
|
1207
1297
|
if (!response.ok) {
|
|
1208
1298
|
const error = await response.json().catch(() => ({}));
|
|
1299
|
+
const errorCode = error.error || error.error_code || "";
|
|
1209
1300
|
if (response.status === 402) {
|
|
1210
|
-
if (
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
amountUsd
|
|
1215
|
-
);
|
|
1301
|
+
if (errorCode === "session_limit_exceeded") {
|
|
1302
|
+
const limit = error.sessionLimitUsd || error.session_limit_usd || 0;
|
|
1303
|
+
const remaining = error.remainingUsd || error.remaining_usd || 0;
|
|
1304
|
+
throw new SessionLimitExceededError(limit, amountUsd, remaining, sessionId);
|
|
1216
1305
|
}
|
|
1217
|
-
if (
|
|
1306
|
+
if (errorCode === "insufficient_balance") {
|
|
1218
1307
|
throw new InsufficientBalanceError(
|
|
1219
1308
|
amountUsd,
|
|
1220
1309
|
error.availableUsd || error.available_usd || 0
|
|
1221
1310
|
);
|
|
1222
1311
|
}
|
|
1223
1312
|
}
|
|
1313
|
+
if (response.status === 404 || errorCode === "session_not_found") {
|
|
1314
|
+
throw new SessionNotFoundError(sessionId);
|
|
1315
|
+
}
|
|
1316
|
+
if (errorCode === "session_expired") {
|
|
1317
|
+
throw new SessionExpiredError(sessionId, error.expiredAt || error.expires_at);
|
|
1318
|
+
}
|
|
1319
|
+
if (errorCode === "session_revoked") {
|
|
1320
|
+
throw new SessionRevokedError(sessionId, error.reason);
|
|
1321
|
+
}
|
|
1224
1322
|
throw new MixrPayError(error.message || `Charge failed: ${response.status}`);
|
|
1225
1323
|
}
|
|
1226
1324
|
const data = await response.json();
|
|
@@ -1315,7 +1413,8 @@ var AgentWallet = class {
|
|
|
1315
1413
|
}
|
|
1316
1414
|
if (response.status === 402) {
|
|
1317
1415
|
const errorData = await response.json().catch(() => ({}));
|
|
1318
|
-
|
|
1416
|
+
const errorCode = errorData.error || errorData.error_code || "";
|
|
1417
|
+
if (errorCode === "session_expired") {
|
|
1319
1418
|
this.logger.info("Session expired, creating new one...");
|
|
1320
1419
|
const newSession = await this.getOrCreateSession({
|
|
1321
1420
|
merchantPublicKey,
|
|
@@ -1330,17 +1429,21 @@ var AgentWallet = class {
|
|
|
1330
1429
|
signal: AbortSignal.timeout(this.timeout)
|
|
1331
1430
|
});
|
|
1332
1431
|
}
|
|
1333
|
-
if (
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1432
|
+
if (errorCode === "session_limit_exceeded") {
|
|
1433
|
+
const limit = errorData.sessionLimitUsd || errorData.session_limit_usd || 0;
|
|
1434
|
+
const remaining = errorData.remainingUsd || errorData.remaining_usd || 0;
|
|
1435
|
+
throw new SessionLimitExceededError(limit, priceUsd || 0, remaining, session.id);
|
|
1436
|
+
}
|
|
1437
|
+
if (errorCode === "session_revoked") {
|
|
1438
|
+
throw new SessionRevokedError(session.id, errorData.reason);
|
|
1439
|
+
}
|
|
1440
|
+
if (errorCode === "session_not_found") {
|
|
1441
|
+
throw new SessionNotFoundError(session.id);
|
|
1339
1442
|
}
|
|
1340
|
-
if (
|
|
1443
|
+
if (errorCode === "insufficient_balance") {
|
|
1341
1444
|
throw new InsufficientBalanceError(
|
|
1342
1445
|
priceUsd || 0,
|
|
1343
|
-
errorData.availableUsd || 0
|
|
1446
|
+
errorData.availableUsd || errorData.available_usd || 0
|
|
1344
1447
|
);
|
|
1345
1448
|
}
|
|
1346
1449
|
}
|
|
@@ -1368,26 +1471,16 @@ var AgentWallet = class {
|
|
|
1368
1471
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1369
1472
|
0 && (module.exports = {
|
|
1370
1473
|
AgentWallet,
|
|
1371
|
-
DEFAULT_BASE_URL,
|
|
1372
|
-
DEFAULT_FACILITATOR_URL,
|
|
1373
|
-
DEFAULT_TIMEOUT,
|
|
1374
1474
|
InsufficientBalanceError,
|
|
1375
1475
|
InvalidSessionKeyError,
|
|
1376
1476
|
MixrPayError,
|
|
1377
|
-
NETWORKS,
|
|
1378
1477
|
PaymentFailedError,
|
|
1379
1478
|
SDK_VERSION,
|
|
1380
|
-
|
|
1479
|
+
SessionExpiredError,
|
|
1381
1480
|
SessionKeyExpiredError,
|
|
1481
|
+
SessionLimitExceededError,
|
|
1482
|
+
SessionNotFoundError,
|
|
1483
|
+
SessionRevokedError,
|
|
1382
1484
|
SpendingLimitExceededError,
|
|
1383
|
-
|
|
1384
|
-
buildTransferAuthorizationData,
|
|
1385
|
-
buildXPaymentHeader,
|
|
1386
|
-
generateNonce,
|
|
1387
|
-
getAmountUsd,
|
|
1388
|
-
getErrorMessage,
|
|
1389
|
-
isMixrPayError,
|
|
1390
|
-
isPaymentExpired,
|
|
1391
|
-
parse402Response,
|
|
1392
|
-
validatePaymentAmount
|
|
1485
|
+
isMixrPayError
|
|
1393
1486
|
});
|