@atxp/client 0.7.3 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/atxpAccount.d.ts +20 -0
  2. package/dist/atxpAccount.d.ts.map +1 -0
  3. package/dist/atxpAccount.js +82 -19
  4. package/dist/atxpAccount.js.map +1 -1
  5. package/dist/atxpClient.d.ts +12 -0
  6. package/dist/atxpClient.d.ts.map +1 -0
  7. package/dist/atxpClient.js +18 -2
  8. package/dist/atxpClient.js.map +1 -1
  9. package/dist/atxpFetcher.d.ts +77 -0
  10. package/dist/atxpFetcher.d.ts.map +1 -0
  11. package/dist/atxpFetcher.js +95 -140
  12. package/dist/atxpFetcher.js.map +1 -1
  13. package/dist/atxpLocalAccount.d.ts +50 -0
  14. package/dist/atxpLocalAccount.d.ts.map +1 -0
  15. package/dist/baseAccount.d.ts +20 -0
  16. package/dist/baseAccount.d.ts.map +1 -0
  17. package/dist/baseAccount.js +15 -4
  18. package/dist/baseAccount.js.map +1 -1
  19. package/dist/baseConstants.d.ts +10 -0
  20. package/dist/baseConstants.d.ts.map +1 -0
  21. package/dist/basePaymentMaker.d.ts +23 -0
  22. package/dist/basePaymentMaker.d.ts.map +1 -0
  23. package/dist/basePaymentMaker.js +23 -3
  24. package/dist/basePaymentMaker.js.map +1 -1
  25. package/dist/clientTestHelpers.d.ts +6 -0
  26. package/dist/clientTestHelpers.d.ts.map +1 -0
  27. package/dist/destinationMakers/atxpDestinationMaker.d.ts +15 -0
  28. package/dist/destinationMakers/atxpDestinationMaker.d.ts.map +1 -0
  29. package/dist/destinationMakers/atxpDestinationMaker.js +128 -0
  30. package/dist/destinationMakers/atxpDestinationMaker.js.map +1 -0
  31. package/dist/destinationMakers/index.d.ts +9 -0
  32. package/dist/destinationMakers/index.d.ts.map +1 -0
  33. package/dist/destinationMakers/index.js +42 -0
  34. package/dist/destinationMakers/index.js.map +1 -0
  35. package/dist/destinationMakers/passthroughDestinationMaker.d.ts +8 -0
  36. package/dist/destinationMakers/passthroughDestinationMaker.d.ts.map +1 -0
  37. package/dist/destinationMakers/passthroughDestinationMaker.js +27 -0
  38. package/dist/destinationMakers/passthroughDestinationMaker.js.map +1 -0
  39. package/dist/index.cjs +793 -454
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.ts +111 -36
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +788 -456
  44. package/dist/index.js.map +1 -1
  45. package/dist/oAuth.d.ts +44 -0
  46. package/dist/oAuth.d.ts.map +1 -0
  47. package/dist/polygonConstants.d.ts +46 -0
  48. package/dist/polygonConstants.d.ts.map +1 -0
  49. package/dist/polygonConstants.js +54 -0
  50. package/dist/polygonConstants.js.map +1 -0
  51. package/dist/setup.expo.d.ts +2 -0
  52. package/dist/setup.expo.d.ts.map +1 -0
  53. package/dist/solanaAccount.d.ts +13 -0
  54. package/dist/solanaAccount.d.ts.map +1 -0
  55. package/dist/solanaAccount.js +16 -4
  56. package/dist/solanaAccount.js.map +1 -1
  57. package/dist/solanaPaymentMaker.d.ts +25 -0
  58. package/dist/solanaPaymentMaker.d.ts.map +1 -0
  59. package/dist/solanaPaymentMaker.js +27 -5
  60. package/dist/solanaPaymentMaker.js.map +1 -1
  61. package/dist/types.d.ts +63 -0
  62. package/dist/types.d.ts.map +1 -0
  63. package/dist/types.js.map +1 -1
  64. package/dist/worldConstants.d.ts +53 -0
  65. package/dist/worldConstants.d.ts.map +1 -0
  66. package/package.json +2 -2
package/dist/index.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  var common = require('@atxp/common');
4
- var BigNumber = require('bignumber.js');
5
4
  var oauth = require('oauth4webapi');
5
+ var BigNumber = require('bignumber.js');
6
6
  var web3_js = require('@solana/web3.js');
7
7
  var pay = require('@solana/pay');
8
8
  var splToken = require('@solana/spl-token');
@@ -333,9 +333,9 @@ class PaymentNetworkError extends Error {
333
333
  */
334
334
  function atxpFetch(config) {
335
335
  const fetcher = new ATXPFetcher({
336
- accountId: config.account.accountId,
336
+ account: config.account,
337
337
  db: config.oAuthDb,
338
- paymentMakers: config.account.paymentMakers,
338
+ destinationMakers: config.destinationMakers,
339
339
  fetchFn: config.fetchFn,
340
340
  sideChannelFetch: config.oAuthChannelFetch,
341
341
  allowInsecureRequests: config.allowHttp,
@@ -353,7 +353,8 @@ class ATXPFetcher {
353
353
  constructor(config) {
354
354
  this.defaultPaymentFailureHandler = async ({ payment, error }) => {
355
355
  if (error instanceof InsufficientFundsError) {
356
- this.logger.info(`PAYMENT FAILED: Insufficient ${error.currency} funds on ${payment.network}`);
356
+ const networkText = error.network ? ` on ${error.network}` : '';
357
+ this.logger.info(`PAYMENT FAILED: Insufficient ${error.currency} funds${networkText}`);
357
358
  this.logger.info(`Required: ${error.required} ${error.currency}`);
358
359
  if (error.available) {
359
360
  this.logger.info(`Available: ${error.available} ${error.currency}`);
@@ -361,7 +362,7 @@ class ATXPFetcher {
361
362
  this.logger.info(`Account: ${payment.accountId}`);
362
363
  }
363
364
  else if (error instanceof PaymentNetworkError) {
364
- this.logger.info(`PAYMENT FAILED: Network error on ${payment.network}: ${error.message}`);
365
+ this.logger.info(`PAYMENT FAILED: Network error: ${error.message}`);
365
366
  }
366
367
  else {
367
368
  this.logger.info(`PAYMENT FAILED: ${error.message}`);
@@ -371,35 +372,61 @@ class ATXPFetcher {
371
372
  if (!paymentRequestData.destinations || paymentRequestData.destinations.length === 0) {
372
373
  return false;
373
374
  }
374
- // Try each destination in order
375
- for (const dest of paymentRequestData.destinations) {
376
- const paymentMaker = this.paymentMakers.get(dest.network);
377
- if (!paymentMaker) {
378
- this.logger.debug(`ATXP: payment network '${dest.network}' not available, trying next destination`);
375
+ // Get sources from the account
376
+ const sources = await this.account.getSources();
377
+ // Apply destination mappers to transform destinations
378
+ // Convert PaymentRequestDestination[] to Destination[] for mapper compatibility
379
+ const mappedDestinations = [];
380
+ for (const option of paymentRequestData.destinations) {
381
+ const destinationMaker = this.destinationMakers.get(option.network);
382
+ if (!destinationMaker) {
383
+ this.logger.debug(`ATXP: destination maker for network '${option.network}' not available, trying next destination`);
379
384
  continue;
380
385
  }
381
- // Convert amount to BigNumber since it comes as a string from JSON
382
- const amount = new BigNumber.BigNumber(dest.amount);
383
- const prospectivePayment = {
384
- accountId: this.accountId,
385
- resourceUrl: paymentRequestData.resource?.toString() ?? '',
386
- resourceName: paymentRequestData.resourceName ?? '',
387
- network: dest.network,
388
- currency: dest.currency,
389
- amount: amount,
390
- iss: paymentRequestData.iss ?? '',
391
- };
392
- if (!await this.approvePayment(prospectivePayment)) {
393
- this.logger.info(`ATXP: payment request denied by callback function for destination on ${dest.network}`);
394
- continue;
386
+ mappedDestinations.push(...(await destinationMaker.makeDestinations(option, this.logger, paymentRequestId, sources)));
387
+ }
388
+ if (mappedDestinations.length === 0) {
389
+ this.logger.info(`ATXP: no destinations found after mapping`);
390
+ return false;
391
+ }
392
+ // Validate amounts are not negative
393
+ for (const dest of mappedDestinations) {
394
+ if (dest.amount.isLessThan(0)) {
395
+ throw new Error(`ATXP: payment amount cannot be negative: ${dest.amount.toString()} ${dest.currency}`);
395
396
  }
396
- let paymentId;
397
+ }
398
+ // Create prospective payment for approval (using first destination for display)
399
+ const firstDest = mappedDestinations[0];
400
+ const prospectivePayment = {
401
+ accountId: this.account.accountId,
402
+ resourceUrl: paymentRequestData.resource?.toString() ?? '',
403
+ resourceName: paymentRequestData.resourceName ?? '',
404
+ currency: firstDest.currency,
405
+ amount: firstDest.amount,
406
+ iss: paymentRequestData.iss ?? '',
407
+ };
408
+ // Ask for approval once for all payment attempts
409
+ if (!await this.approvePayment(prospectivePayment)) {
410
+ this.logger.info(`ATXP: payment request denied by callback function`);
411
+ return false;
412
+ }
413
+ // Try each payment maker in order
414
+ let lastPaymentError = null;
415
+ let paymentAttempted = false;
416
+ for (const paymentMaker of this.account.paymentMakers) {
397
417
  try {
398
- paymentId = await paymentMaker.makePayment(amount, dest.currency, dest.address, paymentRequestData.iss);
399
- this.logger.info(`ATXP: made payment of ${amount.toString()} ${dest.currency} on ${dest.network}: ${paymentId}`);
418
+ // Pass all destinations to payment maker - it will filter and pick the one it can handle
419
+ const result = await paymentMaker.makePayment(mappedDestinations, paymentRequestData.iss, paymentRequestId);
420
+ if (result === null) {
421
+ this.logger.debug(`ATXP: payment maker cannot handle these destinations, trying next`);
422
+ continue; // Try next payment maker
423
+ }
424
+ paymentAttempted = true;
425
+ // Payment was successful
426
+ this.logger.info(`ATXP: made payment of ${firstDest.amount.toString()} ${firstDest.currency} on ${result.chain}: ${result.transactionId}`);
400
427
  await this.onPayment({ payment: prospectivePayment });
401
428
  // Submit payment to the server
402
- const jwt = await paymentMaker.generateJWT({ paymentRequestId, codeChallenge: '' });
429
+ const jwt = await paymentMaker.generateJWT({ paymentRequestId, codeChallenge: '', accountId: this.account.accountId });
403
430
  const response = await this.sideChannelFetch(paymentRequestUrl.toString(), {
404
431
  method: 'PUT',
405
432
  headers: {
@@ -407,9 +434,10 @@ class ATXPFetcher {
407
434
  'Content-Type': 'application/json'
408
435
  },
409
436
  body: JSON.stringify({
410
- transactionId: paymentId,
411
- network: dest.network,
412
- currency: dest.currency
437
+ transactionId: result.transactionId,
438
+ ...(result.transactionSubId ? { transactionSubId: result.transactionSubId } : {}),
439
+ chain: result.chain,
440
+ currency: result.currency
413
441
  })
414
442
  });
415
443
  this.logger.debug(`ATXP: payment was ${response.ok ? 'successfully' : 'not successfully'} PUT to ${paymentRequestUrl} : status ${response.status} ${response.statusText}`);
@@ -422,13 +450,18 @@ class ATXPFetcher {
422
450
  }
423
451
  catch (error) {
424
452
  const typedError = error;
425
- this.logger.warn(`ATXP: payment failed on ${dest.network}: ${typedError.message}`);
453
+ paymentAttempted = true;
454
+ lastPaymentError = typedError;
455
+ this.logger.warn(`ATXP: payment maker failed: ${typedError.message}`);
426
456
  await this.onPaymentFailure({ payment: prospectivePayment, error: typedError });
427
- // Try next destination
428
- continue;
457
+ // Continue to next payment maker
429
458
  }
430
459
  }
431
- this.logger.info(`ATXP: no suitable payment destination found among ${paymentRequestData.destinations.length} options`);
460
+ // If payment was attempted but all failed, rethrow the last error
461
+ if (paymentAttempted && lastPaymentError) {
462
+ throw lastPaymentError;
463
+ }
464
+ this.logger.info(`ATXP: no payment maker could handle these destinations`);
432
465
  return false;
433
466
  };
434
467
  this.handlePaymentRequestError = async (paymentRequestError) => {
@@ -455,94 +488,8 @@ class ATXPFetcher {
455
488
  if (paymentRequestData.destinations && paymentRequestData.destinations.length > 0) {
456
489
  return this.handleMultiDestinationPayment(paymentRequestData, paymentRequestUrl, paymentRequestId);
457
490
  }
458
- // Handle legacy single destination format
459
- const requestedNetwork = paymentRequestData.network;
460
- if (!requestedNetwork) {
461
- throw new Error(`Payment network not provided`);
462
- }
463
- const destination = paymentRequestData.destination;
464
- if (!destination) {
465
- throw new Error(`destination not provided`);
466
- }
467
- let amount = new BigNumber.BigNumber(0);
468
- if (!paymentRequestData.amount) {
469
- throw new Error(`amount not provided`);
470
- }
471
- try {
472
- amount = new BigNumber.BigNumber(paymentRequestData.amount);
473
- }
474
- catch {
475
- throw new Error(`Invalid amount ${paymentRequestData.amount}`);
476
- }
477
- if (amount.lte(0)) {
478
- throw new Error(`Invalid amount ${paymentRequestData.amount}`);
479
- }
480
- const currency = paymentRequestData.currency;
481
- if (!currency) {
482
- throw new Error(`Currency not provided`);
483
- }
484
- const paymentMaker = this.paymentMakers.get(requestedNetwork);
485
- if (!paymentMaker) {
486
- this.logger.info(`ATXP: payment network '${requestedNetwork}' not set up for this client (available networks: ${Array.from(this.paymentMakers.keys()).join(', ')})`);
487
- return false;
488
- }
489
- const prospectivePayment = {
490
- accountId: this.accountId,
491
- resourceUrl: paymentRequestData.resource?.toString() ?? '',
492
- resourceName: paymentRequestData.resourceName ?? '',
493
- network: requestedNetwork,
494
- currency,
495
- amount,
496
- iss: paymentRequestData.iss ?? '',
497
- };
498
- if (!await this.approvePayment(prospectivePayment)) {
499
- this.logger.info(`ATXP: payment request denied by callback function`);
500
- return false;
501
- }
502
- let paymentId;
503
- try {
504
- paymentId = await paymentMaker.makePayment(amount, currency, destination, paymentRequestData.iss);
505
- this.logger.info(`ATXP: made payment of ${amount} ${currency} on ${requestedNetwork}: ${paymentId}`);
506
- // Call onPayment callback after successful payment
507
- await this.onPayment({ payment: prospectivePayment });
508
- }
509
- catch (paymentError) {
510
- // Call onPaymentFailure callback if payment fails
511
- await this.onPaymentFailure({
512
- payment: prospectivePayment,
513
- error: paymentError
514
- });
515
- throw paymentError;
516
- }
517
- const jwt = await paymentMaker.generateJWT({ paymentRequestId, codeChallenge: '' });
518
- // Make a fetch call to the authorization URL with the payment ID
519
- // redirect=false is a hack
520
- // The OAuth spec calls for the authorization url to return with a redirect, but fetch
521
- // on mobile will automatically follow the redirect (it doesn't support the redirect=manual option)
522
- // We want the redirect URL so we can extract the code from it, not the contents of the
523
- // redirect URL (which might not even exist for agentic ATXP clients)
524
- // So ATXP servers are set up to instead return a 200 with the redirect URL in the body
525
- // if we pass redirect=false.
526
- // TODO: Remove the redirect=false hack once we have a way to handle the redirect on mobile
527
- const response = await this.sideChannelFetch(paymentRequestUrl.toString(), {
528
- method: 'PUT',
529
- headers: {
530
- 'Authorization': `Bearer ${jwt}`,
531
- 'Content-Type': 'application/json'
532
- },
533
- body: JSON.stringify({
534
- transactionId: paymentId,
535
- network: requestedNetwork,
536
- currency: currency
537
- })
538
- });
539
- this.logger.debug(`ATXP: payment was ${response.ok ? 'successfully' : 'not successfully'} PUT to ${paymentRequestUrl} : status ${response.status} ${response.statusText}`);
540
- if (!response.ok) {
541
- const msg = `ATXP: payment to ${paymentRequestUrl} failed: HTTP ${response.status} ${await response.text()}`;
542
- this.logger.info(msg);
543
- throw new Error(msg);
544
- }
545
- return true;
491
+ // Payment request doesn't have destinations - this shouldn't happen with new SDK
492
+ throw new Error(`ATXP: payment request does not contain destinations array`);
546
493
  };
547
494
  this.getPaymentRequestData = async (paymentRequestUrl) => {
548
495
  const prRequest = await this.sideChannelFetch(paymentRequestUrl);
@@ -550,6 +497,14 @@ class ATXPFetcher {
550
497
  throw new Error(`ATXP: GET ${paymentRequestUrl} failed: ${prRequest.status} ${prRequest.statusText}`);
551
498
  }
552
499
  const paymentRequest = await prRequest.json();
500
+ // Parse amount strings to BigNumber objects
501
+ if (paymentRequest.destinations) {
502
+ for (const dest of paymentRequest.destinations) {
503
+ if (typeof dest.amount === 'string' || typeof dest.amount === 'number') {
504
+ dest.amount = new BigNumber.BigNumber(dest.amount);
505
+ }
506
+ }
507
+ }
553
508
  return paymentRequest;
554
509
  };
555
510
  this.isAllowedAuthServer = (url) => {
@@ -563,15 +518,15 @@ class ATXPFetcher {
563
518
  throw new Error(`Code challenge not provided`);
564
519
  }
565
520
  if (!paymentMaker) {
566
- const availableNetworks = Array.from(this.paymentMakers.keys()).join(', ');
567
- throw new Error(`Payment maker is null/undefined. Available payment makers: [${availableNetworks}]. This usually indicates a payment maker object was not properly instantiated.`);
521
+ const paymentMakerCount = this.account.paymentMakers.length;
522
+ throw new Error(`Payment maker is null/undefined. Available payment maker count: ${paymentMakerCount}. This usually indicates a payment maker object was not properly instantiated.`);
568
523
  }
569
524
  // TypeScript should prevent this, but add runtime check for edge cases (untyped JS, version mismatches, etc.)
570
525
  if (!paymentMaker.generateJWT) {
571
- const availableNetworks = Array.from(this.paymentMakers.keys()).join(', ');
572
- throw new Error(`Payment maker is missing generateJWT method. Available payment makers: [${availableNetworks}]. This indicates the payment maker object does not implement the PaymentMaker interface. If using TypeScript, ensure your payment maker properly implements the PaymentMaker interface.`);
526
+ const paymentMakerCount = this.account.paymentMakers.length;
527
+ throw new Error(`Payment maker is missing generateJWT method. Available payment maker count: ${paymentMakerCount}. This indicates the payment maker object does not implement the PaymentMaker interface. If using TypeScript, ensure your payment maker properly implements the PaymentMaker interface.`);
573
528
  }
574
- const authToken = await paymentMaker.generateJWT({ paymentRequestId: '', codeChallenge: codeChallenge });
529
+ const authToken = await paymentMaker.generateJWT({ paymentRequestId: '', codeChallenge: codeChallenge, accountId: this.account.accountId });
575
530
  // Make a fetch call to the authorization URL with the payment ID
576
531
  // redirect=false is a hack
577
532
  // The OAuth spec calls for the authorization url to return with a redirect, but fetch
@@ -617,11 +572,11 @@ class ATXPFetcher {
617
572
  throw new Error(`Expected redirect response from authorization URL, got ${response.status}`);
618
573
  };
619
574
  this.authToService = async (error) => {
620
- // TODO: We need to generalize this - we can't assume that there's a single paymentMaker for the auth flow.
621
- if (this.paymentMakers.size > 1) {
575
+ // TODO: We need to generalize this - we can't assume that there's a single paymentMaker for the auth flow.
576
+ if (this.account.paymentMakers.length > 1) {
622
577
  throw new Error(`ATXP: multiple payment makers found - cannot determine which one to use for auth`);
623
578
  }
624
- const paymentMaker = Array.from(this.paymentMakers.values())[0];
579
+ const paymentMaker = this.account.paymentMakers[0];
625
580
  if (paymentMaker) {
626
581
  // We can do the full OAuth flow - we'll generate a signed JWT and call /authorize on the
627
582
  // AS to get a code, then exchange the code for an access token
@@ -636,14 +591,14 @@ class ATXPFetcher {
636
591
  // Call onAuthorize callback after successful authorization
637
592
  await this.onAuthorize({
638
593
  authorizationServer: authorizationUrl.origin,
639
- userId: this.accountId
594
+ userId: this.account.accountId
640
595
  });
641
596
  }
642
597
  catch (authError) {
643
598
  // Call onAuthorizeFailure callback if authorization fails
644
599
  await this.onAuthorizeFailure({
645
600
  authorizationServer: authorizationUrl.origin,
646
- userId: this.accountId,
601
+ userId: this.account.accountId,
647
602
  error: authError
648
603
  });
649
604
  throw authError;
@@ -654,13 +609,13 @@ class ATXPFetcher {
654
609
  // If we do, we'll use it to auth to the downstream resource
655
610
  // (In pass-through scenarios, the atxpServer() middleware stores the incoming
656
611
  // token in the DB under the '' resource URL).
657
- const existingToken = await this.db.getAccessToken(this.accountId, '');
612
+ const existingToken = await this.db.getAccessToken(this.account.accountId, '');
658
613
  if (!existingToken) {
659
614
  this.logger.info(`ATXP: no token found for the current server - we can't exchange a token if we don't have one`);
660
615
  throw error;
661
616
  }
662
617
  const newToken = await this.exchangeToken(existingToken, error.resourceServerUrl);
663
- this.db.saveAccessToken(this.accountId, error.resourceServerUrl, newToken);
618
+ this.db.saveAccessToken(this.account.accountId, error.resourceServerUrl, newToken);
664
619
  }
665
620
  };
666
621
  this.exchangeToken = async (myToken, newResourceUrl) => {
@@ -751,15 +706,15 @@ class ATXPFetcher {
751
706
  throw error;
752
707
  }
753
708
  };
754
- const { accountId, db, paymentMakers, 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 = async () => { } } = config;
709
+ 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 = async () => { } } = config;
755
710
  // Use React Native safe fetch if in React Native environment
756
711
  const safeFetchFn = common.getIsReactNative() ? common.createReactNativeSafeFetch(fetchFn) : fetchFn;
757
712
  const safeSideChannelFetch = common.getIsReactNative() ? common.createReactNativeSafeFetch(sideChannelFetch) : sideChannelFetch;
758
- // ATXPClient should never actually use the callback url - instead of redirecting the user to
713
+ // ATXPClient should never actually use the callback url - instead of redirecting the user to
759
714
  // an authorization url which redirects back to the callback url, ATXPClient posts the payment
760
715
  // directly to the authorization server, then does the token exchange itself
761
716
  this.oauthClient = new OAuthClient({
762
- userId: accountId,
717
+ userId: account.accountId,
763
718
  db,
764
719
  callbackUrl: 'http://localhost:3000/unused-dummy-atxp-callback',
765
720
  isPublic: false,
@@ -769,10 +724,10 @@ class ATXPFetcher {
769
724
  allowInsecureRequests,
770
725
  logger: logger
771
726
  });
772
- this.paymentMakers = new Map(Object.entries(paymentMakers));
727
+ this.account = account;
728
+ this.destinationMakers = destinationMakers;
773
729
  this.sideChannelFetch = safeSideChannelFetch;
774
730
  this.db = db;
775
- this.accountId = accountId;
776
731
  this.allowedAuthorizationServers = allowedAuthorizationServers;
777
732
  this.approvePayment = approvePayment;
778
733
  this.logger = logger;
@@ -15865,312 +15820,186 @@ class StreamableHTTPClientTransport {
15865
15820
  }
15866
15821
  }
15867
15822
 
15868
- // Detect if we're in a browser environment and bind fetch appropriately
15869
- const getFetch = () => {
15870
- if (typeof window !== 'undefined' && typeof window.fetch === 'function') {
15871
- // In browser, bind fetch to window to avoid "Illegal invocation" errors
15872
- return fetch.bind(window);
15873
- }
15874
- // In Node.js or other environments, use fetch as-is
15875
- return fetch;
15876
- };
15877
- const DEFAULT_CLIENT_CONFIG = {
15878
- allowedAuthorizationServers: [common.DEFAULT_AUTHORIZATION_SERVER],
15879
- approvePayment: async (_p) => true,
15880
- fetchFn: getFetch(),
15881
- oAuthChannelFetch: getFetch(),
15882
- allowHttp: false, // may be overridden in buildClientConfig by process.env.NODE_ENV
15883
- clientInfo: {
15884
- name: 'ATXPClient',
15885
- version: '0.0.1'
15886
- },
15887
- clientOptions: {
15888
- capabilities: {}
15889
- },
15890
- onAuthorize: async () => { },
15891
- onAuthorizeFailure: async () => { },
15892
- onPayment: async () => { },
15893
- onPaymentFailure: async () => { }
15894
- };
15895
- function buildClientConfig(args) {
15896
- // Use fetchFn for oAuthChannelFetch if the latter isn't explicitly set
15897
- if (args.fetchFn && !args.oAuthChannelFetch) {
15898
- args.oAuthChannelFetch = args.fetchFn;
15899
- }
15900
- // Read environment variable at runtime, not module load time
15901
- const envDefaults = {
15902
- ...DEFAULT_CLIENT_CONFIG,
15903
- allowHttp: process.env.NODE_ENV === 'development',
15904
- };
15905
- const withDefaults = { ...envDefaults, ...args };
15906
- const logger = withDefaults.logger ?? new common.ConsoleLogger();
15907
- const oAuthDb = withDefaults.oAuthDb ?? new common.MemoryOAuthDb({ logger });
15908
- const built = { oAuthDb, logger };
15909
- return Object.freeze({ ...withDefaults, ...built });
15823
+ function isDestinationResponse(obj) {
15824
+ return (typeof obj === 'object' &&
15825
+ obj !== null &&
15826
+ 'chain' in obj &&
15827
+ 'address' in obj &&
15828
+ 'currency' in obj &&
15829
+ 'amount' in obj &&
15830
+ typeof obj.chain === 'string' &&
15831
+ typeof obj.address === 'string' &&
15832
+ typeof obj.currency === 'string' &&
15833
+ typeof obj.amount === 'string');
15910
15834
  }
15911
- function buildStreamableTransport(args) {
15912
- const config = buildClientConfig(args);
15913
- // Apply the ATXP wrapper to the fetch function
15914
- const wrappedFetch = atxpFetch(config);
15915
- const transport = new StreamableHTTPClientTransport(new URL(args.mcpServer), { fetch: wrappedFetch });
15916
- return transport;
15917
- }
15918
- async function atxpClient(args) {
15919
- const config = buildClientConfig(args);
15920
- const transport = buildStreamableTransport(config);
15921
- const client = new Client(config.clientInfo, config.clientOptions);
15922
- await client.connect(transport);
15923
- return client;
15835
+ function isDestinationsApiResponse(obj) {
15836
+ return (typeof obj === 'object' &&
15837
+ obj !== null &&
15838
+ 'destinations' in obj &&
15839
+ 'paymentRequestId' in obj &&
15840
+ Array.isArray(obj.destinations) &&
15841
+ typeof obj.paymentRequestId === 'string');
15924
15842
  }
15925
-
15926
- // this is a global public key for USDC on the solana mainnet
15927
- const USDC_MINT = new web3_js.PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
15928
- const ValidateTransferError = pay.ValidateTransferError;
15929
- class SolanaPaymentMaker {
15930
- constructor(solanaEndpoint, sourceSecretKey, logger) {
15931
- this.generateJWT = async ({ paymentRequestId, codeChallenge }) => {
15932
- // Solana/Web3.js secretKey is 64 bytes:
15933
- // first 32 bytes are the private scalar, last 32 are the public key.
15934
- // JWK expects only the 32-byte private scalar for 'd'
15935
- const jwk = {
15936
- kty: 'OKP',
15937
- crv: 'Ed25519',
15938
- d: Buffer.from(this.source.secretKey.slice(0, 32)).toString('base64url'),
15939
- x: Buffer.from(this.source.publicKey.toBytes()).toString('base64url'),
15940
- };
15941
- const privateKey = await jose.importJWK(jwk, 'EdDSA');
15942
- if (!(privateKey instanceof CryptoKey)) {
15943
- throw new Error('Expected CryptoKey from importJWK');
15944
- }
15945
- return common.generateJWT(this.source.publicKey.toBase58(), privateKey, paymentRequestId || '', codeChallenge || '');
15843
+ function parseDestinationsResponse(data) {
15844
+ // Validate response structure
15845
+ if (!isDestinationsApiResponse(data)) {
15846
+ throw new Error('Invalid response: expected object with destinations array and paymentRequestId');
15847
+ }
15848
+ // Validate and convert each destination
15849
+ return data.destinations.map((dest, index) => {
15850
+ if (!isDestinationResponse(dest)) {
15851
+ throw new Error(`Invalid destination at index ${index}: missing required fields (chain, address, currency, amount)`);
15852
+ }
15853
+ // Validate chain is a valid Chain enum value
15854
+ if (!common.isEnumValue(common.ChainEnum, dest.chain)) {
15855
+ const validChains = Object.values(common.ChainEnum).join(', ');
15856
+ throw new Error(`Invalid destination at index ${index}: chain "${dest.chain}" is not a valid chain. Valid chains are: ${validChains}`);
15857
+ }
15858
+ // Validate currency is a valid Currency enum value
15859
+ if (!common.isEnumValue(common.CurrencyEnum, dest.currency)) {
15860
+ const validCurrencies = Object.values(common.CurrencyEnum).join(', ');
15861
+ throw new Error(`Invalid destination at index ${index}: currency "${dest.currency}" is not a valid currency. Valid currencies are: ${validCurrencies}`);
15862
+ }
15863
+ // Validate amount is a valid number
15864
+ const amount = new BigNumber.BigNumber(dest.amount);
15865
+ if (amount.isNaN()) {
15866
+ throw new Error(`Invalid destination at index ${index}: amount "${dest.amount}" is not a valid number`);
15867
+ }
15868
+ return {
15869
+ chain: dest.chain,
15870
+ currency: dest.currency,
15871
+ address: dest.address,
15872
+ amount: amount
15946
15873
  };
15947
- this.makePayment = async (amount, currency, receiver, memo) => {
15948
- if (currency.toUpperCase() !== 'USDC') {
15949
- throw new PaymentNetworkError('Only USDC currency is supported; received ' + currency);
15874
+ });
15875
+ }
15876
+ /**
15877
+ * Destination mapper for ATXP network destinations.
15878
+ * Converts destinations with network='atxp' to actual blockchain network destinations
15879
+ * by resolving the account ID to its associated blockchain addresses.
15880
+ */
15881
+ class ATXPDestinationMaker {
15882
+ constructor(accountsServiceUrl, fetchFn = fetch) {
15883
+ this.accountsServiceUrl = accountsServiceUrl;
15884
+ this.fetchFn = fetchFn;
15885
+ }
15886
+ async makeDestinations(option, logger, paymentRequestId, sources) {
15887
+ if (option.network !== 'atxp') {
15888
+ return [];
15889
+ }
15890
+ try {
15891
+ // The address field contains the account ID (e.g., atxp_acct_xxx) for atxp options
15892
+ const accountId = option.address;
15893
+ // Always use the destinations endpoint
15894
+ const destinations = await this.getDestinations(accountId, paymentRequestId, option, sources, logger);
15895
+ if (destinations.length === 0) {
15896
+ logger.warn(`ATXPDestinationMaker: No destinations found for account ${accountId}`);
15950
15897
  }
15951
- const receiverKey = new web3_js.PublicKey(receiver);
15952
- this.logger.info(`Making payment of ${amount} ${currency} to ${receiver} on Solana from ${this.source.publicKey.toBase58()}`);
15953
- try {
15954
- // Check balance before attempting payment
15955
- const tokenAccountAddress = await splToken.getAssociatedTokenAddress(USDC_MINT, this.source.publicKey);
15956
- const tokenAccount = await splToken.getAccount(this.connection, tokenAccountAddress);
15957
- const balance = new BigNumber(tokenAccount.amount.toString()).dividedBy(10 ** 6); // USDC has 6 decimals
15958
- if (balance.lt(amount)) {
15959
- this.logger.warn(`Insufficient ${currency} balance for payment. Required: ${amount}, Available: ${balance}`);
15960
- throw new InsufficientFundsError(currency, amount, balance, 'solana');
15961
- }
15962
- // Increase compute units to handle both memo and token transfer
15963
- // Memo uses ~6000 CUs, token transfer needs ~6500 CUs
15964
- const modifyComputeUnits = web3_js.ComputeBudgetProgram.setComputeUnitLimit({
15965
- units: 50000,
15966
- });
15967
- const addPriorityFee = web3_js.ComputeBudgetProgram.setComputeUnitPrice({
15968
- microLamports: 20000,
15969
- });
15970
- const transaction = await pay.createTransfer(this.connection, this.source.publicKey, {
15971
- amount: amount,
15972
- recipient: receiverKey,
15973
- splToken: USDC_MINT,
15974
- memo,
15975
- });
15976
- transaction.add(modifyComputeUnits);
15977
- transaction.add(addPriorityFee);
15978
- const transactionHash = await web3_js.sendAndConfirmTransaction(this.connection, transaction, [this.source]);
15979
- return transactionHash;
15898
+ else {
15899
+ logger.debug(`ATXPDestinationMaker: Got ${destinations.length} destinations for account ${accountId}`);
15980
15900
  }
15981
- catch (error) {
15982
- if (error instanceof InsufficientFundsError || error instanceof PaymentNetworkError) {
15983
- throw error;
15984
- }
15985
- // Wrap other errors in PaymentNetworkError
15986
- throw new PaymentNetworkError(`Payment failed on Solana network: ${error.message}`, error);
15901
+ return destinations;
15902
+ }
15903
+ catch (error) {
15904
+ logger.error(`ATXPDestinationMaker: Failed to make ATXP destinations: ${error}`);
15905
+ throw error;
15906
+ }
15907
+ }
15908
+ async getDestinations(accountId, paymentRequestId, option, sources, logger) {
15909
+ // Strip any network prefix if present
15910
+ const unqualifiedId = accountId.includes(':') ? accountId.split(':')[1] : accountId;
15911
+ const url = `${this.accountsServiceUrl}/account/${unqualifiedId}/destinations`;
15912
+ logger?.debug(`ATXPDestinationMaker: Fetching destinations from ${url}`);
15913
+ try {
15914
+ const requestBody = {
15915
+ paymentRequestId,
15916
+ options: [{
15917
+ network: option.network,
15918
+ currency: option.currency.toString(),
15919
+ address: option.address,
15920
+ amount: option.amount.toString()
15921
+ }],
15922
+ sources: sources
15923
+ };
15924
+ const response = await this.fetchFn(url, {
15925
+ method: 'POST',
15926
+ headers: {
15927
+ 'Accept': 'application/json',
15928
+ 'Content-Type': 'application/json',
15929
+ },
15930
+ body: JSON.stringify(requestBody),
15931
+ });
15932
+ if (!response.ok) {
15933
+ const text = await response.text();
15934
+ throw new Error(`Failed to fetch destinations: ${response.status} ${response.statusText} - ${text}`);
15987
15935
  }
15988
- };
15989
- if (!solanaEndpoint) {
15990
- throw new Error('Solana endpoint is required');
15936
+ const data = await response.json();
15937
+ return parseDestinationsResponse(data);
15991
15938
  }
15992
- if (!sourceSecretKey) {
15993
- throw new Error('Source secret key is required');
15939
+ catch (error) {
15940
+ logger?.error(`ATXPDestinationMaker: Error fetching destinations: ${error}`);
15941
+ throw error;
15994
15942
  }
15995
- this.connection = new web3_js.Connection(solanaEndpoint, { commitment: 'confirmed' });
15996
- this.source = web3_js.Keypair.fromSecretKey(bs58.decode(sourceSecretKey));
15997
- this.logger = logger ?? new common.ConsoleLogger();
15998
15943
  }
15999
15944
  }
16000
15945
 
16001
- const USDC_CONTRACT_ADDRESS_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; // USDC on Base mainnet
16002
- const USDC_CONTRACT_ADDRESS_BASE_SEPOLIA = "0x036CbD53842c5426634e7929541eC2318f3dCF7e"; // USDC on Base Sepolia testnet
16003
- /**
16004
- * Get USDC contract address for Base chain by chain ID
16005
- * @param chainId - Chain ID (8453 for mainnet, 84532 for sepolia)
16006
- * @returns USDC contract address
16007
- * @throws Error if chain ID is not supported
16008
- */
16009
- const getBaseUSDCAddress = (chainId) => {
16010
- switch (chainId) {
16011
- case 8453: // Base mainnet
16012
- return USDC_CONTRACT_ADDRESS_BASE;
16013
- case 84532: // Base Sepolia
16014
- return USDC_CONTRACT_ADDRESS_BASE_SEPOLIA;
16015
- default:
16016
- throw new Error(`Unsupported Base Chain ID: ${chainId}. Supported chains: 8453 (mainnet), 84532 (sepolia)`);
15946
+ class PassthroughDestinationMaker {
15947
+ constructor(network) {
15948
+ this.network = network;
16017
15949
  }
16018
- };
15950
+ async makeDestinations(option, _logger, _paymentRequestId, _sources) {
15951
+ if (option.network !== this.network) {
15952
+ return [];
15953
+ }
15954
+ // Check if option.network is also a Chain by inspecting the ChainEnum values
15955
+ if (Object.values(common.ChainEnum).includes(option.network)) {
15956
+ // It's a chain, so return a single passthrough destination
15957
+ const destination = {
15958
+ chain: option.network,
15959
+ currency: option.currency,
15960
+ address: option.address,
15961
+ amount: option.amount
15962
+ };
15963
+ return [destination];
15964
+ }
15965
+ return [];
15966
+ }
15967
+ }
16019
15968
 
16020
- // Helper function to convert to base64url that works in both Node.js and browsers
16021
- function toBase64Url(data) {
16022
- // Convert string to base64
16023
- const base64 = typeof Buffer !== 'undefined'
16024
- ? Buffer.from(data).toString('base64')
16025
- : btoa(data);
16026
- // Convert base64 to base64url
16027
- return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
16028
- }
16029
- const USDC_DECIMALS = 6;
16030
- const ERC20_ABI = [
16031
- {
16032
- constant: false,
16033
- inputs: [
16034
- { name: "_to", type: "address" },
16035
- { name: "_value", type: "uint256" },
16036
- ],
16037
- name: "transfer",
16038
- outputs: [{ name: "", type: "bool" }],
16039
- type: "function",
16040
- },
16041
- {
16042
- "constant": true,
16043
- "inputs": [
16044
- {
16045
- "name": "_owner",
16046
- "type": "address"
16047
- }
16048
- ],
16049
- "name": "balanceOf",
16050
- "outputs": [
16051
- {
16052
- "name": "balance",
16053
- "type": "uint256"
16054
- }
16055
- ],
16056
- "payable": false,
16057
- "stateMutability": "view",
16058
- "type": "function"
16059
- }
16060
- ];
16061
- class BasePaymentMaker {
16062
- constructor(baseRPCUrl, walletClient, logger) {
16063
- if (!baseRPCUrl) {
16064
- throw new Error('baseRPCUrl was empty');
16065
- }
16066
- if (!walletClient) {
16067
- throw new Error('walletClient was empty');
16068
- }
16069
- if (!walletClient.account) {
16070
- throw new Error('walletClient.account was empty');
16071
- }
16072
- this.signingClient = walletClient.extend(viem.publicActions);
16073
- this.logger = logger ?? new common.ConsoleLogger();
16074
- }
16075
- async generateJWT({ paymentRequestId, codeChallenge }) {
16076
- const headerObj = { alg: 'ES256K' };
16077
- const payloadObj = {
16078
- sub: this.signingClient.account.address,
16079
- iss: 'accounts.atxp.ai',
16080
- aud: 'https://auth.atxp.ai',
16081
- iat: Math.floor(Date.now() / 1000),
16082
- exp: Math.floor(Date.now() / 1000) + 60 * 60,
16083
- ...(codeChallenge ? { code_challenge: codeChallenge } : {}),
16084
- ...(paymentRequestId ? { payment_request_id: paymentRequestId } : {}),
16085
- };
16086
- const header = toBase64Url(JSON.stringify(headerObj));
16087
- const payload = toBase64Url(JSON.stringify(payloadObj));
16088
- const message = `${header}.${payload}`;
16089
- const messageBytes = typeof Buffer !== 'undefined'
16090
- ? Buffer.from(message, 'utf8')
16091
- : new TextEncoder().encode(message);
16092
- const signResult = await this.signingClient.signMessage({
16093
- account: this.signingClient.account,
16094
- message: { raw: messageBytes },
16095
- });
16096
- // For ES256K, signature is typically 65 bytes (r,s,v)
16097
- // Server expects the hex signature string (with 0x prefix) to be base64url encoded
16098
- // This creates: base64url("0x6eb2565...") not base64url(rawBytes)
16099
- // Pass the hex string directly to toBase64Url which will UTF-8 encode and base64url it
16100
- const signature = toBase64Url(signResult);
16101
- const jwt = `${header}.${payload}.${signature}`;
16102
- this.logger.info(`Generated ES256K JWT: ${jwt}`);
16103
- return jwt;
16104
- }
16105
- async makePayment(amount, currency, receiver) {
16106
- if (currency.toUpperCase() !== 'USDC') {
16107
- throw new PaymentNetworkError('Only USDC currency is supported; received ' + currency);
16108
- }
16109
- this.logger.info(`Making payment of ${amount} ${currency} to ${receiver} on Base from ${this.signingClient.account.address}`);
16110
- try {
16111
- // Check balance before attempting payment
16112
- const balanceRaw = await this.signingClient.readContract({
16113
- address: USDC_CONTRACT_ADDRESS_BASE,
16114
- abi: ERC20_ABI,
16115
- functionName: 'balanceOf',
16116
- args: [this.signingClient.account.address],
16117
- });
16118
- const balance = new BigNumber.BigNumber(balanceRaw.toString()).dividedBy(10 ** USDC_DECIMALS);
16119
- if (balance.lt(amount)) {
16120
- this.logger.warn(`Insufficient ${currency} balance for payment. Required: ${amount}, Available: ${balance}`);
16121
- throw new InsufficientFundsError(currency, amount, balance, 'base');
16122
- }
16123
- // Convert amount to USDC units (6 decimals) as BigInt
16124
- const amountInUSDCUnits = BigInt(amount.multipliedBy(10 ** USDC_DECIMALS).toFixed(0));
16125
- const data = viem.encodeFunctionData({
16126
- abi: ERC20_ABI,
16127
- functionName: "transfer",
16128
- args: [receiver, amountInUSDCUnits],
16129
- });
16130
- const hash = await this.signingClient.sendTransaction({
16131
- chain: chains.base,
16132
- account: this.signingClient.account,
16133
- to: USDC_CONTRACT_ADDRESS_BASE,
16134
- data: data,
16135
- value: viem.parseEther('0'),
16136
- maxPriorityFeePerGas: viem.parseEther('0.000000001')
16137
- });
16138
- // Wait for transaction confirmation with more blocks to ensure propagation
16139
- this.logger.info(`Waiting for transaction confirmation: ${hash}`);
16140
- const receipt = await this.signingClient.waitForTransactionReceipt({
16141
- hash: hash,
16142
- confirmations: 1
16143
- });
16144
- if (receipt.status === 'reverted') {
16145
- throw new PaymentNetworkError(`Transaction reverted: ${hash}`, new Error('Transaction reverted on chain'));
16146
- }
16147
- this.logger.info(`Transaction confirmed: ${hash} in block ${receipt.blockNumber}`);
16148
- return hash;
16149
- }
16150
- catch (error) {
16151
- if (error instanceof InsufficientFundsError || error instanceof PaymentNetworkError) {
16152
- throw error;
16153
- }
16154
- // Wrap other errors in PaymentNetworkError
16155
- throw new PaymentNetworkError(`Payment failed on Base network: ${error.message}`, error);
16156
- }
16157
- }
16158
- }
16159
-
16160
- class SolanaAccount {
16161
- constructor(solanaEndpoint, sourceSecretKey) {
16162
- if (!solanaEndpoint) {
16163
- throw new Error('Solana endpoint is required');
16164
- }
16165
- if (!sourceSecretKey) {
16166
- throw new Error('Source secret key is required');
16167
- }
16168
- const source = web3_js.Keypair.fromSecretKey(bs58.decode(sourceSecretKey));
16169
- this.accountId = source.publicKey.toBase58();
16170
- this.paymentMakers = {
16171
- 'solana': new SolanaPaymentMaker(solanaEndpoint, sourceSecretKey),
16172
- };
16173
- }
15969
+ function createDestinationMakers(config) {
15970
+ const { atxpAccountsServer, fetchFn = fetch } = config;
15971
+ // Build the map by exhaustively checking all Network values
15972
+ const makers = new Map();
15973
+ for (const network of Object.values(common.NetworkEnum)) {
15974
+ // Exhaustiveness check using switch with assertNever
15975
+ switch (network) {
15976
+ case common.NetworkEnum.Solana:
15977
+ makers.set(network, new PassthroughDestinationMaker(network));
15978
+ break;
15979
+ case common.NetworkEnum.Base:
15980
+ makers.set(network, new PassthroughDestinationMaker(network));
15981
+ break;
15982
+ case common.NetworkEnum.World:
15983
+ makers.set(network, new PassthroughDestinationMaker(network));
15984
+ break;
15985
+ case common.NetworkEnum.Polygon:
15986
+ makers.set(network, new PassthroughDestinationMaker(network));
15987
+ break;
15988
+ case common.NetworkEnum.BaseSepolia:
15989
+ makers.set(network, new PassthroughDestinationMaker(network));
15990
+ break;
15991
+ case common.NetworkEnum.WorldSepolia:
15992
+ makers.set(network, new PassthroughDestinationMaker(network));
15993
+ break;
15994
+ case common.NetworkEnum.ATXP:
15995
+ makers.set(network, new ATXPDestinationMaker(atxpAccountsServer, fetchFn));
15996
+ break;
15997
+ default:
15998
+ // This will cause a compilation error if a new Network is added but not handled above
15999
+ common.assertNever(network);
16000
+ }
16001
+ }
16002
+ return makers;
16174
16003
  }
16175
16004
 
16176
16005
  function toBasicAuth$1(token) {
@@ -16286,6 +16115,9 @@ function parseConnectionString(connectionString) {
16286
16115
  if (!token) {
16287
16116
  throw new Error('ATXPAccount: connection string missing connection token');
16288
16117
  }
16118
+ if (!accountId) {
16119
+ throw new Error('ATXPAccount: connection string missing account id');
16120
+ }
16289
16121
  return { origin, token, accountId };
16290
16122
  }
16291
16123
  class ATXPHttpPaymentMaker {
@@ -16294,8 +16126,33 @@ class ATXPHttpPaymentMaker {
16294
16126
  this.token = token;
16295
16127
  this.fetchFn = fetchFn;
16296
16128
  }
16297
- async makePayment(amount, currency, receiver, memo) {
16298
- // Make a regular payment via the /pay endpoint
16129
+ async getSourceAddress(params) {
16130
+ // Call the /address_for_payment endpoint to get the source address for this account
16131
+ const response = await this.fetchFn(`${this.origin}/address_for_payment`, {
16132
+ method: 'POST',
16133
+ headers: {
16134
+ 'Authorization': toBasicAuth(this.token),
16135
+ 'Content-Type': 'application/json',
16136
+ },
16137
+ body: JSON.stringify({
16138
+ amount: params.amount.toString(),
16139
+ currency: params.currency,
16140
+ receiver: params.receiver,
16141
+ memo: params.memo,
16142
+ }),
16143
+ });
16144
+ if (!response.ok) {
16145
+ const text = await response.text();
16146
+ throw new Error(`ATXPAccount: /address_for_payment failed: ${response.status} ${response.statusText} ${text}`);
16147
+ }
16148
+ const json = await response.json();
16149
+ if (!json?.sourceAddress) {
16150
+ throw new Error('ATXPAccount: /address_for_payment did not return sourceAddress');
16151
+ }
16152
+ return json.sourceAddress;
16153
+ }
16154
+ async makePayment(destinations, memo, paymentRequestId) {
16155
+ // Make a payment via the /pay endpoint with multiple destinations
16299
16156
  const response = await this.fetchFn(`${this.origin}/pay`, {
16300
16157
  method: 'POST',
16301
16158
  headers: {
@@ -16303,10 +16160,14 @@ class ATXPHttpPaymentMaker {
16303
16160
  'Content-Type': 'application/json',
16304
16161
  },
16305
16162
  body: JSON.stringify({
16306
- amount: amount.toString(),
16307
- currency,
16308
- receiver,
16163
+ destinations: destinations.map(d => ({
16164
+ chain: d.chain,
16165
+ address: d.address,
16166
+ amount: d.amount.toString(),
16167
+ currency: d.currency
16168
+ })),
16309
16169
  memo,
16170
+ ...(paymentRequestId && { paymentRequestId })
16310
16171
  }),
16311
16172
  });
16312
16173
  if (!response.ok) {
@@ -16314,10 +16175,22 @@ class ATXPHttpPaymentMaker {
16314
16175
  throw new Error(`ATXPAccount: /pay failed: ${response.status} ${response.statusText} ${text}`);
16315
16176
  }
16316
16177
  const json = await response.json();
16317
- if (!json?.txHash) {
16318
- throw new Error('ATXPAccount: /pay did not return txHash');
16178
+ const transactionId = json.transactionId;
16179
+ if (!transactionId) {
16180
+ throw new Error('ATXPAccount: /pay did not return transactionId or txHash');
16319
16181
  }
16320
- return json.txHash;
16182
+ if (!json?.chain) {
16183
+ throw new Error('ATXPAccount: /pay did not return chain');
16184
+ }
16185
+ if (!json?.currency) {
16186
+ throw new Error('ATXPAccount: /pay did not return currency');
16187
+ }
16188
+ return {
16189
+ transactionId,
16190
+ ...(json.transactionSubId ? { transactionSubId: json.transactionSubId } : {}),
16191
+ chain: json.chain,
16192
+ currency: json.currency
16193
+ };
16321
16194
  }
16322
16195
  async generateJWT(params) {
16323
16196
  const response = await this.fetchFn(`${this.origin}/sign`, {
@@ -16329,6 +16202,7 @@ class ATXPHttpPaymentMaker {
16329
16202
  body: JSON.stringify({
16330
16203
  paymentRequestId: params.paymentRequestId,
16331
16204
  codeChallenge: params.codeChallenge,
16205
+ ...(params.accountId ? { accountId: params.accountId } : {}),
16332
16206
  }),
16333
16207
  });
16334
16208
  if (!response.ok) {
@@ -16346,23 +16220,418 @@ class ATXPAccount {
16346
16220
  constructor(connectionString, opts) {
16347
16221
  const { origin, token, accountId } = parseConnectionString(connectionString);
16348
16222
  const fetchFn = opts?.fetchFn ?? fetch;
16349
- const network = opts?.network ?? 'base';
16350
16223
  // Store for use in X402 payment creation
16351
16224
  this.origin = origin;
16352
16225
  this.token = token;
16353
16226
  this.fetchFn = fetchFn;
16354
- if (accountId) {
16355
- this.accountId = `atxp:${accountId}`;
16227
+ // Format accountId as network:address
16228
+ // Connection string provides just the atxp_acct_xxx part (no prefix for UI)
16229
+ this.unqualifiedAccountId = accountId;
16230
+ this.accountId = `atxp:${accountId}`;
16231
+ this.paymentMakers = [
16232
+ new ATXPHttpPaymentMaker(origin, token, fetchFn)
16233
+ ];
16234
+ }
16235
+ async getSigner() {
16236
+ return ATXPLocalAccount.create(this.origin, this.token, this.fetchFn);
16237
+ }
16238
+ /**
16239
+ * Get sources for this account by calling the accounts service
16240
+ */
16241
+ async getSources() {
16242
+ // Use the unqualified account ID (without atxp: prefix) for the API call
16243
+ const response = await this.fetchFn(`${this.origin}/account/${this.unqualifiedAccountId}/sources`, {
16244
+ method: 'GET',
16245
+ headers: {
16246
+ 'Accept': 'application/json',
16247
+ }
16248
+ });
16249
+ if (!response.ok) {
16250
+ const text = await response.text();
16251
+ throw new Error(`ATXPAccount: /account/${this.unqualifiedAccountId}/sources failed: ${response.status} ${response.statusText} ${text}`);
16356
16252
  }
16357
- else {
16358
- this.accountId = `atxp:${common.crypto.randomUUID()}`;
16253
+ const json = await response.json();
16254
+ // The accounts service returns the sources array directly, not wrapped in an object
16255
+ if (!Array.isArray(json)) {
16256
+ throw new Error(`ATXPAccount: /account/${this.unqualifiedAccountId}/sources did not return sources array`);
16257
+ }
16258
+ return json;
16259
+ }
16260
+ }
16261
+
16262
+ // Detect if we're in a browser environment and bind fetch appropriately
16263
+ const getFetch = () => {
16264
+ if (typeof window !== 'undefined' && typeof window.fetch === 'function') {
16265
+ // In browser, bind fetch to window to avoid "Illegal invocation" errors
16266
+ return fetch.bind(window);
16267
+ }
16268
+ // In Node.js or other environments, use fetch as-is
16269
+ return fetch;
16270
+ };
16271
+ const DEFAULT_CLIENT_CONFIG = {
16272
+ allowedAuthorizationServers: [common.DEFAULT_AUTHORIZATION_SERVER],
16273
+ atxpAccountsServer: common.DEFAULT_ATXP_ACCOUNTS_SERVER,
16274
+ approvePayment: async (_p) => true,
16275
+ fetchFn: getFetch(),
16276
+ oAuthChannelFetch: getFetch(),
16277
+ allowHttp: false, // may be overridden in buildClientConfig by process.env.NODE_ENV
16278
+ clientInfo: {
16279
+ name: 'ATXPClient',
16280
+ version: '0.0.1'
16281
+ },
16282
+ clientOptions: {
16283
+ capabilities: {}
16284
+ },
16285
+ onAuthorize: async () => { },
16286
+ onAuthorizeFailure: async () => { },
16287
+ onPayment: async () => { },
16288
+ onPaymentFailure: async () => { }
16289
+ };
16290
+ function buildClientConfig(args) {
16291
+ // Use fetchFn for oAuthChannelFetch if the latter isn't explicitly set
16292
+ if (args.fetchFn && !args.oAuthChannelFetch) {
16293
+ args.oAuthChannelFetch = args.fetchFn;
16294
+ }
16295
+ // Read environment variable at runtime, not module load time
16296
+ const envDefaults = {
16297
+ ...DEFAULT_CLIENT_CONFIG,
16298
+ allowHttp: process.env.NODE_ENV === 'development',
16299
+ };
16300
+ const withDefaults = { ...envDefaults, ...args };
16301
+ const logger = withDefaults.logger ?? new common.ConsoleLogger();
16302
+ const oAuthDb = withDefaults.oAuthDb ?? new common.MemoryOAuthDb({ logger });
16303
+ const fetchFn = withDefaults.fetchFn;
16304
+ // Build destination makers if not provided
16305
+ let accountsServer = withDefaults.atxpAccountsServer;
16306
+ // QoL hack for unspecified accounts server - if the caller is passing an atxpAccount, then assume the origin for that
16307
+ // is what we should use for the accounts server. In practice, the only option is accounts.atxp.ai,
16308
+ // but this supports staging environment
16309
+ if (args.atxpAccountsServer === undefined && withDefaults.account && withDefaults.account instanceof ATXPAccount) {
16310
+ accountsServer = withDefaults.account.origin;
16311
+ }
16312
+ const destinationMakers = withDefaults.destinationMakers ?? createDestinationMakers({
16313
+ atxpAccountsServer: accountsServer,
16314
+ fetchFn
16315
+ });
16316
+ const built = { oAuthDb, logger, destinationMakers };
16317
+ return Object.freeze({ ...withDefaults, ...built });
16318
+ }
16319
+ function buildStreamableTransport(args) {
16320
+ const config = buildClientConfig(args);
16321
+ // Apply the ATXP wrapper to the fetch function
16322
+ const wrappedFetch = atxpFetch(config);
16323
+ const transport = new StreamableHTTPClientTransport(new URL(args.mcpServer), { fetch: wrappedFetch });
16324
+ return transport;
16325
+ }
16326
+ async function atxpClient(args) {
16327
+ const config = buildClientConfig(args);
16328
+ const transport = buildStreamableTransport(config);
16329
+ const client = new Client(config.clientInfo, config.clientOptions);
16330
+ await client.connect(transport);
16331
+ return client;
16332
+ }
16333
+
16334
+ // this is a global public key for USDC on the solana mainnet
16335
+ const USDC_MINT = new web3_js.PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
16336
+ const ValidateTransferError = pay.ValidateTransferError;
16337
+ class SolanaPaymentMaker {
16338
+ constructor(solanaEndpoint, sourceSecretKey, logger) {
16339
+ this.generateJWT = async ({ paymentRequestId, codeChallenge, accountId }) => {
16340
+ // Solana/Web3.js secretKey is 64 bytes:
16341
+ // first 32 bytes are the private scalar, last 32 are the public key.
16342
+ // JWK expects only the 32-byte private scalar for 'd'
16343
+ const jwk = {
16344
+ kty: 'OKP',
16345
+ crv: 'Ed25519',
16346
+ d: Buffer.from(this.source.secretKey.slice(0, 32)).toString('base64url'),
16347
+ x: Buffer.from(this.source.publicKey.toBytes()).toString('base64url'),
16348
+ };
16349
+ const privateKey = await jose.importJWK(jwk, 'EdDSA');
16350
+ if (!(privateKey instanceof CryptoKey)) {
16351
+ throw new Error('Expected CryptoKey from importJWK');
16352
+ }
16353
+ return common.generateJWT(this.source.publicKey.toBase58(), privateKey, paymentRequestId || '', codeChallenge || '', accountId);
16354
+ };
16355
+ this.makePayment = async (destinations, memo, _paymentRequestId) => {
16356
+ // Filter to solana chain destinations
16357
+ const solanaDestinations = destinations.filter(d => d.chain === 'solana');
16358
+ if (solanaDestinations.length === 0) {
16359
+ this.logger.debug('SolanaPaymentMaker: No solana destinations found, cannot handle payment');
16360
+ return null; // Cannot handle these destinations
16361
+ }
16362
+ // Pick first solana destination
16363
+ const dest = solanaDestinations[0];
16364
+ const amount = dest.amount;
16365
+ const currency = dest.currency;
16366
+ const receiver = dest.address;
16367
+ if (currency.toUpperCase() !== 'USDC') {
16368
+ throw new PaymentNetworkError('Only USDC currency is supported; received ' + currency);
16369
+ }
16370
+ const receiverKey = new web3_js.PublicKey(receiver);
16371
+ this.logger.info(`Making payment of ${amount} ${currency} to ${receiver} on Solana from ${this.source.publicKey.toBase58()}`);
16372
+ try {
16373
+ // Check balance before attempting payment
16374
+ const tokenAccountAddress = await splToken.getAssociatedTokenAddress(USDC_MINT, this.source.publicKey);
16375
+ const tokenAccount = await splToken.getAccount(this.connection, tokenAccountAddress);
16376
+ const balance = new BigNumber(tokenAccount.amount.toString()).dividedBy(10 ** 6); // USDC has 6 decimals
16377
+ if (balance.lt(amount)) {
16378
+ this.logger.warn(`Insufficient ${currency} balance for payment. Required: ${amount}, Available: ${balance}`);
16379
+ throw new InsufficientFundsError(currency, amount, balance, 'solana');
16380
+ }
16381
+ // Get the destination token account address (this will be the transactionId)
16382
+ const destinationTokenAccount = await splToken.getAssociatedTokenAddress(USDC_MINT, receiverKey);
16383
+ // Increase compute units to handle both memo and token transfer
16384
+ // Memo uses ~6000 CUs, token transfer needs ~6500 CUs
16385
+ const modifyComputeUnits = web3_js.ComputeBudgetProgram.setComputeUnitLimit({
16386
+ units: 50000,
16387
+ });
16388
+ const addPriorityFee = web3_js.ComputeBudgetProgram.setComputeUnitPrice({
16389
+ microLamports: 20000,
16390
+ });
16391
+ const transaction = await pay.createTransfer(this.connection, this.source.publicKey, {
16392
+ amount: amount,
16393
+ recipient: receiverKey,
16394
+ splToken: USDC_MINT,
16395
+ memo,
16396
+ });
16397
+ transaction.add(modifyComputeUnits);
16398
+ transaction.add(addPriorityFee);
16399
+ const transactionSignature = await web3_js.sendAndConfirmTransaction(this.connection, transaction, [this.source]);
16400
+ // Return transaction signature as transactionId and token account address as transactionSubId
16401
+ return {
16402
+ transactionId: transactionSignature,
16403
+ transactionSubId: destinationTokenAccount.toBase58(),
16404
+ chain: 'solana',
16405
+ currency: 'USDC'
16406
+ };
16407
+ }
16408
+ catch (error) {
16409
+ if (error instanceof InsufficientFundsError || error instanceof PaymentNetworkError) {
16410
+ throw error;
16411
+ }
16412
+ // Wrap other errors in PaymentNetworkError
16413
+ throw new PaymentNetworkError(`Payment failed on Solana network: ${error.message}`, error);
16414
+ }
16415
+ };
16416
+ if (!solanaEndpoint) {
16417
+ throw new Error('Solana endpoint is required');
16418
+ }
16419
+ if (!sourceSecretKey) {
16420
+ throw new Error('Source secret key is required');
16421
+ }
16422
+ this.connection = new web3_js.Connection(solanaEndpoint, { commitment: 'confirmed' });
16423
+ this.source = web3_js.Keypair.fromSecretKey(bs58.decode(sourceSecretKey));
16424
+ this.logger = logger ?? new common.ConsoleLogger();
16425
+ }
16426
+ getSourceAddress(_params) {
16427
+ return this.source.publicKey.toBase58();
16428
+ }
16429
+ }
16430
+
16431
+ const USDC_CONTRACT_ADDRESS_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; // USDC on Base mainnet
16432
+ const USDC_CONTRACT_ADDRESS_BASE_SEPOLIA = "0x036CbD53842c5426634e7929541eC2318f3dCF7e"; // USDC on Base Sepolia testnet
16433
+ /**
16434
+ * Get USDC contract address for Base chain by chain ID
16435
+ * @param chainId - Chain ID (8453 for mainnet, 84532 for sepolia)
16436
+ * @returns USDC contract address
16437
+ * @throws Error if chain ID is not supported
16438
+ */
16439
+ const getBaseUSDCAddress = (chainId) => {
16440
+ switch (chainId) {
16441
+ case 8453: // Base mainnet
16442
+ return USDC_CONTRACT_ADDRESS_BASE;
16443
+ case 84532: // Base Sepolia
16444
+ return USDC_CONTRACT_ADDRESS_BASE_SEPOLIA;
16445
+ default:
16446
+ throw new Error(`Unsupported Base Chain ID: ${chainId}. Supported chains: 8453 (mainnet), 84532 (sepolia)`);
16447
+ }
16448
+ };
16449
+
16450
+ // Helper function to convert to base64url that works in both Node.js and browsers
16451
+ function toBase64Url(data) {
16452
+ // Convert string to base64
16453
+ const base64 = typeof Buffer !== 'undefined'
16454
+ ? Buffer.from(data).toString('base64')
16455
+ : btoa(data);
16456
+ // Convert base64 to base64url
16457
+ return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
16458
+ }
16459
+ const USDC_DECIMALS = 6;
16460
+ const ERC20_ABI = [
16461
+ {
16462
+ constant: false,
16463
+ inputs: [
16464
+ { name: "_to", type: "address" },
16465
+ { name: "_value", type: "uint256" },
16466
+ ],
16467
+ name: "transfer",
16468
+ outputs: [{ name: "", type: "bool" }],
16469
+ type: "function",
16470
+ },
16471
+ {
16472
+ "constant": true,
16473
+ "inputs": [
16474
+ {
16475
+ "name": "_owner",
16476
+ "type": "address"
16477
+ }
16478
+ ],
16479
+ "name": "balanceOf",
16480
+ "outputs": [
16481
+ {
16482
+ "name": "balance",
16483
+ "type": "uint256"
16484
+ }
16485
+ ],
16486
+ "payable": false,
16487
+ "stateMutability": "view",
16488
+ "type": "function"
16489
+ }
16490
+ ];
16491
+ class BasePaymentMaker {
16492
+ constructor(baseRPCUrl, walletClient, logger) {
16493
+ if (!baseRPCUrl) {
16494
+ throw new Error('baseRPCUrl was empty');
16495
+ }
16496
+ if (!walletClient) {
16497
+ throw new Error('walletClient was empty');
16498
+ }
16499
+ if (!walletClient.account) {
16500
+ throw new Error('walletClient.account was empty');
16359
16501
  }
16360
- this.paymentMakers = {
16361
- [network]: new ATXPHttpPaymentMaker(origin, token, fetchFn),
16502
+ this.signingClient = walletClient.extend(viem.publicActions);
16503
+ this.logger = logger ?? new common.ConsoleLogger();
16504
+ }
16505
+ getSourceAddress(_params) {
16506
+ return this.signingClient.account.address;
16507
+ }
16508
+ async generateJWT({ paymentRequestId, codeChallenge, accountId }) {
16509
+ const headerObj = { alg: 'ES256K' };
16510
+ const payloadObj = {
16511
+ sub: this.signingClient.account.address,
16512
+ iss: 'accounts.atxp.ai',
16513
+ aud: 'https://auth.atxp.ai',
16514
+ iat: Math.floor(Date.now() / 1000),
16515
+ exp: Math.floor(Date.now() / 1000) + 60 * 60,
16516
+ ...(codeChallenge ? { code_challenge: codeChallenge } : {}),
16517
+ ...(paymentRequestId ? { payment_request_id: paymentRequestId } : {}),
16518
+ ...(accountId ? { account_id: accountId } : {}),
16362
16519
  };
16520
+ const header = toBase64Url(JSON.stringify(headerObj));
16521
+ const payload = toBase64Url(JSON.stringify(payloadObj));
16522
+ const message = `${header}.${payload}`;
16523
+ const messageBytes = typeof Buffer !== 'undefined'
16524
+ ? Buffer.from(message, 'utf8')
16525
+ : new TextEncoder().encode(message);
16526
+ const signResult = await this.signingClient.signMessage({
16527
+ account: this.signingClient.account,
16528
+ message: { raw: messageBytes },
16529
+ });
16530
+ // For ES256K, signature is typically 65 bytes (r,s,v)
16531
+ // Server expects the hex signature string (with 0x prefix) to be base64url encoded
16532
+ // This creates: base64url("0x6eb2565...") not base64url(rawBytes)
16533
+ // Pass the hex string directly to toBase64Url which will UTF-8 encode and base64url it
16534
+ const signature = toBase64Url(signResult);
16535
+ const jwt = `${header}.${payload}.${signature}`;
16536
+ this.logger.info(`Generated ES256K JWT: ${jwt}`);
16537
+ return jwt;
16363
16538
  }
16364
- async getSigner() {
16365
- return ATXPLocalAccount.create(this.origin, this.token, this.fetchFn);
16539
+ async makePayment(destinations, _memo, _paymentRequestId) {
16540
+ // Filter to base chain destinations
16541
+ const baseDestinations = destinations.filter(d => d.chain === 'base');
16542
+ if (baseDestinations.length === 0) {
16543
+ this.logger.debug('BasePaymentMaker: No base destinations found, cannot handle payment');
16544
+ return null; // Cannot handle these destinations
16545
+ }
16546
+ // Pick first base destination
16547
+ const dest = baseDestinations[0];
16548
+ const amount = dest.amount;
16549
+ const currency = dest.currency;
16550
+ const receiver = dest.address;
16551
+ if (currency.toUpperCase() !== 'USDC') {
16552
+ throw new PaymentNetworkError('Only USDC currency is supported; received ' + currency);
16553
+ }
16554
+ this.logger.info(`Making payment of ${amount} ${currency} to ${receiver} on Base from ${this.signingClient.account.address}`);
16555
+ try {
16556
+ // Check balance before attempting payment
16557
+ const balanceRaw = await this.signingClient.readContract({
16558
+ address: USDC_CONTRACT_ADDRESS_BASE,
16559
+ abi: ERC20_ABI,
16560
+ functionName: 'balanceOf',
16561
+ args: [this.signingClient.account.address],
16562
+ });
16563
+ const balance = new BigNumber.BigNumber(balanceRaw.toString()).dividedBy(10 ** USDC_DECIMALS);
16564
+ if (balance.lt(amount)) {
16565
+ this.logger.warn(`Insufficient ${currency} balance for payment. Required: ${amount}, Available: ${balance}`);
16566
+ throw new InsufficientFundsError(currency, amount, balance, 'base');
16567
+ }
16568
+ // Convert amount to USDC units (6 decimals) as BigInt
16569
+ const amountInUSDCUnits = BigInt(amount.multipliedBy(10 ** USDC_DECIMALS).toFixed(0));
16570
+ const data = viem.encodeFunctionData({
16571
+ abi: ERC20_ABI,
16572
+ functionName: "transfer",
16573
+ args: [receiver, amountInUSDCUnits],
16574
+ });
16575
+ const hash = await this.signingClient.sendTransaction({
16576
+ chain: chains.base,
16577
+ account: this.signingClient.account,
16578
+ to: USDC_CONTRACT_ADDRESS_BASE,
16579
+ data: data,
16580
+ value: viem.parseEther('0'),
16581
+ maxPriorityFeePerGas: viem.parseEther('0.000000001')
16582
+ });
16583
+ // Wait for transaction confirmation with more blocks to ensure propagation
16584
+ this.logger.info(`Waiting for transaction confirmation: ${hash}`);
16585
+ const receipt = await this.signingClient.waitForTransactionReceipt({
16586
+ hash: hash,
16587
+ confirmations: 1
16588
+ });
16589
+ if (receipt.status === 'reverted') {
16590
+ throw new PaymentNetworkError(`Transaction reverted: ${hash}`, new Error('Transaction reverted on chain'));
16591
+ }
16592
+ this.logger.info(`Transaction confirmed: ${hash} in block ${receipt.blockNumber}`);
16593
+ // Return payment result with chain and currency
16594
+ return {
16595
+ transactionId: hash,
16596
+ chain: 'base',
16597
+ currency: 'USDC'
16598
+ };
16599
+ }
16600
+ catch (error) {
16601
+ if (error instanceof InsufficientFundsError || error instanceof PaymentNetworkError) {
16602
+ throw error;
16603
+ }
16604
+ // Wrap other errors in PaymentNetworkError
16605
+ throw new PaymentNetworkError(`Payment failed on Base network: ${error.message}`, error);
16606
+ }
16607
+ }
16608
+ }
16609
+
16610
+ class SolanaAccount {
16611
+ constructor(solanaEndpoint, sourceSecretKey) {
16612
+ if (!solanaEndpoint) {
16613
+ throw new Error('Solana endpoint is required');
16614
+ }
16615
+ if (!sourceSecretKey) {
16616
+ throw new Error('Source secret key is required');
16617
+ }
16618
+ const source = web3_js.Keypair.fromSecretKey(bs58.decode(sourceSecretKey));
16619
+ this.sourcePublicKey = source.publicKey.toBase58();
16620
+ // Format accountId as network:address
16621
+ this.accountId = `solana:${this.sourcePublicKey}`;
16622
+ this.paymentMakers = [
16623
+ new SolanaPaymentMaker(solanaEndpoint, sourceSecretKey)
16624
+ ];
16625
+ }
16626
+ /**
16627
+ * Get sources for this account
16628
+ */
16629
+ async getSources() {
16630
+ return [{
16631
+ address: this.sourcePublicKey,
16632
+ chain: 'solana',
16633
+ walletType: 'eoa'
16634
+ }];
16366
16635
  }
16367
16636
  }
16368
16637
 
@@ -16446,6 +16715,58 @@ const getWorldChainUSDCAddress = (chainId) => {
16446
16715
  }
16447
16716
  };
16448
16717
 
16718
+ const USDC_CONTRACT_ADDRESS_POLYGON_MAINNET = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"; // Native USDC on Polygon mainnet
16719
+ // Polygon Mainnet (Chain ID: 137)
16720
+ const POLYGON_MAINNET = {
16721
+ id: 137,
16722
+ name: 'Polygon',
16723
+ nativeCurrency: { name: 'MATIC', symbol: 'MATIC', decimals: 18 },
16724
+ rpcUrls: {
16725
+ default: { http: ['https://polygon-rpc.com'] }
16726
+ },
16727
+ blockExplorers: {
16728
+ default: { name: 'PolygonScan', url: 'https://polygonscan.com' }
16729
+ }
16730
+ };
16731
+ /**
16732
+ * Get Polygon Mainnet configuration with custom RPC URL (e.g., with API key)
16733
+ * @param rpcUrl - Custom RPC URL, e.g., 'https://polygon-mainnet.g.alchemy.com/v2/YOUR_API_KEY'
16734
+ */
16735
+ const getPolygonMainnetWithRPC = (rpcUrl) => ({
16736
+ ...POLYGON_MAINNET,
16737
+ rpcUrls: {
16738
+ default: { http: [rpcUrl] }
16739
+ }
16740
+ });
16741
+ /**
16742
+ * Get Polygon Chain configuration by chain ID
16743
+ * @param chainId - Chain ID (137 for mainnet)
16744
+ * @returns Polygon Chain configuration
16745
+ * @throws Error if chain ID is not supported
16746
+ */
16747
+ const getPolygonByChainId = (chainId) => {
16748
+ switch (chainId) {
16749
+ case 137:
16750
+ return POLYGON_MAINNET;
16751
+ default:
16752
+ throw new Error(`Unsupported Polygon Chain ID: ${chainId}. Supported chains: 137 (mainnet)`);
16753
+ }
16754
+ };
16755
+ /**
16756
+ * Get USDC contract address for Polygon by chain ID
16757
+ * @param chainId - Chain ID (137 for mainnet)
16758
+ * @returns USDC contract address
16759
+ * @throws Error if chain ID is not supported
16760
+ */
16761
+ const getPolygonUSDCAddress = (chainId) => {
16762
+ switch (chainId) {
16763
+ case 137:
16764
+ return USDC_CONTRACT_ADDRESS_POLYGON_MAINNET;
16765
+ default:
16766
+ throw new Error(`Unsupported Polygon Chain ID: ${chainId}. Supported chains: 137 (mainnet)`);
16767
+ }
16768
+ };
16769
+
16449
16770
  class BaseAccount {
16450
16771
  constructor(baseRPCUrl, sourceSecretKey) {
16451
16772
  if (!baseRPCUrl) {
@@ -16455,15 +16776,16 @@ class BaseAccount {
16455
16776
  throw new Error('Source secret key is required');
16456
16777
  }
16457
16778
  this.account = accounts.privateKeyToAccount(sourceSecretKey);
16458
- this.accountId = this.account.address;
16779
+ // Format accountId as network:address
16780
+ this.accountId = `base:${this.account.address}`;
16459
16781
  this.walletClient = viem.createWalletClient({
16460
16782
  account: this.account,
16461
16783
  chain: chains.base,
16462
16784
  transport: viem.http(baseRPCUrl),
16463
16785
  });
16464
- this.paymentMakers = {
16465
- 'base': new BasePaymentMaker(baseRPCUrl, this.walletClient),
16466
- };
16786
+ this.paymentMakers = [
16787
+ new BasePaymentMaker(baseRPCUrl, this.walletClient)
16788
+ ];
16467
16789
  }
16468
16790
  /**
16469
16791
  * Get a signer that can be used with the x402 library
@@ -16473,9 +16795,20 @@ class BaseAccount {
16473
16795
  // Return the viem account directly - it implements LocalAccount interface
16474
16796
  return this.account;
16475
16797
  }
16798
+ /**
16799
+ * Get sources for this account
16800
+ */
16801
+ async getSources() {
16802
+ return [{
16803
+ address: this.account.address,
16804
+ chain: 'base',
16805
+ walletType: 'eoa'
16806
+ }];
16807
+ }
16476
16808
  }
16477
16809
 
16478
16810
  exports.ATXPAccount = ATXPAccount;
16811
+ exports.ATXPDestinationMaker = ATXPDestinationMaker;
16479
16812
  exports.ATXPLocalAccount = ATXPLocalAccount;
16480
16813
  exports.BaseAccount = BaseAccount;
16481
16814
  exports.BasePaymentMaker = BasePaymentMaker;
@@ -16483,11 +16816,14 @@ exports.DEFAULT_CLIENT_CONFIG = DEFAULT_CLIENT_CONFIG;
16483
16816
  exports.InsufficientFundsError = InsufficientFundsError;
16484
16817
  exports.OAuthAuthenticationRequiredError = OAuthAuthenticationRequiredError;
16485
16818
  exports.OAuthClient = OAuthClient;
16819
+ exports.POLYGON_MAINNET = POLYGON_MAINNET;
16820
+ exports.PassthroughDestinationMaker = PassthroughDestinationMaker;
16486
16821
  exports.PaymentNetworkError = PaymentNetworkError;
16487
16822
  exports.SolanaAccount = SolanaAccount;
16488
16823
  exports.SolanaPaymentMaker = SolanaPaymentMaker;
16489
16824
  exports.USDC_CONTRACT_ADDRESS_BASE = USDC_CONTRACT_ADDRESS_BASE;
16490
16825
  exports.USDC_CONTRACT_ADDRESS_BASE_SEPOLIA = USDC_CONTRACT_ADDRESS_BASE_SEPOLIA;
16826
+ exports.USDC_CONTRACT_ADDRESS_POLYGON_MAINNET = USDC_CONTRACT_ADDRESS_POLYGON_MAINNET;
16491
16827
  exports.USDC_CONTRACT_ADDRESS_WORLD_MAINNET = USDC_CONTRACT_ADDRESS_WORLD_MAINNET;
16492
16828
  exports.USDC_CONTRACT_ADDRESS_WORLD_SEPOLIA = USDC_CONTRACT_ADDRESS_WORLD_SEPOLIA;
16493
16829
  exports.ValidateTransferError = ValidateTransferError;
@@ -16498,6 +16834,9 @@ exports.atxpFetch = atxpFetch;
16498
16834
  exports.buildClientConfig = buildClientConfig;
16499
16835
  exports.buildStreamableTransport = buildStreamableTransport;
16500
16836
  exports.getBaseUSDCAddress = getBaseUSDCAddress;
16837
+ exports.getPolygonByChainId = getPolygonByChainId;
16838
+ exports.getPolygonMainnetWithRPC = getPolygonMainnetWithRPC;
16839
+ exports.getPolygonUSDCAddress = getPolygonUSDCAddress;
16501
16840
  exports.getWorldChainByChainId = getWorldChainByChainId;
16502
16841
  exports.getWorldChainMainnetWithRPC = getWorldChainMainnetWithRPC;
16503
16842
  exports.getWorldChainSepoliaWithRPC = getWorldChainSepoliaWithRPC;