@atxp/client 0.10.7 → 0.10.10

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 (57) hide show
  1. package/dist/_virtual/index10.js +2 -2
  2. package/dist/_virtual/index11.js +2 -2
  3. package/dist/_virtual/index17.js +2 -2
  4. package/dist/_virtual/index18.js +2 -2
  5. package/dist/_virtual/index8.js +2 -2
  6. package/dist/_virtual/index9.js +2 -2
  7. package/dist/atxpClient.d.ts +1 -1
  8. package/dist/atxpClient.d.ts.map +1 -1
  9. package/dist/atxpFetcher.d.ts +18 -1
  10. package/dist/atxpFetcher.d.ts.map +1 -1
  11. package/dist/atxpFetcher.js +65 -2
  12. package/dist/atxpFetcher.js.map +1 -1
  13. package/dist/atxpProtocolHandler.d.ts +17 -0
  14. package/dist/atxpProtocolHandler.d.ts.map +1 -0
  15. package/dist/atxpProtocolHandler.js +77 -0
  16. package/dist/atxpProtocolHandler.js.map +1 -0
  17. package/dist/destinationMakers/index.d.ts.map +1 -1
  18. package/dist/destinationMakers/index.js +6 -0
  19. package/dist/destinationMakers/index.js.map +1 -1
  20. package/dist/index.cjs +558 -2
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.ts +183 -5
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +555 -4
  25. package/dist/index.js.map +1 -1
  26. package/dist/mppProtocolHandler.d.ts +46 -0
  27. package/dist/mppProtocolHandler.d.ts.map +1 -0
  28. package/dist/mppProtocolHandler.js +182 -0
  29. package/dist/mppProtocolHandler.js.map +1 -0
  30. package/dist/node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/index.js +1 -1
  31. package/dist/node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/core/index.js +1 -1
  32. package/dist/node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/format/index.js +1 -1
  33. package/dist/node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/validation/index.js +1 -1
  34. package/dist/node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/core/index.js +1 -1
  35. package/dist/node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/validation/index.js +1 -1
  36. package/dist/paymentClient.d.ts +55 -0
  37. package/dist/paymentClient.d.ts.map +1 -0
  38. package/dist/paymentClient.js +75 -0
  39. package/dist/paymentClient.js.map +1 -0
  40. package/dist/protocolHandler.d.ts +41 -0
  41. package/dist/protocolHandler.d.ts.map +1 -0
  42. package/dist/types.d.ts +6 -1
  43. package/dist/types.d.ts.map +1 -1
  44. package/dist/x402ProtocolHandler.d.ts +22 -0
  45. package/dist/x402ProtocolHandler.d.ts.map +1 -0
  46. package/dist/x402ProtocolHandler.js +166 -0
  47. package/dist/x402ProtocolHandler.js.map +1 -0
  48. package/package.json +5 -3
  49. package/dist/atxpLocalAccount.d.ts +0 -50
  50. package/dist/clientTestHelpers.d.ts +0 -6
  51. package/dist/destinationMakers/atxpDestinationMaker.d.ts +0 -15
  52. package/dist/destinationMakers/index.d.ts +0 -9
  53. package/dist/destinationMakers/passthroughDestinationMaker.d.ts +0 -8
  54. package/dist/errors.d.ts +0 -117
  55. package/dist/polygonConstants.d.ts +0 -53
  56. package/dist/setup.expo.d.ts +0 -2
  57. package/dist/worldConstants.d.ts +0 -53
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
- import { crypto as crypto$1, OAuthResourceClient, ConsoleLogger, getErrorRecoveryHint, PAYMENT_REQUIRED_ERROR_CODE, isSSEResponse, parseMcpMessages, parsePaymentRequests, paymentRequiredError, DEFAULT_AUTHORIZATION_SERVER, createReactNativeSafeFetch, getIsReactNative, isEnumValue, ChainEnum, CurrencyEnum, NetworkEnum, assertNever, DEFAULT_ATXP_ACCOUNTS_SERVER, MemoryOAuthDb, ATXPAccount } from '@atxp/common';
1
+ import { crypto as crypto$1, OAuthResourceClient, ConsoleLogger, getErrorRecoveryHint, PAYMENT_REQUIRED_ERROR_CODE, isSSEResponse, parseMcpMessages, parsePaymentRequests, paymentRequiredError, DEFAULT_AUTHORIZATION_SERVER, createReactNativeSafeFetch, getIsReactNative, isEnumValue, ChainEnum, CurrencyEnum, NetworkEnum, assertNever, DEFAULT_ATXP_ACCOUNTS_SERVER, MemoryOAuthDb, ATXPAccount, AuthorizationError } from '@atxp/common';
2
2
  export { ATXPAccount } from '@atxp/common';
3
3
  import * as oauth from 'oauth4webapi';
4
4
  import { BigNumber } from 'bignumber.js';
5
+ import { hasMPPChallenge, hasMPPMCPError, parseMPPHeader, MPP_ERROR_CODE, parseMPPFromMCPError } from '@atxp/mpp';
5
6
 
6
7
  class OAuthAuthenticationRequiredError extends Error {
7
8
  constructor(url, resourceServerUrl,
@@ -453,7 +454,9 @@ function atxpFetch(config) {
453
454
  onAuthorizeFailure: config.onAuthorizeFailure,
454
455
  onPayment: config.onPayment,
455
456
  onPaymentFailure: config.onPaymentFailure,
456
- onPaymentAttemptFailed: config.onPaymentAttemptFailed
457
+ onPaymentAttemptFailed: config.onPaymentAttemptFailed,
458
+ protocolHandlers: config.protocolHandlers,
459
+ protocolFlag: config.protocolFlag
457
460
  });
458
461
  return fetcher.fetch;
459
462
  }
@@ -842,6 +845,11 @@ class ATXPFetcher {
842
845
  try {
843
846
  // Try to fetch the resource
844
847
  response = await oauthClient.fetch(url, init);
848
+ // Check HTTP-level protocol handlers first (e.g., X402 402 responses)
849
+ const handlerResult = await this.tryProtocolHandlers(response, url, init);
850
+ if (handlerResult) {
851
+ return handlerResult;
852
+ }
845
853
  await this.checkForATXPResponse(response);
846
854
  return response;
847
855
  }
@@ -854,6 +862,11 @@ class ATXPFetcher {
854
862
  try {
855
863
  // Retry the request once - we should be auth'd now
856
864
  response = await oauthClient.fetch(url, init);
865
+ // Check protocol handlers again after auth
866
+ const handlerResult = await this.tryProtocolHandlers(response, url, init);
867
+ if (handlerResult) {
868
+ return handlerResult;
869
+ }
857
870
  await this.checkForATXPResponse(response);
858
871
  return response;
859
872
  }
@@ -886,7 +899,7 @@ class ATXPFetcher {
886
899
  throw error;
887
900
  }
888
901
  };
889
- 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, onPaymentAttemptFailed } = config;
902
+ 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, onPaymentAttemptFailed, protocolHandlers = [], protocolFlag } = config;
890
903
  // Use React Native safe fetch if in React Native environment
891
904
  this.safeFetchFn = getIsReactNative() ? createReactNativeSafeFetch(fetchFn) : fetchFn;
892
905
  const safeSideChannelFetch = getIsReactNative() ? createReactNativeSafeFetch(sideChannelFetch) : sideChannelFetch;
@@ -905,6 +918,8 @@ class ATXPFetcher {
905
918
  this.onPayment = onPayment;
906
919
  this.onPaymentFailure = onPaymentFailure || this.defaultPaymentFailureHandler;
907
920
  this.onPaymentAttemptFailed = onPaymentAttemptFailed;
921
+ this.protocolHandlers = protocolHandlers;
922
+ this.protocolFlag = protocolFlag;
908
923
  }
909
924
  /**
910
925
  * Get or create the OAuthClient (lazy initialization to support async accountId)
@@ -927,6 +942,55 @@ class ATXPFetcher {
927
942
  });
928
943
  return this.oauthClient;
929
944
  }
945
+ /**
946
+ * Build protocol config for passing to protocol handlers.
947
+ */
948
+ getProtocolConfig() {
949
+ return {
950
+ account: this.account,
951
+ logger: this.logger,
952
+ fetchFn: this.safeFetchFn,
953
+ approvePayment: this.approvePayment,
954
+ onPayment: this.onPayment,
955
+ onPaymentFailure: this.onPaymentFailure
956
+ };
957
+ }
958
+ /**
959
+ * Try to handle an HTTP-level payment challenge using registered protocol handlers.
960
+ * For omni-challenges, uses protocolFlag to select the preferred handler.
961
+ * For single-protocol challenges, auto-detects from response format.
962
+ *
963
+ * @returns The retry response from the selected handler, or null if no handler matched
964
+ */
965
+ async tryProtocolHandlers(response, url, init) {
966
+ if (this.protocolHandlers.length === 0)
967
+ return null;
968
+ // Find all handlers that can handle this response
969
+ const matchingHandlers = [];
970
+ for (const handler of this.protocolHandlers) {
971
+ if (await handler.canHandle(response.clone())) {
972
+ matchingHandlers.push(handler);
973
+ }
974
+ }
975
+ if (matchingHandlers.length === 0)
976
+ return null;
977
+ // Select handler: use protocolFlag for omni-challenges, otherwise use first match
978
+ let selectedHandler;
979
+ if (matchingHandlers.length > 1 && this.protocolFlag) {
980
+ const accountId = await this.account.getAccountId();
981
+ const destination = typeof url === 'string' ? url : url.toString();
982
+ const preferredProtocol = this.protocolFlag(accountId, destination);
983
+ const preferred = matchingHandlers.find(h => h.protocol === preferredProtocol);
984
+ selectedHandler = preferred ?? matchingHandlers[0];
985
+ this.logger.info(`Protocol flag selected '${preferredProtocol}', using handler '${selectedHandler.protocol}'`);
986
+ }
987
+ else {
988
+ selectedHandler = matchingHandlers[0];
989
+ this.logger.debug(`Auto-detected protocol handler: ${selectedHandler.protocol}`);
990
+ }
991
+ const config = this.getProtocolConfig();
992
+ return selectedHandler.handlePaymentChallenge(response, { url, init }, config);
993
+ }
930
994
  }
931
995
 
932
996
  /** A special constant with type `never` */
@@ -24522,6 +24586,12 @@ function createDestinationMakers(config) {
24522
24586
  case NetworkEnum.PolygonAmoy:
24523
24587
  makers.set(network, new PassthroughDestinationMaker(network));
24524
24588
  break;
24589
+ case NetworkEnum.Tempo:
24590
+ makers.set(network, new PassthroughDestinationMaker(network));
24591
+ break;
24592
+ case NetworkEnum.TempoModerato:
24593
+ makers.set(network, new PassthroughDestinationMaker(network));
24594
+ break;
24525
24595
  case NetworkEnum.ATXP:
24526
24596
  makers.set(network, new ATXPDestinationMaker(atxpAccountsServer, fetchFn));
24527
24597
  break;
@@ -24868,5 +24938,486 @@ class ATXPLocalAccount {
24868
24938
  }
24869
24939
  }
24870
24940
 
24871
- export { ATXPDestinationMaker, ATXPLocalAccount, ATXPPaymentError, DEFAULT_CLIENT_CONFIG, GasEstimationError, InsufficientFundsError, OAuthAuthenticationRequiredError, OAuthClient, POLYGON_AMOY, POLYGON_MAINNET, PassthroughDestinationMaker, PaymentExpiredError, PaymentNetworkError, PaymentServerError, RpcError, TransactionRevertedError, USDC_CONTRACT_ADDRESS_POLYGON_AMOY, USDC_CONTRACT_ADDRESS_POLYGON_MAINNET, USDC_CONTRACT_ADDRESS_WORLD_MAINNET, USDC_CONTRACT_ADDRESS_WORLD_SEPOLIA, UnsupportedCurrencyError, UserRejectedError, WORLD_CHAIN_MAINNET, WORLD_CHAIN_SEPOLIA, atxpClient, atxpFetch, buildClientConfig, buildStreamableTransport, getPolygonAmoyWithRPC, getPolygonByChainId, getPolygonMainnetWithRPC, getPolygonUSDCAddress, getWorldChainByChainId, getWorldChainMainnetWithRPC, getWorldChainSepoliaWithRPC, getWorldChainUSDCAddress };
24941
+ /**
24942
+ * Build protocol-specific payment headers for retrying a request after authorization.
24943
+ *
24944
+ * @param result - The authorization result containing protocol and credential
24945
+ * @param originalHeaders - Optional original request headers to preserve
24946
+ * @returns New Headers object with protocol-specific payment headers added
24947
+ */
24948
+ function buildPaymentHeaders(result, originalHeaders) {
24949
+ let headers;
24950
+ if (originalHeaders instanceof Headers) {
24951
+ headers = new Headers(originalHeaders);
24952
+ }
24953
+ else if (originalHeaders) {
24954
+ headers = new Headers(originalHeaders);
24955
+ }
24956
+ else {
24957
+ headers = new Headers();
24958
+ }
24959
+ switch (result.protocol) {
24960
+ case 'x402':
24961
+ headers.set('X-PAYMENT', result.credential);
24962
+ headers.set('Access-Control-Expose-Headers', 'X-PAYMENT-RESPONSE');
24963
+ break;
24964
+ case 'mpp':
24965
+ headers.set('Authorization', `Payment ${result.credential}`);
24966
+ break;
24967
+ }
24968
+ return headers;
24969
+ }
24970
+ /**
24971
+ * Client for authorizing payments.
24972
+ *
24973
+ * Resolves the payment protocol via protocolFlag, then delegates to
24974
+ * account.authorize() for the actual authorization logic.
24975
+ */
24976
+ class PaymentClient {
24977
+ constructor(config) {
24978
+ this.protocolFlag = config.protocolFlag;
24979
+ this.logger = config.logger;
24980
+ }
24981
+ /**
24982
+ * Authorize a payment by delegating to the account's authorize method.
24983
+ *
24984
+ * PaymentClient resolves the protocol (via explicit param or protocolFlag),
24985
+ * then delegates all protocol-specific logic to account.authorize().
24986
+ *
24987
+ * @param params.account - The account to authorize the payment through
24988
+ * @param params.userId - Passed to protocolFlag for protocol selection
24989
+ * @param params.destination - Payment destination address
24990
+ * @param params.protocol - Explicit protocol override (skips protocolFlag)
24991
+ * @param params.amount - Payment amount
24992
+ * @param params.memo - Payment memo
24993
+ * @param params.paymentRequirements - X402 payment requirements
24994
+ * @param params.challenge - MPP challenge object
24995
+ * @returns AuthorizeResult with protocol and opaque credential
24996
+ */
24997
+ async authorize(params) {
24998
+ const { account, userId, destination } = params;
24999
+ // Determine protocol
25000
+ const protocol = params.protocol
25001
+ ?? (this.protocolFlag ? this.protocolFlag(userId, destination) : 'atxp');
25002
+ // Delegate to the account's authorize method
25003
+ return account.authorize({
25004
+ protocol,
25005
+ amount: params.amount,
25006
+ destination,
25007
+ memo: params.memo,
25008
+ paymentRequirements: params.paymentRequirements,
25009
+ challenge: params.challenge,
25010
+ });
25011
+ }
25012
+ }
25013
+
25014
+ function isX402Challenge(obj) {
25015
+ if (typeof obj !== 'object' || obj === null)
25016
+ return false;
25017
+ const candidate = obj;
25018
+ return (typeof candidate.x402Version !== 'undefined' &&
25019
+ Array.isArray(candidate.accepts));
25020
+ }
25021
+ /**
25022
+ * Protocol handler for X402 payment challenges.
25023
+ *
25024
+ * Detects HTTP 402 responses with x402Version in the JSON body.
25025
+ * Creates payment headers using the x402 library and retries the request.
25026
+ */
25027
+ class X402ProtocolHandler {
25028
+ constructor(config) {
25029
+ this.protocol = 'x402';
25030
+ this.accountsServer = config?.accountsServer ?? 'https://accounts.atxp.ai';
25031
+ }
25032
+ async canHandle(response) {
25033
+ if (response.status !== 402)
25034
+ return false;
25035
+ try {
25036
+ const cloned = response.clone();
25037
+ const body = await cloned.text();
25038
+ const parsed = JSON.parse(body);
25039
+ return isX402Challenge(parsed);
25040
+ }
25041
+ catch {
25042
+ return false;
25043
+ }
25044
+ }
25045
+ async handlePaymentChallenge(response, originalRequest, config) {
25046
+ const { account, logger, fetchFn, approvePayment, onPayment, onPaymentFailure } = config;
25047
+ const responseBody = await response.text();
25048
+ let paymentChallenge;
25049
+ try {
25050
+ paymentChallenge = JSON.parse(responseBody);
25051
+ }
25052
+ catch {
25053
+ logger.error('X402: failed to parse challenge body');
25054
+ return null;
25055
+ }
25056
+ if (!isX402Challenge(paymentChallenge)) {
25057
+ return null;
25058
+ }
25059
+ try {
25060
+ const { selectPaymentRequirements } = await import('x402/client');
25061
+ const selectedPaymentRequirements = selectPaymentRequirements(paymentChallenge.accepts, undefined, 'exact');
25062
+ if (!selectedPaymentRequirements) {
25063
+ logger.info('X402: no suitable payment option found');
25064
+ return this.reconstructResponse(responseBody, response);
25065
+ }
25066
+ const amountInUsdc = Number(selectedPaymentRequirements.maxAmountRequired) / (10 ** 6);
25067
+ const network = selectedPaymentRequirements.network;
25068
+ logger.debug(`X402: payment required: ${amountInUsdc} USDC on ${network} to ${selectedPaymentRequirements.payTo}`);
25069
+ const url = typeof originalRequest.url === 'string' ? originalRequest.url : originalRequest.url.toString();
25070
+ const accountId = await account.getAccountId();
25071
+ const prospectivePayment = {
25072
+ accountId,
25073
+ resourceUrl: url,
25074
+ resourceName: selectedPaymentRequirements.description || url,
25075
+ currency: 'USDC',
25076
+ amount: new BigNumber(amountInUsdc),
25077
+ iss: selectedPaymentRequirements.payTo
25078
+ };
25079
+ const approved = await approvePayment(prospectivePayment);
25080
+ if (!approved) {
25081
+ logger.info('X402: payment not approved');
25082
+ const error = new Error('Payment not approved');
25083
+ await onPaymentFailure({
25084
+ payment: prospectivePayment,
25085
+ error,
25086
+ attemptedNetworks: [network],
25087
+ failureReasons: new Map([[network, error]]),
25088
+ retryable: true,
25089
+ timestamp: new Date()
25090
+ });
25091
+ return this.reconstructResponse(responseBody, response);
25092
+ }
25093
+ // Authorize via account.authorize() — ATXPAccount calls the accounts
25094
+ // service, BaseAccount signs locally. No fallback — each account type
25095
+ // handles authorization according to its capabilities.
25096
+ const client = new PaymentClient({
25097
+ accountsServer: this.accountsServer,
25098
+ logger,
25099
+ fetchFn,
25100
+ });
25101
+ const authorizeResult = await client.authorize({
25102
+ account,
25103
+ userId: accountId,
25104
+ destination: url,
25105
+ protocol: 'x402',
25106
+ paymentRequirements: selectedPaymentRequirements,
25107
+ });
25108
+ const paymentHeader = authorizeResult.credential;
25109
+ // Retry with X-PAYMENT header
25110
+ const retryHeaders = buildPaymentHeaders({ protocol: 'x402', credential: paymentHeader }, originalRequest.init?.headers);
25111
+ const retryInit = { ...originalRequest.init, headers: retryHeaders };
25112
+ logger.info('X402: retrying request with X-PAYMENT header');
25113
+ const retryResponse = await fetchFn(originalRequest.url, retryInit);
25114
+ if (retryResponse.ok) {
25115
+ logger.info('X402: payment accepted');
25116
+ await onPayment({
25117
+ payment: prospectivePayment,
25118
+ transactionHash: paymentHeader.substring(0, 66),
25119
+ network
25120
+ });
25121
+ }
25122
+ else {
25123
+ logger.warn(`X402: request failed after payment with status ${retryResponse.status}`);
25124
+ const error = new Error(`Request failed with status ${retryResponse.status}`);
25125
+ await onPaymentFailure({
25126
+ payment: prospectivePayment,
25127
+ error,
25128
+ attemptedNetworks: [network],
25129
+ failureReasons: new Map([[network, error]]),
25130
+ retryable: false,
25131
+ timestamp: new Date()
25132
+ });
25133
+ }
25134
+ return retryResponse;
25135
+ }
25136
+ catch (error) {
25137
+ logger.error(`X402: failed to handle payment challenge: ${error}`);
25138
+ if (isX402Challenge(paymentChallenge) && paymentChallenge.accepts[0]) {
25139
+ const firstOption = paymentChallenge.accepts[0];
25140
+ const amount = firstOption.maxAmountRequired ? Number(firstOption.maxAmountRequired) / (10 ** 6) : 0;
25141
+ const url = typeof originalRequest.url === 'string' ? originalRequest.url : originalRequest.url.toString();
25142
+ const accountId = await account.getAccountId();
25143
+ const errorNetwork = firstOption.network || 'unknown';
25144
+ const typedError = error;
25145
+ const isRetryable = typedError instanceof ATXPPaymentError ? typedError.retryable : true;
25146
+ await onPaymentFailure({
25147
+ payment: {
25148
+ accountId,
25149
+ resourceUrl: url,
25150
+ resourceName: firstOption.description || url,
25151
+ currency: 'USDC',
25152
+ amount: new BigNumber(amount),
25153
+ iss: firstOption.payTo || ''
25154
+ },
25155
+ error: typedError,
25156
+ attemptedNetworks: [errorNetwork],
25157
+ failureReasons: new Map([[errorNetwork, typedError]]),
25158
+ retryable: isRetryable,
25159
+ timestamp: new Date()
25160
+ });
25161
+ }
25162
+ return this.reconstructResponse(responseBody, response);
25163
+ }
25164
+ }
25165
+ reconstructResponse(body, original) {
25166
+ return new Response(body, {
25167
+ status: original.status,
25168
+ statusText: original.statusText,
25169
+ headers: original.headers
25170
+ });
25171
+ }
25172
+ }
25173
+
25174
+ /**
25175
+ * Protocol handler for ATXP-MCP payment challenges.
25176
+ *
25177
+ * Detects JSON-RPC errors with code -30402 (PAYMENT_REQUIRED_ERROR_CODE) in
25178
+ * MCP responses (both SSE and JSON formats).
25179
+ */
25180
+ class ATXPProtocolHandler {
25181
+ constructor() {
25182
+ this.protocol = 'atxp';
25183
+ }
25184
+ async canHandle(response) {
25185
+ // ATXP-MCP challenges are embedded in MCP JSON-RPC responses,
25186
+ // not at the HTTP level. We detect them by looking for error code -30402.
25187
+ try {
25188
+ const cloned = response.clone();
25189
+ const body = await cloned.text();
25190
+ if (body.length === 0)
25191
+ return false;
25192
+ const paymentRequests = await this.extractPaymentRequests(body);
25193
+ return paymentRequests.length > 0;
25194
+ }
25195
+ catch {
25196
+ return false;
25197
+ }
25198
+ }
25199
+ async handlePaymentChallenge(response, _originalRequest, config) {
25200
+ const { logger } = config;
25201
+ try {
25202
+ const body = await response.clone().text();
25203
+ const paymentRequests = await this.extractPaymentRequests(body);
25204
+ if (paymentRequests.length === 0) {
25205
+ return null;
25206
+ }
25207
+ // ATXP-MCP payment challenges are handled through the fetcher's
25208
+ // checkForATXPResponse → handlePaymentRequestError flow, not at
25209
+ // the HTTP protocol handler level. Return null so the fetcher's
25210
+ // existing MCP payment flow picks it up.
25211
+ // This handler's role in the strategy pattern is detection via
25212
+ // canHandle() for protocol selection (e.g., protocolFlag), not
25213
+ // direct payment handling.
25214
+ if (paymentRequests.length > 1) {
25215
+ logger.warn(`ATXP: multiple payment requirements found in MCP response. ` +
25216
+ `The client does not support multiple payment requirements. ` +
25217
+ `${paymentRequests.map(pr => pr.url).join(', ')}`);
25218
+ }
25219
+ else {
25220
+ logger.info(`ATXP: payment requirement found in MCP response - ${paymentRequests[0].url}`);
25221
+ }
25222
+ return null;
25223
+ }
25224
+ catch (error) {
25225
+ logger.error(`ATXP: error checking for payment requirements: ${error}`);
25226
+ return null;
25227
+ }
25228
+ }
25229
+ async extractPaymentRequests(body) {
25230
+ try {
25231
+ if (isSSEResponse(body)) {
25232
+ const messages = await parseMcpMessages(body);
25233
+ return messages.flatMap(message => parsePaymentRequests(message)).filter((pr) => pr !== null);
25234
+ }
25235
+ else {
25236
+ const json = JSON.parse(body);
25237
+ const messages = await parseMcpMessages(json);
25238
+ return messages.flatMap(message => parsePaymentRequests(message)).filter((pr) => pr !== null);
25239
+ }
25240
+ }
25241
+ catch {
25242
+ return [];
25243
+ }
25244
+ }
25245
+ }
25246
+
25247
+ /**
25248
+ * Protocol handler for MPP (Machine Payments Protocol) payment challenges.
25249
+ *
25250
+ * Detects MPP challenges in two forms:
25251
+ * 1. HTTP level: HTTP 402 with WWW-Authenticate: Payment header
25252
+ * 2. MCP level: JSON-RPC error with code -32042 containing MPP data
25253
+ *
25254
+ * Handles the challenge by calling /authorize/mpp on the accounts service
25255
+ * and retrying with an Authorization: Payment header.
25256
+ */
25257
+ class MPPProtocolHandler {
25258
+ constructor(config) {
25259
+ this.protocol = 'mpp';
25260
+ this.accountsServer = config?.accountsServer ?? 'https://accounts.atxp.ai';
25261
+ }
25262
+ async canHandle(response) {
25263
+ if (hasMPPChallenge(response))
25264
+ return true;
25265
+ return hasMPPMCPError(response);
25266
+ }
25267
+ async handlePaymentChallenge(response, originalRequest, config) {
25268
+ const { account, logger, approvePayment } = config;
25269
+ // Extract the challenge and body text from the response
25270
+ const extracted = await this.extractChallenge(response, logger);
25271
+ if (!extracted) {
25272
+ logger.error('MPP: failed to extract challenge from response');
25273
+ return null;
25274
+ }
25275
+ const { challenge, bodyText } = extracted;
25276
+ const url = typeof originalRequest.url === 'string'
25277
+ ? originalRequest.url
25278
+ : originalRequest.url.toString();
25279
+ // Build prospective payment for approval
25280
+ const accountId = await account.getAccountId();
25281
+ const prospectivePayment = this.buildProspectivePayment(challenge, url, accountId);
25282
+ // Ask for approval
25283
+ const approved = await approvePayment(prospectivePayment);
25284
+ if (!approved) {
25285
+ logger.info('MPP: payment not approved');
25286
+ await this.reportFailure(config, prospectivePayment, new Error('Payment not approved'), challenge.network, true);
25287
+ return this.reconstructResponse(bodyText, response);
25288
+ }
25289
+ return this.authorizeAndRetry(challenge, prospectivePayment, originalRequest, config, bodyText, response);
25290
+ }
25291
+ /**
25292
+ * Extract MPP challenge from response - tries HTTP header first, then MCP error body.
25293
+ * Returns both the challenge and the body text to avoid double-consumption.
25294
+ */
25295
+ async extractChallenge(response, logger) {
25296
+ // Read body once upfront to avoid double consumption (response.text() can only be called once)
25297
+ let bodyText = '';
25298
+ try {
25299
+ bodyText = await response.text();
25300
+ }
25301
+ catch {
25302
+ // Body may not be available
25303
+ }
25304
+ // Try HTTP header first
25305
+ const header = response.headers.get('WWW-Authenticate');
25306
+ if (header) {
25307
+ const challenge = parseMPPHeader(header);
25308
+ if (challenge) {
25309
+ logger.debug('MPP: parsed challenge from WWW-Authenticate header');
25310
+ return { challenge, bodyText };
25311
+ }
25312
+ }
25313
+ // Try MCP error body (use clone since response body may have been consumed above)
25314
+ try {
25315
+ const parsed = JSON.parse(bodyText);
25316
+ if (typeof parsed === 'object' &&
25317
+ parsed !== null &&
25318
+ typeof parsed.error === 'object' &&
25319
+ parsed.error !== null &&
25320
+ parsed.error.code === MPP_ERROR_CODE) {
25321
+ const challenge = parseMPPFromMCPError(parsed.error.data);
25322
+ if (challenge) {
25323
+ logger.debug('MPP: parsed challenge from MCP error body');
25324
+ return { challenge, bodyText };
25325
+ }
25326
+ }
25327
+ }
25328
+ catch {
25329
+ // Not JSON or malformed
25330
+ }
25331
+ return null;
25332
+ }
25333
+ /**
25334
+ * Build a ProspectivePayment from an MPP challenge.
25335
+ */
25336
+ buildProspectivePayment(challenge, url, accountId) {
25337
+ const amountNum = Number(challenge.amount) / (10 ** 6);
25338
+ return {
25339
+ accountId,
25340
+ resourceUrl: url,
25341
+ resourceName: url,
25342
+ currency: challenge.currency,
25343
+ amount: new BigNumber(amountNum),
25344
+ iss: challenge.recipient,
25345
+ };
25346
+ }
25347
+ /**
25348
+ * Report a payment failure via the onPaymentFailure callback.
25349
+ */
25350
+ async reportFailure(config, payment, error, network, retryable) {
25351
+ await config.onPaymentFailure({
25352
+ payment,
25353
+ error,
25354
+ attemptedNetworks: [network],
25355
+ failureReasons: new Map([[network, error]]),
25356
+ retryable,
25357
+ timestamp: new Date(),
25358
+ });
25359
+ }
25360
+ /**
25361
+ * Call /authorize/mpp on accounts service and retry the original request with the credential.
25362
+ */
25363
+ async authorizeAndRetry(challenge, prospectivePayment, originalRequest, config, bodyText, originalResponse) {
25364
+ const { account, logger, fetchFn, onPayment } = config;
25365
+ try {
25366
+ logger.debug('MPP: calling /authorize/mpp on accounts service');
25367
+ const client = new PaymentClient({
25368
+ accountsServer: this.accountsServer,
25369
+ logger,
25370
+ fetchFn,
25371
+ });
25372
+ const accountId = await account.getAccountId();
25373
+ let authorizeResult;
25374
+ try {
25375
+ authorizeResult = await client.authorize({
25376
+ account,
25377
+ userId: accountId,
25378
+ destination: typeof originalRequest.url === 'string' ? originalRequest.url : originalRequest.url.toString(),
25379
+ protocol: 'mpp',
25380
+ challenge,
25381
+ });
25382
+ }
25383
+ catch (authorizeError) {
25384
+ // AuthorizationError = server rejected the request (HTTP error from accounts)
25385
+ // Other errors = data validation or network failure
25386
+ if (authorizeError instanceof AuthorizationError) {
25387
+ logger.debug(`MPP: /authorize/mpp rejected (${authorizeError.statusCode}), returning original response`);
25388
+ return this.reconstructResponse(bodyText, originalResponse);
25389
+ }
25390
+ throw authorizeError;
25391
+ }
25392
+ const retryHeaders = buildPaymentHeaders(authorizeResult, originalRequest.init?.headers);
25393
+ const retryInit = { ...originalRequest.init, headers: retryHeaders };
25394
+ logger.info('MPP: retrying request with Authorization: Payment header');
25395
+ const retryResponse = await fetchFn(originalRequest.url, retryInit);
25396
+ if (retryResponse.ok) {
25397
+ logger.info('MPP: payment accepted');
25398
+ await onPayment({ payment: prospectivePayment, transactionHash: challenge.id, network: challenge.network });
25399
+ }
25400
+ else {
25401
+ logger.warn(`MPP: request failed after payment with status ${retryResponse.status}`);
25402
+ await this.reportFailure(config, prospectivePayment, new Error(`Request failed with status ${retryResponse.status}`), challenge.network, false);
25403
+ }
25404
+ return retryResponse;
25405
+ }
25406
+ catch (error) {
25407
+ logger.error(`MPP: failed to handle payment challenge: ${error}`);
25408
+ const cause = error instanceof Error ? error : new Error(String(error));
25409
+ await this.reportFailure(config, prospectivePayment, cause, challenge.network, true);
25410
+ return this.reconstructResponse(bodyText, originalResponse);
25411
+ }
25412
+ }
25413
+ reconstructResponse(body, original) {
25414
+ return new Response(body || null, {
25415
+ status: original.status,
25416
+ statusText: original.statusText,
25417
+ headers: original.headers,
25418
+ });
25419
+ }
25420
+ }
25421
+
25422
+ export { ATXPDestinationMaker, ATXPLocalAccount, ATXPPaymentError, ATXPProtocolHandler, DEFAULT_CLIENT_CONFIG, GasEstimationError, InsufficientFundsError, MPPProtocolHandler, OAuthAuthenticationRequiredError, OAuthClient, POLYGON_AMOY, POLYGON_MAINNET, PassthroughDestinationMaker, PaymentClient, PaymentExpiredError, PaymentNetworkError, PaymentServerError, RpcError, TransactionRevertedError, USDC_CONTRACT_ADDRESS_POLYGON_AMOY, USDC_CONTRACT_ADDRESS_POLYGON_MAINNET, USDC_CONTRACT_ADDRESS_WORLD_MAINNET, USDC_CONTRACT_ADDRESS_WORLD_SEPOLIA, UnsupportedCurrencyError, UserRejectedError, WORLD_CHAIN_MAINNET, WORLD_CHAIN_SEPOLIA, X402ProtocolHandler, atxpClient, atxpFetch, buildClientConfig, buildPaymentHeaders, buildStreamableTransport, getPolygonAmoyWithRPC, getPolygonByChainId, getPolygonMainnetWithRPC, getPolygonUSDCAddress, getWorldChainByChainId, getWorldChainMainnetWithRPC, getWorldChainSepoliaWithRPC, getWorldChainUSDCAddress };
24872
25423
  //# sourceMappingURL=index.js.map