@mixrpay/agent-sdk 0.4.1 → 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 +415 -16
- package/dist/index.d.cts +324 -3
- package/dist/index.d.ts +324 -3
- package/dist/index.js +413 -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 = {
|
|
@@ -353,6 +415,9 @@ async function createSessionAuthPayload(sessionKey) {
|
|
|
353
415
|
};
|
|
354
416
|
}
|
|
355
417
|
|
|
418
|
+
// src/agent-wallet.ts
|
|
419
|
+
var import_accounts2 = require("viem/accounts");
|
|
420
|
+
|
|
356
421
|
// src/x402.ts
|
|
357
422
|
async function parse402Response(response) {
|
|
358
423
|
let paymentData = null;
|
|
@@ -442,7 +507,7 @@ function getAmountUsd(requirements) {
|
|
|
442
507
|
}
|
|
443
508
|
|
|
444
509
|
// src/agent-wallet.ts
|
|
445
|
-
var SDK_VERSION = "0.
|
|
510
|
+
var SDK_VERSION = "0.6.0";
|
|
446
511
|
var DEFAULT_BASE_URL = process.env.MIXRPAY_BASE_URL || "https://www.mixrpay.com";
|
|
447
512
|
var DEFAULT_TIMEOUT = 3e4;
|
|
448
513
|
var NETWORKS = {
|
|
@@ -586,6 +651,237 @@ var AgentWallet = class {
|
|
|
586
651
|
}
|
|
587
652
|
}
|
|
588
653
|
// ===========================================================================
|
|
654
|
+
// Static Agent Registration Methods
|
|
655
|
+
// ===========================================================================
|
|
656
|
+
/**
|
|
657
|
+
* Register a new agent with MixrPay.
|
|
658
|
+
*
|
|
659
|
+
* This creates a Privy-managed embedded wallet for the agent's payments.
|
|
660
|
+
* The agent proves ownership of their external wallet by signing a challenge.
|
|
661
|
+
*
|
|
662
|
+
* @param options - Registration options including the private key
|
|
663
|
+
* @returns Registration result with deposit address
|
|
664
|
+
* @throws {MixrPayError} If registration fails
|
|
665
|
+
*
|
|
666
|
+
* @example
|
|
667
|
+
* ```typescript
|
|
668
|
+
* const { depositAddress, userId } = await AgentWallet.register({
|
|
669
|
+
* privateKey: process.env.AGENT_WALLET_KEY as `0x${string}`,
|
|
670
|
+
* name: 'My Trading Agent',
|
|
671
|
+
* });
|
|
672
|
+
*
|
|
673
|
+
* console.log(`Fund your agent at: ${depositAddress}`);
|
|
674
|
+
* ```
|
|
675
|
+
*/
|
|
676
|
+
static async register(options) {
|
|
677
|
+
const { privateKey, name } = options;
|
|
678
|
+
const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
679
|
+
const account = (0, import_accounts2.privateKeyToAccount)(privateKey);
|
|
680
|
+
const walletAddress = account.address;
|
|
681
|
+
const challengeResponse = await fetch(
|
|
682
|
+
`${baseUrl}/api/v1/agent/challenge?wallet=${walletAddress}&action=register`
|
|
683
|
+
);
|
|
684
|
+
if (!challengeResponse.ok) {
|
|
685
|
+
const error = await challengeResponse.json().catch(() => ({}));
|
|
686
|
+
throw new MixrPayError(error.error || `Failed to get challenge: ${challengeResponse.status}`);
|
|
687
|
+
}
|
|
688
|
+
const { challenge, message } = await challengeResponse.json();
|
|
689
|
+
const signature = await (0, import_accounts2.signMessage)({ message, privateKey });
|
|
690
|
+
const registerResponse = await fetch(`${baseUrl}/api/v1/agent/register`, {
|
|
691
|
+
method: "POST",
|
|
692
|
+
headers: { "Content-Type": "application/json" },
|
|
693
|
+
body: JSON.stringify({
|
|
694
|
+
challenge,
|
|
695
|
+
external_wallet: walletAddress,
|
|
696
|
+
signature,
|
|
697
|
+
name
|
|
698
|
+
})
|
|
699
|
+
});
|
|
700
|
+
if (!registerResponse.ok) {
|
|
701
|
+
const error = await registerResponse.json().catch(() => ({}));
|
|
702
|
+
throw new MixrPayError(error.error || `Registration failed: ${registerResponse.status}`);
|
|
703
|
+
}
|
|
704
|
+
const data = await registerResponse.json();
|
|
705
|
+
return {
|
|
706
|
+
userId: data.user_id,
|
|
707
|
+
depositAddress: data.deposit_address
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Get a session key for an already-registered agent.
|
|
712
|
+
*
|
|
713
|
+
* Session keys allow the agent to make payments within the specified limits.
|
|
714
|
+
* The private key is returned ONCE - store it securely!
|
|
715
|
+
*
|
|
716
|
+
* @param options - Session key options
|
|
717
|
+
* @returns Session key result with the sk_live_ format key
|
|
718
|
+
* @throws {MixrPayError} If session key creation fails
|
|
719
|
+
*
|
|
720
|
+
* @example
|
|
721
|
+
* ```typescript
|
|
722
|
+
* const result = await AgentWallet.getSessionKey({
|
|
723
|
+
* privateKey: process.env.AGENT_WALLET_KEY as `0x${string}`,
|
|
724
|
+
* spendingLimitUsd: 100,
|
|
725
|
+
* durationDays: 30,
|
|
726
|
+
* });
|
|
727
|
+
*
|
|
728
|
+
* // Store this securely - it's your payment key!
|
|
729
|
+
* console.log(`Session key: ${result.sessionKey}`);
|
|
730
|
+
*
|
|
731
|
+
* // Use it to create an AgentWallet
|
|
732
|
+
* const wallet = new AgentWallet({ sessionKey: result.sessionKey });
|
|
733
|
+
* ```
|
|
734
|
+
*/
|
|
735
|
+
static async getSessionKey(options) {
|
|
736
|
+
const { privateKey, spendingLimitUsd, maxPerTxUsd, maxDailyUsd, durationDays } = options;
|
|
737
|
+
const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
738
|
+
const account = (0, import_accounts2.privateKeyToAccount)(privateKey);
|
|
739
|
+
const walletAddress = account.address;
|
|
740
|
+
const challengeResponse = await fetch(
|
|
741
|
+
`${baseUrl}/api/v1/agent/challenge?wallet=${walletAddress}&action=session-key`
|
|
742
|
+
);
|
|
743
|
+
if (!challengeResponse.ok) {
|
|
744
|
+
const error = await challengeResponse.json().catch(() => ({}));
|
|
745
|
+
throw new MixrPayError(error.error || `Failed to get challenge: ${challengeResponse.status}`);
|
|
746
|
+
}
|
|
747
|
+
const { challenge, message } = await challengeResponse.json();
|
|
748
|
+
const signature = await (0, import_accounts2.signMessage)({ message, privateKey });
|
|
749
|
+
const sessionKeyResponse = await fetch(`${baseUrl}/api/v1/agent/session-key`, {
|
|
750
|
+
method: "POST",
|
|
751
|
+
headers: { "Content-Type": "application/json" },
|
|
752
|
+
body: JSON.stringify({
|
|
753
|
+
challenge,
|
|
754
|
+
external_wallet: walletAddress,
|
|
755
|
+
signature,
|
|
756
|
+
spending_limit_usd: spendingLimitUsd,
|
|
757
|
+
max_per_tx_usd: maxPerTxUsd,
|
|
758
|
+
max_daily_usd: maxDailyUsd,
|
|
759
|
+
duration_days: durationDays
|
|
760
|
+
})
|
|
761
|
+
});
|
|
762
|
+
if (!sessionKeyResponse.ok) {
|
|
763
|
+
const error = await sessionKeyResponse.json().catch(() => ({}));
|
|
764
|
+
throw new MixrPayError(error.error || `Session key creation failed: ${sessionKeyResponse.status}`);
|
|
765
|
+
}
|
|
766
|
+
const data = await sessionKeyResponse.json();
|
|
767
|
+
return {
|
|
768
|
+
sessionKey: data.session_key,
|
|
769
|
+
address: data.address,
|
|
770
|
+
sessionKeyId: data.session_key_id,
|
|
771
|
+
expiresAt: new Date(data.expires_at),
|
|
772
|
+
limits: {
|
|
773
|
+
maxTotalUsd: data.limits.max_total_usd,
|
|
774
|
+
maxPerTxUsd: data.limits.max_per_tx_usd,
|
|
775
|
+
maxDailyUsd: data.limits.max_daily_usd
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Get the status of a registered agent.
|
|
781
|
+
*
|
|
782
|
+
* Returns balance, active sessions, and spending information.
|
|
783
|
+
*
|
|
784
|
+
* @param options - Status options
|
|
785
|
+
* @returns Agent status
|
|
786
|
+
* @throws {MixrPayError} If status fetch fails
|
|
787
|
+
*
|
|
788
|
+
* @example
|
|
789
|
+
* ```typescript
|
|
790
|
+
* const status = await AgentWallet.getStatus({
|
|
791
|
+
* privateKey: process.env.AGENT_WALLET_KEY as `0x${string}`,
|
|
792
|
+
* });
|
|
793
|
+
*
|
|
794
|
+
* console.log(`Balance: $${status.balanceUsd}`);
|
|
795
|
+
* console.log(`Active sessions: ${status.activeSessions.length}`);
|
|
796
|
+
* ```
|
|
797
|
+
*/
|
|
798
|
+
static async getStatus(options) {
|
|
799
|
+
const { privateKey } = options;
|
|
800
|
+
const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
801
|
+
const account = (0, import_accounts2.privateKeyToAccount)(privateKey);
|
|
802
|
+
const walletAddress = account.address;
|
|
803
|
+
const challengeResponse = await fetch(
|
|
804
|
+
`${baseUrl}/api/v1/agent/challenge?wallet=${walletAddress}&action=status`
|
|
805
|
+
);
|
|
806
|
+
if (!challengeResponse.ok) {
|
|
807
|
+
const error = await challengeResponse.json().catch(() => ({}));
|
|
808
|
+
throw new MixrPayError(error.error || `Failed to get challenge: ${challengeResponse.status}`);
|
|
809
|
+
}
|
|
810
|
+
const { challenge, message } = await challengeResponse.json();
|
|
811
|
+
const signature = await (0, import_accounts2.signMessage)({ message, privateKey });
|
|
812
|
+
const statusResponse = await fetch(
|
|
813
|
+
`${baseUrl}/api/v1/agent/status?challenge=${challenge}&external_wallet=${walletAddress}&signature=${encodeURIComponent(signature)}`
|
|
814
|
+
);
|
|
815
|
+
if (!statusResponse.ok) {
|
|
816
|
+
const error = await statusResponse.json().catch(() => ({}));
|
|
817
|
+
throw new MixrPayError(error.error || `Failed to get status: ${statusResponse.status}`);
|
|
818
|
+
}
|
|
819
|
+
const data = await statusResponse.json();
|
|
820
|
+
return {
|
|
821
|
+
depositAddress: data.deposit_address,
|
|
822
|
+
balanceUsd: data.balance_usd,
|
|
823
|
+
activeSessions: data.active_sessions.map((s) => ({
|
|
824
|
+
id: s.id,
|
|
825
|
+
expiresAt: new Date(s.expires_at),
|
|
826
|
+
totalSpentUsd: s.total_spent_usd,
|
|
827
|
+
remainingUsd: s.remaining_usd,
|
|
828
|
+
maxTotalUsd: s.max_total_usd
|
|
829
|
+
})),
|
|
830
|
+
totalSpentUsd: data.total_spent_usd
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Revoke a session key.
|
|
835
|
+
*
|
|
836
|
+
* After revocation, the session key can no longer be used for payments.
|
|
837
|
+
*
|
|
838
|
+
* @param options - Revoke options
|
|
839
|
+
* @returns true if revoked successfully
|
|
840
|
+
* @throws {MixrPayError} If revocation fails
|
|
841
|
+
*
|
|
842
|
+
* @example
|
|
843
|
+
* ```typescript
|
|
844
|
+
* const success = await AgentWallet.revokeSessionKey({
|
|
845
|
+
* privateKey: process.env.AGENT_WALLET_KEY as `0x${string}`,
|
|
846
|
+
* sessionKeyId: 'session-key-uuid',
|
|
847
|
+
* });
|
|
848
|
+
*
|
|
849
|
+
* if (success) {
|
|
850
|
+
* console.log('Session key revoked');
|
|
851
|
+
* }
|
|
852
|
+
* ```
|
|
853
|
+
*/
|
|
854
|
+
static async revokeSessionKey(options) {
|
|
855
|
+
const { privateKey, sessionKeyId } = options;
|
|
856
|
+
const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
857
|
+
const account = (0, import_accounts2.privateKeyToAccount)(privateKey);
|
|
858
|
+
const walletAddress = account.address;
|
|
859
|
+
const challengeResponse = await fetch(
|
|
860
|
+
`${baseUrl}/api/v1/agent/challenge?wallet=${walletAddress}&action=revoke`
|
|
861
|
+
);
|
|
862
|
+
if (!challengeResponse.ok) {
|
|
863
|
+
const error = await challengeResponse.json().catch(() => ({}));
|
|
864
|
+
throw new MixrPayError(error.error || `Failed to get challenge: ${challengeResponse.status}`);
|
|
865
|
+
}
|
|
866
|
+
const { challenge, message } = await challengeResponse.json();
|
|
867
|
+
const signature = await (0, import_accounts2.signMessage)({ message, privateKey });
|
|
868
|
+
const revokeResponse = await fetch(`${baseUrl}/api/v1/agent/session-key/revoke`, {
|
|
869
|
+
method: "POST",
|
|
870
|
+
headers: { "Content-Type": "application/json" },
|
|
871
|
+
body: JSON.stringify({
|
|
872
|
+
challenge,
|
|
873
|
+
external_wallet: walletAddress,
|
|
874
|
+
signature,
|
|
875
|
+
session_key_id: sessionKeyId
|
|
876
|
+
})
|
|
877
|
+
});
|
|
878
|
+
if (!revokeResponse.ok) {
|
|
879
|
+
const error = await revokeResponse.json().catch(() => ({}));
|
|
880
|
+
throw new MixrPayError(error.error || `Revocation failed: ${revokeResponse.status}`);
|
|
881
|
+
}
|
|
882
|
+
return true;
|
|
883
|
+
}
|
|
884
|
+
// ===========================================================================
|
|
589
885
|
// Core Methods
|
|
590
886
|
// ===========================================================================
|
|
591
887
|
/**
|
|
@@ -623,6 +919,8 @@ var AgentWallet = class {
|
|
|
623
919
|
*/
|
|
624
920
|
async fetch(url, init) {
|
|
625
921
|
this.logger.debug(`Fetching ${init?.method || "GET"} ${url}`);
|
|
922
|
+
const requestId = crypto.randomUUID();
|
|
923
|
+
const correlationId = this.extractCorrelationId(init?.headers);
|
|
626
924
|
const controller = new AbortController();
|
|
627
925
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
628
926
|
try {
|
|
@@ -633,7 +931,7 @@ var AgentWallet = class {
|
|
|
633
931
|
this.logger.debug(`Initial response: ${response.status}`);
|
|
634
932
|
if (response.status === 402) {
|
|
635
933
|
this.logger.info(`Payment required for ${url}`);
|
|
636
|
-
response = await this.handlePaymentRequired(url, init, response);
|
|
934
|
+
response = await this.handlePaymentRequired(url, init, response, requestId, correlationId);
|
|
637
935
|
}
|
|
638
936
|
return response;
|
|
639
937
|
} catch (error) {
|
|
@@ -645,10 +943,27 @@ var AgentWallet = class {
|
|
|
645
943
|
clearTimeout(timeoutId);
|
|
646
944
|
}
|
|
647
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
|
+
}
|
|
648
963
|
/**
|
|
649
964
|
* Handle a 402 Payment Required response.
|
|
650
965
|
*/
|
|
651
|
-
async handlePaymentRequired(url, init, response) {
|
|
966
|
+
async handlePaymentRequired(url, init, response, requestId, correlationId) {
|
|
652
967
|
let requirements;
|
|
653
968
|
try {
|
|
654
969
|
requirements = await parse402Response(response);
|
|
@@ -704,7 +1019,9 @@ var AgentWallet = class {
|
|
|
704
1019
|
txHash: retryResponse.headers.get("X-Payment-TxHash"),
|
|
705
1020
|
timestamp: /* @__PURE__ */ new Date(),
|
|
706
1021
|
description: requirements.description,
|
|
707
|
-
url
|
|
1022
|
+
url,
|
|
1023
|
+
requestId,
|
|
1024
|
+
correlationId
|
|
708
1025
|
};
|
|
709
1026
|
this.payments.push(payment);
|
|
710
1027
|
this.totalSpentUsd += amountUsd;
|
|
@@ -803,6 +1120,36 @@ var AgentWallet = class {
|
|
|
803
1120
|
this.logger.debug("Using estimated balance based on tracking");
|
|
804
1121
|
return Math.max(0, 100 - this.totalSpentUsd);
|
|
805
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
|
+
}
|
|
806
1153
|
/**
|
|
807
1154
|
* Get information about the session key.
|
|
808
1155
|
*
|
|
@@ -943,56 +1290,100 @@ var AgentWallet = class {
|
|
|
943
1290
|
async runDiagnostics() {
|
|
944
1291
|
this.logger.info("Running diagnostics...");
|
|
945
1292
|
const issues = [];
|
|
1293
|
+
const recommendations = [];
|
|
946
1294
|
const checks = {};
|
|
1295
|
+
let latencyMs;
|
|
1296
|
+
let sessionLimits;
|
|
947
1297
|
checks.sessionKeyFormat = true;
|
|
948
1298
|
try {
|
|
1299
|
+
const startTime = Date.now();
|
|
949
1300
|
const response = await fetch(`${this.baseUrl}/health`, {
|
|
950
1301
|
method: "GET",
|
|
951
1302
|
signal: AbortSignal.timeout(5e3)
|
|
952
1303
|
});
|
|
1304
|
+
latencyMs = Date.now() - startTime;
|
|
953
1305
|
checks.apiConnectivity = response.ok;
|
|
954
1306
|
if (!response.ok) {
|
|
955
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.");
|
|
956
1313
|
}
|
|
957
1314
|
} catch {
|
|
958
1315
|
checks.apiConnectivity = false;
|
|
959
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.");
|
|
960
1318
|
}
|
|
961
1319
|
try {
|
|
962
1320
|
const info = await this.getSessionKeyInfo(true);
|
|
963
1321
|
checks.sessionKeyValid = info.isValid;
|
|
964
1322
|
if (!info.isValid) {
|
|
965
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
|
+
}
|
|
1338
|
+
}
|
|
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.");
|
|
966
1349
|
}
|
|
967
|
-
if (
|
|
968
|
-
|
|
969
|
-
|
|
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.");
|
|
970
1353
|
}
|
|
971
1354
|
} catch {
|
|
972
1355
|
checks.sessionKeyValid = false;
|
|
973
1356
|
issues.push("Could not verify session key validity.");
|
|
1357
|
+
recommendations.push("Check network connectivity and try again.");
|
|
974
1358
|
}
|
|
1359
|
+
let balance = 0;
|
|
975
1360
|
try {
|
|
976
|
-
|
|
1361
|
+
balance = await this.getBalance();
|
|
977
1362
|
checks.hasBalance = balance > 0;
|
|
978
1363
|
if (balance <= 0) {
|
|
979
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.");
|
|
980
1366
|
} else if (balance < 1) {
|
|
981
1367
|
issues.push(`Low balance: $${balance.toFixed(2)}. Consider topping up.`);
|
|
1368
|
+
recommendations.push("Top up your wallet balance to avoid payment failures.");
|
|
982
1369
|
}
|
|
983
1370
|
} catch {
|
|
984
1371
|
checks.hasBalance = false;
|
|
985
1372
|
issues.push("Could not fetch wallet balance.");
|
|
1373
|
+
recommendations.push("Check network connectivity and try again.");
|
|
986
1374
|
}
|
|
987
1375
|
const healthy = issues.length === 0;
|
|
988
|
-
this.logger.info("Diagnostics complete:", { healthy, issues });
|
|
1376
|
+
this.logger.info("Diagnostics complete:", { healthy, issues, latencyMs });
|
|
989
1377
|
return {
|
|
990
1378
|
healthy,
|
|
991
1379
|
issues,
|
|
992
1380
|
checks,
|
|
993
1381
|
sdkVersion: SDK_VERSION,
|
|
994
1382
|
network: this.getNetwork().name,
|
|
995
|
-
walletAddress: this.walletAddress
|
|
1383
|
+
walletAddress: this.walletAddress,
|
|
1384
|
+
sessionLimits,
|
|
1385
|
+
latencyMs,
|
|
1386
|
+
recommendations
|
|
996
1387
|
};
|
|
997
1388
|
}
|
|
998
1389
|
/**
|
|
@@ -1401,7 +1792,9 @@ var AgentWallet = class {
|
|
|
1401
1792
|
txHash: response.headers.get("X-Payment-TxHash"),
|
|
1402
1793
|
timestamp: /* @__PURE__ */ new Date(),
|
|
1403
1794
|
description: feature || "API call",
|
|
1404
|
-
url
|
|
1795
|
+
url,
|
|
1796
|
+
requestId: crypto.randomUUID(),
|
|
1797
|
+
correlationId: this.extractCorrelationId(customHeaders)
|
|
1405
1798
|
};
|
|
1406
1799
|
this.payments.push(payment);
|
|
1407
1800
|
this.totalSpentUsd += amountUsd;
|
|
@@ -1600,7 +1993,8 @@ Timestamp: ${timestamp}`;
|
|
|
1600
1993
|
txHash: mixrpay.txHash,
|
|
1601
1994
|
timestamp: /* @__PURE__ */ new Date(),
|
|
1602
1995
|
description: `MCP: ${toolName}`,
|
|
1603
|
-
url: `${this.baseUrl}/api/mcp
|
|
1996
|
+
url: `${this.baseUrl}/api/mcp`,
|
|
1997
|
+
requestId: crypto.randomUUID()
|
|
1604
1998
|
};
|
|
1605
1999
|
this.payments.push(payment);
|
|
1606
2000
|
this.totalSpentUsd += mixrpay.chargedUsd;
|
|
@@ -1717,7 +2111,8 @@ Timestamp: ${timestamp}`;
|
|
|
1717
2111
|
txHash: data.tx_hash,
|
|
1718
2112
|
timestamp: /* @__PURE__ */ new Date(),
|
|
1719
2113
|
description: `Agent run: ${data.run_id}`,
|
|
1720
|
-
url: `${this.baseUrl}/api/v2/agent/run
|
|
2114
|
+
url: `${this.baseUrl}/api/v2/agent/run`,
|
|
2115
|
+
requestId: idempotencyKey || crypto.randomUUID()
|
|
1721
2116
|
};
|
|
1722
2117
|
this.payments.push(payment);
|
|
1723
2118
|
this.totalSpentUsd += data.cost.total_usd;
|
|
@@ -1809,7 +2204,8 @@ Timestamp: ${timestamp}`;
|
|
|
1809
2204
|
txHash: data.tx_hash,
|
|
1810
2205
|
timestamp: /* @__PURE__ */ new Date(),
|
|
1811
2206
|
description: `Agent run: ${data.run_id}`,
|
|
1812
|
-
url: `${this.baseUrl}/api/v2/agent/run
|
|
2207
|
+
url: `${this.baseUrl}/api/v2/agent/run`,
|
|
2208
|
+
requestId: body.idempotency_key || crypto.randomUUID()
|
|
1813
2209
|
};
|
|
1814
2210
|
this.payments.push(payment);
|
|
1815
2211
|
this.totalSpentUsd += data.total_cost_usd;
|
|
@@ -1985,7 +2381,8 @@ Timestamp: ${timestamp}`;
|
|
|
1985
2381
|
txHash: mixrpay.txHash,
|
|
1986
2382
|
timestamp: /* @__PURE__ */ new Date(),
|
|
1987
2383
|
description: `MCP: ${toolName}`,
|
|
1988
|
-
url: `${this.baseUrl}/api/mcp
|
|
2384
|
+
url: `${this.baseUrl}/api/mcp`,
|
|
2385
|
+
requestId: crypto.randomUUID()
|
|
1989
2386
|
};
|
|
1990
2387
|
this.payments.push(payment);
|
|
1991
2388
|
this.totalSpentUsd += mixrpay.chargedUsd;
|
|
@@ -2016,5 +2413,7 @@ Timestamp: ${timestamp}`;
|
|
|
2016
2413
|
SessionNotFoundError,
|
|
2017
2414
|
SessionRevokedError,
|
|
2018
2415
|
SpendingLimitExceededError,
|
|
2416
|
+
X402ProtocolError,
|
|
2417
|
+
getErrorMessage,
|
|
2019
2418
|
isMixrPayError
|
|
2020
2419
|
});
|