@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.
Files changed (42) hide show
  1. package/dist/atxpClient.d.ts.map +1 -1
  2. package/dist/atxpClient.js +2 -1
  3. package/dist/atxpClient.js.map +1 -1
  4. package/dist/atxpFetcher.d.ts +13 -5
  5. package/dist/atxpFetcher.d.ts.map +1 -1
  6. package/dist/atxpFetcher.js +67 -18
  7. package/dist/atxpFetcher.js.map +1 -1
  8. package/dist/errors.d.ts +117 -0
  9. package/dist/errors.d.ts.map +1 -0
  10. package/dist/errors.js +152 -0
  11. package/dist/errors.js.map +1 -0
  12. package/dist/index.cjs +213 -386
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.ts +144 -96
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +205 -378
  17. package/dist/index.js.map +1 -1
  18. package/dist/types.d.ts +25 -13
  19. package/dist/types.d.ts.map +1 -1
  20. package/package.json +3 -11
  21. package/dist/baseAccount.d.ts +0 -20
  22. package/dist/baseAccount.d.ts.map +0 -1
  23. package/dist/baseAccount.js +0 -46
  24. package/dist/baseAccount.js.map +0 -1
  25. package/dist/baseConstants.d.ts +0 -10
  26. package/dist/baseConstants.d.ts.map +0 -1
  27. package/dist/baseConstants.js +0 -21
  28. package/dist/baseConstants.js.map +0 -1
  29. package/dist/basePaymentMaker.d.ts +0 -23
  30. package/dist/basePaymentMaker.d.ts.map +0 -1
  31. package/dist/basePaymentMaker.js +0 -169
  32. package/dist/basePaymentMaker.js.map +0 -1
  33. package/dist/solanaAccount.d.ts +0 -13
  34. package/dist/solanaAccount.d.ts.map +0 -1
  35. package/dist/solanaAccount.js +0 -34
  36. package/dist/solanaAccount.js.map +0 -1
  37. package/dist/solanaPaymentMaker.d.ts +0 -25
  38. package/dist/solanaPaymentMaker.d.ts.map +0 -1
  39. package/dist/solanaPaymentMaker.js +0 -108
  40. package/dist/solanaPaymentMaker.js.map +0 -1
  41. package/dist/types.js +0 -24
  42. 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, generateJWT } 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
- import BigNumber$1, { BigNumber } from 'bignumber.js';
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
- 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 {
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.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.`;
298
308
  }
299
309
  }
300
- class PaymentNetworkError extends Error {
301
- constructor(message, originalError) {
302
- super(`Payment failed due to network error: ${message}`);
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.name = 'PaymentNetworkError';
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 ({ 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
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 PaymentNetworkError) {
346
- this.logger.info(`PAYMENT FAILED: Network error: ${error.message}`);
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({ payment: prospectivePayment });
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
- this.logger.warn(`ATXP: payment maker failed: ${typedError.message}`);
437
- 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
+ }
438
594
  // Continue to next payment maker
439
595
  }
440
596
  }
441
- // 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
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 = 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;
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, BaseAccount, BasePaymentMaker, DEFAULT_CLIENT_CONFIG, InsufficientFundsError, OAuthAuthenticationRequiredError, OAuthClient, POLYGON_AMOY, POLYGON_MAINNET, PassthroughDestinationMaker, PaymentNetworkError, SolanaAccount, SolanaPaymentMaker, USDC_CONTRACT_ADDRESS_BASE, USDC_CONTRACT_ADDRESS_BASE_SEPOLIA, USDC_CONTRACT_ADDRESS_POLYGON_AMOY, USDC_CONTRACT_ADDRESS_POLYGON_MAINNET, USDC_CONTRACT_ADDRESS_WORLD_MAINNET, USDC_CONTRACT_ADDRESS_WORLD_SEPOLIA, ValidateTransferError, WORLD_CHAIN_MAINNET, WORLD_CHAIN_SEPOLIA, atxpClient, atxpFetch, buildClientConfig, buildStreamableTransport, getBaseUSDCAddress, 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 };
16667
16494
  //# sourceMappingURL=index.js.map