@atxp/client 0.9.0 → 0.9.1
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/atxpClient.d.ts.map +1 -1
- package/dist/atxpClient.js +2 -1
- package/dist/atxpClient.js.map +1 -1
- package/dist/atxpFetcher.d.ts +13 -5
- package/dist/atxpFetcher.d.ts.map +1 -1
- package/dist/atxpFetcher.js +67 -18
- package/dist/atxpFetcher.js.map +1 -1
- package/dist/errors.d.ts +117 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +152 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.cjs +210 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +143 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +204 -26
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +25 -13
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/types.js +0 -24
- package/dist/types.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -295,25 +295,153 @@ class OAuthClient extends common.OAuthResourceClient {
|
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
-
|
|
298
|
+
/**
|
|
299
|
+
* Base class for all ATXP payment errors with structured error codes and actionable guidance
|
|
300
|
+
*/
|
|
301
|
+
class ATXPPaymentError extends Error {
|
|
302
|
+
constructor(message, context) {
|
|
303
|
+
super(message);
|
|
304
|
+
this.context = context;
|
|
305
|
+
this.name = this.constructor.name;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Thrown when the user's wallet has insufficient funds for a payment
|
|
310
|
+
*/
|
|
311
|
+
class InsufficientFundsError extends ATXPPaymentError {
|
|
299
312
|
constructor(currency, required, available, network) {
|
|
313
|
+
const shortfall = available ? required.minus(available).toString() : required.toString();
|
|
300
314
|
const availableText = available ? `, Available: ${available}` : '';
|
|
301
315
|
const networkText = network ? ` on ${network}` : '';
|
|
302
316
|
super(`Payment failed due to insufficient ${currency} funds${networkText}. ` +
|
|
303
317
|
`Required: ${required}${availableText}. ` +
|
|
304
|
-
`Please ensure your account has adequate balance before retrying
|
|
318
|
+
`Please ensure your account has adequate balance before retrying.`, { currency, required: required.toString(), available: available?.toString(), network, shortfall });
|
|
305
319
|
this.currency = currency;
|
|
306
320
|
this.required = required;
|
|
307
321
|
this.available = available;
|
|
308
322
|
this.network = network;
|
|
309
|
-
this.
|
|
323
|
+
this.code = 'INSUFFICIENT_FUNDS';
|
|
324
|
+
this.retryable = true;
|
|
325
|
+
this.actionableMessage = available
|
|
326
|
+
? `Add at least ${shortfall} ${currency} to your ${network} wallet and try again.`
|
|
327
|
+
: `Ensure your ${network} wallet has at least ${required} ${currency} and try again.`;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Thrown when a blockchain transaction is reverted
|
|
332
|
+
*/
|
|
333
|
+
class TransactionRevertedError extends ATXPPaymentError {
|
|
334
|
+
constructor(transactionHash, network, revertReason) {
|
|
335
|
+
super(`Transaction ${transactionHash} reverted on ${network}${revertReason ? `: ${revertReason}` : ''}`, { transactionHash, network, revertReason });
|
|
336
|
+
this.transactionHash = transactionHash;
|
|
337
|
+
this.network = network;
|
|
338
|
+
this.revertReason = revertReason;
|
|
339
|
+
this.code = 'TRANSACTION_REVERTED';
|
|
340
|
+
this.retryable = false;
|
|
341
|
+
// Provide specific guidance based on revert reason
|
|
342
|
+
if (revertReason?.toLowerCase().includes('allowance')) {
|
|
343
|
+
this.actionableMessage = 'Approve token spending before making the payment. You may need to increase the token allowance.';
|
|
344
|
+
}
|
|
345
|
+
else if (revertReason?.toLowerCase().includes('balance')) {
|
|
346
|
+
this.actionableMessage = 'Ensure your wallet has sufficient token balance and native token for gas fees.';
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
this.actionableMessage = 'The transaction was rejected by the blockchain. Check the transaction details on a block explorer and verify your wallet settings.';
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Thrown when an unsupported currency is requested
|
|
355
|
+
*/
|
|
356
|
+
class UnsupportedCurrencyError extends ATXPPaymentError {
|
|
357
|
+
constructor(currency, network, supportedCurrencies) {
|
|
358
|
+
super(`Currency ${currency} is not supported on ${network}`, { currency, network, supportedCurrencies });
|
|
359
|
+
this.currency = currency;
|
|
360
|
+
this.network = network;
|
|
361
|
+
this.supportedCurrencies = supportedCurrencies;
|
|
362
|
+
this.code = 'UNSUPPORTED_CURRENCY';
|
|
363
|
+
this.retryable = false;
|
|
364
|
+
this.actionableMessage = `Please use one of the supported currencies: ${supportedCurrencies.join(', ')}`;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Thrown when gas estimation fails for a transaction
|
|
369
|
+
*/
|
|
370
|
+
class GasEstimationError extends ATXPPaymentError {
|
|
371
|
+
constructor(network, reason) {
|
|
372
|
+
super(`Failed to estimate gas on ${network}${reason ? `: ${reason}` : ''}`, { network, reason });
|
|
373
|
+
this.network = network;
|
|
374
|
+
this.reason = reason;
|
|
375
|
+
this.code = 'GAS_ESTIMATION_FAILED';
|
|
376
|
+
this.retryable = true;
|
|
377
|
+
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.';
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Thrown when RPC/network connectivity fails
|
|
382
|
+
*/
|
|
383
|
+
class RpcError extends ATXPPaymentError {
|
|
384
|
+
constructor(network, rpcUrl, originalError) {
|
|
385
|
+
super(`RPC call failed on ${network}${rpcUrl ? ` (${rpcUrl})` : ''}`, { network, rpcUrl, originalError: originalError?.message });
|
|
386
|
+
this.network = network;
|
|
387
|
+
this.rpcUrl = rpcUrl;
|
|
388
|
+
this.originalError = originalError;
|
|
389
|
+
this.code = 'RPC_ERROR';
|
|
390
|
+
this.retryable = true;
|
|
391
|
+
this.actionableMessage = 'Unable to connect to the blockchain network. Please check your internet connection and try again.';
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Thrown when the user rejects a transaction in their wallet
|
|
396
|
+
*/
|
|
397
|
+
class UserRejectedError extends ATXPPaymentError {
|
|
398
|
+
constructor(network) {
|
|
399
|
+
super(`User rejected transaction on ${network}`, { network });
|
|
400
|
+
this.network = network;
|
|
401
|
+
this.code = 'USER_REJECTED';
|
|
402
|
+
this.retryable = true;
|
|
403
|
+
this.actionableMessage = 'You cancelled the transaction. To complete the payment, please approve the transaction in your wallet.';
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Thrown when the payment server returns an error
|
|
408
|
+
*/
|
|
409
|
+
class PaymentServerError extends ATXPPaymentError {
|
|
410
|
+
constructor(statusCode, endpoint, serverMessage, errorCode, details) {
|
|
411
|
+
super(`Payment server returned ${statusCode} from ${endpoint}${serverMessage ? `: ${serverMessage}` : ''}`, { statusCode, endpoint, serverMessage, errorCode, details });
|
|
412
|
+
this.statusCode = statusCode;
|
|
413
|
+
this.endpoint = endpoint;
|
|
414
|
+
this.serverMessage = serverMessage;
|
|
415
|
+
this.details = details;
|
|
416
|
+
this.retryable = true;
|
|
417
|
+
this.actionableMessage = 'The payment server encountered an error. Please try again in a few moments.';
|
|
418
|
+
this.code = errorCode || 'PAYMENT_SERVER_ERROR';
|
|
310
419
|
}
|
|
311
420
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
421
|
+
/**
|
|
422
|
+
* Thrown when a payment request has expired
|
|
423
|
+
*/
|
|
424
|
+
class PaymentExpiredError extends ATXPPaymentError {
|
|
425
|
+
constructor(paymentRequestId, expiresAt) {
|
|
426
|
+
super(`Payment request ${paymentRequestId} has expired`, { paymentRequestId, expiresAt: expiresAt?.toISOString() });
|
|
427
|
+
this.paymentRequestId = paymentRequestId;
|
|
428
|
+
this.expiresAt = expiresAt;
|
|
429
|
+
this.code = 'PAYMENT_EXPIRED';
|
|
430
|
+
this.retryable = false;
|
|
431
|
+
this.actionableMessage = 'This payment request has expired. Please make a new request to the service.';
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Generic network error for backward compatibility and uncategorized errors
|
|
436
|
+
*/
|
|
437
|
+
class PaymentNetworkError extends ATXPPaymentError {
|
|
438
|
+
constructor(network, message, originalError) {
|
|
439
|
+
super(`Payment failed on ${network} network: ${message}`, { network, originalError: originalError?.message });
|
|
440
|
+
this.network = network;
|
|
315
441
|
this.originalError = originalError;
|
|
316
|
-
this.
|
|
442
|
+
this.code = 'NETWORK_ERROR';
|
|
443
|
+
this.retryable = true;
|
|
444
|
+
this.actionableMessage = 'A network error occurred during payment processing. Please try again.';
|
|
317
445
|
}
|
|
318
446
|
}
|
|
319
447
|
|
|
@@ -338,27 +466,41 @@ function atxpFetch(config) {
|
|
|
338
466
|
onAuthorize: config.onAuthorize,
|
|
339
467
|
onAuthorizeFailure: config.onAuthorizeFailure,
|
|
340
468
|
onPayment: config.onPayment,
|
|
341
|
-
onPaymentFailure: config.onPaymentFailure
|
|
469
|
+
onPaymentFailure: config.onPaymentFailure,
|
|
470
|
+
onPaymentAttemptFailed: config.onPaymentAttemptFailed
|
|
342
471
|
});
|
|
343
472
|
return fetcher.fetch;
|
|
344
473
|
}
|
|
345
474
|
class ATXPFetcher {
|
|
346
475
|
constructor(config) {
|
|
347
|
-
this.defaultPaymentFailureHandler = async (
|
|
476
|
+
this.defaultPaymentFailureHandler = async (context) => {
|
|
477
|
+
const { payment, error, attemptedNetworks, retryable } = context;
|
|
478
|
+
const recoveryHint = common.getErrorRecoveryHint(error);
|
|
479
|
+
this.logger.info(`PAYMENT FAILED: ${recoveryHint.title}`);
|
|
480
|
+
this.logger.info(`Description: ${recoveryHint.description}`);
|
|
481
|
+
if (attemptedNetworks.length > 0) {
|
|
482
|
+
this.logger.info(`Attempted networks: ${attemptedNetworks.join(', ')}`);
|
|
483
|
+
}
|
|
484
|
+
this.logger.info(`Account: ${payment.accountId}`);
|
|
485
|
+
// Log actionable guidance
|
|
486
|
+
if (recoveryHint.actions.length > 0) {
|
|
487
|
+
this.logger.info(`What to do:`);
|
|
488
|
+
recoveryHint.actions.forEach((action, index) => {
|
|
489
|
+
this.logger.info(` ${index + 1}. ${action}`);
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
if (retryable) {
|
|
493
|
+
this.logger.info(`This payment can be retried.`);
|
|
494
|
+
}
|
|
495
|
+
// Log additional context for specific error types
|
|
348
496
|
if (error instanceof InsufficientFundsError) {
|
|
349
|
-
const networkText = error.network ? ` on ${error.network}` : '';
|
|
350
|
-
this.logger.info(`PAYMENT FAILED: Insufficient ${error.currency} funds${networkText}`);
|
|
351
497
|
this.logger.info(`Required: ${error.required} ${error.currency}`);
|
|
352
498
|
if (error.available) {
|
|
353
499
|
this.logger.info(`Available: ${error.available} ${error.currency}`);
|
|
354
500
|
}
|
|
355
|
-
this.logger.info(`Account: ${payment.accountId}`);
|
|
356
501
|
}
|
|
357
|
-
else if (error instanceof
|
|
358
|
-
this.logger.
|
|
359
|
-
}
|
|
360
|
-
else {
|
|
361
|
-
this.logger.info(`PAYMENT FAILED: ${error.message}`);
|
|
502
|
+
else if (error instanceof ATXPPaymentError && error.context) {
|
|
503
|
+
this.logger.debug(`Error context: ${JSON.stringify(error.context)}`);
|
|
362
504
|
}
|
|
363
505
|
};
|
|
364
506
|
this.handleMultiDestinationPayment = async (paymentRequest, paymentRequestUrl, paymentRequestId) => {
|
|
@@ -403,9 +545,11 @@ class ATXPFetcher {
|
|
|
403
545
|
this.logger.info(`ATXP: payment request denied by callback function`);
|
|
404
546
|
return false;
|
|
405
547
|
}
|
|
406
|
-
// Try each payment maker in order
|
|
548
|
+
// Try each payment maker in order, tracking attempts
|
|
407
549
|
let lastPaymentError = null;
|
|
408
550
|
let paymentAttempted = false;
|
|
551
|
+
const attemptedNetworks = [];
|
|
552
|
+
const failureReasons = new Map();
|
|
409
553
|
for (const paymentMaker of this.account.paymentMakers) {
|
|
410
554
|
try {
|
|
411
555
|
// Pass all destinations to payment maker - it will filter and pick the one it can handle
|
|
@@ -417,7 +561,11 @@ class ATXPFetcher {
|
|
|
417
561
|
paymentAttempted = true;
|
|
418
562
|
// Payment was successful
|
|
419
563
|
this.logger.info(`ATXP: made payment of ${firstDest.amount.toString()} ${firstDest.currency} on ${result.chain}: ${result.transactionId}`);
|
|
420
|
-
await this.onPayment({
|
|
564
|
+
await this.onPayment({
|
|
565
|
+
payment: prospectivePayment,
|
|
566
|
+
transactionHash: result.transactionId,
|
|
567
|
+
network: result.chain
|
|
568
|
+
});
|
|
421
569
|
// Submit payment to the server
|
|
422
570
|
const jwt = await paymentMaker.generateJWT({ paymentRequestId, codeChallenge: '', accountId: this.account.accountId });
|
|
423
571
|
const response = await this.sideChannelFetch(paymentRequestUrl.toString(), {
|
|
@@ -445,13 +593,41 @@ class ATXPFetcher {
|
|
|
445
593
|
const typedError = error;
|
|
446
594
|
paymentAttempted = true;
|
|
447
595
|
lastPaymentError = typedError;
|
|
448
|
-
|
|
449
|
-
|
|
596
|
+
// Extract network from error context if available
|
|
597
|
+
let network = 'unknown';
|
|
598
|
+
if (typedError instanceof ATXPPaymentError && typedError.context?.network) {
|
|
599
|
+
network = typeof typedError.context.network === 'string' ? typedError.context.network : 'unknown';
|
|
600
|
+
}
|
|
601
|
+
attemptedNetworks.push(network);
|
|
602
|
+
failureReasons.set(network, typedError);
|
|
603
|
+
this.logger.warn(`ATXP: payment maker failed on ${network}: ${typedError.message}`);
|
|
604
|
+
// Call optional per-attempt failure callback
|
|
605
|
+
if (this.onPaymentAttemptFailed) {
|
|
606
|
+
const remainingMakers = this.account.paymentMakers.length - (attemptedNetworks.length);
|
|
607
|
+
const remainingNetworks = remainingMakers > 0 ? ['next available'] : [];
|
|
608
|
+
await this.onPaymentAttemptFailed({
|
|
609
|
+
network,
|
|
610
|
+
error: typedError,
|
|
611
|
+
remainingNetworks
|
|
612
|
+
});
|
|
613
|
+
}
|
|
450
614
|
// Continue to next payment maker
|
|
451
615
|
}
|
|
452
616
|
}
|
|
453
|
-
// If payment was attempted but all failed,
|
|
617
|
+
// If payment was attempted but all failed, create full context and call onPaymentFailure
|
|
454
618
|
if (paymentAttempted && lastPaymentError) {
|
|
619
|
+
const isRetryable = lastPaymentError instanceof ATXPPaymentError
|
|
620
|
+
? lastPaymentError.retryable
|
|
621
|
+
: true; // Default to retryable for unknown errors
|
|
622
|
+
const failureContext = {
|
|
623
|
+
payment: prospectivePayment,
|
|
624
|
+
error: lastPaymentError,
|
|
625
|
+
attemptedNetworks,
|
|
626
|
+
failureReasons,
|
|
627
|
+
retryable: isRetryable,
|
|
628
|
+
timestamp: new Date()
|
|
629
|
+
};
|
|
630
|
+
await this.onPaymentFailure(failureContext);
|
|
455
631
|
throw lastPaymentError;
|
|
456
632
|
}
|
|
457
633
|
this.logger.info(`ATXP: no payment maker could handle these destinations`);
|
|
@@ -699,7 +875,7 @@ class ATXPFetcher {
|
|
|
699
875
|
throw error;
|
|
700
876
|
}
|
|
701
877
|
};
|
|
702
|
-
const { account, db, destinationMakers, fetchFn = fetch, sideChannelFetch = fetchFn, strict = true, allowInsecureRequests = process.env.NODE_ENV === 'development', allowedAuthorizationServers = [common.DEFAULT_AUTHORIZATION_SERVER], approvePayment = async () => true, logger = new common.ConsoleLogger(), onAuthorize = async () => { }, onAuthorizeFailure = async () => { }, onPayment = async () => { }, onPaymentFailure
|
|
878
|
+
const { account, db, destinationMakers, fetchFn = fetch, sideChannelFetch = fetchFn, strict = true, allowInsecureRequests = process.env.NODE_ENV === 'development', allowedAuthorizationServers = [common.DEFAULT_AUTHORIZATION_SERVER], approvePayment = async () => true, logger = new common.ConsoleLogger(), onAuthorize = async () => { }, onAuthorizeFailure = async () => { }, onPayment = async () => { }, onPaymentFailure, onPaymentAttemptFailed } = config;
|
|
703
879
|
// Use React Native safe fetch if in React Native environment
|
|
704
880
|
const safeFetchFn = common.getIsReactNative() ? common.createReactNativeSafeFetch(fetchFn) : fetchFn;
|
|
705
881
|
const safeSideChannelFetch = common.getIsReactNative() ? common.createReactNativeSafeFetch(sideChannelFetch) : sideChannelFetch;
|
|
@@ -728,6 +904,7 @@ class ATXPFetcher {
|
|
|
728
904
|
this.onAuthorizeFailure = onAuthorizeFailure;
|
|
729
905
|
this.onPayment = onPayment;
|
|
730
906
|
this.onPaymentFailure = onPaymentFailure || this.defaultPaymentFailureHandler;
|
|
907
|
+
this.onPaymentAttemptFailed = onPaymentAttemptFailed;
|
|
731
908
|
}
|
|
732
909
|
}
|
|
733
910
|
|
|
@@ -16024,7 +16201,8 @@ const DEFAULT_CLIENT_CONFIG = {
|
|
|
16024
16201
|
onAuthorize: async () => { },
|
|
16025
16202
|
onAuthorizeFailure: async () => { },
|
|
16026
16203
|
onPayment: async () => { },
|
|
16027
|
-
onPaymentFailure: async () => { }
|
|
16204
|
+
onPaymentFailure: async () => { },
|
|
16205
|
+
onPaymentAttemptFailed: async () => { }
|
|
16028
16206
|
};
|
|
16029
16207
|
function buildClientConfig(args) {
|
|
16030
16208
|
// Use fetchFn for oAuthChannelFetch if the latter isn't explicitly set
|
|
@@ -16338,18 +16516,26 @@ Object.defineProperty(exports, "ATXPAccount", {
|
|
|
16338
16516
|
});
|
|
16339
16517
|
exports.ATXPDestinationMaker = ATXPDestinationMaker;
|
|
16340
16518
|
exports.ATXPLocalAccount = ATXPLocalAccount;
|
|
16519
|
+
exports.ATXPPaymentError = ATXPPaymentError;
|
|
16341
16520
|
exports.DEFAULT_CLIENT_CONFIG = DEFAULT_CLIENT_CONFIG;
|
|
16521
|
+
exports.GasEstimationError = GasEstimationError;
|
|
16342
16522
|
exports.InsufficientFundsError = InsufficientFundsError;
|
|
16343
16523
|
exports.OAuthAuthenticationRequiredError = OAuthAuthenticationRequiredError;
|
|
16344
16524
|
exports.OAuthClient = OAuthClient;
|
|
16345
16525
|
exports.POLYGON_AMOY = POLYGON_AMOY;
|
|
16346
16526
|
exports.POLYGON_MAINNET = POLYGON_MAINNET;
|
|
16347
16527
|
exports.PassthroughDestinationMaker = PassthroughDestinationMaker;
|
|
16528
|
+
exports.PaymentExpiredError = PaymentExpiredError;
|
|
16348
16529
|
exports.PaymentNetworkError = PaymentNetworkError;
|
|
16530
|
+
exports.PaymentServerError = PaymentServerError;
|
|
16531
|
+
exports.RpcError = RpcError;
|
|
16532
|
+
exports.TransactionRevertedError = TransactionRevertedError;
|
|
16349
16533
|
exports.USDC_CONTRACT_ADDRESS_POLYGON_AMOY = USDC_CONTRACT_ADDRESS_POLYGON_AMOY;
|
|
16350
16534
|
exports.USDC_CONTRACT_ADDRESS_POLYGON_MAINNET = USDC_CONTRACT_ADDRESS_POLYGON_MAINNET;
|
|
16351
16535
|
exports.USDC_CONTRACT_ADDRESS_WORLD_MAINNET = USDC_CONTRACT_ADDRESS_WORLD_MAINNET;
|
|
16352
16536
|
exports.USDC_CONTRACT_ADDRESS_WORLD_SEPOLIA = USDC_CONTRACT_ADDRESS_WORLD_SEPOLIA;
|
|
16537
|
+
exports.UnsupportedCurrencyError = UnsupportedCurrencyError;
|
|
16538
|
+
exports.UserRejectedError = UserRejectedError;
|
|
16353
16539
|
exports.WORLD_CHAIN_MAINNET = WORLD_CHAIN_MAINNET;
|
|
16354
16540
|
exports.WORLD_CHAIN_SEPOLIA = WORLD_CHAIN_SEPOLIA;
|
|
16355
16541
|
exports.atxpClient = atxpClient;
|