@atxp/client 0.8.3 → 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 +213 -386
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +144 -96
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +205 -378
- 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 +3 -11
- package/dist/baseAccount.d.ts +0 -20
- package/dist/baseAccount.d.ts.map +0 -1
- package/dist/baseAccount.js +0 -46
- package/dist/baseAccount.js.map +0 -1
- package/dist/baseConstants.d.ts +0 -10
- package/dist/baseConstants.d.ts.map +0 -1
- package/dist/baseConstants.js +0 -21
- package/dist/baseConstants.js.map +0 -1
- package/dist/basePaymentMaker.d.ts +0 -23
- package/dist/basePaymentMaker.d.ts.map +0 -1
- package/dist/basePaymentMaker.js +0 -169
- package/dist/basePaymentMaker.js.map +0 -1
- package/dist/solanaAccount.d.ts +0 -13
- package/dist/solanaAccount.d.ts.map +0 -1
- package/dist/solanaAccount.js +0 -34
- package/dist/solanaAccount.js.map +0 -1
- package/dist/solanaPaymentMaker.d.ts +0 -25
- package/dist/solanaPaymentMaker.d.ts.map +0 -1
- package/dist/solanaPaymentMaker.js +0 -108
- package/dist/solanaPaymentMaker.js.map +0 -1
- package/dist/types.js +0 -24
- package/dist/types.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
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
|
|
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
|
-
import
|
|
5
|
-
import { PublicKey, ComputeBudgetProgram, sendAndConfirmTransaction, Connection, Keypair } from '@solana/web3.js';
|
|
6
|
-
import { ValidateTransferError as ValidateTransferError$1, createTransfer } from '@solana/pay';
|
|
7
|
-
import { getAssociatedTokenAddress, getAccount } from '@solana/spl-token';
|
|
8
|
-
import bs58 from 'bs58';
|
|
9
|
-
import { importJWK } from 'jose';
|
|
10
|
-
import { publicActions, encodeFunctionData, parseEther, createWalletClient, http } from 'viem';
|
|
11
|
-
import { base } from 'viem/chains';
|
|
12
|
-
import { privateKeyToAccount } from 'viem/accounts';
|
|
4
|
+
import { BigNumber } from 'bignumber.js';
|
|
13
5
|
|
|
14
6
|
class OAuthAuthenticationRequiredError extends Error {
|
|
15
7
|
constructor(url, resourceServerUrl,
|
|
@@ -283,25 +275,153 @@ class OAuthClient extends OAuthResourceClient {
|
|
|
283
275
|
}
|
|
284
276
|
}
|
|
285
277
|
|
|
286
|
-
|
|
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 {
|
|
287
292
|
constructor(currency, required, available, network) {
|
|
293
|
+
const shortfall = available ? required.minus(available).toString() : required.toString();
|
|
288
294
|
const availableText = available ? `, Available: ${available}` : '';
|
|
289
295
|
const networkText = network ? ` on ${network}` : '';
|
|
290
296
|
super(`Payment failed due to insufficient ${currency} funds${networkText}. ` +
|
|
291
297
|
`Required: ${required}${availableText}. ` +
|
|
292
|
-
`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 });
|
|
293
299
|
this.currency = currency;
|
|
294
300
|
this.required = required;
|
|
295
301
|
this.available = available;
|
|
296
302
|
this.network = network;
|
|
297
|
-
this.
|
|
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.`;
|
|
298
308
|
}
|
|
299
309
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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;
|
|
303
368
|
this.originalError = originalError;
|
|
304
|
-
this.
|
|
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';
|
|
399
|
+
}
|
|
400
|
+
}
|
|
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;
|
|
421
|
+
this.originalError = originalError;
|
|
422
|
+
this.code = 'NETWORK_ERROR';
|
|
423
|
+
this.retryable = true;
|
|
424
|
+
this.actionableMessage = 'A network error occurred during payment processing. Please try again.';
|
|
305
425
|
}
|
|
306
426
|
}
|
|
307
427
|
|
|
@@ -326,27 +446,41 @@ function atxpFetch(config) {
|
|
|
326
446
|
onAuthorize: config.onAuthorize,
|
|
327
447
|
onAuthorizeFailure: config.onAuthorizeFailure,
|
|
328
448
|
onPayment: config.onPayment,
|
|
329
|
-
onPaymentFailure: config.onPaymentFailure
|
|
449
|
+
onPaymentFailure: config.onPaymentFailure,
|
|
450
|
+
onPaymentAttemptFailed: config.onPaymentAttemptFailed
|
|
330
451
|
});
|
|
331
452
|
return fetcher.fetch;
|
|
332
453
|
}
|
|
333
454
|
class ATXPFetcher {
|
|
334
455
|
constructor(config) {
|
|
335
|
-
this.defaultPaymentFailureHandler = async (
|
|
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
|
|
336
476
|
if (error instanceof InsufficientFundsError) {
|
|
337
|
-
const networkText = error.network ? ` on ${error.network}` : '';
|
|
338
|
-
this.logger.info(`PAYMENT FAILED: Insufficient ${error.currency} funds${networkText}`);
|
|
339
477
|
this.logger.info(`Required: ${error.required} ${error.currency}`);
|
|
340
478
|
if (error.available) {
|
|
341
479
|
this.logger.info(`Available: ${error.available} ${error.currency}`);
|
|
342
480
|
}
|
|
343
|
-
this.logger.info(`Account: ${payment.accountId}`);
|
|
344
481
|
}
|
|
345
|
-
else if (error instanceof
|
|
346
|
-
this.logger.
|
|
347
|
-
}
|
|
348
|
-
else {
|
|
349
|
-
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)}`);
|
|
350
484
|
}
|
|
351
485
|
};
|
|
352
486
|
this.handleMultiDestinationPayment = async (paymentRequest, paymentRequestUrl, paymentRequestId) => {
|
|
@@ -391,9 +525,11 @@ class ATXPFetcher {
|
|
|
391
525
|
this.logger.info(`ATXP: payment request denied by callback function`);
|
|
392
526
|
return false;
|
|
393
527
|
}
|
|
394
|
-
// Try each payment maker in order
|
|
528
|
+
// Try each payment maker in order, tracking attempts
|
|
395
529
|
let lastPaymentError = null;
|
|
396
530
|
let paymentAttempted = false;
|
|
531
|
+
const attemptedNetworks = [];
|
|
532
|
+
const failureReasons = new Map();
|
|
397
533
|
for (const paymentMaker of this.account.paymentMakers) {
|
|
398
534
|
try {
|
|
399
535
|
// Pass all destinations to payment maker - it will filter and pick the one it can handle
|
|
@@ -405,7 +541,11 @@ class ATXPFetcher {
|
|
|
405
541
|
paymentAttempted = true;
|
|
406
542
|
// Payment was successful
|
|
407
543
|
this.logger.info(`ATXP: made payment of ${firstDest.amount.toString()} ${firstDest.currency} on ${result.chain}: ${result.transactionId}`);
|
|
408
|
-
await this.onPayment({
|
|
544
|
+
await this.onPayment({
|
|
545
|
+
payment: prospectivePayment,
|
|
546
|
+
transactionHash: result.transactionId,
|
|
547
|
+
network: result.chain
|
|
548
|
+
});
|
|
409
549
|
// Submit payment to the server
|
|
410
550
|
const jwt = await paymentMaker.generateJWT({ paymentRequestId, codeChallenge: '', accountId: this.account.accountId });
|
|
411
551
|
const response = await this.sideChannelFetch(paymentRequestUrl.toString(), {
|
|
@@ -433,13 +573,41 @@ class ATXPFetcher {
|
|
|
433
573
|
const typedError = error;
|
|
434
574
|
paymentAttempted = true;
|
|
435
575
|
lastPaymentError = typedError;
|
|
436
|
-
|
|
437
|
-
|
|
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
|
+
}
|
|
438
594
|
// Continue to next payment maker
|
|
439
595
|
}
|
|
440
596
|
}
|
|
441
|
-
// If payment was attempted but all failed,
|
|
597
|
+
// If payment was attempted but all failed, create full context and call onPaymentFailure
|
|
442
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);
|
|
443
611
|
throw lastPaymentError;
|
|
444
612
|
}
|
|
445
613
|
this.logger.info(`ATXP: no payment maker could handle these destinations`);
|
|
@@ -687,7 +855,7 @@ class ATXPFetcher {
|
|
|
687
855
|
throw error;
|
|
688
856
|
}
|
|
689
857
|
};
|
|
690
|
-
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
|
|
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;
|
|
691
859
|
// Use React Native safe fetch if in React Native environment
|
|
692
860
|
const safeFetchFn = getIsReactNative() ? createReactNativeSafeFetch(fetchFn) : fetchFn;
|
|
693
861
|
const safeSideChannelFetch = getIsReactNative() ? createReactNativeSafeFetch(sideChannelFetch) : sideChannelFetch;
|
|
@@ -716,6 +884,7 @@ class ATXPFetcher {
|
|
|
716
884
|
this.onAuthorizeFailure = onAuthorizeFailure;
|
|
717
885
|
this.onPayment = onPayment;
|
|
718
886
|
this.onPaymentFailure = onPaymentFailure || this.defaultPaymentFailureHandler;
|
|
887
|
+
this.onPaymentAttemptFailed = onPaymentAttemptFailed;
|
|
719
888
|
}
|
|
720
889
|
}
|
|
721
890
|
|
|
@@ -16012,7 +16181,8 @@ const DEFAULT_CLIENT_CONFIG = {
|
|
|
16012
16181
|
onAuthorize: async () => { },
|
|
16013
16182
|
onAuthorizeFailure: async () => { },
|
|
16014
16183
|
onPayment: async () => { },
|
|
16015
|
-
onPaymentFailure: async () => { }
|
|
16184
|
+
onPaymentFailure: async () => { },
|
|
16185
|
+
onPaymentAttemptFailed: async () => { }
|
|
16016
16186
|
};
|
|
16017
16187
|
function buildClientConfig(args) {
|
|
16018
16188
|
// Use fetchFn for oAuthChannelFetch if the latter isn't explicitly set
|
|
@@ -16058,310 +16228,6 @@ async function atxpClient(args) {
|
|
|
16058
16228
|
return client;
|
|
16059
16229
|
}
|
|
16060
16230
|
|
|
16061
|
-
// this is a global public key for USDC on the solana mainnet
|
|
16062
|
-
const USDC_MINT = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
|
|
16063
|
-
const ValidateTransferError = ValidateTransferError$1;
|
|
16064
|
-
class SolanaPaymentMaker {
|
|
16065
|
-
constructor(solanaEndpoint, sourceSecretKey, logger) {
|
|
16066
|
-
this.generateJWT = async ({ paymentRequestId, codeChallenge, accountId }) => {
|
|
16067
|
-
// Solana/Web3.js secretKey is 64 bytes:
|
|
16068
|
-
// first 32 bytes are the private scalar, last 32 are the public key.
|
|
16069
|
-
// JWK expects only the 32-byte private scalar for 'd'
|
|
16070
|
-
const jwk = {
|
|
16071
|
-
kty: 'OKP',
|
|
16072
|
-
crv: 'Ed25519',
|
|
16073
|
-
d: Buffer.from(this.source.secretKey.slice(0, 32)).toString('base64url'),
|
|
16074
|
-
x: Buffer.from(this.source.publicKey.toBytes()).toString('base64url'),
|
|
16075
|
-
};
|
|
16076
|
-
const privateKey = await importJWK(jwk, 'EdDSA');
|
|
16077
|
-
if (!(privateKey instanceof CryptoKey)) {
|
|
16078
|
-
throw new Error('Expected CryptoKey from importJWK');
|
|
16079
|
-
}
|
|
16080
|
-
return generateJWT(this.source.publicKey.toBase58(), privateKey, paymentRequestId || '', codeChallenge || '', accountId);
|
|
16081
|
-
};
|
|
16082
|
-
this.makePayment = async (destinations, memo, _paymentRequestId) => {
|
|
16083
|
-
// Filter to solana chain destinations
|
|
16084
|
-
const solanaDestinations = destinations.filter(d => d.chain === 'solana');
|
|
16085
|
-
if (solanaDestinations.length === 0) {
|
|
16086
|
-
this.logger.debug('SolanaPaymentMaker: No solana destinations found, cannot handle payment');
|
|
16087
|
-
return null; // Cannot handle these destinations
|
|
16088
|
-
}
|
|
16089
|
-
// Pick first solana destination
|
|
16090
|
-
const dest = solanaDestinations[0];
|
|
16091
|
-
const amount = dest.amount;
|
|
16092
|
-
const currency = dest.currency;
|
|
16093
|
-
const receiver = dest.address;
|
|
16094
|
-
if (currency.toUpperCase() !== 'USDC') {
|
|
16095
|
-
throw new PaymentNetworkError('Only USDC currency is supported; received ' + currency);
|
|
16096
|
-
}
|
|
16097
|
-
const receiverKey = new PublicKey(receiver);
|
|
16098
|
-
this.logger.info(`Making payment of ${amount} ${currency} to ${receiver} on Solana from ${this.source.publicKey.toBase58()}`);
|
|
16099
|
-
try {
|
|
16100
|
-
// Check balance before attempting payment
|
|
16101
|
-
const tokenAccountAddress = await getAssociatedTokenAddress(USDC_MINT, this.source.publicKey);
|
|
16102
|
-
const tokenAccount = await getAccount(this.connection, tokenAccountAddress);
|
|
16103
|
-
const balance = new BigNumber$1(tokenAccount.amount.toString()).dividedBy(10 ** 6); // USDC has 6 decimals
|
|
16104
|
-
if (balance.lt(amount)) {
|
|
16105
|
-
this.logger.warn(`Insufficient ${currency} balance for payment. Required: ${amount}, Available: ${balance}`);
|
|
16106
|
-
throw new InsufficientFundsError(currency, amount, balance, 'solana');
|
|
16107
|
-
}
|
|
16108
|
-
// Get the destination token account address (this will be the transactionId)
|
|
16109
|
-
const destinationTokenAccount = await getAssociatedTokenAddress(USDC_MINT, receiverKey);
|
|
16110
|
-
// Increase compute units to handle both memo and token transfer
|
|
16111
|
-
// Memo uses ~6000 CUs, token transfer needs ~6500 CUs
|
|
16112
|
-
const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({
|
|
16113
|
-
units: 50000,
|
|
16114
|
-
});
|
|
16115
|
-
const addPriorityFee = ComputeBudgetProgram.setComputeUnitPrice({
|
|
16116
|
-
microLamports: 20000,
|
|
16117
|
-
});
|
|
16118
|
-
const transaction = await createTransfer(this.connection, this.source.publicKey, {
|
|
16119
|
-
amount: amount,
|
|
16120
|
-
recipient: receiverKey,
|
|
16121
|
-
splToken: USDC_MINT,
|
|
16122
|
-
memo,
|
|
16123
|
-
});
|
|
16124
|
-
transaction.add(modifyComputeUnits);
|
|
16125
|
-
transaction.add(addPriorityFee);
|
|
16126
|
-
const transactionSignature = await sendAndConfirmTransaction(this.connection, transaction, [this.source]);
|
|
16127
|
-
// Return transaction signature as transactionId and token account address as transactionSubId
|
|
16128
|
-
return {
|
|
16129
|
-
transactionId: transactionSignature,
|
|
16130
|
-
transactionSubId: destinationTokenAccount.toBase58(),
|
|
16131
|
-
chain: 'solana',
|
|
16132
|
-
currency: 'USDC'
|
|
16133
|
-
};
|
|
16134
|
-
}
|
|
16135
|
-
catch (error) {
|
|
16136
|
-
if (error instanceof InsufficientFundsError || error instanceof PaymentNetworkError) {
|
|
16137
|
-
throw error;
|
|
16138
|
-
}
|
|
16139
|
-
// Wrap other errors in PaymentNetworkError
|
|
16140
|
-
throw new PaymentNetworkError(`Payment failed on Solana network: ${error.message}`, error);
|
|
16141
|
-
}
|
|
16142
|
-
};
|
|
16143
|
-
if (!solanaEndpoint) {
|
|
16144
|
-
throw new Error('Solana endpoint is required');
|
|
16145
|
-
}
|
|
16146
|
-
if (!sourceSecretKey) {
|
|
16147
|
-
throw new Error('Source secret key is required');
|
|
16148
|
-
}
|
|
16149
|
-
this.connection = new Connection(solanaEndpoint, { commitment: 'confirmed' });
|
|
16150
|
-
this.source = Keypair.fromSecretKey(bs58.decode(sourceSecretKey));
|
|
16151
|
-
this.logger = logger ?? new ConsoleLogger();
|
|
16152
|
-
}
|
|
16153
|
-
getSourceAddress(_params) {
|
|
16154
|
-
return this.source.publicKey.toBase58();
|
|
16155
|
-
}
|
|
16156
|
-
}
|
|
16157
|
-
|
|
16158
|
-
const USDC_CONTRACT_ADDRESS_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; // USDC on Base mainnet
|
|
16159
|
-
const USDC_CONTRACT_ADDRESS_BASE_SEPOLIA = "0x036CbD53842c5426634e7929541eC2318f3dCF7e"; // USDC on Base Sepolia testnet
|
|
16160
|
-
/**
|
|
16161
|
-
* Get USDC contract address for Base chain by chain ID
|
|
16162
|
-
* @param chainId - Chain ID (8453 for mainnet, 84532 for sepolia)
|
|
16163
|
-
* @returns USDC contract address
|
|
16164
|
-
* @throws Error if chain ID is not supported
|
|
16165
|
-
*/
|
|
16166
|
-
const getBaseUSDCAddress = (chainId) => {
|
|
16167
|
-
switch (chainId) {
|
|
16168
|
-
case 8453: // Base mainnet
|
|
16169
|
-
return USDC_CONTRACT_ADDRESS_BASE;
|
|
16170
|
-
case 84532: // Base Sepolia
|
|
16171
|
-
return USDC_CONTRACT_ADDRESS_BASE_SEPOLIA;
|
|
16172
|
-
default:
|
|
16173
|
-
throw new Error(`Unsupported Base Chain ID: ${chainId}. Supported chains: 8453 (mainnet), 84532 (sepolia)`);
|
|
16174
|
-
}
|
|
16175
|
-
};
|
|
16176
|
-
|
|
16177
|
-
// Helper function to convert to base64url that works in both Node.js and browsers
|
|
16178
|
-
function toBase64Url(data) {
|
|
16179
|
-
// Convert string to base64
|
|
16180
|
-
const base64 = typeof Buffer !== 'undefined'
|
|
16181
|
-
? Buffer.from(data).toString('base64')
|
|
16182
|
-
: btoa(data);
|
|
16183
|
-
// Convert base64 to base64url
|
|
16184
|
-
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
16185
|
-
}
|
|
16186
|
-
const USDC_DECIMALS = 6;
|
|
16187
|
-
const ERC20_ABI = [
|
|
16188
|
-
{
|
|
16189
|
-
constant: false,
|
|
16190
|
-
inputs: [
|
|
16191
|
-
{ name: "_to", type: "address" },
|
|
16192
|
-
{ name: "_value", type: "uint256" },
|
|
16193
|
-
],
|
|
16194
|
-
name: "transfer",
|
|
16195
|
-
outputs: [{ name: "", type: "bool" }],
|
|
16196
|
-
type: "function",
|
|
16197
|
-
},
|
|
16198
|
-
{
|
|
16199
|
-
"constant": true,
|
|
16200
|
-
"inputs": [
|
|
16201
|
-
{
|
|
16202
|
-
"name": "_owner",
|
|
16203
|
-
"type": "address"
|
|
16204
|
-
}
|
|
16205
|
-
],
|
|
16206
|
-
"name": "balanceOf",
|
|
16207
|
-
"outputs": [
|
|
16208
|
-
{
|
|
16209
|
-
"name": "balance",
|
|
16210
|
-
"type": "uint256"
|
|
16211
|
-
}
|
|
16212
|
-
],
|
|
16213
|
-
"payable": false,
|
|
16214
|
-
"stateMutability": "view",
|
|
16215
|
-
"type": "function"
|
|
16216
|
-
}
|
|
16217
|
-
];
|
|
16218
|
-
class BasePaymentMaker {
|
|
16219
|
-
constructor(baseRPCUrl, walletClient, logger) {
|
|
16220
|
-
if (!baseRPCUrl) {
|
|
16221
|
-
throw new Error('baseRPCUrl was empty');
|
|
16222
|
-
}
|
|
16223
|
-
if (!walletClient) {
|
|
16224
|
-
throw new Error('walletClient was empty');
|
|
16225
|
-
}
|
|
16226
|
-
if (!walletClient.account) {
|
|
16227
|
-
throw new Error('walletClient.account was empty');
|
|
16228
|
-
}
|
|
16229
|
-
this.signingClient = walletClient.extend(publicActions);
|
|
16230
|
-
this.logger = logger ?? new ConsoleLogger();
|
|
16231
|
-
}
|
|
16232
|
-
getSourceAddress(_params) {
|
|
16233
|
-
return this.signingClient.account.address;
|
|
16234
|
-
}
|
|
16235
|
-
async generateJWT({ paymentRequestId, codeChallenge, accountId }) {
|
|
16236
|
-
const headerObj = { alg: 'ES256K' };
|
|
16237
|
-
const payloadObj = {
|
|
16238
|
-
sub: this.signingClient.account.address,
|
|
16239
|
-
iss: 'accounts.atxp.ai',
|
|
16240
|
-
aud: 'https://auth.atxp.ai',
|
|
16241
|
-
iat: Math.floor(Date.now() / 1000),
|
|
16242
|
-
exp: Math.floor(Date.now() / 1000) + 60 * 60,
|
|
16243
|
-
...(codeChallenge ? { code_challenge: codeChallenge } : {}),
|
|
16244
|
-
...(paymentRequestId ? { payment_request_id: paymentRequestId } : {}),
|
|
16245
|
-
...(accountId ? { account_id: accountId } : {}),
|
|
16246
|
-
};
|
|
16247
|
-
const header = toBase64Url(JSON.stringify(headerObj));
|
|
16248
|
-
const payload = toBase64Url(JSON.stringify(payloadObj));
|
|
16249
|
-
const message = `${header}.${payload}`;
|
|
16250
|
-
const messageBytes = typeof Buffer !== 'undefined'
|
|
16251
|
-
? Buffer.from(message, 'utf8')
|
|
16252
|
-
: new TextEncoder().encode(message);
|
|
16253
|
-
const signResult = await this.signingClient.signMessage({
|
|
16254
|
-
account: this.signingClient.account,
|
|
16255
|
-
message: { raw: messageBytes },
|
|
16256
|
-
});
|
|
16257
|
-
// For ES256K, signature is typically 65 bytes (r,s,v)
|
|
16258
|
-
// Server expects the hex signature string (with 0x prefix) to be base64url encoded
|
|
16259
|
-
// This creates: base64url("0x6eb2565...") not base64url(rawBytes)
|
|
16260
|
-
// Pass the hex string directly to toBase64Url which will UTF-8 encode and base64url it
|
|
16261
|
-
const signature = toBase64Url(signResult);
|
|
16262
|
-
const jwt = `${header}.${payload}.${signature}`;
|
|
16263
|
-
this.logger.info(`Generated ES256K JWT: ${jwt}`);
|
|
16264
|
-
return jwt;
|
|
16265
|
-
}
|
|
16266
|
-
async makePayment(destinations, _memo, _paymentRequestId) {
|
|
16267
|
-
// Filter to base chain destinations
|
|
16268
|
-
const baseDestinations = destinations.filter(d => d.chain === 'base');
|
|
16269
|
-
if (baseDestinations.length === 0) {
|
|
16270
|
-
this.logger.debug('BasePaymentMaker: No base destinations found, cannot handle payment');
|
|
16271
|
-
return null; // Cannot handle these destinations
|
|
16272
|
-
}
|
|
16273
|
-
// Pick first base destination
|
|
16274
|
-
const dest = baseDestinations[0];
|
|
16275
|
-
const amount = dest.amount;
|
|
16276
|
-
const currency = dest.currency;
|
|
16277
|
-
const receiver = dest.address;
|
|
16278
|
-
if (currency.toUpperCase() !== 'USDC') {
|
|
16279
|
-
throw new PaymentNetworkError('Only USDC currency is supported; received ' + currency);
|
|
16280
|
-
}
|
|
16281
|
-
this.logger.info(`Making payment of ${amount} ${currency} to ${receiver} on Base from ${this.signingClient.account.address}`);
|
|
16282
|
-
try {
|
|
16283
|
-
// Check balance before attempting payment
|
|
16284
|
-
const balanceRaw = await this.signingClient.readContract({
|
|
16285
|
-
address: USDC_CONTRACT_ADDRESS_BASE,
|
|
16286
|
-
abi: ERC20_ABI,
|
|
16287
|
-
functionName: 'balanceOf',
|
|
16288
|
-
args: [this.signingClient.account.address],
|
|
16289
|
-
});
|
|
16290
|
-
const balance = new BigNumber(balanceRaw.toString()).dividedBy(10 ** USDC_DECIMALS);
|
|
16291
|
-
if (balance.lt(amount)) {
|
|
16292
|
-
this.logger.warn(`Insufficient ${currency} balance for payment. Required: ${amount}, Available: ${balance}`);
|
|
16293
|
-
throw new InsufficientFundsError(currency, amount, balance, 'base');
|
|
16294
|
-
}
|
|
16295
|
-
// Convert amount to USDC units (6 decimals) as BigInt
|
|
16296
|
-
const amountInUSDCUnits = BigInt(amount.multipliedBy(10 ** USDC_DECIMALS).toFixed(0));
|
|
16297
|
-
const data = encodeFunctionData({
|
|
16298
|
-
abi: ERC20_ABI,
|
|
16299
|
-
functionName: "transfer",
|
|
16300
|
-
args: [receiver, amountInUSDCUnits],
|
|
16301
|
-
});
|
|
16302
|
-
const hash = await this.signingClient.sendTransaction({
|
|
16303
|
-
chain: base,
|
|
16304
|
-
account: this.signingClient.account,
|
|
16305
|
-
to: USDC_CONTRACT_ADDRESS_BASE,
|
|
16306
|
-
data: data,
|
|
16307
|
-
value: parseEther('0'),
|
|
16308
|
-
maxPriorityFeePerGas: parseEther('0.000000001')
|
|
16309
|
-
});
|
|
16310
|
-
// Wait for transaction confirmation with more blocks to ensure propagation
|
|
16311
|
-
this.logger.info(`Waiting for transaction confirmation: ${hash}`);
|
|
16312
|
-
const receipt = await this.signingClient.waitForTransactionReceipt({
|
|
16313
|
-
hash: hash,
|
|
16314
|
-
confirmations: 1
|
|
16315
|
-
});
|
|
16316
|
-
if (receipt.status === 'reverted') {
|
|
16317
|
-
throw new PaymentNetworkError(`Transaction reverted: ${hash}`, new Error('Transaction reverted on chain'));
|
|
16318
|
-
}
|
|
16319
|
-
this.logger.info(`Transaction confirmed: ${hash} in block ${receipt.blockNumber}`);
|
|
16320
|
-
// Return payment result with chain and currency
|
|
16321
|
-
return {
|
|
16322
|
-
transactionId: hash,
|
|
16323
|
-
chain: 'base',
|
|
16324
|
-
currency: 'USDC'
|
|
16325
|
-
};
|
|
16326
|
-
}
|
|
16327
|
-
catch (error) {
|
|
16328
|
-
if (error instanceof InsufficientFundsError || error instanceof PaymentNetworkError) {
|
|
16329
|
-
throw error;
|
|
16330
|
-
}
|
|
16331
|
-
// Wrap other errors in PaymentNetworkError
|
|
16332
|
-
throw new PaymentNetworkError(`Payment failed on Base network: ${error.message}`, error);
|
|
16333
|
-
}
|
|
16334
|
-
}
|
|
16335
|
-
}
|
|
16336
|
-
|
|
16337
|
-
class SolanaAccount {
|
|
16338
|
-
constructor(solanaEndpoint, sourceSecretKey) {
|
|
16339
|
-
if (!solanaEndpoint) {
|
|
16340
|
-
throw new Error('Solana endpoint is required');
|
|
16341
|
-
}
|
|
16342
|
-
if (!sourceSecretKey) {
|
|
16343
|
-
throw new Error('Source secret key is required');
|
|
16344
|
-
}
|
|
16345
|
-
const source = Keypair.fromSecretKey(bs58.decode(sourceSecretKey));
|
|
16346
|
-
this.sourcePublicKey = source.publicKey.toBase58();
|
|
16347
|
-
// Format accountId as network:address
|
|
16348
|
-
this.accountId = `solana:${this.sourcePublicKey}`;
|
|
16349
|
-
this.paymentMakers = [
|
|
16350
|
-
new SolanaPaymentMaker(solanaEndpoint, sourceSecretKey)
|
|
16351
|
-
];
|
|
16352
|
-
}
|
|
16353
|
-
/**
|
|
16354
|
-
* Get sources for this account
|
|
16355
|
-
*/
|
|
16356
|
-
async getSources() {
|
|
16357
|
-
return [{
|
|
16358
|
-
address: this.sourcePublicKey,
|
|
16359
|
-
chain: 'solana',
|
|
16360
|
-
walletType: 'eoa'
|
|
16361
|
-
}];
|
|
16362
|
-
}
|
|
16363
|
-
}
|
|
16364
|
-
|
|
16365
16231
|
const USDC_CONTRACT_ADDRESS_WORLD_MAINNET = "0x79A02482A880bCE3F13e09Da970dC34db4CD24d1"; // USDC.e on World Chain mainnet
|
|
16366
16232
|
const USDC_CONTRACT_ADDRESS_WORLD_SEPOLIA = "0x79A02482A880bCE3F13e09Da970dC34db4CD24d1"; // USDC.e on World Chain Sepolia (placeholder - update with actual address)
|
|
16367
16233
|
// World Chain Mainnet (Chain ID: 480)
|
|
@@ -16524,45 +16390,6 @@ const getPolygonUSDCAddress = (chainId) => {
|
|
|
16524
16390
|
}
|
|
16525
16391
|
};
|
|
16526
16392
|
|
|
16527
|
-
class BaseAccount {
|
|
16528
|
-
constructor(baseRPCUrl, sourceSecretKey) {
|
|
16529
|
-
if (!baseRPCUrl) {
|
|
16530
|
-
throw new Error('Base RPC URL is required');
|
|
16531
|
-
}
|
|
16532
|
-
if (!sourceSecretKey) {
|
|
16533
|
-
throw new Error('Source secret key is required');
|
|
16534
|
-
}
|
|
16535
|
-
this.account = privateKeyToAccount(sourceSecretKey);
|
|
16536
|
-
// Format accountId as network:address
|
|
16537
|
-
this.accountId = `base:${this.account.address}`;
|
|
16538
|
-
this.walletClient = createWalletClient({
|
|
16539
|
-
account: this.account,
|
|
16540
|
-
chain: base,
|
|
16541
|
-
transport: http(baseRPCUrl),
|
|
16542
|
-
});
|
|
16543
|
-
this.paymentMakers = [
|
|
16544
|
-
new BasePaymentMaker(baseRPCUrl, this.walletClient)
|
|
16545
|
-
];
|
|
16546
|
-
}
|
|
16547
|
-
/**
|
|
16548
|
-
* Get the LocalAccount (signer) for this account.
|
|
16549
|
-
* This can be used with the x402 library or other signing operations.
|
|
16550
|
-
*/
|
|
16551
|
-
getLocalAccount() {
|
|
16552
|
-
return this.account;
|
|
16553
|
-
}
|
|
16554
|
-
/**
|
|
16555
|
-
* Get sources for this account
|
|
16556
|
-
*/
|
|
16557
|
-
async getSources() {
|
|
16558
|
-
return [{
|
|
16559
|
-
address: this.account.address,
|
|
16560
|
-
chain: 'base',
|
|
16561
|
-
walletType: 'eoa'
|
|
16562
|
-
}];
|
|
16563
|
-
}
|
|
16564
|
-
}
|
|
16565
|
-
|
|
16566
16393
|
function toBasicAuth(token) {
|
|
16567
16394
|
// Basic auth is base64("username:password"), password is blank
|
|
16568
16395
|
const b64 = Buffer.from(`${token}:`).toString('base64');
|
|
@@ -16663,5 +16490,5 @@ class ATXPLocalAccount {
|
|
|
16663
16490
|
}
|
|
16664
16491
|
}
|
|
16665
16492
|
|
|
16666
|
-
export { ATXPDestinationMaker, ATXPLocalAccount,
|
|
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 };
|
|
16667
16494
|
//# sourceMappingURL=index.js.map
|