@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.js CHANGED
@@ -1,6 +1,7 @@
1
- import { crypto as crypto$1, OAuthResourceClient, ConsoleLogger, PAYMENT_REQUIRED_ERROR_CODE, isSSEResponse, parseMcpMessages, parsePaymentRequests, paymentRequiredError, DEFAULT_AUTHORIZATION_SERVER, getIsReactNative, createReactNativeSafeFetch, MemoryOAuthDb, generateJWT } from '@atxp/common';
2
- import BigNumber$1, { BigNumber } from 'bignumber.js';
1
+ import { crypto as crypto$1, OAuthResourceClient, ConsoleLogger, PAYMENT_REQUIRED_ERROR_CODE, isSSEResponse, parseMcpMessages, parsePaymentRequests, paymentRequiredError, DEFAULT_AUTHORIZATION_SERVER, getIsReactNative, createReactNativeSafeFetch, isEnumValue, ChainEnum, CurrencyEnum, NetworkEnum, assertNever, DEFAULT_ATXP_ACCOUNTS_SERVER, MemoryOAuthDb, ATXPAccount, generateJWT } from '@atxp/common';
2
+ export { ATXPAccount } from '@atxp/common';
3
3
  import * as oauth from 'oauth4webapi';
4
+ import BigNumber$1, { BigNumber } from 'bignumber.js';
4
5
  import { PublicKey, ComputeBudgetProgram, sendAndConfirmTransaction, Connection, Keypair } from '@solana/web3.js';
5
6
  import { ValidateTransferError as ValidateTransferError$1, createTransfer } from '@solana/pay';
6
7
  import { getAssociatedTokenAddress, getAccount } from '@solana/spl-token';
@@ -312,9 +313,9 @@ class PaymentNetworkError extends Error {
312
313
  */
313
314
  function atxpFetch(config) {
314
315
  const fetcher = new ATXPFetcher({
315
- accountId: config.account.accountId,
316
+ account: config.account,
316
317
  db: config.oAuthDb,
317
- paymentMakers: config.account.paymentMakers,
318
+ destinationMakers: config.destinationMakers,
318
319
  fetchFn: config.fetchFn,
319
320
  sideChannelFetch: config.oAuthChannelFetch,
320
321
  allowInsecureRequests: config.allowHttp,
@@ -332,7 +333,8 @@ class ATXPFetcher {
332
333
  constructor(config) {
333
334
  this.defaultPaymentFailureHandler = async ({ payment, error }) => {
334
335
  if (error instanceof InsufficientFundsError) {
335
- this.logger.info(`PAYMENT FAILED: Insufficient ${error.currency} funds on ${payment.network}`);
336
+ const networkText = error.network ? ` on ${error.network}` : '';
337
+ this.logger.info(`PAYMENT FAILED: Insufficient ${error.currency} funds${networkText}`);
336
338
  this.logger.info(`Required: ${error.required} ${error.currency}`);
337
339
  if (error.available) {
338
340
  this.logger.info(`Available: ${error.available} ${error.currency}`);
@@ -340,7 +342,7 @@ class ATXPFetcher {
340
342
  this.logger.info(`Account: ${payment.accountId}`);
341
343
  }
342
344
  else if (error instanceof PaymentNetworkError) {
343
- this.logger.info(`PAYMENT FAILED: Network error on ${payment.network}: ${error.message}`);
345
+ this.logger.info(`PAYMENT FAILED: Network error: ${error.message}`);
344
346
  }
345
347
  else {
346
348
  this.logger.info(`PAYMENT FAILED: ${error.message}`);
@@ -350,43 +352,61 @@ class ATXPFetcher {
350
352
  if (!paymentRequestData.destinations || paymentRequestData.destinations.length === 0) {
351
353
  return false;
352
354
  }
353
- // Try each destination in order
354
- for (const dest of paymentRequestData.destinations) {
355
- // Convert amount to BigNumber since it comes as a string from JSON
356
- const amount = new BigNumber(dest.amount);
357
- // Resolve atxp_base destinations to real base network destinations
358
- let destinationAddress = dest.address;
359
- let destinationNetwork = dest.network;
360
- const resolved = await this.resolveAtxpBaseDestination(dest.network, dest.address, paymentRequestId, amount, dest.currency, dest.address, paymentRequestData.iss);
361
- if (resolved) {
362
- destinationAddress = resolved.destinationAddress;
363
- destinationNetwork = resolved.network;
364
- }
365
- const paymentMaker = this.paymentMakers.get(destinationNetwork);
366
- if (!paymentMaker) {
367
- this.logger.debug(`ATXP: payment network '${destinationNetwork}' not available, trying next destination`);
355
+ // Get sources from the account
356
+ const sources = await this.account.getSources();
357
+ // Apply destination mappers to transform destinations
358
+ // Convert PaymentRequestDestination[] to Destination[] for mapper compatibility
359
+ const mappedDestinations = [];
360
+ for (const option of paymentRequestData.destinations) {
361
+ const destinationMaker = this.destinationMakers.get(option.network);
362
+ if (!destinationMaker) {
363
+ this.logger.debug(`ATXP: destination maker for network '${option.network}' not available, trying next destination`);
368
364
  continue;
369
365
  }
370
- const prospectivePayment = {
371
- accountId: this.accountId,
372
- resourceUrl: paymentRequestData.resource?.toString() ?? '',
373
- resourceName: paymentRequestData.resourceName ?? '',
374
- network: destinationNetwork,
375
- currency: dest.currency,
376
- amount: amount,
377
- iss: paymentRequestData.iss ?? '',
378
- };
379
- if (!await this.approvePayment(prospectivePayment)) {
380
- this.logger.info(`ATXP: payment request denied by callback function for destination on ${destinationNetwork}`);
381
- continue;
366
+ mappedDestinations.push(...(await destinationMaker.makeDestinations(option, this.logger, paymentRequestId, sources)));
367
+ }
368
+ if (mappedDestinations.length === 0) {
369
+ this.logger.info(`ATXP: no destinations found after mapping`);
370
+ return false;
371
+ }
372
+ // Validate amounts are not negative
373
+ for (const dest of mappedDestinations) {
374
+ if (dest.amount.isLessThan(0)) {
375
+ throw new Error(`ATXP: payment amount cannot be negative: ${dest.amount.toString()} ${dest.currency}`);
382
376
  }
383
- let paymentId;
377
+ }
378
+ // Create prospective payment for approval (using first destination for display)
379
+ const firstDest = mappedDestinations[0];
380
+ const prospectivePayment = {
381
+ accountId: this.account.accountId,
382
+ resourceUrl: paymentRequestData.resource?.toString() ?? '',
383
+ resourceName: paymentRequestData.resourceName ?? '',
384
+ currency: firstDest.currency,
385
+ amount: firstDest.amount,
386
+ iss: paymentRequestData.iss ?? '',
387
+ };
388
+ // Ask for approval once for all payment attempts
389
+ if (!await this.approvePayment(prospectivePayment)) {
390
+ this.logger.info(`ATXP: payment request denied by callback function`);
391
+ return false;
392
+ }
393
+ // Try each payment maker in order
394
+ let lastPaymentError = null;
395
+ let paymentAttempted = false;
396
+ for (const paymentMaker of this.account.paymentMakers) {
384
397
  try {
385
- paymentId = await paymentMaker.makePayment(amount, dest.currency, destinationAddress, paymentRequestData.iss);
386
- this.logger.info(`ATXP: made payment of ${amount.toString()} ${dest.currency} on ${destinationNetwork}: ${paymentId}`);
398
+ // Pass all destinations to payment maker - it will filter and pick the one it can handle
399
+ const result = await paymentMaker.makePayment(mappedDestinations, paymentRequestData.iss, paymentRequestId);
400
+ if (result === null) {
401
+ this.logger.debug(`ATXP: payment maker cannot handle these destinations, trying next`);
402
+ continue; // Try next payment maker
403
+ }
404
+ paymentAttempted = true;
405
+ // Payment was successful
406
+ this.logger.info(`ATXP: made payment of ${firstDest.amount.toString()} ${firstDest.currency} on ${result.chain}: ${result.transactionId}`);
387
407
  await this.onPayment({ payment: prospectivePayment });
388
408
  // Submit payment to the server
389
- const jwt = await paymentMaker.generateJWT({ paymentRequestId, codeChallenge: '' });
409
+ const jwt = await paymentMaker.generateJWT({ paymentRequestId, codeChallenge: '', accountId: this.account.accountId });
390
410
  const response = await this.sideChannelFetch(paymentRequestUrl.toString(), {
391
411
  method: 'PUT',
392
412
  headers: {
@@ -394,9 +414,10 @@ class ATXPFetcher {
394
414
  'Content-Type': 'application/json'
395
415
  },
396
416
  body: JSON.stringify({
397
- transactionId: paymentId,
398
- network: destinationNetwork,
399
- currency: dest.currency
417
+ transactionId: result.transactionId,
418
+ ...(result.transactionSubId ? { transactionSubId: result.transactionSubId } : {}),
419
+ chain: result.chain,
420
+ currency: result.currency
400
421
  })
401
422
  });
402
423
  this.logger.debug(`ATXP: payment was ${response.ok ? 'successfully' : 'not successfully'} PUT to ${paymentRequestUrl} : status ${response.status} ${response.statusText}`);
@@ -409,13 +430,18 @@ class ATXPFetcher {
409
430
  }
410
431
  catch (error) {
411
432
  const typedError = error;
412
- this.logger.warn(`ATXP: payment failed on ${destinationNetwork}: ${typedError.message}`);
433
+ paymentAttempted = true;
434
+ lastPaymentError = typedError;
435
+ this.logger.warn(`ATXP: payment maker failed: ${typedError.message}`);
413
436
  await this.onPaymentFailure({ payment: prospectivePayment, error: typedError });
414
- // Try next destination
415
- continue;
437
+ // Continue to next payment maker
416
438
  }
417
439
  }
418
- this.logger.info(`ATXP: no suitable payment destination found among ${paymentRequestData.destinations.length} options`);
440
+ // If payment was attempted but all failed, rethrow the last error
441
+ if (paymentAttempted && lastPaymentError) {
442
+ throw lastPaymentError;
443
+ }
444
+ this.logger.info(`ATXP: no payment maker could handle these destinations`);
419
445
  return false;
420
446
  };
421
447
  this.handlePaymentRequestError = async (paymentRequestError) => {
@@ -442,102 +468,8 @@ class ATXPFetcher {
442
468
  if (paymentRequestData.destinations && paymentRequestData.destinations.length > 0) {
443
469
  return this.handleMultiDestinationPayment(paymentRequestData, paymentRequestUrl, paymentRequestId);
444
470
  }
445
- // Handle legacy single destination format
446
- const requestedNetwork = paymentRequestData.network;
447
- if (!requestedNetwork) {
448
- throw new Error(`Payment network not provided`);
449
- }
450
- const destination = paymentRequestData.destination;
451
- if (!destination) {
452
- throw new Error(`destination not provided`);
453
- }
454
- let amount = new BigNumber(0);
455
- if (!paymentRequestData.amount) {
456
- throw new Error(`amount not provided`);
457
- }
458
- try {
459
- amount = new BigNumber(paymentRequestData.amount);
460
- }
461
- catch {
462
- throw new Error(`Invalid amount ${paymentRequestData.amount}`);
463
- }
464
- if (amount.lte(0)) {
465
- throw new Error(`Invalid amount ${paymentRequestData.amount}`);
466
- }
467
- const currency = paymentRequestData.currency;
468
- if (!currency) {
469
- throw new Error(`Currency not provided`);
470
- }
471
- // Resolve atxp_base destinations to real base network destinations
472
- let destinationAddress = destination;
473
- let destinationNetwork = requestedNetwork;
474
- const resolved = await this.resolveAtxpBaseDestination(requestedNetwork, destination, paymentRequestId, amount, currency, destination, paymentRequestData.iss);
475
- if (resolved) {
476
- destinationAddress = resolved.destinationAddress;
477
- destinationNetwork = resolved.network;
478
- }
479
- const paymentMaker = this.paymentMakers.get(destinationNetwork);
480
- if (!paymentMaker) {
481
- this.logger.info(`ATXP: payment network '${destinationNetwork}' not set up for this client (available networks: ${Array.from(this.paymentMakers.keys()).join(', ')})`);
482
- return false;
483
- }
484
- const prospectivePayment = {
485
- accountId: this.accountId,
486
- resourceUrl: paymentRequestData.resource?.toString() ?? '',
487
- resourceName: paymentRequestData.resourceName ?? '',
488
- network: destinationNetwork,
489
- currency,
490
- amount,
491
- iss: paymentRequestData.iss ?? '',
492
- };
493
- if (!await this.approvePayment(prospectivePayment)) {
494
- this.logger.info(`ATXP: payment request denied by callback function`);
495
- return false;
496
- }
497
- let paymentId;
498
- try {
499
- paymentId = await paymentMaker.makePayment(amount, currency, destinationAddress, paymentRequestData.iss);
500
- this.logger.info(`ATXP: made payment of ${amount} ${currency} on ${destinationNetwork}: ${paymentId}`);
501
- // Call onPayment callback after successful payment
502
- await this.onPayment({ payment: prospectivePayment });
503
- }
504
- catch (paymentError) {
505
- // Call onPaymentFailure callback if payment fails
506
- await this.onPaymentFailure({
507
- payment: prospectivePayment,
508
- error: paymentError
509
- });
510
- throw paymentError;
511
- }
512
- const jwt = await paymentMaker.generateJWT({ paymentRequestId, codeChallenge: '' });
513
- // Make a fetch call to the authorization URL with the payment ID
514
- // redirect=false is a hack
515
- // The OAuth spec calls for the authorization url to return with a redirect, but fetch
516
- // on mobile will automatically follow the redirect (it doesn't support the redirect=manual option)
517
- // We want the redirect URL so we can extract the code from it, not the contents of the
518
- // redirect URL (which might not even exist for agentic ATXP clients)
519
- // So ATXP servers are set up to instead return a 200 with the redirect URL in the body
520
- // if we pass redirect=false.
521
- // TODO: Remove the redirect=false hack once we have a way to handle the redirect on mobile
522
- const response = await this.sideChannelFetch(paymentRequestUrl.toString(), {
523
- method: 'PUT',
524
- headers: {
525
- 'Authorization': `Bearer ${jwt}`,
526
- 'Content-Type': 'application/json'
527
- },
528
- body: JSON.stringify({
529
- transactionId: paymentId,
530
- network: destinationNetwork,
531
- currency: currency
532
- })
533
- });
534
- this.logger.debug(`ATXP: payment was ${response.ok ? 'successfully' : 'not successfully'} PUT to ${paymentRequestUrl} : status ${response.status} ${response.statusText}`);
535
- if (!response.ok) {
536
- const msg = `ATXP: payment to ${paymentRequestUrl} failed: HTTP ${response.status} ${await response.text()}`;
537
- this.logger.info(msg);
538
- throw new Error(msg);
539
- }
540
- return true;
471
+ // Payment request doesn't have destinations - this shouldn't happen with new SDK
472
+ throw new Error(`ATXP: payment request does not contain destinations array`);
541
473
  };
542
474
  this.getPaymentRequestData = async (paymentRequestUrl) => {
543
475
  const prRequest = await this.sideChannelFetch(paymentRequestUrl);
@@ -545,6 +477,14 @@ class ATXPFetcher {
545
477
  throw new Error(`ATXP: GET ${paymentRequestUrl} failed: ${prRequest.status} ${prRequest.statusText}`);
546
478
  }
547
479
  const paymentRequest = await prRequest.json();
480
+ // Parse amount strings to BigNumber objects
481
+ if (paymentRequest.destinations) {
482
+ for (const dest of paymentRequest.destinations) {
483
+ if (typeof dest.amount === 'string' || typeof dest.amount === 'number') {
484
+ dest.amount = new BigNumber(dest.amount);
485
+ }
486
+ }
487
+ }
548
488
  return paymentRequest;
549
489
  };
550
490
  this.isAllowedAuthServer = (url) => {
@@ -558,15 +498,15 @@ class ATXPFetcher {
558
498
  throw new Error(`Code challenge not provided`);
559
499
  }
560
500
  if (!paymentMaker) {
561
- const availableNetworks = Array.from(this.paymentMakers.keys()).join(', ');
562
- throw new Error(`Payment maker is null/undefined. Available payment makers: [${availableNetworks}]. This usually indicates a payment maker object was not properly instantiated.`);
501
+ const paymentMakerCount = this.account.paymentMakers.length;
502
+ throw new Error(`Payment maker is null/undefined. Available payment maker count: ${paymentMakerCount}. This usually indicates a payment maker object was not properly instantiated.`);
563
503
  }
564
504
  // TypeScript should prevent this, but add runtime check for edge cases (untyped JS, version mismatches, etc.)
565
505
  if (!paymentMaker.generateJWT) {
566
- const availableNetworks = Array.from(this.paymentMakers.keys()).join(', ');
567
- 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.`);
506
+ const paymentMakerCount = this.account.paymentMakers.length;
507
+ 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.`);
568
508
  }
569
- const authToken = await paymentMaker.generateJWT({ paymentRequestId: '', codeChallenge: codeChallenge });
509
+ const authToken = await paymentMaker.generateJWT({ paymentRequestId: '', codeChallenge: codeChallenge, accountId: this.account.accountId });
570
510
  // Make a fetch call to the authorization URL with the payment ID
571
511
  // redirect=false is a hack
572
512
  // The OAuth spec calls for the authorization url to return with a redirect, but fetch
@@ -612,11 +552,11 @@ class ATXPFetcher {
612
552
  throw new Error(`Expected redirect response from authorization URL, got ${response.status}`);
613
553
  };
614
554
  this.authToService = async (error) => {
615
- // TODO: We need to generalize this - we can't assume that there's a single paymentMaker for the auth flow.
616
- if (this.paymentMakers.size > 1) {
555
+ // TODO: We need to generalize this - we can't assume that there's a single paymentMaker for the auth flow.
556
+ if (this.account.paymentMakers.length > 1) {
617
557
  throw new Error(`ATXP: multiple payment makers found - cannot determine which one to use for auth`);
618
558
  }
619
- const paymentMaker = Array.from(this.paymentMakers.values())[0];
559
+ const paymentMaker = this.account.paymentMakers[0];
620
560
  if (paymentMaker) {
621
561
  // We can do the full OAuth flow - we'll generate a signed JWT and call /authorize on the
622
562
  // AS to get a code, then exchange the code for an access token
@@ -631,14 +571,14 @@ class ATXPFetcher {
631
571
  // Call onAuthorize callback after successful authorization
632
572
  await this.onAuthorize({
633
573
  authorizationServer: authorizationUrl.origin,
634
- userId: this.accountId
574
+ userId: this.account.accountId
635
575
  });
636
576
  }
637
577
  catch (authError) {
638
578
  // Call onAuthorizeFailure callback if authorization fails
639
579
  await this.onAuthorizeFailure({
640
580
  authorizationServer: authorizationUrl.origin,
641
- userId: this.accountId,
581
+ userId: this.account.accountId,
642
582
  error: authError
643
583
  });
644
584
  throw authError;
@@ -649,13 +589,13 @@ class ATXPFetcher {
649
589
  // If we do, we'll use it to auth to the downstream resource
650
590
  // (In pass-through scenarios, the atxpServer() middleware stores the incoming
651
591
  // token in the DB under the '' resource URL).
652
- const existingToken = await this.db.getAccessToken(this.accountId, '');
592
+ const existingToken = await this.db.getAccessToken(this.account.accountId, '');
653
593
  if (!existingToken) {
654
594
  this.logger.info(`ATXP: no token found for the current server - we can't exchange a token if we don't have one`);
655
595
  throw error;
656
596
  }
657
597
  const newToken = await this.exchangeToken(existingToken, error.resourceServerUrl);
658
- this.db.saveAccessToken(this.accountId, error.resourceServerUrl, newToken);
598
+ this.db.saveAccessToken(this.account.accountId, error.resourceServerUrl, newToken);
659
599
  }
660
600
  };
661
601
  this.exchangeToken = async (myToken, newResourceUrl) => {
@@ -746,15 +686,15 @@ class ATXPFetcher {
746
686
  throw error;
747
687
  }
748
688
  };
749
- const { accountId, db, paymentMakers, fetchFn = fetch, sideChannelFetch = fetchFn, strict = true, allowInsecureRequests = process.env.NODE_ENV === 'development', allowedAuthorizationServers = [DEFAULT_AUTHORIZATION_SERVER], approvePayment = async () => true, logger = new ConsoleLogger(), onAuthorize = async () => { }, onAuthorizeFailure = async () => { }, onPayment = async () => { }, onPaymentFailure = async () => { } } = config;
689
+ const { account, db, destinationMakers, fetchFn = fetch, sideChannelFetch = fetchFn, strict = true, allowInsecureRequests = process.env.NODE_ENV === 'development', allowedAuthorizationServers = [DEFAULT_AUTHORIZATION_SERVER], approvePayment = async () => true, logger = new ConsoleLogger(), onAuthorize = async () => { }, onAuthorizeFailure = async () => { }, onPayment = async () => { }, onPaymentFailure = async () => { } } = config;
750
690
  // Use React Native safe fetch if in React Native environment
751
691
  const safeFetchFn = getIsReactNative() ? createReactNativeSafeFetch(fetchFn) : fetchFn;
752
692
  const safeSideChannelFetch = getIsReactNative() ? createReactNativeSafeFetch(sideChannelFetch) : sideChannelFetch;
753
- // ATXPClient should never actually use the callback url - instead of redirecting the user to
693
+ // ATXPClient should never actually use the callback url - instead of redirecting the user to
754
694
  // an authorization url which redirects back to the callback url, ATXPClient posts the payment
755
695
  // directly to the authorization server, then does the token exchange itself
756
696
  this.oauthClient = new OAuthClient({
757
- userId: accountId,
697
+ userId: account.accountId,
758
698
  db,
759
699
  callbackUrl: 'http://localhost:3000/unused-dummy-atxp-callback',
760
700
  isPublic: false,
@@ -764,10 +704,10 @@ class ATXPFetcher {
764
704
  allowInsecureRequests,
765
705
  logger: logger
766
706
  });
767
- this.paymentMakers = new Map(Object.entries(paymentMakers));
707
+ this.account = account;
708
+ this.destinationMakers = destinationMakers;
768
709
  this.sideChannelFetch = safeSideChannelFetch;
769
710
  this.db = db;
770
- this.accountId = accountId;
771
711
  this.allowedAuthorizationServers = allowedAuthorizationServers;
772
712
  this.approvePayment = approvePayment;
773
713
  this.logger = logger;
@@ -776,79 +716,6 @@ class ATXPFetcher {
776
716
  this.onPayment = onPayment;
777
717
  this.onPaymentFailure = onPaymentFailure || this.defaultPaymentFailureHandler;
778
718
  }
779
- /**
780
- * Resolves atxp_base or atxp_base_sepolia destinations to real base network destinations
781
- * by calling the payment_info endpoint to get the destination address and network
782
- */
783
- async resolveAtxpBaseDestination(network, paymentInfoUrl, paymentRequestId, amount, currency, receiver, memo) {
784
- // Check if this is an atxp_base network that needs resolution
785
- if (network !== 'atxp_base' && network !== 'atxp_base_sepolia') {
786
- return null;
787
- }
788
- // Map atxp_base networks to their real counterparts
789
- const realNetwork = network === 'atxp_base' ? 'base' : 'base_sepolia';
790
- // Get the payment maker for the real network
791
- const paymentMaker = this.paymentMakers.get(realNetwork);
792
- if (!paymentMaker) {
793
- this.logger.debug(`ATXP: payment network '${realNetwork}' not available for atxp_base resolution`);
794
- return null;
795
- }
796
- // Get the buyer address (source address) from the payment maker
797
- let buyerAddress;
798
- try {
799
- buyerAddress = await paymentMaker.getSourceAddress({
800
- amount,
801
- currency,
802
- receiver,
803
- memo
804
- });
805
- }
806
- catch (error) {
807
- this.logger.warn(`ATXP: failed to get source address from payment maker for ${realNetwork}: ${error.message}`);
808
- return null;
809
- }
810
- // Call the payment_info endpoint
811
- this.logger.debug(`ATXP: resolving ${network} destination via ${paymentInfoUrl}`);
812
- try {
813
- const response = await this.sideChannelFetch(paymentInfoUrl, {
814
- method: 'POST',
815
- headers: {
816
- 'Content-Type': 'application/json',
817
- },
818
- body: JSON.stringify({
819
- paymentRequestId,
820
- buyerAddress,
821
- }),
822
- });
823
- if (!response.ok) {
824
- const text = await response.text();
825
- this.logger.warn(`ATXP: payment_info endpoint failed: ${response.status} ${response.statusText} ${text}`);
826
- return null;
827
- }
828
- const data = await response.json();
829
- if (data.status !== 'success') {
830
- this.logger.warn(`ATXP: payment_info endpoint returned non-success status: ${JSON.stringify(data)}`);
831
- return null;
832
- }
833
- if (!data.destinationAddress) {
834
- this.logger.warn(`ATXP: payment_info endpoint did not return destinationAddress`);
835
- return null;
836
- }
837
- if (!data.network) {
838
- this.logger.warn(`ATXP: payment_info endpoint did not return network`);
839
- return null;
840
- }
841
- this.logger.info(`ATXP: resolved ${network} destination to ${data.destinationAddress} on ${data.network}`);
842
- return {
843
- destinationAddress: data.destinationAddress,
844
- network: data.network,
845
- };
846
- }
847
- catch (error) {
848
- this.logger.warn(`ATXP: failed to resolve ${network} destination: ${error.message}`);
849
- return null;
850
- }
851
- }
852
719
  }
853
720
 
854
721
  var util$1;
@@ -15933,6 +15800,191 @@ class StreamableHTTPClientTransport {
15933
15800
  }
15934
15801
  }
15935
15802
 
15803
+ function isDestinationResponse(obj) {
15804
+ return (typeof obj === 'object' &&
15805
+ obj !== null &&
15806
+ 'chain' in obj &&
15807
+ 'address' in obj &&
15808
+ 'currency' in obj &&
15809
+ 'amount' in obj &&
15810
+ typeof obj.chain === 'string' &&
15811
+ typeof obj.address === 'string' &&
15812
+ typeof obj.currency === 'string' &&
15813
+ typeof obj.amount === 'string');
15814
+ }
15815
+ function isDestinationsApiResponse(obj) {
15816
+ return (typeof obj === 'object' &&
15817
+ obj !== null &&
15818
+ 'destinations' in obj &&
15819
+ 'paymentRequestId' in obj &&
15820
+ Array.isArray(obj.destinations) &&
15821
+ typeof obj.paymentRequestId === 'string');
15822
+ }
15823
+ function parseDestinationsResponse(data) {
15824
+ // Validate response structure
15825
+ if (!isDestinationsApiResponse(data)) {
15826
+ throw new Error('Invalid response: expected object with destinations array and paymentRequestId');
15827
+ }
15828
+ // Validate and convert each destination
15829
+ return data.destinations.map((dest, index) => {
15830
+ if (!isDestinationResponse(dest)) {
15831
+ throw new Error(`Invalid destination at index ${index}: missing required fields (chain, address, currency, amount)`);
15832
+ }
15833
+ // Validate chain is a valid Chain enum value
15834
+ if (!isEnumValue(ChainEnum, dest.chain)) {
15835
+ const validChains = Object.values(ChainEnum).join(', ');
15836
+ throw new Error(`Invalid destination at index ${index}: chain "${dest.chain}" is not a valid chain. Valid chains are: ${validChains}`);
15837
+ }
15838
+ // Validate currency is a valid Currency enum value
15839
+ if (!isEnumValue(CurrencyEnum, dest.currency)) {
15840
+ const validCurrencies = Object.values(CurrencyEnum).join(', ');
15841
+ throw new Error(`Invalid destination at index ${index}: currency "${dest.currency}" is not a valid currency. Valid currencies are: ${validCurrencies}`);
15842
+ }
15843
+ // Validate amount is a valid number
15844
+ const amount = new BigNumber(dest.amount);
15845
+ if (amount.isNaN()) {
15846
+ throw new Error(`Invalid destination at index ${index}: amount "${dest.amount}" is not a valid number`);
15847
+ }
15848
+ return {
15849
+ chain: dest.chain,
15850
+ currency: dest.currency,
15851
+ address: dest.address,
15852
+ amount: amount
15853
+ };
15854
+ });
15855
+ }
15856
+ /**
15857
+ * Destination mapper for ATXP network destinations.
15858
+ * Converts destinations with network='atxp' to actual blockchain network destinations
15859
+ * by resolving the account ID to its associated blockchain addresses.
15860
+ */
15861
+ class ATXPDestinationMaker {
15862
+ constructor(accountsServiceUrl, fetchFn = fetch) {
15863
+ this.accountsServiceUrl = accountsServiceUrl;
15864
+ this.fetchFn = fetchFn;
15865
+ }
15866
+ async makeDestinations(option, logger, paymentRequestId, sources) {
15867
+ if (option.network !== 'atxp') {
15868
+ return [];
15869
+ }
15870
+ try {
15871
+ // The address field contains the account ID (e.g., atxp_acct_xxx) for atxp options
15872
+ const accountId = option.address;
15873
+ // Always use the destinations endpoint
15874
+ const destinations = await this.getDestinations(accountId, paymentRequestId, option, sources, logger);
15875
+ if (destinations.length === 0) {
15876
+ logger.warn(`ATXPDestinationMaker: No destinations found for account ${accountId}`);
15877
+ }
15878
+ else {
15879
+ logger.debug(`ATXPDestinationMaker: Got ${destinations.length} destinations for account ${accountId}`);
15880
+ }
15881
+ return destinations;
15882
+ }
15883
+ catch (error) {
15884
+ logger.error(`ATXPDestinationMaker: Failed to make ATXP destinations: ${error}`);
15885
+ throw error;
15886
+ }
15887
+ }
15888
+ async getDestinations(accountId, paymentRequestId, option, sources, logger) {
15889
+ // Strip any network prefix if present
15890
+ const unqualifiedId = accountId.includes(':') ? accountId.split(':')[1] : accountId;
15891
+ const url = `${this.accountsServiceUrl}/account/${unqualifiedId}/destinations`;
15892
+ logger?.debug(`ATXPDestinationMaker: Fetching destinations from ${url}`);
15893
+ try {
15894
+ const requestBody = {
15895
+ paymentRequestId,
15896
+ options: [{
15897
+ network: option.network,
15898
+ currency: option.currency.toString(),
15899
+ address: option.address,
15900
+ amount: option.amount.toString()
15901
+ }],
15902
+ sources: sources
15903
+ };
15904
+ const response = await this.fetchFn(url, {
15905
+ method: 'POST',
15906
+ headers: {
15907
+ 'Accept': 'application/json',
15908
+ 'Content-Type': 'application/json',
15909
+ },
15910
+ body: JSON.stringify(requestBody),
15911
+ });
15912
+ if (!response.ok) {
15913
+ const text = await response.text();
15914
+ throw new Error(`Failed to fetch destinations: ${response.status} ${response.statusText} - ${text}`);
15915
+ }
15916
+ const data = await response.json();
15917
+ return parseDestinationsResponse(data);
15918
+ }
15919
+ catch (error) {
15920
+ logger?.error(`ATXPDestinationMaker: Error fetching destinations: ${error}`);
15921
+ throw error;
15922
+ }
15923
+ }
15924
+ }
15925
+
15926
+ class PassthroughDestinationMaker {
15927
+ constructor(network) {
15928
+ this.network = network;
15929
+ }
15930
+ async makeDestinations(option, _logger, _paymentRequestId, _sources) {
15931
+ if (option.network !== this.network) {
15932
+ return [];
15933
+ }
15934
+ // Check if option.network is also a Chain by inspecting the ChainEnum values
15935
+ if (Object.values(ChainEnum).includes(option.network)) {
15936
+ // It's a chain, so return a single passthrough destination
15937
+ const destination = {
15938
+ chain: option.network,
15939
+ currency: option.currency,
15940
+ address: option.address,
15941
+ amount: option.amount
15942
+ };
15943
+ return [destination];
15944
+ }
15945
+ return [];
15946
+ }
15947
+ }
15948
+
15949
+ function createDestinationMakers(config) {
15950
+ const { atxpAccountsServer, fetchFn = fetch } = config;
15951
+ // Build the map by exhaustively checking all Network values
15952
+ const makers = new Map();
15953
+ for (const network of Object.values(NetworkEnum)) {
15954
+ // Exhaustiveness check using switch with assertNever
15955
+ switch (network) {
15956
+ case NetworkEnum.Solana:
15957
+ makers.set(network, new PassthroughDestinationMaker(network));
15958
+ break;
15959
+ case NetworkEnum.Base:
15960
+ makers.set(network, new PassthroughDestinationMaker(network));
15961
+ break;
15962
+ case NetworkEnum.World:
15963
+ makers.set(network, new PassthroughDestinationMaker(network));
15964
+ break;
15965
+ case NetworkEnum.Polygon:
15966
+ makers.set(network, new PassthroughDestinationMaker(network));
15967
+ break;
15968
+ case NetworkEnum.BaseSepolia:
15969
+ makers.set(network, new PassthroughDestinationMaker(network));
15970
+ break;
15971
+ case NetworkEnum.WorldSepolia:
15972
+ makers.set(network, new PassthroughDestinationMaker(network));
15973
+ break;
15974
+ case NetworkEnum.PolygonAmoy:
15975
+ makers.set(network, new PassthroughDestinationMaker(network));
15976
+ break;
15977
+ case NetworkEnum.ATXP:
15978
+ makers.set(network, new ATXPDestinationMaker(atxpAccountsServer, fetchFn));
15979
+ break;
15980
+ default:
15981
+ // This will cause a compilation error if a new Network is added but not handled above
15982
+ assertNever(network);
15983
+ }
15984
+ }
15985
+ return makers;
15986
+ }
15987
+
15936
15988
  // Detect if we're in a browser environment and bind fetch appropriately
15937
15989
  const getFetch = () => {
15938
15990
  if (typeof window !== 'undefined' && typeof window.fetch === 'function') {
@@ -15944,6 +15996,7 @@ const getFetch = () => {
15944
15996
  };
15945
15997
  const DEFAULT_CLIENT_CONFIG = {
15946
15998
  allowedAuthorizationServers: [DEFAULT_AUTHORIZATION_SERVER],
15999
+ atxpAccountsServer: DEFAULT_ATXP_ACCOUNTS_SERVER,
15947
16000
  approvePayment: async (_p) => true,
15948
16001
  fetchFn: getFetch(),
15949
16002
  oAuthChannelFetch: getFetch(),
@@ -15973,7 +16026,20 @@ function buildClientConfig(args) {
15973
16026
  const withDefaults = { ...envDefaults, ...args };
15974
16027
  const logger = withDefaults.logger ?? new ConsoleLogger();
15975
16028
  const oAuthDb = withDefaults.oAuthDb ?? new MemoryOAuthDb({ logger });
15976
- const built = { oAuthDb, logger };
16029
+ const fetchFn = withDefaults.fetchFn;
16030
+ // Build destination makers if not provided
16031
+ let accountsServer = withDefaults.atxpAccountsServer;
16032
+ // QoL hack for unspecified accounts server - if the caller is passing an atxpAccount, then assume the origin for that
16033
+ // is what we should use for the accounts server. In practice, the only option is accounts.atxp.ai,
16034
+ // but this supports staging environment
16035
+ if (args.atxpAccountsServer === undefined && withDefaults.account && withDefaults.account instanceof ATXPAccount) {
16036
+ accountsServer = withDefaults.account.origin;
16037
+ }
16038
+ const destinationMakers = withDefaults.destinationMakers ?? createDestinationMakers({
16039
+ atxpAccountsServer: accountsServer,
16040
+ fetchFn
16041
+ });
16042
+ const built = { oAuthDb, logger, destinationMakers };
15977
16043
  return Object.freeze({ ...withDefaults, ...built });
15978
16044
  }
15979
16045
  function buildStreamableTransport(args) {
@@ -15996,7 +16062,7 @@ const USDC_MINT = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
15996
16062
  const ValidateTransferError = ValidateTransferError$1;
15997
16063
  class SolanaPaymentMaker {
15998
16064
  constructor(solanaEndpoint, sourceSecretKey, logger) {
15999
- this.generateJWT = async ({ paymentRequestId, codeChallenge }) => {
16065
+ this.generateJWT = async ({ paymentRequestId, codeChallenge, accountId }) => {
16000
16066
  // Solana/Web3.js secretKey is 64 bytes:
16001
16067
  // first 32 bytes are the private scalar, last 32 are the public key.
16002
16068
  // JWK expects only the 32-byte private scalar for 'd'
@@ -16010,9 +16076,20 @@ class SolanaPaymentMaker {
16010
16076
  if (!(privateKey instanceof CryptoKey)) {
16011
16077
  throw new Error('Expected CryptoKey from importJWK');
16012
16078
  }
16013
- return generateJWT(this.source.publicKey.toBase58(), privateKey, paymentRequestId || '', codeChallenge || '');
16079
+ return generateJWT(this.source.publicKey.toBase58(), privateKey, paymentRequestId || '', codeChallenge || '', accountId);
16014
16080
  };
16015
- this.makePayment = async (amount, currency, receiver, memo) => {
16081
+ this.makePayment = async (destinations, memo, _paymentRequestId) => {
16082
+ // Filter to solana chain destinations
16083
+ const solanaDestinations = destinations.filter(d => d.chain === 'solana');
16084
+ if (solanaDestinations.length === 0) {
16085
+ this.logger.debug('SolanaPaymentMaker: No solana destinations found, cannot handle payment');
16086
+ return null; // Cannot handle these destinations
16087
+ }
16088
+ // Pick first solana destination
16089
+ const dest = solanaDestinations[0];
16090
+ const amount = dest.amount;
16091
+ const currency = dest.currency;
16092
+ const receiver = dest.address;
16016
16093
  if (currency.toUpperCase() !== 'USDC') {
16017
16094
  throw new PaymentNetworkError('Only USDC currency is supported; received ' + currency);
16018
16095
  }
@@ -16027,6 +16104,8 @@ class SolanaPaymentMaker {
16027
16104
  this.logger.warn(`Insufficient ${currency} balance for payment. Required: ${amount}, Available: ${balance}`);
16028
16105
  throw new InsufficientFundsError(currency, amount, balance, 'solana');
16029
16106
  }
16107
+ // Get the destination token account address (this will be the transactionId)
16108
+ const destinationTokenAccount = await getAssociatedTokenAddress(USDC_MINT, receiverKey);
16030
16109
  // Increase compute units to handle both memo and token transfer
16031
16110
  // Memo uses ~6000 CUs, token transfer needs ~6500 CUs
16032
16111
  const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({
@@ -16043,8 +16122,14 @@ class SolanaPaymentMaker {
16043
16122
  });
16044
16123
  transaction.add(modifyComputeUnits);
16045
16124
  transaction.add(addPriorityFee);
16046
- const transactionHash = await sendAndConfirmTransaction(this.connection, transaction, [this.source]);
16047
- return transactionHash;
16125
+ const transactionSignature = await sendAndConfirmTransaction(this.connection, transaction, [this.source]);
16126
+ // Return transaction signature as transactionId and token account address as transactionSubId
16127
+ return {
16128
+ transactionId: transactionSignature,
16129
+ transactionSubId: destinationTokenAccount.toBase58(),
16130
+ chain: 'solana',
16131
+ currency: 'USDC'
16132
+ };
16048
16133
  }
16049
16134
  catch (error) {
16050
16135
  if (error instanceof InsufficientFundsError || error instanceof PaymentNetworkError) {
@@ -16146,7 +16231,7 @@ class BasePaymentMaker {
16146
16231
  getSourceAddress(_params) {
16147
16232
  return this.signingClient.account.address;
16148
16233
  }
16149
- async generateJWT({ paymentRequestId, codeChallenge }) {
16234
+ async generateJWT({ paymentRequestId, codeChallenge, accountId }) {
16150
16235
  const headerObj = { alg: 'ES256K' };
16151
16236
  const payloadObj = {
16152
16237
  sub: this.signingClient.account.address,
@@ -16156,6 +16241,7 @@ class BasePaymentMaker {
16156
16241
  exp: Math.floor(Date.now() / 1000) + 60 * 60,
16157
16242
  ...(codeChallenge ? { code_challenge: codeChallenge } : {}),
16158
16243
  ...(paymentRequestId ? { payment_request_id: paymentRequestId } : {}),
16244
+ ...(accountId ? { account_id: accountId } : {}),
16159
16245
  };
16160
16246
  const header = toBase64Url(JSON.stringify(headerObj));
16161
16247
  const payload = toBase64Url(JSON.stringify(payloadObj));
@@ -16176,7 +16262,18 @@ class BasePaymentMaker {
16176
16262
  this.logger.info(`Generated ES256K JWT: ${jwt}`);
16177
16263
  return jwt;
16178
16264
  }
16179
- async makePayment(amount, currency, receiver) {
16265
+ async makePayment(destinations, _memo, _paymentRequestId) {
16266
+ // Filter to base chain destinations
16267
+ const baseDestinations = destinations.filter(d => d.chain === 'base');
16268
+ if (baseDestinations.length === 0) {
16269
+ this.logger.debug('BasePaymentMaker: No base destinations found, cannot handle payment');
16270
+ return null; // Cannot handle these destinations
16271
+ }
16272
+ // Pick first base destination
16273
+ const dest = baseDestinations[0];
16274
+ const amount = dest.amount;
16275
+ const currency = dest.currency;
16276
+ const receiver = dest.address;
16180
16277
  if (currency.toUpperCase() !== 'USDC') {
16181
16278
  throw new PaymentNetworkError('Only USDC currency is supported; received ' + currency);
16182
16279
  }
@@ -16219,7 +16316,12 @@ class BasePaymentMaker {
16219
16316
  throw new PaymentNetworkError(`Transaction reverted: ${hash}`, new Error('Transaction reverted on chain'));
16220
16317
  }
16221
16318
  this.logger.info(`Transaction confirmed: ${hash} in block ${receipt.blockNumber}`);
16222
- return hash;
16319
+ // Return payment result with chain and currency
16320
+ return {
16321
+ transactionId: hash,
16322
+ chain: 'base',
16323
+ currency: 'USDC'
16324
+ };
16223
16325
  }
16224
16326
  catch (error) {
16225
16327
  if (error instanceof InsufficientFundsError || error instanceof PaymentNetworkError) {
@@ -16240,228 +16342,22 @@ class SolanaAccount {
16240
16342
  throw new Error('Source secret key is required');
16241
16343
  }
16242
16344
  const source = Keypair.fromSecretKey(bs58.decode(sourceSecretKey));
16243
- this.accountId = source.publicKey.toBase58();
16244
- this.paymentMakers = {
16245
- 'solana': new SolanaPaymentMaker(solanaEndpoint, sourceSecretKey),
16246
- };
16247
- }
16248
- }
16249
-
16250
- function toBasicAuth$1(token) {
16251
- // Basic auth is base64("username:password"), password is blank
16252
- const b64 = Buffer.from(`${token}:`).toString('base64');
16253
- return `Basic ${b64}`;
16254
- }
16255
- /**
16256
- * ATXP implementation of viem's LocalAccount interface.
16257
- * Delegates signing operations to the accounts-x402 API.
16258
- * Includes properties needed by x402 library for wallet client compatibility.
16259
- */
16260
- class ATXPLocalAccount {
16261
- constructor(address, origin, token, fetchFn = fetch) {
16262
- this.address = address;
16263
- this.origin = origin;
16264
- this.token = token;
16265
- this.fetchFn = fetchFn;
16266
- this.type = 'local';
16267
- /**
16268
- * Get public key - required by LocalAccount interface
16269
- */
16270
- this.publicKey = '0x0000000000000000000000000000000000000000000000000000000000000000';
16271
- /**
16272
- * Source - required by LocalAccount interface (set to 'custom')
16273
- */
16274
- this.source = 'custom';
16275
- // x402 library expects these properties for wallet client compatibility
16276
- this.account = this; // Self-reference for x402's isSignerWallet check
16277
- this.chain = { id: 8453 }; // Base mainnet - could make this configurable
16278
- this.transport = {}; // Empty transport object for x402 compatibility
16345
+ this.sourcePublicKey = source.publicKey.toBase58();
16346
+ // Format accountId as network:address
16347
+ this.accountId = `solana:${this.sourcePublicKey}`;
16348
+ this.paymentMakers = [
16349
+ new SolanaPaymentMaker(solanaEndpoint, sourceSecretKey)
16350
+ ];
16279
16351
  }
16280
16352
  /**
16281
- * Fetch the wallet address from the /address endpoint
16353
+ * Get sources for this account
16282
16354
  */
16283
- static async create(origin, token, fetchFn = fetch) {
16284
- // The /address endpoint uses Basic auth like other authenticated endpoints
16285
- // For X402, we need the Ethereum/Base address with USDC currency
16286
- const url = new URL(`${origin}/address`);
16287
- url.searchParams.set('network', 'base'); // X402 operates on Base
16288
- url.searchParams.set('currency', 'USDC'); // Always USDC for X402
16289
- const response = await fetchFn(url.toString(), {
16290
- method: 'GET',
16291
- headers: {
16292
- 'Authorization': toBasicAuth$1(token)
16293
- }
16294
- });
16295
- if (!response.ok) {
16296
- const errorText = await response.text();
16297
- throw new Error(`Failed to fetch destination address: ${response.status} ${response.statusText} ${errorText}`);
16298
- }
16299
- const data = await response.json();
16300
- const address = data.address;
16301
- if (!address) {
16302
- throw new Error('Address endpoint did not return an address');
16303
- }
16304
- // Check that the account is an Ethereum/Base account (required for X402/EVM operations)
16305
- const network = data.network;
16306
- if (!network) {
16307
- throw new Error('Address endpoint did not return a network');
16308
- }
16309
- if (network !== 'ethereum' && network !== 'base') {
16310
- throw new Error(`ATXPLocalAccount requires an Ethereum/Base account, but got ${network} account`);
16311
- }
16312
- return new ATXPLocalAccount(address, origin, token, fetchFn);
16313
- }
16314
- /**
16315
- * Sign a typed data structure using EIP-712
16316
- * This is what x402 library will call for EIP-3009 authorization
16317
- */
16318
- async signTypedData(typedData) {
16319
- const response = await this.fetchFn(`${this.origin}/sign-typed-data`, {
16320
- method: 'POST',
16321
- headers: {
16322
- 'Authorization': toBasicAuth$1(this.token),
16323
- 'Content-Type': 'application/json',
16324
- },
16325
- body: JSON.stringify({
16326
- typedData
16327
- })
16328
- });
16329
- if (!response.ok) {
16330
- const errorText = await response.text();
16331
- throw new Error(`Failed to sign typed data: ${response.status} ${response.statusText} ${errorText}`);
16332
- }
16333
- const result = await response.json();
16334
- return result.signature;
16335
- }
16336
- /**
16337
- * Sign a message - required by LocalAccount interface but not used for X402
16338
- */
16339
- async signMessage(_) {
16340
- throw new Error('Message signing not implemented for ATXP local account');
16341
- }
16342
- /**
16343
- * Sign a transaction - required by LocalAccount interface but not used for X402
16344
- */
16345
- async signTransaction(_transaction, _args) {
16346
- throw new Error('Transaction signing not implemented for ATXP local account');
16347
- }
16348
- }
16349
-
16350
- function toBasicAuth(token) {
16351
- // Basic auth is base64("username:password"), password is blank
16352
- const b64 = Buffer.from(`${token}:`).toString('base64');
16353
- return `Basic ${b64}`;
16354
- }
16355
- function parseConnectionString(connectionString) {
16356
- const url = new URL(connectionString);
16357
- const origin = url.origin;
16358
- const token = url.searchParams.get('connection_token') || '';
16359
- const accountId = url.searchParams.get('account_id');
16360
- if (!token) {
16361
- throw new Error('ATXPAccount: connection string missing connection token');
16362
- }
16363
- return { origin, token, accountId };
16364
- }
16365
- class ATXPHttpPaymentMaker {
16366
- constructor(origin, token, fetchFn = fetch) {
16367
- this.origin = origin;
16368
- this.token = token;
16369
- this.fetchFn = fetchFn;
16370
- }
16371
- async getSourceAddress(params) {
16372
- // Call the /address_for_payment endpoint to get the source address for this account
16373
- const response = await this.fetchFn(`${this.origin}/address_for_payment`, {
16374
- method: 'POST',
16375
- headers: {
16376
- 'Authorization': toBasicAuth(this.token),
16377
- 'Content-Type': 'application/json',
16378
- },
16379
- body: JSON.stringify({
16380
- amount: params.amount.toString(),
16381
- currency: params.currency,
16382
- receiver: params.receiver,
16383
- memo: params.memo,
16384
- }),
16385
- });
16386
- if (!response.ok) {
16387
- const text = await response.text();
16388
- throw new Error(`ATXPAccount: /address_for_payment failed: ${response.status} ${response.statusText} ${text}`);
16389
- }
16390
- const json = await response.json();
16391
- if (!json?.sourceAddress) {
16392
- throw new Error('ATXPAccount: /address_for_payment did not return sourceAddress');
16393
- }
16394
- return json.sourceAddress;
16395
- }
16396
- async makePayment(amount, currency, receiver, memo) {
16397
- // Make a regular payment via the /pay endpoint
16398
- const response = await this.fetchFn(`${this.origin}/pay`, {
16399
- method: 'POST',
16400
- headers: {
16401
- 'Authorization': toBasicAuth(this.token),
16402
- 'Content-Type': 'application/json',
16403
- },
16404
- body: JSON.stringify({
16405
- amount: amount.toString(),
16406
- currency,
16407
- receiver,
16408
- memo,
16409
- }),
16410
- });
16411
- if (!response.ok) {
16412
- const text = await response.text();
16413
- throw new Error(`ATXPAccount: /pay failed: ${response.status} ${response.statusText} ${text}`);
16414
- }
16415
- const json = await response.json();
16416
- if (!json?.txHash) {
16417
- throw new Error('ATXPAccount: /pay did not return txHash');
16418
- }
16419
- return json.txHash;
16420
- }
16421
- async generateJWT(params) {
16422
- const response = await this.fetchFn(`${this.origin}/sign`, {
16423
- method: 'POST',
16424
- headers: {
16425
- 'Authorization': toBasicAuth(this.token),
16426
- 'Content-Type': 'application/json',
16427
- },
16428
- body: JSON.stringify({
16429
- paymentRequestId: params.paymentRequestId,
16430
- codeChallenge: params.codeChallenge,
16431
- }),
16432
- });
16433
- if (!response.ok) {
16434
- const text = await response.text();
16435
- throw new Error(`ATXPAccount: /sign failed: ${response.status} ${response.statusText} ${text}`);
16436
- }
16437
- const json = await response.json();
16438
- if (!json?.jwt) {
16439
- throw new Error('ATXPAccount: /sign did not return jwt');
16440
- }
16441
- return json.jwt;
16442
- }
16443
- }
16444
- class ATXPAccount {
16445
- constructor(connectionString, opts) {
16446
- const { origin, token, accountId } = parseConnectionString(connectionString);
16447
- const fetchFn = opts?.fetchFn ?? fetch;
16448
- const network = opts?.network ?? 'base';
16449
- // Store for use in X402 payment creation
16450
- this.origin = origin;
16451
- this.token = token;
16452
- this.fetchFn = fetchFn;
16453
- if (accountId) {
16454
- this.accountId = `atxp:${accountId}`;
16455
- }
16456
- else {
16457
- this.accountId = `atxp:${crypto$1.randomUUID()}`;
16458
- }
16459
- this.paymentMakers = {
16460
- [network]: new ATXPHttpPaymentMaker(origin, token, fetchFn),
16461
- };
16462
- }
16463
- async getSigner() {
16464
- return ATXPLocalAccount.create(this.origin, this.token, this.fetchFn);
16355
+ async getSources() {
16356
+ return [{
16357
+ address: this.sourcePublicKey,
16358
+ chain: 'solana',
16359
+ walletType: 'eoa'
16360
+ }];
16465
16361
  }
16466
16362
  }
16467
16363
 
@@ -16545,6 +16441,88 @@ const getWorldChainUSDCAddress = (chainId) => {
16545
16441
  }
16546
16442
  };
16547
16443
 
16444
+ const USDC_CONTRACT_ADDRESS_POLYGON_MAINNET = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"; // Native USDC on Polygon mainnet
16445
+ const USDC_CONTRACT_ADDRESS_POLYGON_AMOY = "0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582"; // USDC on Polygon Amoy testnet
16446
+ // Polygon Mainnet (Chain ID: 137)
16447
+ // Note: Native currency upgraded from MATIC to POL on September 4, 2024
16448
+ const POLYGON_MAINNET = {
16449
+ id: 137,
16450
+ name: 'Polygon',
16451
+ nativeCurrency: { name: 'POL', symbol: 'POL', decimals: 18 },
16452
+ rpcUrls: {
16453
+ default: { http: ['https://polygon-rpc.com'] }
16454
+ },
16455
+ blockExplorers: {
16456
+ default: { name: 'PolygonScan', url: 'https://polygonscan.com' }
16457
+ }
16458
+ };
16459
+ // Polygon Amoy Testnet (Chain ID: 80002)
16460
+ // Note: Amoy testnet also uses POL as native currency (following mainnet upgrade)
16461
+ const POLYGON_AMOY = {
16462
+ id: 80002,
16463
+ name: 'Polygon Amoy',
16464
+ nativeCurrency: { name: 'POL', symbol: 'POL', decimals: 18 },
16465
+ rpcUrls: {
16466
+ default: { http: ['https://rpc-amoy.polygon.technology'] }
16467
+ },
16468
+ blockExplorers: {
16469
+ default: { name: 'PolygonScan Amoy', url: 'https://amoy.polygonscan.com' }
16470
+ },
16471
+ testnet: true
16472
+ };
16473
+ /**
16474
+ * Get Polygon Mainnet configuration with custom RPC URL (e.g., with API key)
16475
+ * @param rpcUrl - Custom RPC URL, e.g., 'https://polygon-mainnet.g.alchemy.com/v2/YOUR_API_KEY'
16476
+ */
16477
+ const getPolygonMainnetWithRPC = (rpcUrl) => ({
16478
+ ...POLYGON_MAINNET,
16479
+ rpcUrls: {
16480
+ default: { http: [rpcUrl] }
16481
+ }
16482
+ });
16483
+ /**
16484
+ * Get Polygon Amoy Testnet configuration with custom RPC URL (e.g., with API key)
16485
+ * @param rpcUrl - Custom RPC URL, e.g., 'https://polygon-amoy.g.alchemy.com/v2/YOUR_API_KEY'
16486
+ */
16487
+ const getPolygonAmoyWithRPC = (rpcUrl) => ({
16488
+ ...POLYGON_AMOY,
16489
+ rpcUrls: {
16490
+ default: { http: [rpcUrl] }
16491
+ }
16492
+ });
16493
+ /**
16494
+ * Get Polygon Chain configuration by chain ID
16495
+ * @param chainId - Chain ID (137 for mainnet, 80002 for Amoy testnet)
16496
+ * @returns Polygon Chain configuration
16497
+ * @throws Error if chain ID is not supported
16498
+ */
16499
+ const getPolygonByChainId = (chainId) => {
16500
+ switch (chainId) {
16501
+ case 137:
16502
+ return POLYGON_MAINNET;
16503
+ case 80002:
16504
+ return POLYGON_AMOY;
16505
+ default:
16506
+ throw new Error(`Unsupported Polygon Chain ID: ${chainId}. Supported chains: 137 (mainnet), 80002 (Amoy testnet)`);
16507
+ }
16508
+ };
16509
+ /**
16510
+ * Get USDC contract address for Polygon by chain ID
16511
+ * @param chainId - Chain ID (137 for mainnet, 80002 for Amoy testnet)
16512
+ * @returns USDC contract address
16513
+ * @throws Error if chain ID is not supported
16514
+ */
16515
+ const getPolygonUSDCAddress = (chainId) => {
16516
+ switch (chainId) {
16517
+ case 137:
16518
+ return USDC_CONTRACT_ADDRESS_POLYGON_MAINNET;
16519
+ case 80002:
16520
+ return USDC_CONTRACT_ADDRESS_POLYGON_AMOY;
16521
+ default:
16522
+ throw new Error(`Unsupported Polygon Chain ID: ${chainId}. Supported chains: 137 (mainnet), 80002 (Amoy testnet)`);
16523
+ }
16524
+ };
16525
+
16548
16526
  class BaseAccount {
16549
16527
  constructor(baseRPCUrl, sourceSecretKey) {
16550
16528
  if (!baseRPCUrl) {
@@ -16554,25 +16532,135 @@ class BaseAccount {
16554
16532
  throw new Error('Source secret key is required');
16555
16533
  }
16556
16534
  this.account = privateKeyToAccount(sourceSecretKey);
16557
- this.accountId = this.account.address;
16535
+ // Format accountId as network:address
16536
+ this.accountId = `base:${this.account.address}`;
16558
16537
  this.walletClient = createWalletClient({
16559
16538
  account: this.account,
16560
16539
  chain: base,
16561
16540
  transport: http(baseRPCUrl),
16562
16541
  });
16563
- this.paymentMakers = {
16564
- 'base': new BasePaymentMaker(baseRPCUrl, this.walletClient),
16565
- };
16542
+ this.paymentMakers = [
16543
+ new BasePaymentMaker(baseRPCUrl, this.walletClient)
16544
+ ];
16566
16545
  }
16567
16546
  /**
16568
- * Get a signer that can be used with the x402 library
16569
- * This is only available for EVM-based accounts
16547
+ * Get the LocalAccount (signer) for this account.
16548
+ * This can be used with the x402 library or other signing operations.
16570
16549
  */
16571
- getSigner() {
16572
- // Return the viem account directly - it implements LocalAccount interface
16550
+ getLocalAccount() {
16573
16551
  return this.account;
16574
16552
  }
16553
+ /**
16554
+ * Get sources for this account
16555
+ */
16556
+ async getSources() {
16557
+ return [{
16558
+ address: this.account.address,
16559
+ chain: 'base',
16560
+ walletType: 'eoa'
16561
+ }];
16562
+ }
16563
+ }
16564
+
16565
+ function toBasicAuth(token) {
16566
+ // Basic auth is base64("username:password"), password is blank
16567
+ const b64 = Buffer.from(`${token}:`).toString('base64');
16568
+ return `Basic ${b64}`;
16569
+ }
16570
+ /**
16571
+ * ATXP implementation of viem's LocalAccount interface.
16572
+ * Delegates signing operations to the accounts-x402 API.
16573
+ * Includes properties needed by x402 library for wallet client compatibility.
16574
+ */
16575
+ class ATXPLocalAccount {
16576
+ constructor(address, origin, token, fetchFn = fetch) {
16577
+ this.address = address;
16578
+ this.origin = origin;
16579
+ this.token = token;
16580
+ this.fetchFn = fetchFn;
16581
+ this.type = 'local';
16582
+ /**
16583
+ * Get public key - required by LocalAccount interface
16584
+ */
16585
+ this.publicKey = '0x0000000000000000000000000000000000000000000000000000000000000000';
16586
+ /**
16587
+ * Source - required by LocalAccount interface (set to 'custom')
16588
+ */
16589
+ this.source = 'custom';
16590
+ // x402 library expects these properties for wallet client compatibility
16591
+ this.account = this; // Self-reference for x402's isSignerWallet check
16592
+ this.chain = { id: 8453 }; // Base mainnet - could make this configurable
16593
+ this.transport = {}; // Empty transport object for x402 compatibility
16594
+ }
16595
+ /**
16596
+ * Fetch the wallet address from the /address endpoint
16597
+ */
16598
+ static async create(origin, token, fetchFn = fetch) {
16599
+ // The /address endpoint uses Basic auth like other authenticated endpoints
16600
+ // For X402, we need the Ethereum/Base address with USDC currency
16601
+ const url = new URL(`${origin}/address`);
16602
+ url.searchParams.set('network', 'base'); // X402 operates on Base
16603
+ url.searchParams.set('currency', 'USDC'); // Always USDC for X402
16604
+ const response = await fetchFn(url.toString(), {
16605
+ method: 'GET',
16606
+ headers: {
16607
+ 'Authorization': toBasicAuth(token)
16608
+ }
16609
+ });
16610
+ if (!response.ok) {
16611
+ const errorText = await response.text();
16612
+ throw new Error(`Failed to fetch destination address: ${response.status} ${response.statusText} ${errorText}`);
16613
+ }
16614
+ const data = await response.json();
16615
+ const address = data.address;
16616
+ if (!address) {
16617
+ throw new Error('Address endpoint did not return an address');
16618
+ }
16619
+ // Check that the account is an Ethereum/Base account (required for X402/EVM operations)
16620
+ const network = data.network;
16621
+ if (!network) {
16622
+ throw new Error('Address endpoint did not return a network');
16623
+ }
16624
+ if (network !== 'ethereum' && network !== 'base') {
16625
+ throw new Error(`ATXPLocalAccount requires an Ethereum/Base account, but got ${network} account`);
16626
+ }
16627
+ return new ATXPLocalAccount(address, origin, token, fetchFn);
16628
+ }
16629
+ /**
16630
+ * Sign a typed data structure using EIP-712
16631
+ * This is what x402 library will call for EIP-3009 authorization
16632
+ */
16633
+ async signTypedData(typedData) {
16634
+ const response = await this.fetchFn(`${this.origin}/sign-typed-data`, {
16635
+ method: 'POST',
16636
+ headers: {
16637
+ 'Authorization': toBasicAuth(this.token),
16638
+ 'Content-Type': 'application/json',
16639
+ },
16640
+ body: JSON.stringify({
16641
+ typedData
16642
+ })
16643
+ });
16644
+ if (!response.ok) {
16645
+ const errorText = await response.text();
16646
+ throw new Error(`Failed to sign typed data: ${response.status} ${response.statusText} ${errorText}`);
16647
+ }
16648
+ const result = await response.json();
16649
+ return result.signature;
16650
+ }
16651
+ /**
16652
+ * Sign a message - required by LocalAccount interface but not used for X402
16653
+ */
16654
+ async signMessage(_) {
16655
+ throw new Error('Message signing not implemented for ATXP local account');
16656
+ }
16657
+ /**
16658
+ * Sign a transaction - required by LocalAccount interface but not used for X402
16659
+ */
16660
+ async signTransaction(_transaction, _args) {
16661
+ throw new Error('Transaction signing not implemented for ATXP local account');
16662
+ }
16575
16663
  }
16576
16664
 
16577
- export { ATXPAccount, ATXPLocalAccount, BaseAccount, BasePaymentMaker, DEFAULT_CLIENT_CONFIG, InsufficientFundsError, OAuthAuthenticationRequiredError, OAuthClient, PaymentNetworkError, SolanaAccount, SolanaPaymentMaker, USDC_CONTRACT_ADDRESS_BASE, USDC_CONTRACT_ADDRESS_BASE_SEPOLIA, USDC_CONTRACT_ADDRESS_WORLD_MAINNET, USDC_CONTRACT_ADDRESS_WORLD_SEPOLIA, ValidateTransferError, WORLD_CHAIN_MAINNET, WORLD_CHAIN_SEPOLIA, atxpClient, atxpFetch, buildClientConfig, buildStreamableTransport, getBaseUSDCAddress, getWorldChainByChainId, getWorldChainMainnetWithRPC, getWorldChainSepoliaWithRPC, getWorldChainUSDCAddress };
16665
+ export { ATXPDestinationMaker, ATXPLocalAccount, BaseAccount, BasePaymentMaker, DEFAULT_CLIENT_CONFIG, InsufficientFundsError, OAuthAuthenticationRequiredError, OAuthClient, POLYGON_AMOY, POLYGON_MAINNET, PassthroughDestinationMaker, PaymentNetworkError, SolanaAccount, SolanaPaymentMaker, USDC_CONTRACT_ADDRESS_BASE, USDC_CONTRACT_ADDRESS_BASE_SEPOLIA, USDC_CONTRACT_ADDRESS_POLYGON_AMOY, USDC_CONTRACT_ADDRESS_POLYGON_MAINNET, USDC_CONTRACT_ADDRESS_WORLD_MAINNET, USDC_CONTRACT_ADDRESS_WORLD_SEPOLIA, ValidateTransferError, WORLD_CHAIN_MAINNET, WORLD_CHAIN_SEPOLIA, atxpClient, atxpFetch, buildClientConfig, buildStreamableTransport, getBaseUSDCAddress, getPolygonAmoyWithRPC, getPolygonByChainId, getPolygonMainnetWithRPC, getPolygonUSDCAddress, getWorldChainByChainId, getWorldChainMainnetWithRPC, getWorldChainSepoliaWithRPC, getWorldChainUSDCAddress };
16578
16666
  //# sourceMappingURL=index.js.map