@atxp/client 0.7.4 → 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 (55) hide show
  1. package/dist/atxpAccount.d.ts +8 -6
  2. package/dist/atxpAccount.d.ts.map +1 -1
  3. package/dist/atxpAccount.js +57 -19
  4. package/dist/atxpAccount.js.map +1 -1
  5. package/dist/atxpClient.d.ts +1 -1
  6. package/dist/atxpClient.d.ts.map +1 -1
  7. package/dist/atxpClient.js +18 -2
  8. package/dist/atxpClient.js.map +1 -1
  9. package/dist/atxpFetcher.d.ts +5 -15
  10. package/dist/atxpFetcher.d.ts.map +1 -1
  11. package/dist/atxpFetcher.js +95 -229
  12. package/dist/atxpFetcher.js.map +1 -1
  13. package/dist/baseAccount.d.ts +7 -4
  14. package/dist/baseAccount.d.ts.map +1 -1
  15. package/dist/baseAccount.js +15 -4
  16. package/dist/baseAccount.js.map +1 -1
  17. package/dist/basePaymentMaker.d.ts +4 -3
  18. package/dist/basePaymentMaker.d.ts.map +1 -1
  19. package/dist/basePaymentMaker.js +20 -3
  20. package/dist/basePaymentMaker.js.map +1 -1
  21. package/dist/clientTestHelpers.d.ts.map +1 -1
  22. package/dist/destinationMakers/atxpDestinationMaker.d.ts +15 -0
  23. package/dist/destinationMakers/atxpDestinationMaker.d.ts.map +1 -0
  24. package/dist/destinationMakers/atxpDestinationMaker.js +128 -0
  25. package/dist/destinationMakers/atxpDestinationMaker.js.map +1 -0
  26. package/dist/destinationMakers/index.d.ts +9 -0
  27. package/dist/destinationMakers/index.d.ts.map +1 -0
  28. package/dist/destinationMakers/index.js +42 -0
  29. package/dist/destinationMakers/index.js.map +1 -0
  30. package/dist/destinationMakers/passthroughDestinationMaker.d.ts +8 -0
  31. package/dist/destinationMakers/passthroughDestinationMaker.d.ts.map +1 -0
  32. package/dist/destinationMakers/passthroughDestinationMaker.js +27 -0
  33. package/dist/destinationMakers/passthroughDestinationMaker.js.map +1 -0
  34. package/dist/index.cjs +803 -584
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.ts +99 -42
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +798 -586
  39. package/dist/index.js.map +1 -1
  40. package/dist/polygonConstants.d.ts +46 -0
  41. package/dist/polygonConstants.d.ts.map +1 -0
  42. package/dist/polygonConstants.js +54 -0
  43. package/dist/polygonConstants.js.map +1 -0
  44. package/dist/solanaAccount.d.ts +8 -4
  45. package/dist/solanaAccount.d.ts.map +1 -1
  46. package/dist/solanaAccount.js +16 -4
  47. package/dist/solanaAccount.js.map +1 -1
  48. package/dist/solanaPaymentMaker.d.ts +4 -3
  49. package/dist/solanaPaymentMaker.d.ts.map +1 -1
  50. package/dist/solanaPaymentMaker.js +24 -5
  51. package/dist/solanaPaymentMaker.js.map +1 -1
  52. package/dist/types.d.ts +5 -23
  53. package/dist/types.d.ts.map +1 -1
  54. package/dist/types.js.map +1 -1
  55. 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,43 +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
- // Convert amount to BigNumber since it comes as a string from JSON
377
- const amount = new BigNumber.BigNumber(dest.amount);
378
- // Resolve atxp_base destinations to real base network destinations
379
- let destinationAddress = dest.address;
380
- let destinationNetwork = dest.network;
381
- const resolved = await this.resolveAtxpBaseDestination(dest.network, dest.address, paymentRequestId, amount, dest.currency, dest.address, paymentRequestData.iss);
382
- if (resolved) {
383
- destinationAddress = resolved.destinationAddress;
384
- destinationNetwork = resolved.network;
385
- }
386
- const paymentMaker = this.paymentMakers.get(destinationNetwork);
387
- if (!paymentMaker) {
388
- this.logger.debug(`ATXP: payment network '${destinationNetwork}' 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`);
389
384
  continue;
390
385
  }
391
- const prospectivePayment = {
392
- accountId: this.accountId,
393
- resourceUrl: paymentRequestData.resource?.toString() ?? '',
394
- resourceName: paymentRequestData.resourceName ?? '',
395
- network: destinationNetwork,
396
- currency: dest.currency,
397
- amount: amount,
398
- iss: paymentRequestData.iss ?? '',
399
- };
400
- if (!await this.approvePayment(prospectivePayment)) {
401
- this.logger.info(`ATXP: payment request denied by callback function for destination on ${destinationNetwork}`);
402
- 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}`);
403
396
  }
404
- 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) {
405
417
  try {
406
- paymentId = await paymentMaker.makePayment(amount, dest.currency, destinationAddress, paymentRequestData.iss);
407
- this.logger.info(`ATXP: made payment of ${amount.toString()} ${dest.currency} on ${destinationNetwork}: ${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}`);
408
427
  await this.onPayment({ payment: prospectivePayment });
409
428
  // Submit payment to the server
410
- const jwt = await paymentMaker.generateJWT({ paymentRequestId, codeChallenge: '' });
429
+ const jwt = await paymentMaker.generateJWT({ paymentRequestId, codeChallenge: '', accountId: this.account.accountId });
411
430
  const response = await this.sideChannelFetch(paymentRequestUrl.toString(), {
412
431
  method: 'PUT',
413
432
  headers: {
@@ -415,9 +434,10 @@ class ATXPFetcher {
415
434
  'Content-Type': 'application/json'
416
435
  },
417
436
  body: JSON.stringify({
418
- transactionId: paymentId,
419
- network: destinationNetwork,
420
- currency: dest.currency
437
+ transactionId: result.transactionId,
438
+ ...(result.transactionSubId ? { transactionSubId: result.transactionSubId } : {}),
439
+ chain: result.chain,
440
+ currency: result.currency
421
441
  })
422
442
  });
423
443
  this.logger.debug(`ATXP: payment was ${response.ok ? 'successfully' : 'not successfully'} PUT to ${paymentRequestUrl} : status ${response.status} ${response.statusText}`);
@@ -430,13 +450,18 @@ class ATXPFetcher {
430
450
  }
431
451
  catch (error) {
432
452
  const typedError = error;
433
- this.logger.warn(`ATXP: payment failed on ${destinationNetwork}: ${typedError.message}`);
453
+ paymentAttempted = true;
454
+ lastPaymentError = typedError;
455
+ this.logger.warn(`ATXP: payment maker failed: ${typedError.message}`);
434
456
  await this.onPaymentFailure({ payment: prospectivePayment, error: typedError });
435
- // Try next destination
436
- continue;
457
+ // Continue to next payment maker
437
458
  }
438
459
  }
439
- 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`);
440
465
  return false;
441
466
  };
442
467
  this.handlePaymentRequestError = async (paymentRequestError) => {
@@ -463,102 +488,8 @@ class ATXPFetcher {
463
488
  if (paymentRequestData.destinations && paymentRequestData.destinations.length > 0) {
464
489
  return this.handleMultiDestinationPayment(paymentRequestData, paymentRequestUrl, paymentRequestId);
465
490
  }
466
- // Handle legacy single destination format
467
- const requestedNetwork = paymentRequestData.network;
468
- if (!requestedNetwork) {
469
- throw new Error(`Payment network not provided`);
470
- }
471
- const destination = paymentRequestData.destination;
472
- if (!destination) {
473
- throw new Error(`destination not provided`);
474
- }
475
- let amount = new BigNumber.BigNumber(0);
476
- if (!paymentRequestData.amount) {
477
- throw new Error(`amount not provided`);
478
- }
479
- try {
480
- amount = new BigNumber.BigNumber(paymentRequestData.amount);
481
- }
482
- catch {
483
- throw new Error(`Invalid amount ${paymentRequestData.amount}`);
484
- }
485
- if (amount.lte(0)) {
486
- throw new Error(`Invalid amount ${paymentRequestData.amount}`);
487
- }
488
- const currency = paymentRequestData.currency;
489
- if (!currency) {
490
- throw new Error(`Currency not provided`);
491
- }
492
- // Resolve atxp_base destinations to real base network destinations
493
- let destinationAddress = destination;
494
- let destinationNetwork = requestedNetwork;
495
- const resolved = await this.resolveAtxpBaseDestination(requestedNetwork, destination, paymentRequestId, amount, currency, destination, paymentRequestData.iss);
496
- if (resolved) {
497
- destinationAddress = resolved.destinationAddress;
498
- destinationNetwork = resolved.network;
499
- }
500
- const paymentMaker = this.paymentMakers.get(destinationNetwork);
501
- if (!paymentMaker) {
502
- this.logger.info(`ATXP: payment network '${destinationNetwork}' not set up for this client (available networks: ${Array.from(this.paymentMakers.keys()).join(', ')})`);
503
- return false;
504
- }
505
- const prospectivePayment = {
506
- accountId: this.accountId,
507
- resourceUrl: paymentRequestData.resource?.toString() ?? '',
508
- resourceName: paymentRequestData.resourceName ?? '',
509
- network: destinationNetwork,
510
- currency,
511
- amount,
512
- iss: paymentRequestData.iss ?? '',
513
- };
514
- if (!await this.approvePayment(prospectivePayment)) {
515
- this.logger.info(`ATXP: payment request denied by callback function`);
516
- return false;
517
- }
518
- let paymentId;
519
- try {
520
- paymentId = await paymentMaker.makePayment(amount, currency, destinationAddress, paymentRequestData.iss);
521
- this.logger.info(`ATXP: made payment of ${amount} ${currency} on ${destinationNetwork}: ${paymentId}`);
522
- // Call onPayment callback after successful payment
523
- await this.onPayment({ payment: prospectivePayment });
524
- }
525
- catch (paymentError) {
526
- // Call onPaymentFailure callback if payment fails
527
- await this.onPaymentFailure({
528
- payment: prospectivePayment,
529
- error: paymentError
530
- });
531
- throw paymentError;
532
- }
533
- const jwt = await paymentMaker.generateJWT({ paymentRequestId, codeChallenge: '' });
534
- // Make a fetch call to the authorization URL with the payment ID
535
- // redirect=false is a hack
536
- // The OAuth spec calls for the authorization url to return with a redirect, but fetch
537
- // on mobile will automatically follow the redirect (it doesn't support the redirect=manual option)
538
- // We want the redirect URL so we can extract the code from it, not the contents of the
539
- // redirect URL (which might not even exist for agentic ATXP clients)
540
- // So ATXP servers are set up to instead return a 200 with the redirect URL in the body
541
- // if we pass redirect=false.
542
- // TODO: Remove the redirect=false hack once we have a way to handle the redirect on mobile
543
- const response = await this.sideChannelFetch(paymentRequestUrl.toString(), {
544
- method: 'PUT',
545
- headers: {
546
- 'Authorization': `Bearer ${jwt}`,
547
- 'Content-Type': 'application/json'
548
- },
549
- body: JSON.stringify({
550
- transactionId: paymentId,
551
- network: destinationNetwork,
552
- currency: currency
553
- })
554
- });
555
- this.logger.debug(`ATXP: payment was ${response.ok ? 'successfully' : 'not successfully'} PUT to ${paymentRequestUrl} : status ${response.status} ${response.statusText}`);
556
- if (!response.ok) {
557
- const msg = `ATXP: payment to ${paymentRequestUrl} failed: HTTP ${response.status} ${await response.text()}`;
558
- this.logger.info(msg);
559
- throw new Error(msg);
560
- }
561
- 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`);
562
493
  };
563
494
  this.getPaymentRequestData = async (paymentRequestUrl) => {
564
495
  const prRequest = await this.sideChannelFetch(paymentRequestUrl);
@@ -566,6 +497,14 @@ class ATXPFetcher {
566
497
  throw new Error(`ATXP: GET ${paymentRequestUrl} failed: ${prRequest.status} ${prRequest.statusText}`);
567
498
  }
568
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
+ }
569
508
  return paymentRequest;
570
509
  };
571
510
  this.isAllowedAuthServer = (url) => {
@@ -579,15 +518,15 @@ class ATXPFetcher {
579
518
  throw new Error(`Code challenge not provided`);
580
519
  }
581
520
  if (!paymentMaker) {
582
- const availableNetworks = Array.from(this.paymentMakers.keys()).join(', ');
583
- 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.`);
584
523
  }
585
524
  // TypeScript should prevent this, but add runtime check for edge cases (untyped JS, version mismatches, etc.)
586
525
  if (!paymentMaker.generateJWT) {
587
- const availableNetworks = Array.from(this.paymentMakers.keys()).join(', ');
588
- 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.`);
589
528
  }
590
- const authToken = await paymentMaker.generateJWT({ paymentRequestId: '', codeChallenge: codeChallenge });
529
+ const authToken = await paymentMaker.generateJWT({ paymentRequestId: '', codeChallenge: codeChallenge, accountId: this.account.accountId });
591
530
  // Make a fetch call to the authorization URL with the payment ID
592
531
  // redirect=false is a hack
593
532
  // The OAuth spec calls for the authorization url to return with a redirect, but fetch
@@ -633,11 +572,11 @@ class ATXPFetcher {
633
572
  throw new Error(`Expected redirect response from authorization URL, got ${response.status}`);
634
573
  };
635
574
  this.authToService = async (error) => {
636
- // TODO: We need to generalize this - we can't assume that there's a single paymentMaker for the auth flow.
637
- 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) {
638
577
  throw new Error(`ATXP: multiple payment makers found - cannot determine which one to use for auth`);
639
578
  }
640
- const paymentMaker = Array.from(this.paymentMakers.values())[0];
579
+ const paymentMaker = this.account.paymentMakers[0];
641
580
  if (paymentMaker) {
642
581
  // We can do the full OAuth flow - we'll generate a signed JWT and call /authorize on the
643
582
  // AS to get a code, then exchange the code for an access token
@@ -652,14 +591,14 @@ class ATXPFetcher {
652
591
  // Call onAuthorize callback after successful authorization
653
592
  await this.onAuthorize({
654
593
  authorizationServer: authorizationUrl.origin,
655
- userId: this.accountId
594
+ userId: this.account.accountId
656
595
  });
657
596
  }
658
597
  catch (authError) {
659
598
  // Call onAuthorizeFailure callback if authorization fails
660
599
  await this.onAuthorizeFailure({
661
600
  authorizationServer: authorizationUrl.origin,
662
- userId: this.accountId,
601
+ userId: this.account.accountId,
663
602
  error: authError
664
603
  });
665
604
  throw authError;
@@ -670,13 +609,13 @@ class ATXPFetcher {
670
609
  // If we do, we'll use it to auth to the downstream resource
671
610
  // (In pass-through scenarios, the atxpServer() middleware stores the incoming
672
611
  // token in the DB under the '' resource URL).
673
- const existingToken = await this.db.getAccessToken(this.accountId, '');
612
+ const existingToken = await this.db.getAccessToken(this.account.accountId, '');
674
613
  if (!existingToken) {
675
614
  this.logger.info(`ATXP: no token found for the current server - we can't exchange a token if we don't have one`);
676
615
  throw error;
677
616
  }
678
617
  const newToken = await this.exchangeToken(existingToken, error.resourceServerUrl);
679
- this.db.saveAccessToken(this.accountId, error.resourceServerUrl, newToken);
618
+ this.db.saveAccessToken(this.account.accountId, error.resourceServerUrl, newToken);
680
619
  }
681
620
  };
682
621
  this.exchangeToken = async (myToken, newResourceUrl) => {
@@ -767,15 +706,15 @@ class ATXPFetcher {
767
706
  throw error;
768
707
  }
769
708
  };
770
- 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;
771
710
  // Use React Native safe fetch if in React Native environment
772
711
  const safeFetchFn = common.getIsReactNative() ? common.createReactNativeSafeFetch(fetchFn) : fetchFn;
773
712
  const safeSideChannelFetch = common.getIsReactNative() ? common.createReactNativeSafeFetch(sideChannelFetch) : sideChannelFetch;
774
- // 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
775
714
  // an authorization url which redirects back to the callback url, ATXPClient posts the payment
776
715
  // directly to the authorization server, then does the token exchange itself
777
716
  this.oauthClient = new OAuthClient({
778
- userId: accountId,
717
+ userId: account.accountId,
779
718
  db,
780
719
  callbackUrl: 'http://localhost:3000/unused-dummy-atxp-callback',
781
720
  isPublic: false,
@@ -785,10 +724,10 @@ class ATXPFetcher {
785
724
  allowInsecureRequests,
786
725
  logger: logger
787
726
  });
788
- this.paymentMakers = new Map(Object.entries(paymentMakers));
727
+ this.account = account;
728
+ this.destinationMakers = destinationMakers;
789
729
  this.sideChannelFetch = safeSideChannelFetch;
790
730
  this.db = db;
791
- this.accountId = accountId;
792
731
  this.allowedAuthorizationServers = allowedAuthorizationServers;
793
732
  this.approvePayment = approvePayment;
794
733
  this.logger = logger;
@@ -797,79 +736,6 @@ class ATXPFetcher {
797
736
  this.onPayment = onPayment;
798
737
  this.onPaymentFailure = onPaymentFailure || this.defaultPaymentFailureHandler;
799
738
  }
800
- /**
801
- * Resolves atxp_base or atxp_base_sepolia destinations to real base network destinations
802
- * by calling the payment_info endpoint to get the destination address and network
803
- */
804
- async resolveAtxpBaseDestination(network, paymentInfoUrl, paymentRequestId, amount, currency, receiver, memo) {
805
- // Check if this is an atxp_base network that needs resolution
806
- if (network !== 'atxp_base' && network !== 'atxp_base_sepolia') {
807
- return null;
808
- }
809
- // Map atxp_base networks to their real counterparts
810
- const realNetwork = network === 'atxp_base' ? 'base' : 'base_sepolia';
811
- // Get the payment maker for the real network
812
- const paymentMaker = this.paymentMakers.get(realNetwork);
813
- if (!paymentMaker) {
814
- this.logger.debug(`ATXP: payment network '${realNetwork}' not available for atxp_base resolution`);
815
- return null;
816
- }
817
- // Get the buyer address (source address) from the payment maker
818
- let buyerAddress;
819
- try {
820
- buyerAddress = await paymentMaker.getSourceAddress({
821
- amount,
822
- currency,
823
- receiver,
824
- memo
825
- });
826
- }
827
- catch (error) {
828
- this.logger.warn(`ATXP: failed to get source address from payment maker for ${realNetwork}: ${error.message}`);
829
- return null;
830
- }
831
- // Call the payment_info endpoint
832
- this.logger.debug(`ATXP: resolving ${network} destination via ${paymentInfoUrl}`);
833
- try {
834
- const response = await this.sideChannelFetch(paymentInfoUrl, {
835
- method: 'POST',
836
- headers: {
837
- 'Content-Type': 'application/json',
838
- },
839
- body: JSON.stringify({
840
- paymentRequestId,
841
- buyerAddress,
842
- }),
843
- });
844
- if (!response.ok) {
845
- const text = await response.text();
846
- this.logger.warn(`ATXP: payment_info endpoint failed: ${response.status} ${response.statusText} ${text}`);
847
- return null;
848
- }
849
- const data = await response.json();
850
- if (data.status !== 'success') {
851
- this.logger.warn(`ATXP: payment_info endpoint returned non-success status: ${JSON.stringify(data)}`);
852
- return null;
853
- }
854
- if (!data.destinationAddress) {
855
- this.logger.warn(`ATXP: payment_info endpoint did not return destinationAddress`);
856
- return null;
857
- }
858
- if (!data.network) {
859
- this.logger.warn(`ATXP: payment_info endpoint did not return network`);
860
- return null;
861
- }
862
- this.logger.info(`ATXP: resolved ${network} destination to ${data.destinationAddress} on ${data.network}`);
863
- return {
864
- destinationAddress: data.destinationAddress,
865
- network: data.network,
866
- };
867
- }
868
- catch (error) {
869
- this.logger.warn(`ATXP: failed to resolve ${network} destination: ${error.message}`);
870
- return null;
871
- }
872
- }
873
739
  }
874
740
 
875
741
  var util$1;
@@ -15954,146 +15820,618 @@ class StreamableHTTPClientTransport {
15954
15820
  }
15955
15821
  }
15956
15822
 
15957
- // Detect if we're in a browser environment and bind fetch appropriately
15958
- const getFetch = () => {
15959
- if (typeof window !== 'undefined' && typeof window.fetch === 'function') {
15960
- // In browser, bind fetch to window to avoid "Illegal invocation" errors
15961
- return fetch.bind(window);
15962
- }
15963
- // In Node.js or other environments, use fetch as-is
15964
- return fetch;
15965
- };
15966
- const DEFAULT_CLIENT_CONFIG = {
15967
- allowedAuthorizationServers: [common.DEFAULT_AUTHORIZATION_SERVER],
15968
- approvePayment: async (_p) => true,
15969
- fetchFn: getFetch(),
15970
- oAuthChannelFetch: getFetch(),
15971
- allowHttp: false, // may be overridden in buildClientConfig by process.env.NODE_ENV
15972
- clientInfo: {
15973
- name: 'ATXPClient',
15974
- version: '0.0.1'
15975
- },
15976
- clientOptions: {
15977
- capabilities: {}
15978
- },
15979
- onAuthorize: async () => { },
15980
- onAuthorizeFailure: async () => { },
15981
- onPayment: async () => { },
15982
- onPaymentFailure: async () => { }
15983
- };
15984
- function buildClientConfig(args) {
15985
- // Use fetchFn for oAuthChannelFetch if the latter isn't explicitly set
15986
- if (args.fetchFn && !args.oAuthChannelFetch) {
15987
- args.oAuthChannelFetch = args.fetchFn;
15988
- }
15989
- // Read environment variable at runtime, not module load time
15990
- const envDefaults = {
15991
- ...DEFAULT_CLIENT_CONFIG,
15992
- allowHttp: process.env.NODE_ENV === 'development',
15993
- };
15994
- const withDefaults = { ...envDefaults, ...args };
15995
- const logger = withDefaults.logger ?? new common.ConsoleLogger();
15996
- const oAuthDb = withDefaults.oAuthDb ?? new common.MemoryOAuthDb({ logger });
15997
- const built = { oAuthDb, logger };
15998
- return Object.freeze({ ...withDefaults, ...built });
15999
- }
16000
- function buildStreamableTransport(args) {
16001
- const config = buildClientConfig(args);
16002
- // Apply the ATXP wrapper to the fetch function
16003
- const wrappedFetch = atxpFetch(config);
16004
- const transport = new StreamableHTTPClientTransport(new URL(args.mcpServer), { fetch: wrappedFetch });
16005
- return transport;
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');
16006
15834
  }
16007
- async function atxpClient(args) {
16008
- const config = buildClientConfig(args);
16009
- const transport = buildStreamableTransport(config);
16010
- const client = new Client(config.clientInfo, config.clientOptions);
16011
- await client.connect(transport);
16012
- 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');
16013
15842
  }
16014
-
16015
- // this is a global public key for USDC on the solana mainnet
16016
- const USDC_MINT = new web3_js.PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
16017
- const ValidateTransferError = pay.ValidateTransferError;
16018
- class SolanaPaymentMaker {
16019
- constructor(solanaEndpoint, sourceSecretKey, logger) {
16020
- this.generateJWT = async ({ paymentRequestId, codeChallenge }) => {
16021
- // Solana/Web3.js secretKey is 64 bytes:
16022
- // first 32 bytes are the private scalar, last 32 are the public key.
16023
- // JWK expects only the 32-byte private scalar for 'd'
16024
- const jwk = {
16025
- kty: 'OKP',
16026
- crv: 'Ed25519',
16027
- d: Buffer.from(this.source.secretKey.slice(0, 32)).toString('base64url'),
16028
- x: Buffer.from(this.source.publicKey.toBytes()).toString('base64url'),
16029
- };
16030
- const privateKey = await jose.importJWK(jwk, 'EdDSA');
16031
- if (!(privateKey instanceof CryptoKey)) {
16032
- throw new Error('Expected CryptoKey from importJWK');
16033
- }
16034
- 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
16035
15873
  };
16036
- this.makePayment = async (amount, currency, receiver, memo) => {
16037
- if (currency.toUpperCase() !== 'USDC') {
16038
- throw new PaymentNetworkError('Only USDC currency is supported; received ' + currency);
16039
- }
16040
- const receiverKey = new web3_js.PublicKey(receiver);
16041
- this.logger.info(`Making payment of ${amount} ${currency} to ${receiver} on Solana from ${this.source.publicKey.toBase58()}`);
16042
- try {
16043
- // Check balance before attempting payment
16044
- const tokenAccountAddress = await splToken.getAssociatedTokenAddress(USDC_MINT, this.source.publicKey);
16045
- const tokenAccount = await splToken.getAccount(this.connection, tokenAccountAddress);
16046
- const balance = new BigNumber(tokenAccount.amount.toString()).dividedBy(10 ** 6); // USDC has 6 decimals
16047
- if (balance.lt(amount)) {
16048
- this.logger.warn(`Insufficient ${currency} balance for payment. Required: ${amount}, Available: ${balance}`);
16049
- throw new InsufficientFundsError(currency, amount, balance, 'solana');
16050
- }
16051
- // Increase compute units to handle both memo and token transfer
16052
- // Memo uses ~6000 CUs, token transfer needs ~6500 CUs
16053
- const modifyComputeUnits = web3_js.ComputeBudgetProgram.setComputeUnitLimit({
16054
- units: 50000,
16055
- });
16056
- const addPriorityFee = web3_js.ComputeBudgetProgram.setComputeUnitPrice({
16057
- microLamports: 20000,
16058
- });
16059
- const transaction = await pay.createTransfer(this.connection, this.source.publicKey, {
16060
- amount: amount,
16061
- recipient: receiverKey,
16062
- splToken: USDC_MINT,
16063
- memo,
16064
- });
16065
- transaction.add(modifyComputeUnits);
16066
- transaction.add(addPriorityFee);
16067
- const transactionHash = await web3_js.sendAndConfirmTransaction(this.connection, transaction, [this.source]);
16068
- return transactionHash;
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}`);
16069
15897
  }
16070
- catch (error) {
16071
- if (error instanceof InsufficientFundsError || error instanceof PaymentNetworkError) {
16072
- throw error;
16073
- }
16074
- // Wrap other errors in PaymentNetworkError
16075
- throw new PaymentNetworkError(`Payment failed on Solana network: ${error.message}`, error);
15898
+ else {
15899
+ logger.debug(`ATXPDestinationMaker: Got ${destinations.length} destinations for account ${accountId}`);
16076
15900
  }
16077
- };
16078
- if (!solanaEndpoint) {
16079
- throw new Error('Solana endpoint is required');
15901
+ return destinations;
16080
15902
  }
16081
- if (!sourceSecretKey) {
16082
- throw new Error('Source secret key is required');
15903
+ catch (error) {
15904
+ logger.error(`ATXPDestinationMaker: Failed to make ATXP destinations: ${error}`);
15905
+ throw error;
16083
15906
  }
16084
- this.connection = new web3_js.Connection(solanaEndpoint, { commitment: 'confirmed' });
16085
- this.source = web3_js.Keypair.fromSecretKey(bs58.decode(sourceSecretKey));
16086
- this.logger = logger ?? new common.ConsoleLogger();
16087
15907
  }
16088
- getSourceAddress(_params) {
16089
- return this.source.publicKey.toBase58();
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}`);
15935
+ }
15936
+ const data = await response.json();
15937
+ return parseDestinationsResponse(data);
15938
+ }
15939
+ catch (error) {
15940
+ logger?.error(`ATXPDestinationMaker: Error fetching destinations: ${error}`);
15941
+ throw error;
15942
+ }
16090
15943
  }
16091
15944
  }
16092
15945
 
16093
- const USDC_CONTRACT_ADDRESS_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; // USDC on Base mainnet
16094
- const USDC_CONTRACT_ADDRESS_BASE_SEPOLIA = "0x036CbD53842c5426634e7929541eC2318f3dCF7e"; // USDC on Base Sepolia testnet
16095
- /**
16096
- * Get USDC contract address for Base chain by chain ID
15946
+ class PassthroughDestinationMaker {
15947
+ constructor(network) {
15948
+ this.network = network;
15949
+ }
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
+ }
15968
+
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;
16003
+ }
16004
+
16005
+ function toBasicAuth$1(token) {
16006
+ // Basic auth is base64("username:password"), password is blank
16007
+ const b64 = Buffer.from(`${token}:`).toString('base64');
16008
+ return `Basic ${b64}`;
16009
+ }
16010
+ /**
16011
+ * ATXP implementation of viem's LocalAccount interface.
16012
+ * Delegates signing operations to the accounts-x402 API.
16013
+ * Includes properties needed by x402 library for wallet client compatibility.
16014
+ */
16015
+ class ATXPLocalAccount {
16016
+ constructor(address, origin, token, fetchFn = fetch) {
16017
+ this.address = address;
16018
+ this.origin = origin;
16019
+ this.token = token;
16020
+ this.fetchFn = fetchFn;
16021
+ this.type = 'local';
16022
+ /**
16023
+ * Get public key - required by LocalAccount interface
16024
+ */
16025
+ this.publicKey = '0x0000000000000000000000000000000000000000000000000000000000000000';
16026
+ /**
16027
+ * Source - required by LocalAccount interface (set to 'custom')
16028
+ */
16029
+ this.source = 'custom';
16030
+ // x402 library expects these properties for wallet client compatibility
16031
+ this.account = this; // Self-reference for x402's isSignerWallet check
16032
+ this.chain = { id: 8453 }; // Base mainnet - could make this configurable
16033
+ this.transport = {}; // Empty transport object for x402 compatibility
16034
+ }
16035
+ /**
16036
+ * Fetch the wallet address from the /address endpoint
16037
+ */
16038
+ static async create(origin, token, fetchFn = fetch) {
16039
+ // The /address endpoint uses Basic auth like other authenticated endpoints
16040
+ // For X402, we need the Ethereum/Base address with USDC currency
16041
+ const url = new URL(`${origin}/address`);
16042
+ url.searchParams.set('network', 'base'); // X402 operates on Base
16043
+ url.searchParams.set('currency', 'USDC'); // Always USDC for X402
16044
+ const response = await fetchFn(url.toString(), {
16045
+ method: 'GET',
16046
+ headers: {
16047
+ 'Authorization': toBasicAuth$1(token)
16048
+ }
16049
+ });
16050
+ if (!response.ok) {
16051
+ const errorText = await response.text();
16052
+ throw new Error(`Failed to fetch destination address: ${response.status} ${response.statusText} ${errorText}`);
16053
+ }
16054
+ const data = await response.json();
16055
+ const address = data.address;
16056
+ if (!address) {
16057
+ throw new Error('Address endpoint did not return an address');
16058
+ }
16059
+ // Check that the account is an Ethereum/Base account (required for X402/EVM operations)
16060
+ const network = data.network;
16061
+ if (!network) {
16062
+ throw new Error('Address endpoint did not return a network');
16063
+ }
16064
+ if (network !== 'ethereum' && network !== 'base') {
16065
+ throw new Error(`ATXPLocalAccount requires an Ethereum/Base account, but got ${network} account`);
16066
+ }
16067
+ return new ATXPLocalAccount(address, origin, token, fetchFn);
16068
+ }
16069
+ /**
16070
+ * Sign a typed data structure using EIP-712
16071
+ * This is what x402 library will call for EIP-3009 authorization
16072
+ */
16073
+ async signTypedData(typedData) {
16074
+ const response = await this.fetchFn(`${this.origin}/sign-typed-data`, {
16075
+ method: 'POST',
16076
+ headers: {
16077
+ 'Authorization': toBasicAuth$1(this.token),
16078
+ 'Content-Type': 'application/json',
16079
+ },
16080
+ body: JSON.stringify({
16081
+ typedData
16082
+ })
16083
+ });
16084
+ if (!response.ok) {
16085
+ const errorText = await response.text();
16086
+ throw new Error(`Failed to sign typed data: ${response.status} ${response.statusText} ${errorText}`);
16087
+ }
16088
+ const result = await response.json();
16089
+ return result.signature;
16090
+ }
16091
+ /**
16092
+ * Sign a message - required by LocalAccount interface but not used for X402
16093
+ */
16094
+ async signMessage(_) {
16095
+ throw new Error('Message signing not implemented for ATXP local account');
16096
+ }
16097
+ /**
16098
+ * Sign a transaction - required by LocalAccount interface but not used for X402
16099
+ */
16100
+ async signTransaction(_transaction, _args) {
16101
+ throw new Error('Transaction signing not implemented for ATXP local account');
16102
+ }
16103
+ }
16104
+
16105
+ function toBasicAuth(token) {
16106
+ // Basic auth is base64("username:password"), password is blank
16107
+ const b64 = Buffer.from(`${token}:`).toString('base64');
16108
+ return `Basic ${b64}`;
16109
+ }
16110
+ function parseConnectionString(connectionString) {
16111
+ const url = new URL(connectionString);
16112
+ const origin = url.origin;
16113
+ const token = url.searchParams.get('connection_token') || '';
16114
+ const accountId = url.searchParams.get('account_id');
16115
+ if (!token) {
16116
+ throw new Error('ATXPAccount: connection string missing connection token');
16117
+ }
16118
+ if (!accountId) {
16119
+ throw new Error('ATXPAccount: connection string missing account id');
16120
+ }
16121
+ return { origin, token, accountId };
16122
+ }
16123
+ class ATXPHttpPaymentMaker {
16124
+ constructor(origin, token, fetchFn = fetch) {
16125
+ this.origin = origin;
16126
+ this.token = token;
16127
+ this.fetchFn = fetchFn;
16128
+ }
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
16156
+ const response = await this.fetchFn(`${this.origin}/pay`, {
16157
+ method: 'POST',
16158
+ headers: {
16159
+ 'Authorization': toBasicAuth(this.token),
16160
+ 'Content-Type': 'application/json',
16161
+ },
16162
+ body: JSON.stringify({
16163
+ destinations: destinations.map(d => ({
16164
+ chain: d.chain,
16165
+ address: d.address,
16166
+ amount: d.amount.toString(),
16167
+ currency: d.currency
16168
+ })),
16169
+ memo,
16170
+ ...(paymentRequestId && { paymentRequestId })
16171
+ }),
16172
+ });
16173
+ if (!response.ok) {
16174
+ const text = await response.text();
16175
+ throw new Error(`ATXPAccount: /pay failed: ${response.status} ${response.statusText} ${text}`);
16176
+ }
16177
+ const json = await response.json();
16178
+ const transactionId = json.transactionId;
16179
+ if (!transactionId) {
16180
+ throw new Error('ATXPAccount: /pay did not return transactionId or txHash');
16181
+ }
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
+ };
16194
+ }
16195
+ async generateJWT(params) {
16196
+ const response = await this.fetchFn(`${this.origin}/sign`, {
16197
+ method: 'POST',
16198
+ headers: {
16199
+ 'Authorization': toBasicAuth(this.token),
16200
+ 'Content-Type': 'application/json',
16201
+ },
16202
+ body: JSON.stringify({
16203
+ paymentRequestId: params.paymentRequestId,
16204
+ codeChallenge: params.codeChallenge,
16205
+ ...(params.accountId ? { accountId: params.accountId } : {}),
16206
+ }),
16207
+ });
16208
+ if (!response.ok) {
16209
+ const text = await response.text();
16210
+ throw new Error(`ATXPAccount: /sign failed: ${response.status} ${response.statusText} ${text}`);
16211
+ }
16212
+ const json = await response.json();
16213
+ if (!json?.jwt) {
16214
+ throw new Error('ATXPAccount: /sign did not return jwt');
16215
+ }
16216
+ return json.jwt;
16217
+ }
16218
+ }
16219
+ class ATXPAccount {
16220
+ constructor(connectionString, opts) {
16221
+ const { origin, token, accountId } = parseConnectionString(connectionString);
16222
+ const fetchFn = opts?.fetchFn ?? fetch;
16223
+ // Store for use in X402 payment creation
16224
+ this.origin = origin;
16225
+ this.token = token;
16226
+ this.fetchFn = fetchFn;
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}`);
16252
+ }
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
16097
16435
  * @param chainId - Chain ID (8453 for mainnet, 84532 for sepolia)
16098
16436
  * @returns USDC contract address
16099
16437
  * @throws Error if chain ID is not supported
@@ -16167,7 +16505,7 @@ class BasePaymentMaker {
16167
16505
  getSourceAddress(_params) {
16168
16506
  return this.signingClient.account.address;
16169
16507
  }
16170
- async generateJWT({ paymentRequestId, codeChallenge }) {
16508
+ async generateJWT({ paymentRequestId, codeChallenge, accountId }) {
16171
16509
  const headerObj = { alg: 'ES256K' };
16172
16510
  const payloadObj = {
16173
16511
  sub: this.signingClient.account.address,
@@ -16177,6 +16515,7 @@ class BasePaymentMaker {
16177
16515
  exp: Math.floor(Date.now() / 1000) + 60 * 60,
16178
16516
  ...(codeChallenge ? { code_challenge: codeChallenge } : {}),
16179
16517
  ...(paymentRequestId ? { payment_request_id: paymentRequestId } : {}),
16518
+ ...(accountId ? { account_id: accountId } : {}),
16180
16519
  };
16181
16520
  const header = toBase64Url(JSON.stringify(headerObj));
16182
16521
  const payload = toBase64Url(JSON.stringify(payloadObj));
@@ -16197,7 +16536,18 @@ class BasePaymentMaker {
16197
16536
  this.logger.info(`Generated ES256K JWT: ${jwt}`);
16198
16537
  return jwt;
16199
16538
  }
16200
- async makePayment(amount, currency, receiver) {
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;
16201
16551
  if (currency.toUpperCase() !== 'USDC') {
16202
16552
  throw new PaymentNetworkError('Only USDC currency is supported; received ' + currency);
16203
16553
  }
@@ -16240,7 +16590,12 @@ class BasePaymentMaker {
16240
16590
  throw new PaymentNetworkError(`Transaction reverted: ${hash}`, new Error('Transaction reverted on chain'));
16241
16591
  }
16242
16592
  this.logger.info(`Transaction confirmed: ${hash} in block ${receipt.blockNumber}`);
16243
- return hash;
16593
+ // Return payment result with chain and currency
16594
+ return {
16595
+ transactionId: hash,
16596
+ chain: 'base',
16597
+ currency: 'USDC'
16598
+ };
16244
16599
  }
16245
16600
  catch (error) {
16246
16601
  if (error instanceof InsufficientFundsError || error instanceof PaymentNetworkError) {
@@ -16261,228 +16616,22 @@ class SolanaAccount {
16261
16616
  throw new Error('Source secret key is required');
16262
16617
  }
16263
16618
  const source = web3_js.Keypair.fromSecretKey(bs58.decode(sourceSecretKey));
16264
- this.accountId = source.publicKey.toBase58();
16265
- this.paymentMakers = {
16266
- 'solana': new SolanaPaymentMaker(solanaEndpoint, sourceSecretKey),
16267
- };
16268
- }
16269
- }
16270
-
16271
- function toBasicAuth$1(token) {
16272
- // Basic auth is base64("username:password"), password is blank
16273
- const b64 = Buffer.from(`${token}:`).toString('base64');
16274
- return `Basic ${b64}`;
16275
- }
16276
- /**
16277
- * ATXP implementation of viem's LocalAccount interface.
16278
- * Delegates signing operations to the accounts-x402 API.
16279
- * Includes properties needed by x402 library for wallet client compatibility.
16280
- */
16281
- class ATXPLocalAccount {
16282
- constructor(address, origin, token, fetchFn = fetch) {
16283
- this.address = address;
16284
- this.origin = origin;
16285
- this.token = token;
16286
- this.fetchFn = fetchFn;
16287
- this.type = 'local';
16288
- /**
16289
- * Get public key - required by LocalAccount interface
16290
- */
16291
- this.publicKey = '0x0000000000000000000000000000000000000000000000000000000000000000';
16292
- /**
16293
- * Source - required by LocalAccount interface (set to 'custom')
16294
- */
16295
- this.source = 'custom';
16296
- // x402 library expects these properties for wallet client compatibility
16297
- this.account = this; // Self-reference for x402's isSignerWallet check
16298
- this.chain = { id: 8453 }; // Base mainnet - could make this configurable
16299
- this.transport = {}; // Empty transport object for x402 compatibility
16300
- }
16301
- /**
16302
- * Fetch the wallet address from the /address endpoint
16303
- */
16304
- static async create(origin, token, fetchFn = fetch) {
16305
- // The /address endpoint uses Basic auth like other authenticated endpoints
16306
- // For X402, we need the Ethereum/Base address with USDC currency
16307
- const url = new URL(`${origin}/address`);
16308
- url.searchParams.set('network', 'base'); // X402 operates on Base
16309
- url.searchParams.set('currency', 'USDC'); // Always USDC for X402
16310
- const response = await fetchFn(url.toString(), {
16311
- method: 'GET',
16312
- headers: {
16313
- 'Authorization': toBasicAuth$1(token)
16314
- }
16315
- });
16316
- if (!response.ok) {
16317
- const errorText = await response.text();
16318
- throw new Error(`Failed to fetch destination address: ${response.status} ${response.statusText} ${errorText}`);
16319
- }
16320
- const data = await response.json();
16321
- const address = data.address;
16322
- if (!address) {
16323
- throw new Error('Address endpoint did not return an address');
16324
- }
16325
- // Check that the account is an Ethereum/Base account (required for X402/EVM operations)
16326
- const network = data.network;
16327
- if (!network) {
16328
- throw new Error('Address endpoint did not return a network');
16329
- }
16330
- if (network !== 'ethereum' && network !== 'base') {
16331
- throw new Error(`ATXPLocalAccount requires an Ethereum/Base account, but got ${network} account`);
16332
- }
16333
- return new ATXPLocalAccount(address, origin, token, fetchFn);
16334
- }
16335
- /**
16336
- * Sign a typed data structure using EIP-712
16337
- * This is what x402 library will call for EIP-3009 authorization
16338
- */
16339
- async signTypedData(typedData) {
16340
- const response = await this.fetchFn(`${this.origin}/sign-typed-data`, {
16341
- method: 'POST',
16342
- headers: {
16343
- 'Authorization': toBasicAuth$1(this.token),
16344
- 'Content-Type': 'application/json',
16345
- },
16346
- body: JSON.stringify({
16347
- typedData
16348
- })
16349
- });
16350
- if (!response.ok) {
16351
- const errorText = await response.text();
16352
- throw new Error(`Failed to sign typed data: ${response.status} ${response.statusText} ${errorText}`);
16353
- }
16354
- const result = await response.json();
16355
- return result.signature;
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
+ ];
16356
16625
  }
16357
16626
  /**
16358
- * Sign a message - required by LocalAccount interface but not used for X402
16359
- */
16360
- async signMessage(_) {
16361
- throw new Error('Message signing not implemented for ATXP local account');
16362
- }
16363
- /**
16364
- * Sign a transaction - required by LocalAccount interface but not used for X402
16627
+ * Get sources for this account
16365
16628
  */
16366
- async signTransaction(_transaction, _args) {
16367
- throw new Error('Transaction signing not implemented for ATXP local account');
16368
- }
16369
- }
16370
-
16371
- function toBasicAuth(token) {
16372
- // Basic auth is base64("username:password"), password is blank
16373
- const b64 = Buffer.from(`${token}:`).toString('base64');
16374
- return `Basic ${b64}`;
16375
- }
16376
- function parseConnectionString(connectionString) {
16377
- const url = new URL(connectionString);
16378
- const origin = url.origin;
16379
- const token = url.searchParams.get('connection_token') || '';
16380
- const accountId = url.searchParams.get('account_id');
16381
- if (!token) {
16382
- throw new Error('ATXPAccount: connection string missing connection token');
16383
- }
16384
- return { origin, token, accountId };
16385
- }
16386
- class ATXPHttpPaymentMaker {
16387
- constructor(origin, token, fetchFn = fetch) {
16388
- this.origin = origin;
16389
- this.token = token;
16390
- this.fetchFn = fetchFn;
16391
- }
16392
- async getSourceAddress(params) {
16393
- // Call the /address_for_payment endpoint to get the source address for this account
16394
- const response = await this.fetchFn(`${this.origin}/address_for_payment`, {
16395
- method: 'POST',
16396
- headers: {
16397
- 'Authorization': toBasicAuth(this.token),
16398
- 'Content-Type': 'application/json',
16399
- },
16400
- body: JSON.stringify({
16401
- amount: params.amount.toString(),
16402
- currency: params.currency,
16403
- receiver: params.receiver,
16404
- memo: params.memo,
16405
- }),
16406
- });
16407
- if (!response.ok) {
16408
- const text = await response.text();
16409
- throw new Error(`ATXPAccount: /address_for_payment failed: ${response.status} ${response.statusText} ${text}`);
16410
- }
16411
- const json = await response.json();
16412
- if (!json?.sourceAddress) {
16413
- throw new Error('ATXPAccount: /address_for_payment did not return sourceAddress');
16414
- }
16415
- return json.sourceAddress;
16416
- }
16417
- async makePayment(amount, currency, receiver, memo) {
16418
- // Make a regular payment via the /pay endpoint
16419
- const response = await this.fetchFn(`${this.origin}/pay`, {
16420
- method: 'POST',
16421
- headers: {
16422
- 'Authorization': toBasicAuth(this.token),
16423
- 'Content-Type': 'application/json',
16424
- },
16425
- body: JSON.stringify({
16426
- amount: amount.toString(),
16427
- currency,
16428
- receiver,
16429
- memo,
16430
- }),
16431
- });
16432
- if (!response.ok) {
16433
- const text = await response.text();
16434
- throw new Error(`ATXPAccount: /pay failed: ${response.status} ${response.statusText} ${text}`);
16435
- }
16436
- const json = await response.json();
16437
- if (!json?.txHash) {
16438
- throw new Error('ATXPAccount: /pay did not return txHash');
16439
- }
16440
- return json.txHash;
16441
- }
16442
- async generateJWT(params) {
16443
- const response = await this.fetchFn(`${this.origin}/sign`, {
16444
- method: 'POST',
16445
- headers: {
16446
- 'Authorization': toBasicAuth(this.token),
16447
- 'Content-Type': 'application/json',
16448
- },
16449
- body: JSON.stringify({
16450
- paymentRequestId: params.paymentRequestId,
16451
- codeChallenge: params.codeChallenge,
16452
- }),
16453
- });
16454
- if (!response.ok) {
16455
- const text = await response.text();
16456
- throw new Error(`ATXPAccount: /sign failed: ${response.status} ${response.statusText} ${text}`);
16457
- }
16458
- const json = await response.json();
16459
- if (!json?.jwt) {
16460
- throw new Error('ATXPAccount: /sign did not return jwt');
16461
- }
16462
- return json.jwt;
16463
- }
16464
- }
16465
- class ATXPAccount {
16466
- constructor(connectionString, opts) {
16467
- const { origin, token, accountId } = parseConnectionString(connectionString);
16468
- const fetchFn = opts?.fetchFn ?? fetch;
16469
- const network = opts?.network ?? 'base';
16470
- // Store for use in X402 payment creation
16471
- this.origin = origin;
16472
- this.token = token;
16473
- this.fetchFn = fetchFn;
16474
- if (accountId) {
16475
- this.accountId = `atxp:${accountId}`;
16476
- }
16477
- else {
16478
- this.accountId = `atxp:${common.crypto.randomUUID()}`;
16479
- }
16480
- this.paymentMakers = {
16481
- [network]: new ATXPHttpPaymentMaker(origin, token, fetchFn),
16482
- };
16483
- }
16484
- async getSigner() {
16485
- return ATXPLocalAccount.create(this.origin, this.token, this.fetchFn);
16629
+ async getSources() {
16630
+ return [{
16631
+ address: this.sourcePublicKey,
16632
+ chain: 'solana',
16633
+ walletType: 'eoa'
16634
+ }];
16486
16635
  }
16487
16636
  }
16488
16637
 
@@ -16566,6 +16715,58 @@ const getWorldChainUSDCAddress = (chainId) => {
16566
16715
  }
16567
16716
  };
16568
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
+
16569
16770
  class BaseAccount {
16570
16771
  constructor(baseRPCUrl, sourceSecretKey) {
16571
16772
  if (!baseRPCUrl) {
@@ -16575,15 +16776,16 @@ class BaseAccount {
16575
16776
  throw new Error('Source secret key is required');
16576
16777
  }
16577
16778
  this.account = accounts.privateKeyToAccount(sourceSecretKey);
16578
- this.accountId = this.account.address;
16779
+ // Format accountId as network:address
16780
+ this.accountId = `base:${this.account.address}`;
16579
16781
  this.walletClient = viem.createWalletClient({
16580
16782
  account: this.account,
16581
16783
  chain: chains.base,
16582
16784
  transport: viem.http(baseRPCUrl),
16583
16785
  });
16584
- this.paymentMakers = {
16585
- 'base': new BasePaymentMaker(baseRPCUrl, this.walletClient),
16586
- };
16786
+ this.paymentMakers = [
16787
+ new BasePaymentMaker(baseRPCUrl, this.walletClient)
16788
+ ];
16587
16789
  }
16588
16790
  /**
16589
16791
  * Get a signer that can be used with the x402 library
@@ -16593,9 +16795,20 @@ class BaseAccount {
16593
16795
  // Return the viem account directly - it implements LocalAccount interface
16594
16796
  return this.account;
16595
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
+ }
16596
16808
  }
16597
16809
 
16598
16810
  exports.ATXPAccount = ATXPAccount;
16811
+ exports.ATXPDestinationMaker = ATXPDestinationMaker;
16599
16812
  exports.ATXPLocalAccount = ATXPLocalAccount;
16600
16813
  exports.BaseAccount = BaseAccount;
16601
16814
  exports.BasePaymentMaker = BasePaymentMaker;
@@ -16603,11 +16816,14 @@ exports.DEFAULT_CLIENT_CONFIG = DEFAULT_CLIENT_CONFIG;
16603
16816
  exports.InsufficientFundsError = InsufficientFundsError;
16604
16817
  exports.OAuthAuthenticationRequiredError = OAuthAuthenticationRequiredError;
16605
16818
  exports.OAuthClient = OAuthClient;
16819
+ exports.POLYGON_MAINNET = POLYGON_MAINNET;
16820
+ exports.PassthroughDestinationMaker = PassthroughDestinationMaker;
16606
16821
  exports.PaymentNetworkError = PaymentNetworkError;
16607
16822
  exports.SolanaAccount = SolanaAccount;
16608
16823
  exports.SolanaPaymentMaker = SolanaPaymentMaker;
16609
16824
  exports.USDC_CONTRACT_ADDRESS_BASE = USDC_CONTRACT_ADDRESS_BASE;
16610
16825
  exports.USDC_CONTRACT_ADDRESS_BASE_SEPOLIA = USDC_CONTRACT_ADDRESS_BASE_SEPOLIA;
16826
+ exports.USDC_CONTRACT_ADDRESS_POLYGON_MAINNET = USDC_CONTRACT_ADDRESS_POLYGON_MAINNET;
16611
16827
  exports.USDC_CONTRACT_ADDRESS_WORLD_MAINNET = USDC_CONTRACT_ADDRESS_WORLD_MAINNET;
16612
16828
  exports.USDC_CONTRACT_ADDRESS_WORLD_SEPOLIA = USDC_CONTRACT_ADDRESS_WORLD_SEPOLIA;
16613
16829
  exports.ValidateTransferError = ValidateTransferError;
@@ -16618,6 +16834,9 @@ exports.atxpFetch = atxpFetch;
16618
16834
  exports.buildClientConfig = buildClientConfig;
16619
16835
  exports.buildStreamableTransport = buildStreamableTransport;
16620
16836
  exports.getBaseUSDCAddress = getBaseUSDCAddress;
16837
+ exports.getPolygonByChainId = getPolygonByChainId;
16838
+ exports.getPolygonMainnetWithRPC = getPolygonMainnetWithRPC;
16839
+ exports.getPolygonUSDCAddress = getPolygonUSDCAddress;
16621
16840
  exports.getWorldChainByChainId = getWorldChainByChainId;
16622
16841
  exports.getWorldChainMainnetWithRPC = getWorldChainMainnetWithRPC;
16623
16842
  exports.getWorldChainSepoliaWithRPC = getWorldChainSepoliaWithRPC;