@atxp/client 0.7.4 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/atxpClient.d.ts +1 -1
  2. package/dist/atxpClient.d.ts.map +1 -1
  3. package/dist/atxpClient.js +17 -2
  4. package/dist/atxpClient.js.map +1 -1
  5. package/dist/atxpFetcher.d.ts +5 -15
  6. package/dist/atxpFetcher.d.ts.map +1 -1
  7. package/dist/atxpFetcher.js +95 -229
  8. package/dist/atxpFetcher.js.map +1 -1
  9. package/dist/baseAccount.d.ts +10 -7
  10. package/dist/baseAccount.d.ts.map +1 -1
  11. package/dist/baseAccount.js +18 -8
  12. package/dist/baseAccount.js.map +1 -1
  13. package/dist/basePaymentMaker.d.ts +4 -3
  14. package/dist/basePaymentMaker.d.ts.map +1 -1
  15. package/dist/basePaymentMaker.js +20 -3
  16. package/dist/basePaymentMaker.js.map +1 -1
  17. package/dist/clientTestHelpers.d.ts.map +1 -1
  18. package/dist/destinationMakers/atxpDestinationMaker.d.ts +15 -0
  19. package/dist/destinationMakers/atxpDestinationMaker.d.ts.map +1 -0
  20. package/dist/destinationMakers/atxpDestinationMaker.js +128 -0
  21. package/dist/destinationMakers/atxpDestinationMaker.js.map +1 -0
  22. package/dist/destinationMakers/index.d.ts +9 -0
  23. package/dist/destinationMakers/index.d.ts.map +1 -0
  24. package/dist/destinationMakers/index.js +45 -0
  25. package/dist/destinationMakers/index.js.map +1 -0
  26. package/dist/destinationMakers/passthroughDestinationMaker.d.ts +8 -0
  27. package/dist/destinationMakers/passthroughDestinationMaker.d.ts.map +1 -0
  28. package/dist/destinationMakers/passthroughDestinationMaker.js +27 -0
  29. package/dist/destinationMakers/passthroughDestinationMaker.js.map +1 -0
  30. package/dist/index.cjs +566 -466
  31. package/dist/index.cjs.map +1 -1
  32. package/dist/index.d.ts +102 -55
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +555 -467
  35. package/dist/index.js.map +1 -1
  36. package/dist/polygonConstants.d.ts +53 -0
  37. package/dist/polygonConstants.d.ts.map +1 -0
  38. package/dist/polygonConstants.js +84 -0
  39. package/dist/polygonConstants.js.map +1 -0
  40. package/dist/solanaAccount.d.ts +8 -4
  41. package/dist/solanaAccount.d.ts.map +1 -1
  42. package/dist/solanaAccount.js +16 -4
  43. package/dist/solanaAccount.js.map +1 -1
  44. package/dist/solanaPaymentMaker.d.ts +4 -3
  45. package/dist/solanaPaymentMaker.d.ts.map +1 -1
  46. package/dist/solanaPaymentMaker.js +24 -5
  47. package/dist/solanaPaymentMaker.js.map +1 -1
  48. package/dist/types.d.ts +5 -23
  49. package/dist/types.d.ts.map +1 -1
  50. package/dist/types.js.map +1 -1
  51. package/package.json +2 -2
  52. package/dist/atxpAccount.d.ts +0 -18
  53. package/dist/atxpAccount.d.ts.map +0 -1
  54. package/dist/atxpAccount.js +0 -123
  55. package/dist/atxpAccount.js.map +0 -1
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,6 +15820,191 @@ class StreamableHTTPClientTransport {
15954
15820
  }
15955
15821
  }
15956
15822
 
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');
15834
+ }
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');
15842
+ }
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
15873
+ };
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}`);
15897
+ }
15898
+ else {
15899
+ logger.debug(`ATXPDestinationMaker: Got ${destinations.length} destinations for account ${accountId}`);
15900
+ }
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}`);
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
+ }
15943
+ }
15944
+ }
15945
+
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.PolygonAmoy:
15995
+ makers.set(network, new PassthroughDestinationMaker(network));
15996
+ break;
15997
+ case common.NetworkEnum.ATXP:
15998
+ makers.set(network, new ATXPDestinationMaker(atxpAccountsServer, fetchFn));
15999
+ break;
16000
+ default:
16001
+ // This will cause a compilation error if a new Network is added but not handled above
16002
+ common.assertNever(network);
16003
+ }
16004
+ }
16005
+ return makers;
16006
+ }
16007
+
15957
16008
  // Detect if we're in a browser environment and bind fetch appropriately
15958
16009
  const getFetch = () => {
15959
16010
  if (typeof window !== 'undefined' && typeof window.fetch === 'function') {
@@ -15965,6 +16016,7 @@ const getFetch = () => {
15965
16016
  };
15966
16017
  const DEFAULT_CLIENT_CONFIG = {
15967
16018
  allowedAuthorizationServers: [common.DEFAULT_AUTHORIZATION_SERVER],
16019
+ atxpAccountsServer: common.DEFAULT_ATXP_ACCOUNTS_SERVER,
15968
16020
  approvePayment: async (_p) => true,
15969
16021
  fetchFn: getFetch(),
15970
16022
  oAuthChannelFetch: getFetch(),
@@ -15994,7 +16046,20 @@ function buildClientConfig(args) {
15994
16046
  const withDefaults = { ...envDefaults, ...args };
15995
16047
  const logger = withDefaults.logger ?? new common.ConsoleLogger();
15996
16048
  const oAuthDb = withDefaults.oAuthDb ?? new common.MemoryOAuthDb({ logger });
15997
- const built = { oAuthDb, logger };
16049
+ const fetchFn = withDefaults.fetchFn;
16050
+ // Build destination makers if not provided
16051
+ let accountsServer = withDefaults.atxpAccountsServer;
16052
+ // QoL hack for unspecified accounts server - if the caller is passing an atxpAccount, then assume the origin for that
16053
+ // is what we should use for the accounts server. In practice, the only option is accounts.atxp.ai,
16054
+ // but this supports staging environment
16055
+ if (args.atxpAccountsServer === undefined && withDefaults.account && withDefaults.account instanceof common.ATXPAccount) {
16056
+ accountsServer = withDefaults.account.origin;
16057
+ }
16058
+ const destinationMakers = withDefaults.destinationMakers ?? createDestinationMakers({
16059
+ atxpAccountsServer: accountsServer,
16060
+ fetchFn
16061
+ });
16062
+ const built = { oAuthDb, logger, destinationMakers };
15998
16063
  return Object.freeze({ ...withDefaults, ...built });
15999
16064
  }
16000
16065
  function buildStreamableTransport(args) {
@@ -16017,7 +16082,7 @@ const USDC_MINT = new web3_js.PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwy
16017
16082
  const ValidateTransferError = pay.ValidateTransferError;
16018
16083
  class SolanaPaymentMaker {
16019
16084
  constructor(solanaEndpoint, sourceSecretKey, logger) {
16020
- this.generateJWT = async ({ paymentRequestId, codeChallenge }) => {
16085
+ this.generateJWT = async ({ paymentRequestId, codeChallenge, accountId }) => {
16021
16086
  // Solana/Web3.js secretKey is 64 bytes:
16022
16087
  // first 32 bytes are the private scalar, last 32 are the public key.
16023
16088
  // JWK expects only the 32-byte private scalar for 'd'
@@ -16031,9 +16096,20 @@ class SolanaPaymentMaker {
16031
16096
  if (!(privateKey instanceof CryptoKey)) {
16032
16097
  throw new Error('Expected CryptoKey from importJWK');
16033
16098
  }
16034
- return common.generateJWT(this.source.publicKey.toBase58(), privateKey, paymentRequestId || '', codeChallenge || '');
16099
+ return common.generateJWT(this.source.publicKey.toBase58(), privateKey, paymentRequestId || '', codeChallenge || '', accountId);
16035
16100
  };
16036
- this.makePayment = async (amount, currency, receiver, memo) => {
16101
+ this.makePayment = async (destinations, memo, _paymentRequestId) => {
16102
+ // Filter to solana chain destinations
16103
+ const solanaDestinations = destinations.filter(d => d.chain === 'solana');
16104
+ if (solanaDestinations.length === 0) {
16105
+ this.logger.debug('SolanaPaymentMaker: No solana destinations found, cannot handle payment');
16106
+ return null; // Cannot handle these destinations
16107
+ }
16108
+ // Pick first solana destination
16109
+ const dest = solanaDestinations[0];
16110
+ const amount = dest.amount;
16111
+ const currency = dest.currency;
16112
+ const receiver = dest.address;
16037
16113
  if (currency.toUpperCase() !== 'USDC') {
16038
16114
  throw new PaymentNetworkError('Only USDC currency is supported; received ' + currency);
16039
16115
  }
@@ -16048,6 +16124,8 @@ class SolanaPaymentMaker {
16048
16124
  this.logger.warn(`Insufficient ${currency} balance for payment. Required: ${amount}, Available: ${balance}`);
16049
16125
  throw new InsufficientFundsError(currency, amount, balance, 'solana');
16050
16126
  }
16127
+ // Get the destination token account address (this will be the transactionId)
16128
+ const destinationTokenAccount = await splToken.getAssociatedTokenAddress(USDC_MINT, receiverKey);
16051
16129
  // Increase compute units to handle both memo and token transfer
16052
16130
  // Memo uses ~6000 CUs, token transfer needs ~6500 CUs
16053
16131
  const modifyComputeUnits = web3_js.ComputeBudgetProgram.setComputeUnitLimit({
@@ -16064,8 +16142,14 @@ class SolanaPaymentMaker {
16064
16142
  });
16065
16143
  transaction.add(modifyComputeUnits);
16066
16144
  transaction.add(addPriorityFee);
16067
- const transactionHash = await web3_js.sendAndConfirmTransaction(this.connection, transaction, [this.source]);
16068
- return transactionHash;
16145
+ const transactionSignature = await web3_js.sendAndConfirmTransaction(this.connection, transaction, [this.source]);
16146
+ // Return transaction signature as transactionId and token account address as transactionSubId
16147
+ return {
16148
+ transactionId: transactionSignature,
16149
+ transactionSubId: destinationTokenAccount.toBase58(),
16150
+ chain: 'solana',
16151
+ currency: 'USDC'
16152
+ };
16069
16153
  }
16070
16154
  catch (error) {
16071
16155
  if (error instanceof InsufficientFundsError || error instanceof PaymentNetworkError) {
@@ -16167,7 +16251,7 @@ class BasePaymentMaker {
16167
16251
  getSourceAddress(_params) {
16168
16252
  return this.signingClient.account.address;
16169
16253
  }
16170
- async generateJWT({ paymentRequestId, codeChallenge }) {
16254
+ async generateJWT({ paymentRequestId, codeChallenge, accountId }) {
16171
16255
  const headerObj = { alg: 'ES256K' };
16172
16256
  const payloadObj = {
16173
16257
  sub: this.signingClient.account.address,
@@ -16177,6 +16261,7 @@ class BasePaymentMaker {
16177
16261
  exp: Math.floor(Date.now() / 1000) + 60 * 60,
16178
16262
  ...(codeChallenge ? { code_challenge: codeChallenge } : {}),
16179
16263
  ...(paymentRequestId ? { payment_request_id: paymentRequestId } : {}),
16264
+ ...(accountId ? { account_id: accountId } : {}),
16180
16265
  };
16181
16266
  const header = toBase64Url(JSON.stringify(headerObj));
16182
16267
  const payload = toBase64Url(JSON.stringify(payloadObj));
@@ -16197,7 +16282,18 @@ class BasePaymentMaker {
16197
16282
  this.logger.info(`Generated ES256K JWT: ${jwt}`);
16198
16283
  return jwt;
16199
16284
  }
16200
- async makePayment(amount, currency, receiver) {
16285
+ async makePayment(destinations, _memo, _paymentRequestId) {
16286
+ // Filter to base chain destinations
16287
+ const baseDestinations = destinations.filter(d => d.chain === 'base');
16288
+ if (baseDestinations.length === 0) {
16289
+ this.logger.debug('BasePaymentMaker: No base destinations found, cannot handle payment');
16290
+ return null; // Cannot handle these destinations
16291
+ }
16292
+ // Pick first base destination
16293
+ const dest = baseDestinations[0];
16294
+ const amount = dest.amount;
16295
+ const currency = dest.currency;
16296
+ const receiver = dest.address;
16201
16297
  if (currency.toUpperCase() !== 'USDC') {
16202
16298
  throw new PaymentNetworkError('Only USDC currency is supported; received ' + currency);
16203
16299
  }
@@ -16240,7 +16336,12 @@ class BasePaymentMaker {
16240
16336
  throw new PaymentNetworkError(`Transaction reverted: ${hash}`, new Error('Transaction reverted on chain'));
16241
16337
  }
16242
16338
  this.logger.info(`Transaction confirmed: ${hash} in block ${receipt.blockNumber}`);
16243
- return hash;
16339
+ // Return payment result with chain and currency
16340
+ return {
16341
+ transactionId: hash,
16342
+ chain: 'base',
16343
+ currency: 'USDC'
16344
+ };
16244
16345
  }
16245
16346
  catch (error) {
16246
16347
  if (error instanceof InsufficientFundsError || error instanceof PaymentNetworkError) {
@@ -16261,228 +16362,22 @@ class SolanaAccount {
16261
16362
  throw new Error('Source secret key is required');
16262
16363
  }
16263
16364
  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;
16365
+ this.sourcePublicKey = source.publicKey.toBase58();
16366
+ // Format accountId as network:address
16367
+ this.accountId = `solana:${this.sourcePublicKey}`;
16368
+ this.paymentMakers = [
16369
+ new SolanaPaymentMaker(solanaEndpoint, sourceSecretKey)
16370
+ ];
16356
16371
  }
16357
16372
  /**
16358
- * Sign a message - required by LocalAccount interface but not used for X402
16373
+ * Get sources for this account
16359
16374
  */
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
16365
- */
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);
16375
+ async getSources() {
16376
+ return [{
16377
+ address: this.sourcePublicKey,
16378
+ chain: 'solana',
16379
+ walletType: 'eoa'
16380
+ }];
16486
16381
  }
16487
16382
  }
16488
16383
 
@@ -16566,6 +16461,88 @@ const getWorldChainUSDCAddress = (chainId) => {
16566
16461
  }
16567
16462
  };
16568
16463
 
16464
+ const USDC_CONTRACT_ADDRESS_POLYGON_MAINNET = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"; // Native USDC on Polygon mainnet
16465
+ const USDC_CONTRACT_ADDRESS_POLYGON_AMOY = "0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582"; // USDC on Polygon Amoy testnet
16466
+ // Polygon Mainnet (Chain ID: 137)
16467
+ // Note: Native currency upgraded from MATIC to POL on September 4, 2024
16468
+ const POLYGON_MAINNET = {
16469
+ id: 137,
16470
+ name: 'Polygon',
16471
+ nativeCurrency: { name: 'POL', symbol: 'POL', decimals: 18 },
16472
+ rpcUrls: {
16473
+ default: { http: ['https://polygon-rpc.com'] }
16474
+ },
16475
+ blockExplorers: {
16476
+ default: { name: 'PolygonScan', url: 'https://polygonscan.com' }
16477
+ }
16478
+ };
16479
+ // Polygon Amoy Testnet (Chain ID: 80002)
16480
+ // Note: Amoy testnet also uses POL as native currency (following mainnet upgrade)
16481
+ const POLYGON_AMOY = {
16482
+ id: 80002,
16483
+ name: 'Polygon Amoy',
16484
+ nativeCurrency: { name: 'POL', symbol: 'POL', decimals: 18 },
16485
+ rpcUrls: {
16486
+ default: { http: ['https://rpc-amoy.polygon.technology'] }
16487
+ },
16488
+ blockExplorers: {
16489
+ default: { name: 'PolygonScan Amoy', url: 'https://amoy.polygonscan.com' }
16490
+ },
16491
+ testnet: true
16492
+ };
16493
+ /**
16494
+ * Get Polygon Mainnet configuration with custom RPC URL (e.g., with API key)
16495
+ * @param rpcUrl - Custom RPC URL, e.g., 'https://polygon-mainnet.g.alchemy.com/v2/YOUR_API_KEY'
16496
+ */
16497
+ const getPolygonMainnetWithRPC = (rpcUrl) => ({
16498
+ ...POLYGON_MAINNET,
16499
+ rpcUrls: {
16500
+ default: { http: [rpcUrl] }
16501
+ }
16502
+ });
16503
+ /**
16504
+ * Get Polygon Amoy Testnet configuration with custom RPC URL (e.g., with API key)
16505
+ * @param rpcUrl - Custom RPC URL, e.g., 'https://polygon-amoy.g.alchemy.com/v2/YOUR_API_KEY'
16506
+ */
16507
+ const getPolygonAmoyWithRPC = (rpcUrl) => ({
16508
+ ...POLYGON_AMOY,
16509
+ rpcUrls: {
16510
+ default: { http: [rpcUrl] }
16511
+ }
16512
+ });
16513
+ /**
16514
+ * Get Polygon Chain configuration by chain ID
16515
+ * @param chainId - Chain ID (137 for mainnet, 80002 for Amoy testnet)
16516
+ * @returns Polygon Chain configuration
16517
+ * @throws Error if chain ID is not supported
16518
+ */
16519
+ const getPolygonByChainId = (chainId) => {
16520
+ switch (chainId) {
16521
+ case 137:
16522
+ return POLYGON_MAINNET;
16523
+ case 80002:
16524
+ return POLYGON_AMOY;
16525
+ default:
16526
+ throw new Error(`Unsupported Polygon Chain ID: ${chainId}. Supported chains: 137 (mainnet), 80002 (Amoy testnet)`);
16527
+ }
16528
+ };
16529
+ /**
16530
+ * Get USDC contract address for Polygon by chain ID
16531
+ * @param chainId - Chain ID (137 for mainnet, 80002 for Amoy testnet)
16532
+ * @returns USDC contract address
16533
+ * @throws Error if chain ID is not supported
16534
+ */
16535
+ const getPolygonUSDCAddress = (chainId) => {
16536
+ switch (chainId) {
16537
+ case 137:
16538
+ return USDC_CONTRACT_ADDRESS_POLYGON_MAINNET;
16539
+ case 80002:
16540
+ return USDC_CONTRACT_ADDRESS_POLYGON_AMOY;
16541
+ default:
16542
+ throw new Error(`Unsupported Polygon Chain ID: ${chainId}. Supported chains: 137 (mainnet), 80002 (Amoy testnet)`);
16543
+ }
16544
+ };
16545
+
16569
16546
  class BaseAccount {
16570
16547
  constructor(baseRPCUrl, sourceSecretKey) {
16571
16548
  if (!baseRPCUrl) {
@@ -16575,27 +16552,141 @@ class BaseAccount {
16575
16552
  throw new Error('Source secret key is required');
16576
16553
  }
16577
16554
  this.account = accounts.privateKeyToAccount(sourceSecretKey);
16578
- this.accountId = this.account.address;
16555
+ // Format accountId as network:address
16556
+ this.accountId = `base:${this.account.address}`;
16579
16557
  this.walletClient = viem.createWalletClient({
16580
16558
  account: this.account,
16581
16559
  chain: chains.base,
16582
16560
  transport: viem.http(baseRPCUrl),
16583
16561
  });
16584
- this.paymentMakers = {
16585
- 'base': new BasePaymentMaker(baseRPCUrl, this.walletClient),
16586
- };
16562
+ this.paymentMakers = [
16563
+ new BasePaymentMaker(baseRPCUrl, this.walletClient)
16564
+ ];
16587
16565
  }
16588
16566
  /**
16589
- * Get a signer that can be used with the x402 library
16590
- * This is only available for EVM-based accounts
16567
+ * Get the LocalAccount (signer) for this account.
16568
+ * This can be used with the x402 library or other signing operations.
16591
16569
  */
16592
- getSigner() {
16593
- // Return the viem account directly - it implements LocalAccount interface
16570
+ getLocalAccount() {
16594
16571
  return this.account;
16595
16572
  }
16573
+ /**
16574
+ * Get sources for this account
16575
+ */
16576
+ async getSources() {
16577
+ return [{
16578
+ address: this.account.address,
16579
+ chain: 'base',
16580
+ walletType: 'eoa'
16581
+ }];
16582
+ }
16596
16583
  }
16597
16584
 
16598
- exports.ATXPAccount = ATXPAccount;
16585
+ function toBasicAuth(token) {
16586
+ // Basic auth is base64("username:password"), password is blank
16587
+ const b64 = Buffer.from(`${token}:`).toString('base64');
16588
+ return `Basic ${b64}`;
16589
+ }
16590
+ /**
16591
+ * ATXP implementation of viem's LocalAccount interface.
16592
+ * Delegates signing operations to the accounts-x402 API.
16593
+ * Includes properties needed by x402 library for wallet client compatibility.
16594
+ */
16595
+ class ATXPLocalAccount {
16596
+ constructor(address, origin, token, fetchFn = fetch) {
16597
+ this.address = address;
16598
+ this.origin = origin;
16599
+ this.token = token;
16600
+ this.fetchFn = fetchFn;
16601
+ this.type = 'local';
16602
+ /**
16603
+ * Get public key - required by LocalAccount interface
16604
+ */
16605
+ this.publicKey = '0x0000000000000000000000000000000000000000000000000000000000000000';
16606
+ /**
16607
+ * Source - required by LocalAccount interface (set to 'custom')
16608
+ */
16609
+ this.source = 'custom';
16610
+ // x402 library expects these properties for wallet client compatibility
16611
+ this.account = this; // Self-reference for x402's isSignerWallet check
16612
+ this.chain = { id: 8453 }; // Base mainnet - could make this configurable
16613
+ this.transport = {}; // Empty transport object for x402 compatibility
16614
+ }
16615
+ /**
16616
+ * Fetch the wallet address from the /address endpoint
16617
+ */
16618
+ static async create(origin, token, fetchFn = fetch) {
16619
+ // The /address endpoint uses Basic auth like other authenticated endpoints
16620
+ // For X402, we need the Ethereum/Base address with USDC currency
16621
+ const url = new URL(`${origin}/address`);
16622
+ url.searchParams.set('network', 'base'); // X402 operates on Base
16623
+ url.searchParams.set('currency', 'USDC'); // Always USDC for X402
16624
+ const response = await fetchFn(url.toString(), {
16625
+ method: 'GET',
16626
+ headers: {
16627
+ 'Authorization': toBasicAuth(token)
16628
+ }
16629
+ });
16630
+ if (!response.ok) {
16631
+ const errorText = await response.text();
16632
+ throw new Error(`Failed to fetch destination address: ${response.status} ${response.statusText} ${errorText}`);
16633
+ }
16634
+ const data = await response.json();
16635
+ const address = data.address;
16636
+ if (!address) {
16637
+ throw new Error('Address endpoint did not return an address');
16638
+ }
16639
+ // Check that the account is an Ethereum/Base account (required for X402/EVM operations)
16640
+ const network = data.network;
16641
+ if (!network) {
16642
+ throw new Error('Address endpoint did not return a network');
16643
+ }
16644
+ if (network !== 'ethereum' && network !== 'base') {
16645
+ throw new Error(`ATXPLocalAccount requires an Ethereum/Base account, but got ${network} account`);
16646
+ }
16647
+ return new ATXPLocalAccount(address, origin, token, fetchFn);
16648
+ }
16649
+ /**
16650
+ * Sign a typed data structure using EIP-712
16651
+ * This is what x402 library will call for EIP-3009 authorization
16652
+ */
16653
+ async signTypedData(typedData) {
16654
+ const response = await this.fetchFn(`${this.origin}/sign-typed-data`, {
16655
+ method: 'POST',
16656
+ headers: {
16657
+ 'Authorization': toBasicAuth(this.token),
16658
+ 'Content-Type': 'application/json',
16659
+ },
16660
+ body: JSON.stringify({
16661
+ typedData
16662
+ })
16663
+ });
16664
+ if (!response.ok) {
16665
+ const errorText = await response.text();
16666
+ throw new Error(`Failed to sign typed data: ${response.status} ${response.statusText} ${errorText}`);
16667
+ }
16668
+ const result = await response.json();
16669
+ return result.signature;
16670
+ }
16671
+ /**
16672
+ * Sign a message - required by LocalAccount interface but not used for X402
16673
+ */
16674
+ async signMessage(_) {
16675
+ throw new Error('Message signing not implemented for ATXP local account');
16676
+ }
16677
+ /**
16678
+ * Sign a transaction - required by LocalAccount interface but not used for X402
16679
+ */
16680
+ async signTransaction(_transaction, _args) {
16681
+ throw new Error('Transaction signing not implemented for ATXP local account');
16682
+ }
16683
+ }
16684
+
16685
+ Object.defineProperty(exports, "ATXPAccount", {
16686
+ enumerable: true,
16687
+ get: function () { return common.ATXPAccount; }
16688
+ });
16689
+ exports.ATXPDestinationMaker = ATXPDestinationMaker;
16599
16690
  exports.ATXPLocalAccount = ATXPLocalAccount;
16600
16691
  exports.BaseAccount = BaseAccount;
16601
16692
  exports.BasePaymentMaker = BasePaymentMaker;
@@ -16603,11 +16694,16 @@ exports.DEFAULT_CLIENT_CONFIG = DEFAULT_CLIENT_CONFIG;
16603
16694
  exports.InsufficientFundsError = InsufficientFundsError;
16604
16695
  exports.OAuthAuthenticationRequiredError = OAuthAuthenticationRequiredError;
16605
16696
  exports.OAuthClient = OAuthClient;
16697
+ exports.POLYGON_AMOY = POLYGON_AMOY;
16698
+ exports.POLYGON_MAINNET = POLYGON_MAINNET;
16699
+ exports.PassthroughDestinationMaker = PassthroughDestinationMaker;
16606
16700
  exports.PaymentNetworkError = PaymentNetworkError;
16607
16701
  exports.SolanaAccount = SolanaAccount;
16608
16702
  exports.SolanaPaymentMaker = SolanaPaymentMaker;
16609
16703
  exports.USDC_CONTRACT_ADDRESS_BASE = USDC_CONTRACT_ADDRESS_BASE;
16610
16704
  exports.USDC_CONTRACT_ADDRESS_BASE_SEPOLIA = USDC_CONTRACT_ADDRESS_BASE_SEPOLIA;
16705
+ exports.USDC_CONTRACT_ADDRESS_POLYGON_AMOY = USDC_CONTRACT_ADDRESS_POLYGON_AMOY;
16706
+ exports.USDC_CONTRACT_ADDRESS_POLYGON_MAINNET = USDC_CONTRACT_ADDRESS_POLYGON_MAINNET;
16611
16707
  exports.USDC_CONTRACT_ADDRESS_WORLD_MAINNET = USDC_CONTRACT_ADDRESS_WORLD_MAINNET;
16612
16708
  exports.USDC_CONTRACT_ADDRESS_WORLD_SEPOLIA = USDC_CONTRACT_ADDRESS_WORLD_SEPOLIA;
16613
16709
  exports.ValidateTransferError = ValidateTransferError;
@@ -16618,6 +16714,10 @@ exports.atxpFetch = atxpFetch;
16618
16714
  exports.buildClientConfig = buildClientConfig;
16619
16715
  exports.buildStreamableTransport = buildStreamableTransport;
16620
16716
  exports.getBaseUSDCAddress = getBaseUSDCAddress;
16717
+ exports.getPolygonAmoyWithRPC = getPolygonAmoyWithRPC;
16718
+ exports.getPolygonByChainId = getPolygonByChainId;
16719
+ exports.getPolygonMainnetWithRPC = getPolygonMainnetWithRPC;
16720
+ exports.getPolygonUSDCAddress = getPolygonUSDCAddress;
16621
16721
  exports.getWorldChainByChainId = getWorldChainByChainId;
16622
16722
  exports.getWorldChainMainnetWithRPC = getWorldChainMainnetWithRPC;
16623
16723
  exports.getWorldChainSepoliaWithRPC = getWorldChainSepoliaWithRPC;