@atxp/client 0.7.4 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/atxpAccount.d.ts +8 -6
  2. package/dist/atxpAccount.d.ts.map +1 -1
  3. package/dist/atxpAccount.js +57 -19
  4. package/dist/atxpAccount.js.map +1 -1
  5. package/dist/atxpClient.d.ts +1 -1
  6. package/dist/atxpClient.d.ts.map +1 -1
  7. package/dist/atxpClient.js +18 -2
  8. package/dist/atxpClient.js.map +1 -1
  9. package/dist/atxpFetcher.d.ts +5 -15
  10. package/dist/atxpFetcher.d.ts.map +1 -1
  11. package/dist/atxpFetcher.js +95 -229
  12. package/dist/atxpFetcher.js.map +1 -1
  13. package/dist/baseAccount.d.ts +7 -4
  14. package/dist/baseAccount.d.ts.map +1 -1
  15. package/dist/baseAccount.js +15 -4
  16. package/dist/baseAccount.js.map +1 -1
  17. package/dist/basePaymentMaker.d.ts +4 -3
  18. package/dist/basePaymentMaker.d.ts.map +1 -1
  19. package/dist/basePaymentMaker.js +20 -3
  20. package/dist/basePaymentMaker.js.map +1 -1
  21. package/dist/clientTestHelpers.d.ts.map +1 -1
  22. package/dist/destinationMakers/atxpDestinationMaker.d.ts +15 -0
  23. package/dist/destinationMakers/atxpDestinationMaker.d.ts.map +1 -0
  24. package/dist/destinationMakers/atxpDestinationMaker.js +128 -0
  25. package/dist/destinationMakers/atxpDestinationMaker.js.map +1 -0
  26. package/dist/destinationMakers/index.d.ts +9 -0
  27. package/dist/destinationMakers/index.d.ts.map +1 -0
  28. package/dist/destinationMakers/index.js +42 -0
  29. package/dist/destinationMakers/index.js.map +1 -0
  30. package/dist/destinationMakers/passthroughDestinationMaker.d.ts +8 -0
  31. package/dist/destinationMakers/passthroughDestinationMaker.d.ts.map +1 -0
  32. package/dist/destinationMakers/passthroughDestinationMaker.js +27 -0
  33. package/dist/destinationMakers/passthroughDestinationMaker.js.map +1 -0
  34. package/dist/index.cjs +803 -584
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.ts +99 -42
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +798 -586
  39. package/dist/index.js.map +1 -1
  40. package/dist/polygonConstants.d.ts +46 -0
  41. package/dist/polygonConstants.d.ts.map +1 -0
  42. package/dist/polygonConstants.js +54 -0
  43. package/dist/polygonConstants.js.map +1 -0
  44. package/dist/solanaAccount.d.ts +8 -4
  45. package/dist/solanaAccount.d.ts.map +1 -1
  46. package/dist/solanaAccount.js +16 -4
  47. package/dist/solanaAccount.js.map +1 -1
  48. package/dist/solanaPaymentMaker.d.ts +4 -3
  49. package/dist/solanaPaymentMaker.d.ts.map +1 -1
  50. package/dist/solanaPaymentMaker.js +24 -5
  51. package/dist/solanaPaymentMaker.js.map +1 -1
  52. package/dist/types.d.ts +5 -23
  53. package/dist/types.d.ts.map +1 -1
  54. package/dist/types.js.map +1 -1
  55. package/package.json +2 -2
package/dist/index.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,43 +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
- // 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`);
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`);
368
363
  continue;
369
364
  }
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;
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}`);
382
375
  }
383
- 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) {
384
396
  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}`);
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}`);
387
406
  await this.onPayment({ payment: prospectivePayment });
388
407
  // Submit payment to the server
389
- const jwt = await paymentMaker.generateJWT({ paymentRequestId, codeChallenge: '' });
408
+ const jwt = await paymentMaker.generateJWT({ paymentRequestId, codeChallenge: '', accountId: this.account.accountId });
390
409
  const response = await this.sideChannelFetch(paymentRequestUrl.toString(), {
391
410
  method: 'PUT',
392
411
  headers: {
@@ -394,9 +413,10 @@ class ATXPFetcher {
394
413
  'Content-Type': 'application/json'
395
414
  },
396
415
  body: JSON.stringify({
397
- transactionId: paymentId,
398
- network: destinationNetwork,
399
- currency: dest.currency
416
+ transactionId: result.transactionId,
417
+ ...(result.transactionSubId ? { transactionSubId: result.transactionSubId } : {}),
418
+ chain: result.chain,
419
+ currency: result.currency
400
420
  })
401
421
  });
402
422
  this.logger.debug(`ATXP: payment was ${response.ok ? 'successfully' : 'not successfully'} PUT to ${paymentRequestUrl} : status ${response.status} ${response.statusText}`);
@@ -409,13 +429,18 @@ class ATXPFetcher {
409
429
  }
410
430
  catch (error) {
411
431
  const typedError = error;
412
- this.logger.warn(`ATXP: payment failed on ${destinationNetwork}: ${typedError.message}`);
432
+ paymentAttempted = true;
433
+ lastPaymentError = typedError;
434
+ this.logger.warn(`ATXP: payment maker failed: ${typedError.message}`);
413
435
  await this.onPaymentFailure({ payment: prospectivePayment, error: typedError });
414
- // Try next destination
415
- continue;
436
+ // Continue to next payment maker
416
437
  }
417
438
  }
418
- 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`);
419
444
  return false;
420
445
  };
421
446
  this.handlePaymentRequestError = async (paymentRequestError) => {
@@ -442,102 +467,8 @@ class ATXPFetcher {
442
467
  if (paymentRequestData.destinations && paymentRequestData.destinations.length > 0) {
443
468
  return this.handleMultiDestinationPayment(paymentRequestData, paymentRequestUrl, paymentRequestId);
444
469
  }
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;
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`);
541
472
  };
542
473
  this.getPaymentRequestData = async (paymentRequestUrl) => {
543
474
  const prRequest = await this.sideChannelFetch(paymentRequestUrl);
@@ -545,6 +476,14 @@ class ATXPFetcher {
545
476
  throw new Error(`ATXP: GET ${paymentRequestUrl} failed: ${prRequest.status} ${prRequest.statusText}`);
546
477
  }
547
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
+ }
548
487
  return paymentRequest;
549
488
  };
550
489
  this.isAllowedAuthServer = (url) => {
@@ -558,15 +497,15 @@ class ATXPFetcher {
558
497
  throw new Error(`Code challenge not provided`);
559
498
  }
560
499
  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.`);
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.`);
563
502
  }
564
503
  // TypeScript should prevent this, but add runtime check for edge cases (untyped JS, version mismatches, etc.)
565
504
  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.`);
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.`);
568
507
  }
569
- const authToken = await paymentMaker.generateJWT({ paymentRequestId: '', codeChallenge: codeChallenge });
508
+ const authToken = await paymentMaker.generateJWT({ paymentRequestId: '', codeChallenge: codeChallenge, accountId: this.account.accountId });
570
509
  // Make a fetch call to the authorization URL with the payment ID
571
510
  // redirect=false is a hack
572
511
  // The OAuth spec calls for the authorization url to return with a redirect, but fetch
@@ -612,11 +551,11 @@ class ATXPFetcher {
612
551
  throw new Error(`Expected redirect response from authorization URL, got ${response.status}`);
613
552
  };
614
553
  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) {
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) {
617
556
  throw new Error(`ATXP: multiple payment makers found - cannot determine which one to use for auth`);
618
557
  }
619
- const paymentMaker = Array.from(this.paymentMakers.values())[0];
558
+ const paymentMaker = this.account.paymentMakers[0];
620
559
  if (paymentMaker) {
621
560
  // We can do the full OAuth flow - we'll generate a signed JWT and call /authorize on the
622
561
  // AS to get a code, then exchange the code for an access token
@@ -631,14 +570,14 @@ class ATXPFetcher {
631
570
  // Call onAuthorize callback after successful authorization
632
571
  await this.onAuthorize({
633
572
  authorizationServer: authorizationUrl.origin,
634
- userId: this.accountId
573
+ userId: this.account.accountId
635
574
  });
636
575
  }
637
576
  catch (authError) {
638
577
  // Call onAuthorizeFailure callback if authorization fails
639
578
  await this.onAuthorizeFailure({
640
579
  authorizationServer: authorizationUrl.origin,
641
- userId: this.accountId,
580
+ userId: this.account.accountId,
642
581
  error: authError
643
582
  });
644
583
  throw authError;
@@ -649,13 +588,13 @@ class ATXPFetcher {
649
588
  // If we do, we'll use it to auth to the downstream resource
650
589
  // (In pass-through scenarios, the atxpServer() middleware stores the incoming
651
590
  // token in the DB under the '' resource URL).
652
- const existingToken = await this.db.getAccessToken(this.accountId, '');
591
+ const existingToken = await this.db.getAccessToken(this.account.accountId, '');
653
592
  if (!existingToken) {
654
593
  this.logger.info(`ATXP: no token found for the current server - we can't exchange a token if we don't have one`);
655
594
  throw error;
656
595
  }
657
596
  const newToken = await this.exchangeToken(existingToken, error.resourceServerUrl);
658
- this.db.saveAccessToken(this.accountId, error.resourceServerUrl, newToken);
597
+ this.db.saveAccessToken(this.account.accountId, error.resourceServerUrl, newToken);
659
598
  }
660
599
  };
661
600
  this.exchangeToken = async (myToken, newResourceUrl) => {
@@ -746,15 +685,15 @@ class ATXPFetcher {
746
685
  throw error;
747
686
  }
748
687
  };
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;
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;
750
689
  // Use React Native safe fetch if in React Native environment
751
690
  const safeFetchFn = getIsReactNative() ? createReactNativeSafeFetch(fetchFn) : fetchFn;
752
691
  const safeSideChannelFetch = getIsReactNative() ? createReactNativeSafeFetch(sideChannelFetch) : sideChannelFetch;
753
- // 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
754
693
  // an authorization url which redirects back to the callback url, ATXPClient posts the payment
755
694
  // directly to the authorization server, then does the token exchange itself
756
695
  this.oauthClient = new OAuthClient({
757
- userId: accountId,
696
+ userId: account.accountId,
758
697
  db,
759
698
  callbackUrl: 'http://localhost:3000/unused-dummy-atxp-callback',
760
699
  isPublic: false,
@@ -764,10 +703,10 @@ class ATXPFetcher {
764
703
  allowInsecureRequests,
765
704
  logger: logger
766
705
  });
767
- this.paymentMakers = new Map(Object.entries(paymentMakers));
706
+ this.account = account;
707
+ this.destinationMakers = destinationMakers;
768
708
  this.sideChannelFetch = safeSideChannelFetch;
769
709
  this.db = db;
770
- this.accountId = accountId;
771
710
  this.allowedAuthorizationServers = allowedAuthorizationServers;
772
711
  this.approvePayment = approvePayment;
773
712
  this.logger = logger;
@@ -776,79 +715,6 @@ class ATXPFetcher {
776
715
  this.onPayment = onPayment;
777
716
  this.onPaymentFailure = onPaymentFailure || this.defaultPaymentFailureHandler;
778
717
  }
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
718
  }
853
719
 
854
720
  var util$1;
@@ -15933,146 +15799,618 @@ class StreamableHTTPClientTransport {
15933
15799
  }
15934
15800
  }
15935
15801
 
15936
- // Detect if we're in a browser environment and bind fetch appropriately
15937
- const getFetch = () => {
15938
- if (typeof window !== 'undefined' && typeof window.fetch === 'function') {
15939
- // In browser, bind fetch to window to avoid "Illegal invocation" errors
15940
- return fetch.bind(window);
15941
- }
15942
- // In Node.js or other environments, use fetch as-is
15943
- return fetch;
15944
- };
15945
- const DEFAULT_CLIENT_CONFIG = {
15946
- allowedAuthorizationServers: [DEFAULT_AUTHORIZATION_SERVER],
15947
- approvePayment: async (_p) => true,
15948
- fetchFn: getFetch(),
15949
- oAuthChannelFetch: getFetch(),
15950
- allowHttp: false, // may be overridden in buildClientConfig by process.env.NODE_ENV
15951
- clientInfo: {
15952
- name: 'ATXPClient',
15953
- version: '0.0.1'
15954
- },
15955
- clientOptions: {
15956
- capabilities: {}
15957
- },
15958
- onAuthorize: async () => { },
15959
- onAuthorizeFailure: async () => { },
15960
- onPayment: async () => { },
15961
- onPaymentFailure: async () => { }
15962
- };
15963
- function buildClientConfig(args) {
15964
- // Use fetchFn for oAuthChannelFetch if the latter isn't explicitly set
15965
- if (args.fetchFn && !args.oAuthChannelFetch) {
15966
- args.oAuthChannelFetch = args.fetchFn;
15967
- }
15968
- // Read environment variable at runtime, not module load time
15969
- const envDefaults = {
15970
- ...DEFAULT_CLIENT_CONFIG,
15971
- allowHttp: process.env.NODE_ENV === 'development',
15972
- };
15973
- const withDefaults = { ...envDefaults, ...args };
15974
- const logger = withDefaults.logger ?? new ConsoleLogger();
15975
- const oAuthDb = withDefaults.oAuthDb ?? new MemoryOAuthDb({ logger });
15976
- const built = { oAuthDb, logger };
15977
- return Object.freeze({ ...withDefaults, ...built });
15978
- }
15979
- function buildStreamableTransport(args) {
15980
- const config = buildClientConfig(args);
15981
- // Apply the ATXP wrapper to the fetch function
15982
- const wrappedFetch = atxpFetch(config);
15983
- const transport = new StreamableHTTPClientTransport(new URL(args.mcpServer), { fetch: wrappedFetch });
15984
- return transport;
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');
15985
15813
  }
15986
- async function atxpClient(args) {
15987
- const config = buildClientConfig(args);
15988
- const transport = buildStreamableTransport(config);
15989
- const client = new Client(config.clientInfo, config.clientOptions);
15990
- await client.connect(transport);
15991
- 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');
15992
15821
  }
15993
-
15994
- // this is a global public key for USDC on the solana mainnet
15995
- const USDC_MINT = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
15996
- const ValidateTransferError = ValidateTransferError$1;
15997
- class SolanaPaymentMaker {
15998
- constructor(solanaEndpoint, sourceSecretKey, logger) {
15999
- this.generateJWT = async ({ paymentRequestId, codeChallenge }) => {
16000
- // Solana/Web3.js secretKey is 64 bytes:
16001
- // first 32 bytes are the private scalar, last 32 are the public key.
16002
- // JWK expects only the 32-byte private scalar for 'd'
16003
- const jwk = {
16004
- kty: 'OKP',
16005
- crv: 'Ed25519',
16006
- d: Buffer.from(this.source.secretKey.slice(0, 32)).toString('base64url'),
16007
- x: Buffer.from(this.source.publicKey.toBytes()).toString('base64url'),
16008
- };
16009
- const privateKey = await importJWK(jwk, 'EdDSA');
16010
- if (!(privateKey instanceof CryptoKey)) {
16011
- throw new Error('Expected CryptoKey from importJWK');
16012
- }
16013
- 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
16014
15852
  };
16015
- this.makePayment = async (amount, currency, receiver, memo) => {
16016
- if (currency.toUpperCase() !== 'USDC') {
16017
- throw new PaymentNetworkError('Only USDC currency is supported; received ' + currency);
16018
- }
16019
- const receiverKey = new PublicKey(receiver);
16020
- this.logger.info(`Making payment of ${amount} ${currency} to ${receiver} on Solana from ${this.source.publicKey.toBase58()}`);
16021
- try {
16022
- // Check balance before attempting payment
16023
- const tokenAccountAddress = await getAssociatedTokenAddress(USDC_MINT, this.source.publicKey);
16024
- const tokenAccount = await getAccount(this.connection, tokenAccountAddress);
16025
- const balance = new BigNumber$1(tokenAccount.amount.toString()).dividedBy(10 ** 6); // USDC has 6 decimals
16026
- if (balance.lt(amount)) {
16027
- this.logger.warn(`Insufficient ${currency} balance for payment. Required: ${amount}, Available: ${balance}`);
16028
- throw new InsufficientFundsError(currency, amount, balance, 'solana');
16029
- }
16030
- // Increase compute units to handle both memo and token transfer
16031
- // Memo uses ~6000 CUs, token transfer needs ~6500 CUs
16032
- const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({
16033
- units: 50000,
16034
- });
16035
- const addPriorityFee = ComputeBudgetProgram.setComputeUnitPrice({
16036
- microLamports: 20000,
16037
- });
16038
- const transaction = await createTransfer(this.connection, this.source.publicKey, {
16039
- amount: amount,
16040
- recipient: receiverKey,
16041
- splToken: USDC_MINT,
16042
- memo,
16043
- });
16044
- transaction.add(modifyComputeUnits);
16045
- transaction.add(addPriorityFee);
16046
- const transactionHash = await sendAndConfirmTransaction(this.connection, transaction, [this.source]);
16047
- return transactionHash;
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}`);
16048
15876
  }
16049
- catch (error) {
16050
- if (error instanceof InsufficientFundsError || error instanceof PaymentNetworkError) {
16051
- throw error;
16052
- }
16053
- // Wrap other errors in PaymentNetworkError
16054
- throw new PaymentNetworkError(`Payment failed on Solana network: ${error.message}`, error);
15877
+ else {
15878
+ logger.debug(`ATXPDestinationMaker: Got ${destinations.length} destinations for account ${accountId}`);
16055
15879
  }
16056
- };
16057
- if (!solanaEndpoint) {
16058
- throw new Error('Solana endpoint is required');
15880
+ return destinations;
16059
15881
  }
16060
- if (!sourceSecretKey) {
16061
- throw new Error('Source secret key is required');
15882
+ catch (error) {
15883
+ logger.error(`ATXPDestinationMaker: Failed to make ATXP destinations: ${error}`);
15884
+ throw error;
16062
15885
  }
16063
- this.connection = new Connection(solanaEndpoint, { commitment: 'confirmed' });
16064
- this.source = Keypair.fromSecretKey(bs58.decode(sourceSecretKey));
16065
- this.logger = logger ?? new ConsoleLogger();
16066
15886
  }
16067
- getSourceAddress(_params) {
16068
- return this.source.publicKey.toBase58();
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}`);
15914
+ }
15915
+ const data = await response.json();
15916
+ return parseDestinationsResponse(data);
15917
+ }
15918
+ catch (error) {
15919
+ logger?.error(`ATXPDestinationMaker: Error fetching destinations: ${error}`);
15920
+ throw error;
15921
+ }
16069
15922
  }
16070
15923
  }
16071
15924
 
16072
- const USDC_CONTRACT_ADDRESS_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; // USDC on Base mainnet
16073
- const USDC_CONTRACT_ADDRESS_BASE_SEPOLIA = "0x036CbD53842c5426634e7929541eC2318f3dCF7e"; // USDC on Base Sepolia testnet
16074
- /**
16075
- * Get USDC contract address for Base chain by chain ID
15925
+ class PassthroughDestinationMaker {
15926
+ constructor(network) {
15927
+ this.network = network;
15928
+ }
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
+ }
15947
+
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;
15982
+ }
15983
+
15984
+ function toBasicAuth$1(token) {
15985
+ // Basic auth is base64("username:password"), password is blank
15986
+ const b64 = Buffer.from(`${token}:`).toString('base64');
15987
+ return `Basic ${b64}`;
15988
+ }
15989
+ /**
15990
+ * ATXP implementation of viem's LocalAccount interface.
15991
+ * Delegates signing operations to the accounts-x402 API.
15992
+ * Includes properties needed by x402 library for wallet client compatibility.
15993
+ */
15994
+ class ATXPLocalAccount {
15995
+ constructor(address, origin, token, fetchFn = fetch) {
15996
+ this.address = address;
15997
+ this.origin = origin;
15998
+ this.token = token;
15999
+ this.fetchFn = fetchFn;
16000
+ this.type = 'local';
16001
+ /**
16002
+ * Get public key - required by LocalAccount interface
16003
+ */
16004
+ this.publicKey = '0x0000000000000000000000000000000000000000000000000000000000000000';
16005
+ /**
16006
+ * Source - required by LocalAccount interface (set to 'custom')
16007
+ */
16008
+ this.source = 'custom';
16009
+ // x402 library expects these properties for wallet client compatibility
16010
+ this.account = this; // Self-reference for x402's isSignerWallet check
16011
+ this.chain = { id: 8453 }; // Base mainnet - could make this configurable
16012
+ this.transport = {}; // Empty transport object for x402 compatibility
16013
+ }
16014
+ /**
16015
+ * Fetch the wallet address from the /address endpoint
16016
+ */
16017
+ static async create(origin, token, fetchFn = fetch) {
16018
+ // The /address endpoint uses Basic auth like other authenticated endpoints
16019
+ // For X402, we need the Ethereum/Base address with USDC currency
16020
+ const url = new URL(`${origin}/address`);
16021
+ url.searchParams.set('network', 'base'); // X402 operates on Base
16022
+ url.searchParams.set('currency', 'USDC'); // Always USDC for X402
16023
+ const response = await fetchFn(url.toString(), {
16024
+ method: 'GET',
16025
+ headers: {
16026
+ 'Authorization': toBasicAuth$1(token)
16027
+ }
16028
+ });
16029
+ if (!response.ok) {
16030
+ const errorText = await response.text();
16031
+ throw new Error(`Failed to fetch destination address: ${response.status} ${response.statusText} ${errorText}`);
16032
+ }
16033
+ const data = await response.json();
16034
+ const address = data.address;
16035
+ if (!address) {
16036
+ throw new Error('Address endpoint did not return an address');
16037
+ }
16038
+ // Check that the account is an Ethereum/Base account (required for X402/EVM operations)
16039
+ const network = data.network;
16040
+ if (!network) {
16041
+ throw new Error('Address endpoint did not return a network');
16042
+ }
16043
+ if (network !== 'ethereum' && network !== 'base') {
16044
+ throw new Error(`ATXPLocalAccount requires an Ethereum/Base account, but got ${network} account`);
16045
+ }
16046
+ return new ATXPLocalAccount(address, origin, token, fetchFn);
16047
+ }
16048
+ /**
16049
+ * Sign a typed data structure using EIP-712
16050
+ * This is what x402 library will call for EIP-3009 authorization
16051
+ */
16052
+ async signTypedData(typedData) {
16053
+ const response = await this.fetchFn(`${this.origin}/sign-typed-data`, {
16054
+ method: 'POST',
16055
+ headers: {
16056
+ 'Authorization': toBasicAuth$1(this.token),
16057
+ 'Content-Type': 'application/json',
16058
+ },
16059
+ body: JSON.stringify({
16060
+ typedData
16061
+ })
16062
+ });
16063
+ if (!response.ok) {
16064
+ const errorText = await response.text();
16065
+ throw new Error(`Failed to sign typed data: ${response.status} ${response.statusText} ${errorText}`);
16066
+ }
16067
+ const result = await response.json();
16068
+ return result.signature;
16069
+ }
16070
+ /**
16071
+ * Sign a message - required by LocalAccount interface but not used for X402
16072
+ */
16073
+ async signMessage(_) {
16074
+ throw new Error('Message signing not implemented for ATXP local account');
16075
+ }
16076
+ /**
16077
+ * Sign a transaction - required by LocalAccount interface but not used for X402
16078
+ */
16079
+ async signTransaction(_transaction, _args) {
16080
+ throw new Error('Transaction signing not implemented for ATXP local account');
16081
+ }
16082
+ }
16083
+
16084
+ function toBasicAuth(token) {
16085
+ // Basic auth is base64("username:password"), password is blank
16086
+ const b64 = Buffer.from(`${token}:`).toString('base64');
16087
+ return `Basic ${b64}`;
16088
+ }
16089
+ function parseConnectionString(connectionString) {
16090
+ const url = new URL(connectionString);
16091
+ const origin = url.origin;
16092
+ const token = url.searchParams.get('connection_token') || '';
16093
+ const accountId = url.searchParams.get('account_id');
16094
+ if (!token) {
16095
+ throw new Error('ATXPAccount: connection string missing connection token');
16096
+ }
16097
+ if (!accountId) {
16098
+ throw new Error('ATXPAccount: connection string missing account id');
16099
+ }
16100
+ return { origin, token, accountId };
16101
+ }
16102
+ class ATXPHttpPaymentMaker {
16103
+ constructor(origin, token, fetchFn = fetch) {
16104
+ this.origin = origin;
16105
+ this.token = token;
16106
+ this.fetchFn = fetchFn;
16107
+ }
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
16135
+ const response = await this.fetchFn(`${this.origin}/pay`, {
16136
+ method: 'POST',
16137
+ headers: {
16138
+ 'Authorization': toBasicAuth(this.token),
16139
+ 'Content-Type': 'application/json',
16140
+ },
16141
+ body: JSON.stringify({
16142
+ destinations: destinations.map(d => ({
16143
+ chain: d.chain,
16144
+ address: d.address,
16145
+ amount: d.amount.toString(),
16146
+ currency: d.currency
16147
+ })),
16148
+ memo,
16149
+ ...(paymentRequestId && { paymentRequestId })
16150
+ }),
16151
+ });
16152
+ if (!response.ok) {
16153
+ const text = await response.text();
16154
+ throw new Error(`ATXPAccount: /pay failed: ${response.status} ${response.statusText} ${text}`);
16155
+ }
16156
+ const json = await response.json();
16157
+ const transactionId = json.transactionId;
16158
+ if (!transactionId) {
16159
+ throw new Error('ATXPAccount: /pay did not return transactionId or txHash');
16160
+ }
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
+ };
16173
+ }
16174
+ async generateJWT(params) {
16175
+ const response = await this.fetchFn(`${this.origin}/sign`, {
16176
+ method: 'POST',
16177
+ headers: {
16178
+ 'Authorization': toBasicAuth(this.token),
16179
+ 'Content-Type': 'application/json',
16180
+ },
16181
+ body: JSON.stringify({
16182
+ paymentRequestId: params.paymentRequestId,
16183
+ codeChallenge: params.codeChallenge,
16184
+ ...(params.accountId ? { accountId: params.accountId } : {}),
16185
+ }),
16186
+ });
16187
+ if (!response.ok) {
16188
+ const text = await response.text();
16189
+ throw new Error(`ATXPAccount: /sign failed: ${response.status} ${response.statusText} ${text}`);
16190
+ }
16191
+ const json = await response.json();
16192
+ if (!json?.jwt) {
16193
+ throw new Error('ATXPAccount: /sign did not return jwt');
16194
+ }
16195
+ return json.jwt;
16196
+ }
16197
+ }
16198
+ class ATXPAccount {
16199
+ constructor(connectionString, opts) {
16200
+ const { origin, token, accountId } = parseConnectionString(connectionString);
16201
+ const fetchFn = opts?.fetchFn ?? fetch;
16202
+ // Store for use in X402 payment creation
16203
+ this.origin = origin;
16204
+ this.token = token;
16205
+ this.fetchFn = fetchFn;
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}`);
16231
+ }
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
16076
16414
  * @param chainId - Chain ID (8453 for mainnet, 84532 for sepolia)
16077
16415
  * @returns USDC contract address
16078
16416
  * @throws Error if chain ID is not supported
@@ -16146,7 +16484,7 @@ class BasePaymentMaker {
16146
16484
  getSourceAddress(_params) {
16147
16485
  return this.signingClient.account.address;
16148
16486
  }
16149
- async generateJWT({ paymentRequestId, codeChallenge }) {
16487
+ async generateJWT({ paymentRequestId, codeChallenge, accountId }) {
16150
16488
  const headerObj = { alg: 'ES256K' };
16151
16489
  const payloadObj = {
16152
16490
  sub: this.signingClient.account.address,
@@ -16156,6 +16494,7 @@ class BasePaymentMaker {
16156
16494
  exp: Math.floor(Date.now() / 1000) + 60 * 60,
16157
16495
  ...(codeChallenge ? { code_challenge: codeChallenge } : {}),
16158
16496
  ...(paymentRequestId ? { payment_request_id: paymentRequestId } : {}),
16497
+ ...(accountId ? { account_id: accountId } : {}),
16159
16498
  };
16160
16499
  const header = toBase64Url(JSON.stringify(headerObj));
16161
16500
  const payload = toBase64Url(JSON.stringify(payloadObj));
@@ -16176,7 +16515,18 @@ class BasePaymentMaker {
16176
16515
  this.logger.info(`Generated ES256K JWT: ${jwt}`);
16177
16516
  return jwt;
16178
16517
  }
16179
- async makePayment(amount, currency, receiver) {
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;
16180
16530
  if (currency.toUpperCase() !== 'USDC') {
16181
16531
  throw new PaymentNetworkError('Only USDC currency is supported; received ' + currency);
16182
16532
  }
@@ -16219,7 +16569,12 @@ class BasePaymentMaker {
16219
16569
  throw new PaymentNetworkError(`Transaction reverted: ${hash}`, new Error('Transaction reverted on chain'));
16220
16570
  }
16221
16571
  this.logger.info(`Transaction confirmed: ${hash} in block ${receipt.blockNumber}`);
16222
- return hash;
16572
+ // Return payment result with chain and currency
16573
+ return {
16574
+ transactionId: hash,
16575
+ chain: 'base',
16576
+ currency: 'USDC'
16577
+ };
16223
16578
  }
16224
16579
  catch (error) {
16225
16580
  if (error instanceof InsufficientFundsError || error instanceof PaymentNetworkError) {
@@ -16240,228 +16595,22 @@ class SolanaAccount {
16240
16595
  throw new Error('Source secret key is required');
16241
16596
  }
16242
16597
  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
16279
- }
16280
- /**
16281
- * Fetch the wallet address from the /address endpoint
16282
- */
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;
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
+ ];
16335
16604
  }
16336
16605
  /**
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
16606
+ * Get sources for this account
16344
16607
  */
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);
16608
+ async getSources() {
16609
+ return [{
16610
+ address: this.sourcePublicKey,
16611
+ chain: 'solana',
16612
+ walletType: 'eoa'
16613
+ }];
16465
16614
  }
16466
16615
  }
16467
16616
 
@@ -16545,6 +16694,58 @@ const getWorldChainUSDCAddress = (chainId) => {
16545
16694
  }
16546
16695
  };
16547
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
+
16548
16749
  class BaseAccount {
16549
16750
  constructor(baseRPCUrl, sourceSecretKey) {
16550
16751
  if (!baseRPCUrl) {
@@ -16554,15 +16755,16 @@ class BaseAccount {
16554
16755
  throw new Error('Source secret key is required');
16555
16756
  }
16556
16757
  this.account = privateKeyToAccount(sourceSecretKey);
16557
- this.accountId = this.account.address;
16758
+ // Format accountId as network:address
16759
+ this.accountId = `base:${this.account.address}`;
16558
16760
  this.walletClient = createWalletClient({
16559
16761
  account: this.account,
16560
16762
  chain: base,
16561
16763
  transport: http(baseRPCUrl),
16562
16764
  });
16563
- this.paymentMakers = {
16564
- 'base': new BasePaymentMaker(baseRPCUrl, this.walletClient),
16565
- };
16765
+ this.paymentMakers = [
16766
+ new BasePaymentMaker(baseRPCUrl, this.walletClient)
16767
+ ];
16566
16768
  }
16567
16769
  /**
16568
16770
  * Get a signer that can be used with the x402 library
@@ -16572,7 +16774,17 @@ class BaseAccount {
16572
16774
  // Return the viem account directly - it implements LocalAccount interface
16573
16775
  return this.account;
16574
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
+ }
16575
16787
  }
16576
16788
 
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 };
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 };
16578
16790
  //# sourceMappingURL=index.js.map