@atxp/client 0.9.0 → 0.9.2

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.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Account, Network, DestinationMaker, AuthorizationServerUrl, AccountId, Currency, OAuthDb, FetchLike, Logger, OAuthResourceClient, ClientCredentials, PKCEValues, AccessToken, PaymentRequestOption, Source, Destination } from '@atxp/common';
1
+ import { Currency, Account, Network, DestinationMaker, AuthorizationServerUrl, AccountId, OAuthDb, FetchLike, Logger, OAuthResourceClient, ClientCredentials, PKCEValues, AccessToken, PaymentRequestOption, Source, Destination } from '@atxp/common';
2
2
  export { ATXPAccount, Account, PaymentMaker } from '@atxp/common';
3
3
  import { ClientOptions, Client } from '@modelcontextprotocol/sdk/client/index.js';
4
4
  import { Implementation } from '@modelcontextprotocol/sdk/types.js';
@@ -6,6 +6,122 @@ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/
6
6
  import * as oauth from 'oauth4webapi';
7
7
  import { LocalAccount, Address, TypedData, Hex as Hex$1, SignableMessage, TransactionSerializable } from 'viem';
8
8
 
9
+ /**
10
+ * Base class for all ATXP payment errors with structured error codes and actionable guidance
11
+ */
12
+ declare abstract class ATXPPaymentError extends Error {
13
+ readonly context?: Record<string, unknown> | undefined;
14
+ abstract readonly code: string;
15
+ abstract readonly retryable: boolean;
16
+ abstract readonly actionableMessage: string;
17
+ constructor(message: string, context?: Record<string, unknown> | undefined);
18
+ }
19
+ /**
20
+ * Thrown when the user's wallet has insufficient funds for a payment
21
+ */
22
+ declare class InsufficientFundsError extends ATXPPaymentError {
23
+ readonly currency: Currency;
24
+ readonly required: BigNumber;
25
+ readonly available?: BigNumber | undefined;
26
+ readonly network?: string | undefined;
27
+ readonly code = "INSUFFICIENT_FUNDS";
28
+ readonly retryable = true;
29
+ readonly actionableMessage: string;
30
+ constructor(currency: Currency, required: BigNumber, available?: BigNumber | undefined, network?: string | undefined);
31
+ }
32
+ /**
33
+ * Thrown when a blockchain transaction is reverted
34
+ */
35
+ declare class TransactionRevertedError extends ATXPPaymentError {
36
+ readonly transactionHash: string;
37
+ readonly network: string;
38
+ readonly revertReason?: string | undefined;
39
+ readonly code = "TRANSACTION_REVERTED";
40
+ readonly retryable = false;
41
+ readonly actionableMessage: string;
42
+ constructor(transactionHash: string, network: string, revertReason?: string | undefined);
43
+ }
44
+ /**
45
+ * Thrown when an unsupported currency is requested
46
+ */
47
+ declare class UnsupportedCurrencyError extends ATXPPaymentError {
48
+ readonly currency: string;
49
+ readonly network: string;
50
+ readonly supportedCurrencies: string[];
51
+ readonly code = "UNSUPPORTED_CURRENCY";
52
+ readonly retryable = false;
53
+ readonly actionableMessage: string;
54
+ constructor(currency: string, network: string, supportedCurrencies: string[]);
55
+ }
56
+ /**
57
+ * Thrown when gas estimation fails for a transaction
58
+ */
59
+ declare class GasEstimationError extends ATXPPaymentError {
60
+ readonly network: string;
61
+ readonly reason?: string | undefined;
62
+ readonly code = "GAS_ESTIMATION_FAILED";
63
+ readonly retryable = true;
64
+ readonly actionableMessage = "Unable to estimate gas for this transaction. Ensure you have sufficient funds for both the payment amount and gas fees, then try again.";
65
+ constructor(network: string, reason?: string | undefined);
66
+ }
67
+ /**
68
+ * Thrown when RPC/network connectivity fails
69
+ */
70
+ declare class RpcError extends ATXPPaymentError {
71
+ readonly network: string;
72
+ readonly rpcUrl?: string | undefined;
73
+ readonly originalError?: Error | undefined;
74
+ readonly code = "RPC_ERROR";
75
+ readonly retryable = true;
76
+ readonly actionableMessage = "Unable to connect to the blockchain network. Please check your internet connection and try again.";
77
+ constructor(network: string, rpcUrl?: string | undefined, originalError?: Error | undefined);
78
+ }
79
+ /**
80
+ * Thrown when the user rejects a transaction in their wallet
81
+ */
82
+ declare class UserRejectedError extends ATXPPaymentError {
83
+ readonly network: string;
84
+ readonly code = "USER_REJECTED";
85
+ readonly retryable = true;
86
+ readonly actionableMessage = "You cancelled the transaction. To complete the payment, please approve the transaction in your wallet.";
87
+ constructor(network: string);
88
+ }
89
+ /**
90
+ * Thrown when the payment server returns an error
91
+ */
92
+ declare class PaymentServerError extends ATXPPaymentError {
93
+ readonly statusCode: number;
94
+ readonly endpoint: string;
95
+ readonly serverMessage?: string | undefined;
96
+ readonly details?: unknown | undefined;
97
+ readonly code: string;
98
+ readonly retryable = true;
99
+ readonly actionableMessage = "The payment server encountered an error. Please try again in a few moments.";
100
+ constructor(statusCode: number, endpoint: string, serverMessage?: string | undefined, errorCode?: string, details?: unknown | undefined);
101
+ }
102
+ /**
103
+ * Thrown when a payment request has expired
104
+ */
105
+ declare class PaymentExpiredError extends ATXPPaymentError {
106
+ readonly paymentRequestId: string;
107
+ readonly expiresAt?: Date | undefined;
108
+ readonly code = "PAYMENT_EXPIRED";
109
+ readonly retryable = false;
110
+ readonly actionableMessage = "This payment request has expired. Please make a new request to the service.";
111
+ constructor(paymentRequestId: string, expiresAt?: Date | undefined);
112
+ }
113
+ /**
114
+ * Generic network error for backward compatibility and uncategorized errors
115
+ */
116
+ declare class PaymentNetworkError extends ATXPPaymentError {
117
+ readonly network: string;
118
+ readonly originalError?: Error | undefined;
119
+ readonly code = "NETWORK_ERROR";
120
+ readonly retryable = true;
121
+ readonly actionableMessage = "A network error occurred during payment processing. Please try again.";
122
+ constructor(network: string, message: string, originalError?: Error | undefined);
123
+ }
124
+
9
125
  type Hex = `0x${string}`;
10
126
  type AccountPrefix = Network;
11
127
  type AccountIdString = `${AccountPrefix}${string}`;
@@ -17,6 +133,23 @@ type ProspectivePayment = {
17
133
  amount: BigNumber;
18
134
  iss: string;
19
135
  };
136
+ /**
137
+ * Rich context provided when a payment fails
138
+ */
139
+ interface PaymentFailureContext {
140
+ /** The payment that failed */
141
+ payment: ProspectivePayment;
142
+ /** The error that caused the failure */
143
+ error: Error;
144
+ /** Networks that were attempted for payment */
145
+ attemptedNetworks: string[];
146
+ /** Map of network to error for each failed attempt */
147
+ failureReasons: Map<string, Error>;
148
+ /** Whether the payment can be retried */
149
+ retryable: boolean;
150
+ /** Timestamp when the failure occurred */
151
+ timestamp: Date;
152
+ }
20
153
  type ClientConfig = {
21
154
  mcpServer: string;
22
155
  account: Account;
@@ -42,10 +175,15 @@ type ClientConfig = {
42
175
  }) => Promise<void>;
43
176
  onPayment: (args: {
44
177
  payment: ProspectivePayment;
178
+ transactionHash: string;
179
+ network: string;
45
180
  }) => Promise<void>;
46
- onPaymentFailure: (args: {
47
- payment: ProspectivePayment;
181
+ onPaymentFailure: (context: PaymentFailureContext) => Promise<void>;
182
+ /** Optional callback when a single payment attempt fails (before trying other networks) */
183
+ onPaymentAttemptFailed?: (args: {
184
+ network: string;
48
185
  error: Error;
186
+ remainingNetworks: string[];
49
187
  }) => Promise<void>;
50
188
  };
51
189
  type RequiredClientConfigFields$1 = 'mcpServer' | 'account';
@@ -53,17 +191,6 @@ type RequiredClientConfig = Pick<ClientConfig, RequiredClientConfigFields$1>;
53
191
  type OptionalClientConfig$1 = Omit<ClientConfig, RequiredClientConfigFields$1>;
54
192
  type ClientArgs = RequiredClientConfig & Partial<OptionalClientConfig$1>;
55
193
  type FetchWrapper = (config: ClientArgs) => FetchLike;
56
- declare class InsufficientFundsError extends Error {
57
- readonly currency: Currency;
58
- readonly required: BigNumber;
59
- readonly available?: BigNumber | undefined;
60
- readonly network?: string | undefined;
61
- constructor(currency: Currency, required: BigNumber, available?: BigNumber | undefined, network?: string | undefined);
62
- }
63
- declare class PaymentNetworkError extends Error {
64
- readonly originalError?: Error | undefined;
65
- constructor(message: string, originalError?: Error | undefined);
66
- }
67
194
 
68
195
  type RequiredClientConfigFields = 'mcpServer' | 'account';
69
196
  type OptionalClientConfig = Omit<ClientConfig, RequiredClientConfigFields>;
@@ -296,5 +423,5 @@ declare class PassthroughDestinationMaker implements DestinationMaker {
296
423
  makeDestinations(option: PaymentRequestOption, _logger: Logger, _paymentRequestId: string, _sources: Source[]): Promise<Destination[]>;
297
424
  }
298
425
 
299
- export { ATXPDestinationMaker, ATXPLocalAccount, DEFAULT_CLIENT_CONFIG, InsufficientFundsError, OAuthAuthenticationRequiredError, OAuthClient, POLYGON_AMOY, POLYGON_MAINNET, PassthroughDestinationMaker, PaymentNetworkError, USDC_CONTRACT_ADDRESS_POLYGON_AMOY, USDC_CONTRACT_ADDRESS_POLYGON_MAINNET, USDC_CONTRACT_ADDRESS_WORLD_MAINNET, USDC_CONTRACT_ADDRESS_WORLD_SEPOLIA, WORLD_CHAIN_MAINNET, WORLD_CHAIN_SEPOLIA, atxpClient, atxpFetch, buildClientConfig, buildStreamableTransport, getPolygonAmoyWithRPC, getPolygonByChainId, getPolygonMainnetWithRPC, getPolygonUSDCAddress, getWorldChainByChainId, getWorldChainMainnetWithRPC, getWorldChainSepoliaWithRPC, getWorldChainUSDCAddress };
300
- export type { AccountIdString, ClientArgs, ClientConfig, FetchWrapper, Hex, OAuthClientConfig, PolygonChain, ProspectivePayment, WorldChain };
426
+ export { ATXPDestinationMaker, ATXPLocalAccount, ATXPPaymentError, DEFAULT_CLIENT_CONFIG, GasEstimationError, InsufficientFundsError, OAuthAuthenticationRequiredError, OAuthClient, POLYGON_AMOY, POLYGON_MAINNET, PassthroughDestinationMaker, PaymentExpiredError, PaymentNetworkError, PaymentServerError, RpcError, TransactionRevertedError, USDC_CONTRACT_ADDRESS_POLYGON_AMOY, USDC_CONTRACT_ADDRESS_POLYGON_MAINNET, USDC_CONTRACT_ADDRESS_WORLD_MAINNET, USDC_CONTRACT_ADDRESS_WORLD_SEPOLIA, UnsupportedCurrencyError, UserRejectedError, WORLD_CHAIN_MAINNET, WORLD_CHAIN_SEPOLIA, atxpClient, atxpFetch, buildClientConfig, buildStreamableTransport, getPolygonAmoyWithRPC, getPolygonByChainId, getPolygonMainnetWithRPC, getPolygonUSDCAddress, getWorldChainByChainId, getWorldChainMainnetWithRPC, getWorldChainSepoliaWithRPC, getWorldChainUSDCAddress };
427
+ export type { AccountIdString, ClientArgs, ClientConfig, FetchWrapper, Hex, OAuthClientConfig, PaymentFailureContext, PolygonChain, ProspectivePayment, WorldChain };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,wBAAwB,EACxB,UAAU,EACX,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,gCAAgC,EAChC,KAAK,iBAAiB,EACtB,WAAW,EACZ,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,SAAS,EACV,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,WAAW,EACZ,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,mCAAmC,EACnC,mCAAmC,EACnC,mBAAmB,EACnB,mBAAmB,EACnB,2BAA2B,EAC3B,2BAA2B,EAC3B,sBAAsB,EACtB,wBAAwB,EACxB,KAAK,UAAU,EAChB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EACL,qCAAqC,EACrC,kCAAkC,EAClC,eAAe,EACf,YAAY,EACZ,wBAAwB,EACxB,qBAAqB,EACrB,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,YAAY,EAClB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EACL,KAAK,GAAG,EACR,KAAK,eAAe,EACpB,KAAK,OAAO,EACZ,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,sBAAsB,EACtB,mBAAmB,EACnB,KAAK,YAAY,EAClB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,gBAAgB,EACjB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EACL,oBAAoB,EACpB,2BAA2B,GAC5B,MAAM,8BAA8B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,wBAAwB,EACxB,UAAU,EACX,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,gCAAgC,EAChC,KAAK,iBAAiB,EACtB,WAAW,EACZ,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,SAAS,EACV,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,WAAW,EACZ,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,mCAAmC,EACnC,mCAAmC,EACnC,mBAAmB,EACnB,mBAAmB,EACnB,2BAA2B,EAC3B,2BAA2B,EAC3B,sBAAsB,EACtB,wBAAwB,EACxB,KAAK,UAAU,EAChB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EACL,qCAAqC,EACrC,kCAAkC,EAClC,eAAe,EACf,YAAY,EACZ,wBAAwB,EACxB,qBAAqB,EACrB,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,YAAY,EAClB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EACL,KAAK,GAAG,EACR,KAAK,eAAe,EACpB,KAAK,OAAO,EACZ,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,YAAY,EAClB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,wBAAwB,EACxB,wBAAwB,EACxB,kBAAkB,EAClB,QAAQ,EACR,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,gBAAgB,EACjB,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EACL,oBAAoB,EACpB,2BAA2B,GAC5B,MAAM,8BAA8B,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { crypto as crypto$1, OAuthResourceClient, ConsoleLogger, PAYMENT_REQUIRED_ERROR_CODE, isSSEResponse, parseMcpMessages, parsePaymentRequests, paymentRequiredError, DEFAULT_AUTHORIZATION_SERVER, getIsReactNative, createReactNativeSafeFetch, isEnumValue, ChainEnum, CurrencyEnum, NetworkEnum, assertNever, DEFAULT_ATXP_ACCOUNTS_SERVER, MemoryOAuthDb, ATXPAccount } from '@atxp/common';
1
+ import { crypto as crypto$1, OAuthResourceClient, ConsoleLogger, getErrorRecoveryHint, PAYMENT_REQUIRED_ERROR_CODE, isSSEResponse, parseMcpMessages, parsePaymentRequests, paymentRequiredError, DEFAULT_AUTHORIZATION_SERVER, getIsReactNative, createReactNativeSafeFetch, isEnumValue, ChainEnum, CurrencyEnum, NetworkEnum, assertNever, DEFAULT_ATXP_ACCOUNTS_SERVER, MemoryOAuthDb, ATXPAccount } from '@atxp/common';
2
2
  export { ATXPAccount } from '@atxp/common';
3
3
  import * as oauth from 'oauth4webapi';
4
4
  import { BigNumber } from 'bignumber.js';
@@ -275,25 +275,153 @@ class OAuthClient extends OAuthResourceClient {
275
275
  }
276
276
  }
277
277
 
278
- class InsufficientFundsError extends Error {
278
+ /**
279
+ * Base class for all ATXP payment errors with structured error codes and actionable guidance
280
+ */
281
+ class ATXPPaymentError extends Error {
282
+ constructor(message, context) {
283
+ super(message);
284
+ this.context = context;
285
+ this.name = this.constructor.name;
286
+ }
287
+ }
288
+ /**
289
+ * Thrown when the user's wallet has insufficient funds for a payment
290
+ */
291
+ class InsufficientFundsError extends ATXPPaymentError {
279
292
  constructor(currency, required, available, network) {
293
+ const shortfall = available ? required.minus(available).toString() : required.toString();
280
294
  const availableText = available ? `, Available: ${available}` : '';
281
295
  const networkText = network ? ` on ${network}` : '';
282
296
  super(`Payment failed due to insufficient ${currency} funds${networkText}. ` +
283
297
  `Required: ${required}${availableText}. ` +
284
- `Please ensure your account has adequate balance before retrying.`);
298
+ `Please ensure your account has adequate balance before retrying.`, { currency, required: required.toString(), available: available?.toString(), network, shortfall });
285
299
  this.currency = currency;
286
300
  this.required = required;
287
301
  this.available = available;
288
302
  this.network = network;
289
- this.name = 'InsufficientFundsError';
303
+ this.code = 'INSUFFICIENT_FUNDS';
304
+ this.retryable = true;
305
+ this.actionableMessage = available
306
+ ? `Add at least ${shortfall} ${currency} to your ${network} wallet and try again.`
307
+ : `Ensure your ${network} wallet has at least ${required} ${currency} and try again.`;
308
+ }
309
+ }
310
+ /**
311
+ * Thrown when a blockchain transaction is reverted
312
+ */
313
+ class TransactionRevertedError extends ATXPPaymentError {
314
+ constructor(transactionHash, network, revertReason) {
315
+ super(`Transaction ${transactionHash} reverted on ${network}${revertReason ? `: ${revertReason}` : ''}`, { transactionHash, network, revertReason });
316
+ this.transactionHash = transactionHash;
317
+ this.network = network;
318
+ this.revertReason = revertReason;
319
+ this.code = 'TRANSACTION_REVERTED';
320
+ this.retryable = false;
321
+ // Provide specific guidance based on revert reason
322
+ if (revertReason?.toLowerCase().includes('allowance')) {
323
+ this.actionableMessage = 'Approve token spending before making the payment. You may need to increase the token allowance.';
324
+ }
325
+ else if (revertReason?.toLowerCase().includes('balance')) {
326
+ this.actionableMessage = 'Ensure your wallet has sufficient token balance and native token for gas fees.';
327
+ }
328
+ else {
329
+ this.actionableMessage = 'The transaction was rejected by the blockchain. Check the transaction details on a block explorer and verify your wallet settings.';
330
+ }
331
+ }
332
+ }
333
+ /**
334
+ * Thrown when an unsupported currency is requested
335
+ */
336
+ class UnsupportedCurrencyError extends ATXPPaymentError {
337
+ constructor(currency, network, supportedCurrencies) {
338
+ super(`Currency ${currency} is not supported on ${network}`, { currency, network, supportedCurrencies });
339
+ this.currency = currency;
340
+ this.network = network;
341
+ this.supportedCurrencies = supportedCurrencies;
342
+ this.code = 'UNSUPPORTED_CURRENCY';
343
+ this.retryable = false;
344
+ this.actionableMessage = `Please use one of the supported currencies: ${supportedCurrencies.join(', ')}`;
345
+ }
346
+ }
347
+ /**
348
+ * Thrown when gas estimation fails for a transaction
349
+ */
350
+ class GasEstimationError extends ATXPPaymentError {
351
+ constructor(network, reason) {
352
+ super(`Failed to estimate gas on ${network}${reason ? `: ${reason}` : ''}`, { network, reason });
353
+ this.network = network;
354
+ this.reason = reason;
355
+ this.code = 'GAS_ESTIMATION_FAILED';
356
+ this.retryable = true;
357
+ this.actionableMessage = 'Unable to estimate gas for this transaction. Ensure you have sufficient funds for both the payment amount and gas fees, then try again.';
358
+ }
359
+ }
360
+ /**
361
+ * Thrown when RPC/network connectivity fails
362
+ */
363
+ class RpcError extends ATXPPaymentError {
364
+ constructor(network, rpcUrl, originalError) {
365
+ super(`RPC call failed on ${network}${rpcUrl ? ` (${rpcUrl})` : ''}`, { network, rpcUrl, originalError: originalError?.message });
366
+ this.network = network;
367
+ this.rpcUrl = rpcUrl;
368
+ this.originalError = originalError;
369
+ this.code = 'RPC_ERROR';
370
+ this.retryable = true;
371
+ this.actionableMessage = 'Unable to connect to the blockchain network. Please check your internet connection and try again.';
372
+ }
373
+ }
374
+ /**
375
+ * Thrown when the user rejects a transaction in their wallet
376
+ */
377
+ class UserRejectedError extends ATXPPaymentError {
378
+ constructor(network) {
379
+ super(`User rejected transaction on ${network}`, { network });
380
+ this.network = network;
381
+ this.code = 'USER_REJECTED';
382
+ this.retryable = true;
383
+ this.actionableMessage = 'You cancelled the transaction. To complete the payment, please approve the transaction in your wallet.';
384
+ }
385
+ }
386
+ /**
387
+ * Thrown when the payment server returns an error
388
+ */
389
+ class PaymentServerError extends ATXPPaymentError {
390
+ constructor(statusCode, endpoint, serverMessage, errorCode, details) {
391
+ super(`Payment server returned ${statusCode} from ${endpoint}${serverMessage ? `: ${serverMessage}` : ''}`, { statusCode, endpoint, serverMessage, errorCode, details });
392
+ this.statusCode = statusCode;
393
+ this.endpoint = endpoint;
394
+ this.serverMessage = serverMessage;
395
+ this.details = details;
396
+ this.retryable = true;
397
+ this.actionableMessage = 'The payment server encountered an error. Please try again in a few moments.';
398
+ this.code = errorCode || 'PAYMENT_SERVER_ERROR';
290
399
  }
291
400
  }
292
- class PaymentNetworkError extends Error {
293
- constructor(message, originalError) {
294
- super(`Payment failed due to network error: ${message}`);
401
+ /**
402
+ * Thrown when a payment request has expired
403
+ */
404
+ class PaymentExpiredError extends ATXPPaymentError {
405
+ constructor(paymentRequestId, expiresAt) {
406
+ super(`Payment request ${paymentRequestId} has expired`, { paymentRequestId, expiresAt: expiresAt?.toISOString() });
407
+ this.paymentRequestId = paymentRequestId;
408
+ this.expiresAt = expiresAt;
409
+ this.code = 'PAYMENT_EXPIRED';
410
+ this.retryable = false;
411
+ this.actionableMessage = 'This payment request has expired. Please make a new request to the service.';
412
+ }
413
+ }
414
+ /**
415
+ * Generic network error for backward compatibility and uncategorized errors
416
+ */
417
+ class PaymentNetworkError extends ATXPPaymentError {
418
+ constructor(network, message, originalError) {
419
+ super(`Payment failed on ${network} network: ${message}`, { network, originalError: originalError?.message });
420
+ this.network = network;
295
421
  this.originalError = originalError;
296
- this.name = 'PaymentNetworkError';
422
+ this.code = 'NETWORK_ERROR';
423
+ this.retryable = true;
424
+ this.actionableMessage = 'A network error occurred during payment processing. Please try again.';
297
425
  }
298
426
  }
299
427
 
@@ -318,27 +446,41 @@ function atxpFetch(config) {
318
446
  onAuthorize: config.onAuthorize,
319
447
  onAuthorizeFailure: config.onAuthorizeFailure,
320
448
  onPayment: config.onPayment,
321
- onPaymentFailure: config.onPaymentFailure
449
+ onPaymentFailure: config.onPaymentFailure,
450
+ onPaymentAttemptFailed: config.onPaymentAttemptFailed
322
451
  });
323
452
  return fetcher.fetch;
324
453
  }
325
454
  class ATXPFetcher {
326
455
  constructor(config) {
327
- this.defaultPaymentFailureHandler = async ({ payment, error }) => {
456
+ this.defaultPaymentFailureHandler = async (context) => {
457
+ const { payment, error, attemptedNetworks, retryable } = context;
458
+ const recoveryHint = getErrorRecoveryHint(error);
459
+ this.logger.info(`PAYMENT FAILED: ${recoveryHint.title}`);
460
+ this.logger.info(`Description: ${recoveryHint.description}`);
461
+ if (attemptedNetworks.length > 0) {
462
+ this.logger.info(`Attempted networks: ${attemptedNetworks.join(', ')}`);
463
+ }
464
+ this.logger.info(`Account: ${payment.accountId}`);
465
+ // Log actionable guidance
466
+ if (recoveryHint.actions.length > 0) {
467
+ this.logger.info(`What to do:`);
468
+ recoveryHint.actions.forEach((action, index) => {
469
+ this.logger.info(` ${index + 1}. ${action}`);
470
+ });
471
+ }
472
+ if (retryable) {
473
+ this.logger.info(`This payment can be retried.`);
474
+ }
475
+ // Log additional context for specific error types
328
476
  if (error instanceof InsufficientFundsError) {
329
- const networkText = error.network ? ` on ${error.network}` : '';
330
- this.logger.info(`PAYMENT FAILED: Insufficient ${error.currency} funds${networkText}`);
331
477
  this.logger.info(`Required: ${error.required} ${error.currency}`);
332
478
  if (error.available) {
333
479
  this.logger.info(`Available: ${error.available} ${error.currency}`);
334
480
  }
335
- this.logger.info(`Account: ${payment.accountId}`);
336
481
  }
337
- else if (error instanceof PaymentNetworkError) {
338
- this.logger.info(`PAYMENT FAILED: Network error: ${error.message}`);
339
- }
340
- else {
341
- this.logger.info(`PAYMENT FAILED: ${error.message}`);
482
+ else if (error instanceof ATXPPaymentError && error.context) {
483
+ this.logger.debug(`Error context: ${JSON.stringify(error.context)}`);
342
484
  }
343
485
  };
344
486
  this.handleMultiDestinationPayment = async (paymentRequest, paymentRequestUrl, paymentRequestId) => {
@@ -383,9 +525,11 @@ class ATXPFetcher {
383
525
  this.logger.info(`ATXP: payment request denied by callback function`);
384
526
  return false;
385
527
  }
386
- // Try each payment maker in order
528
+ // Try each payment maker in order, tracking attempts
387
529
  let lastPaymentError = null;
388
530
  let paymentAttempted = false;
531
+ const attemptedNetworks = [];
532
+ const failureReasons = new Map();
389
533
  for (const paymentMaker of this.account.paymentMakers) {
390
534
  try {
391
535
  // Pass all destinations to payment maker - it will filter and pick the one it can handle
@@ -397,7 +541,11 @@ class ATXPFetcher {
397
541
  paymentAttempted = true;
398
542
  // Payment was successful
399
543
  this.logger.info(`ATXP: made payment of ${firstDest.amount.toString()} ${firstDest.currency} on ${result.chain}: ${result.transactionId}`);
400
- await this.onPayment({ payment: prospectivePayment });
544
+ await this.onPayment({
545
+ payment: prospectivePayment,
546
+ transactionHash: result.transactionId,
547
+ network: result.chain
548
+ });
401
549
  // Submit payment to the server
402
550
  const jwt = await paymentMaker.generateJWT({ paymentRequestId, codeChallenge: '', accountId: this.account.accountId });
403
551
  const response = await this.sideChannelFetch(paymentRequestUrl.toString(), {
@@ -425,13 +573,41 @@ class ATXPFetcher {
425
573
  const typedError = error;
426
574
  paymentAttempted = true;
427
575
  lastPaymentError = typedError;
428
- this.logger.warn(`ATXP: payment maker failed: ${typedError.message}`);
429
- await this.onPaymentFailure({ payment: prospectivePayment, error: typedError });
576
+ // Extract network from error context if available
577
+ let network = 'unknown';
578
+ if (typedError instanceof ATXPPaymentError && typedError.context?.network) {
579
+ network = typeof typedError.context.network === 'string' ? typedError.context.network : 'unknown';
580
+ }
581
+ attemptedNetworks.push(network);
582
+ failureReasons.set(network, typedError);
583
+ this.logger.warn(`ATXP: payment maker failed on ${network}: ${typedError.message}`);
584
+ // Call optional per-attempt failure callback
585
+ if (this.onPaymentAttemptFailed) {
586
+ const remainingMakers = this.account.paymentMakers.length - (attemptedNetworks.length);
587
+ const remainingNetworks = remainingMakers > 0 ? ['next available'] : [];
588
+ await this.onPaymentAttemptFailed({
589
+ network,
590
+ error: typedError,
591
+ remainingNetworks
592
+ });
593
+ }
430
594
  // Continue to next payment maker
431
595
  }
432
596
  }
433
- // If payment was attempted but all failed, rethrow the last error
597
+ // If payment was attempted but all failed, create full context and call onPaymentFailure
434
598
  if (paymentAttempted && lastPaymentError) {
599
+ const isRetryable = lastPaymentError instanceof ATXPPaymentError
600
+ ? lastPaymentError.retryable
601
+ : true; // Default to retryable for unknown errors
602
+ const failureContext = {
603
+ payment: prospectivePayment,
604
+ error: lastPaymentError,
605
+ attemptedNetworks,
606
+ failureReasons,
607
+ retryable: isRetryable,
608
+ timestamp: new Date()
609
+ };
610
+ await this.onPaymentFailure(failureContext);
435
611
  throw lastPaymentError;
436
612
  }
437
613
  this.logger.info(`ATXP: no payment maker could handle these destinations`);
@@ -679,7 +855,7 @@ class ATXPFetcher {
679
855
  throw error;
680
856
  }
681
857
  };
682
- const { account, db, destinationMakers, fetchFn = fetch, sideChannelFetch = fetchFn, strict = true, allowInsecureRequests = process.env.NODE_ENV === 'development', allowedAuthorizationServers = [DEFAULT_AUTHORIZATION_SERVER], approvePayment = async () => true, logger = new ConsoleLogger(), onAuthorize = async () => { }, onAuthorizeFailure = async () => { }, onPayment = async () => { }, onPaymentFailure = async () => { } } = config;
858
+ const { account, db, destinationMakers, fetchFn = fetch, sideChannelFetch = fetchFn, strict = true, allowInsecureRequests = process.env.NODE_ENV === 'development', allowedAuthorizationServers = [DEFAULT_AUTHORIZATION_SERVER], approvePayment = async () => true, logger = new ConsoleLogger(), onAuthorize = async () => { }, onAuthorizeFailure = async () => { }, onPayment = async () => { }, onPaymentFailure, onPaymentAttemptFailed } = config;
683
859
  // Use React Native safe fetch if in React Native environment
684
860
  const safeFetchFn = getIsReactNative() ? createReactNativeSafeFetch(fetchFn) : fetchFn;
685
861
  const safeSideChannelFetch = getIsReactNative() ? createReactNativeSafeFetch(sideChannelFetch) : sideChannelFetch;
@@ -708,6 +884,7 @@ class ATXPFetcher {
708
884
  this.onAuthorizeFailure = onAuthorizeFailure;
709
885
  this.onPayment = onPayment;
710
886
  this.onPaymentFailure = onPaymentFailure || this.defaultPaymentFailureHandler;
887
+ this.onPaymentAttemptFailed = onPaymentAttemptFailed;
711
888
  }
712
889
  }
713
890
 
@@ -16004,7 +16181,8 @@ const DEFAULT_CLIENT_CONFIG = {
16004
16181
  onAuthorize: async () => { },
16005
16182
  onAuthorizeFailure: async () => { },
16006
16183
  onPayment: async () => { },
16007
- onPaymentFailure: async () => { }
16184
+ onPaymentFailure: async () => { },
16185
+ onPaymentAttemptFailed: async () => { }
16008
16186
  };
16009
16187
  function buildClientConfig(args) {
16010
16188
  // Use fetchFn for oAuthChannelFetch if the latter isn't explicitly set
@@ -16312,5 +16490,5 @@ class ATXPLocalAccount {
16312
16490
  }
16313
16491
  }
16314
16492
 
16315
- export { ATXPDestinationMaker, ATXPLocalAccount, DEFAULT_CLIENT_CONFIG, InsufficientFundsError, OAuthAuthenticationRequiredError, OAuthClient, POLYGON_AMOY, POLYGON_MAINNET, PassthroughDestinationMaker, PaymentNetworkError, USDC_CONTRACT_ADDRESS_POLYGON_AMOY, USDC_CONTRACT_ADDRESS_POLYGON_MAINNET, USDC_CONTRACT_ADDRESS_WORLD_MAINNET, USDC_CONTRACT_ADDRESS_WORLD_SEPOLIA, WORLD_CHAIN_MAINNET, WORLD_CHAIN_SEPOLIA, atxpClient, atxpFetch, buildClientConfig, buildStreamableTransport, getPolygonAmoyWithRPC, getPolygonByChainId, getPolygonMainnetWithRPC, getPolygonUSDCAddress, getWorldChainByChainId, getWorldChainMainnetWithRPC, getWorldChainSepoliaWithRPC, getWorldChainUSDCAddress };
16493
+ export { ATXPDestinationMaker, ATXPLocalAccount, ATXPPaymentError, DEFAULT_CLIENT_CONFIG, GasEstimationError, InsufficientFundsError, OAuthAuthenticationRequiredError, OAuthClient, POLYGON_AMOY, POLYGON_MAINNET, PassthroughDestinationMaker, PaymentExpiredError, PaymentNetworkError, PaymentServerError, RpcError, TransactionRevertedError, USDC_CONTRACT_ADDRESS_POLYGON_AMOY, USDC_CONTRACT_ADDRESS_POLYGON_MAINNET, USDC_CONTRACT_ADDRESS_WORLD_MAINNET, USDC_CONTRACT_ADDRESS_WORLD_SEPOLIA, UnsupportedCurrencyError, UserRejectedError, WORLD_CHAIN_MAINNET, WORLD_CHAIN_SEPOLIA, atxpClient, atxpFetch, buildClientConfig, buildStreamableTransport, getPolygonAmoyWithRPC, getPolygonByChainId, getPolygonMainnetWithRPC, getPolygonUSDCAddress, getWorldChainByChainId, getWorldChainMainnetWithRPC, getWorldChainSepoliaWithRPC, getWorldChainUSDCAddress };
16316
16494
  //# sourceMappingURL=index.js.map