@atxp/client 0.7.3 → 0.8.0

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