@atxp/client 0.10.7 → 0.10.8

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 (54) 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/index19.js +2 -2
  5. package/dist/_virtual/index20.js +2 -2
  6. package/dist/_virtual/index3.js +2 -2
  7. package/dist/_virtual/index4.js +2 -2
  8. package/dist/_virtual/index8.js +2 -2
  9. package/dist/_virtual/index9.js +2 -2
  10. package/dist/atxpClient.d.ts +1 -1
  11. package/dist/atxpClient.d.ts.map +1 -1
  12. package/dist/atxpFetcher.d.ts +18 -1
  13. package/dist/atxpFetcher.d.ts.map +1 -1
  14. package/dist/atxpFetcher.js +65 -2
  15. package/dist/atxpFetcher.js.map +1 -1
  16. package/dist/atxpProtocolHandler.d.ts +17 -0
  17. package/dist/atxpProtocolHandler.d.ts.map +1 -0
  18. package/dist/atxpProtocolHandler.js +77 -0
  19. package/dist/atxpProtocolHandler.js.map +1 -0
  20. package/dist/destinationMakers/index.d.ts.map +1 -1
  21. package/dist/destinationMakers/index.js +6 -0
  22. package/dist/destinationMakers/index.js.map +1 -1
  23. package/dist/index.cjs +558 -2
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.ts +183 -5
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +555 -4
  28. package/dist/index.js.map +1 -1
  29. package/dist/mppProtocolHandler.d.ts +46 -0
  30. package/dist/mppProtocolHandler.d.ts.map +1 -0
  31. package/dist/mppProtocolHandler.js +182 -0
  32. package/dist/mppProtocolHandler.js.map +1 -0
  33. package/dist/node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/validate/index.js +1 -1
  34. package/dist/node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/applicator/index.js +1 -1
  35. package/dist/node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/core/index.js +1 -1
  36. package/dist/node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/discriminator/index.js +1 -1
  37. package/dist/node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/format/index.js +1 -1
  38. package/dist/node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/vocabularies/validation/index.js +1 -1
  39. package/dist/node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/applicator/index.js +1 -1
  40. package/dist/node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/core/index.js +1 -1
  41. package/dist/node_modules/ajv-formats/node_modules/ajv/dist/vocabularies/format/index.js +1 -1
  42. package/dist/paymentClient.d.ts +55 -0
  43. package/dist/paymentClient.d.ts.map +1 -0
  44. package/dist/paymentClient.js +75 -0
  45. package/dist/paymentClient.js.map +1 -0
  46. package/dist/protocolHandler.d.ts +41 -0
  47. package/dist/protocolHandler.d.ts.map +1 -0
  48. package/dist/types.d.ts +6 -1
  49. package/dist/types.d.ts.map +1 -1
  50. package/dist/x402ProtocolHandler.d.ts +22 -0
  51. package/dist/x402ProtocolHandler.d.ts.map +1 -0
  52. package/dist/x402ProtocolHandler.js +166 -0
  53. package/dist/x402ProtocolHandler.js.map +1 -0
  54. package/package.json +5 -3
package/dist/index.cjs CHANGED
@@ -3,6 +3,7 @@
3
3
  var common = require('@atxp/common');
4
4
  var oauth = require('oauth4webapi');
5
5
  var bignumber_js = require('bignumber.js');
6
+ var mpp = require('@atxp/mpp');
6
7
 
7
8
  function _interopNamespaceDefault(e) {
8
9
  var n = Object.create(null);
@@ -473,7 +474,9 @@ function atxpFetch(config) {
473
474
  onAuthorizeFailure: config.onAuthorizeFailure,
474
475
  onPayment: config.onPayment,
475
476
  onPaymentFailure: config.onPaymentFailure,
476
- onPaymentAttemptFailed: config.onPaymentAttemptFailed
477
+ onPaymentAttemptFailed: config.onPaymentAttemptFailed,
478
+ protocolHandlers: config.protocolHandlers,
479
+ protocolFlag: config.protocolFlag
477
480
  });
478
481
  return fetcher.fetch;
479
482
  }
@@ -862,6 +865,11 @@ class ATXPFetcher {
862
865
  try {
863
866
  // Try to fetch the resource
864
867
  response = await oauthClient.fetch(url, init);
868
+ // Check HTTP-level protocol handlers first (e.g., X402 402 responses)
869
+ const handlerResult = await this.tryProtocolHandlers(response, url, init);
870
+ if (handlerResult) {
871
+ return handlerResult;
872
+ }
865
873
  await this.checkForATXPResponse(response);
866
874
  return response;
867
875
  }
@@ -874,6 +882,11 @@ class ATXPFetcher {
874
882
  try {
875
883
  // Retry the request once - we should be auth'd now
876
884
  response = await oauthClient.fetch(url, init);
885
+ // Check protocol handlers again after auth
886
+ const handlerResult = await this.tryProtocolHandlers(response, url, init);
887
+ if (handlerResult) {
888
+ return handlerResult;
889
+ }
877
890
  await this.checkForATXPResponse(response);
878
891
  return response;
879
892
  }
@@ -906,7 +919,7 @@ class ATXPFetcher {
906
919
  throw error;
907
920
  }
908
921
  };
909
- const { account, db, destinationMakers, fetchFn = fetch, sideChannelFetch = fetchFn, strict = true, allowInsecureRequests = process.env.NODE_ENV === 'development', allowedAuthorizationServers = [common.DEFAULT_AUTHORIZATION_SERVER], approvePayment = async () => true, logger = new common.ConsoleLogger(), onAuthorize = async () => { }, onAuthorizeFailure = async () => { }, onPayment = async () => { }, onPaymentFailure, onPaymentAttemptFailed } = config;
922
+ const { account, db, destinationMakers, fetchFn = fetch, sideChannelFetch = fetchFn, strict = true, allowInsecureRequests = process.env.NODE_ENV === 'development', allowedAuthorizationServers = [common.DEFAULT_AUTHORIZATION_SERVER], approvePayment = async () => true, logger = new common.ConsoleLogger(), onAuthorize = async () => { }, onAuthorizeFailure = async () => { }, onPayment = async () => { }, onPaymentFailure, onPaymentAttemptFailed, protocolHandlers = [], protocolFlag } = config;
910
923
  // Use React Native safe fetch if in React Native environment
911
924
  this.safeFetchFn = common.getIsReactNative() ? common.createReactNativeSafeFetch(fetchFn) : fetchFn;
912
925
  const safeSideChannelFetch = common.getIsReactNative() ? common.createReactNativeSafeFetch(sideChannelFetch) : sideChannelFetch;
@@ -925,6 +938,8 @@ class ATXPFetcher {
925
938
  this.onPayment = onPayment;
926
939
  this.onPaymentFailure = onPaymentFailure || this.defaultPaymentFailureHandler;
927
940
  this.onPaymentAttemptFailed = onPaymentAttemptFailed;
941
+ this.protocolHandlers = protocolHandlers;
942
+ this.protocolFlag = protocolFlag;
928
943
  }
929
944
  /**
930
945
  * Get or create the OAuthClient (lazy initialization to support async accountId)
@@ -947,6 +962,55 @@ class ATXPFetcher {
947
962
  });
948
963
  return this.oauthClient;
949
964
  }
965
+ /**
966
+ * Build protocol config for passing to protocol handlers.
967
+ */
968
+ getProtocolConfig() {
969
+ return {
970
+ account: this.account,
971
+ logger: this.logger,
972
+ fetchFn: this.safeFetchFn,
973
+ approvePayment: this.approvePayment,
974
+ onPayment: this.onPayment,
975
+ onPaymentFailure: this.onPaymentFailure
976
+ };
977
+ }
978
+ /**
979
+ * Try to handle an HTTP-level payment challenge using registered protocol handlers.
980
+ * For omni-challenges, uses protocolFlag to select the preferred handler.
981
+ * For single-protocol challenges, auto-detects from response format.
982
+ *
983
+ * @returns The retry response from the selected handler, or null if no handler matched
984
+ */
985
+ async tryProtocolHandlers(response, url, init) {
986
+ if (this.protocolHandlers.length === 0)
987
+ return null;
988
+ // Find all handlers that can handle this response
989
+ const matchingHandlers = [];
990
+ for (const handler of this.protocolHandlers) {
991
+ if (await handler.canHandle(response.clone())) {
992
+ matchingHandlers.push(handler);
993
+ }
994
+ }
995
+ if (matchingHandlers.length === 0)
996
+ return null;
997
+ // Select handler: use protocolFlag for omni-challenges, otherwise use first match
998
+ let selectedHandler;
999
+ if (matchingHandlers.length > 1 && this.protocolFlag) {
1000
+ const accountId = await this.account.getAccountId();
1001
+ const destination = typeof url === 'string' ? url : url.toString();
1002
+ const preferredProtocol = this.protocolFlag(accountId, destination);
1003
+ const preferred = matchingHandlers.find(h => h.protocol === preferredProtocol);
1004
+ selectedHandler = preferred ?? matchingHandlers[0];
1005
+ this.logger.info(`Protocol flag selected '${preferredProtocol}', using handler '${selectedHandler.protocol}'`);
1006
+ }
1007
+ else {
1008
+ selectedHandler = matchingHandlers[0];
1009
+ this.logger.debug(`Auto-detected protocol handler: ${selectedHandler.protocol}`);
1010
+ }
1011
+ const config = this.getProtocolConfig();
1012
+ return selectedHandler.handlePaymentChallenge(response, { url, init }, config);
1013
+ }
950
1014
  }
951
1015
 
952
1016
  /** A special constant with type `never` */
@@ -24542,6 +24606,12 @@ function createDestinationMakers(config) {
24542
24606
  case common.NetworkEnum.PolygonAmoy:
24543
24607
  makers.set(network, new PassthroughDestinationMaker(network));
24544
24608
  break;
24609
+ case common.NetworkEnum.Tempo:
24610
+ makers.set(network, new PassthroughDestinationMaker(network));
24611
+ break;
24612
+ case common.NetworkEnum.TempoModerato:
24613
+ makers.set(network, new PassthroughDestinationMaker(network));
24614
+ break;
24545
24615
  case common.NetworkEnum.ATXP:
24546
24616
  makers.set(network, new ATXPDestinationMaker(atxpAccountsServer, fetchFn));
24547
24617
  break;
@@ -24888,6 +24958,487 @@ class ATXPLocalAccount {
24888
24958
  }
24889
24959
  }
24890
24960
 
24961
+ /**
24962
+ * Build protocol-specific payment headers for retrying a request after authorization.
24963
+ *
24964
+ * @param result - The authorization result containing protocol and credential
24965
+ * @param originalHeaders - Optional original request headers to preserve
24966
+ * @returns New Headers object with protocol-specific payment headers added
24967
+ */
24968
+ function buildPaymentHeaders(result, originalHeaders) {
24969
+ let headers;
24970
+ if (originalHeaders instanceof Headers) {
24971
+ headers = new Headers(originalHeaders);
24972
+ }
24973
+ else if (originalHeaders) {
24974
+ headers = new Headers(originalHeaders);
24975
+ }
24976
+ else {
24977
+ headers = new Headers();
24978
+ }
24979
+ switch (result.protocol) {
24980
+ case 'x402':
24981
+ headers.set('X-PAYMENT', result.credential);
24982
+ headers.set('Access-Control-Expose-Headers', 'X-PAYMENT-RESPONSE');
24983
+ break;
24984
+ case 'mpp':
24985
+ headers.set('Authorization', `Payment ${result.credential}`);
24986
+ break;
24987
+ }
24988
+ return headers;
24989
+ }
24990
+ /**
24991
+ * Client for authorizing payments.
24992
+ *
24993
+ * Resolves the payment protocol via protocolFlag, then delegates to
24994
+ * account.authorize() for the actual authorization logic.
24995
+ */
24996
+ class PaymentClient {
24997
+ constructor(config) {
24998
+ this.protocolFlag = config.protocolFlag;
24999
+ this.logger = config.logger;
25000
+ }
25001
+ /**
25002
+ * Authorize a payment by delegating to the account's authorize method.
25003
+ *
25004
+ * PaymentClient resolves the protocol (via explicit param or protocolFlag),
25005
+ * then delegates all protocol-specific logic to account.authorize().
25006
+ *
25007
+ * @param params.account - The account to authorize the payment through
25008
+ * @param params.userId - Passed to protocolFlag for protocol selection
25009
+ * @param params.destination - Payment destination address
25010
+ * @param params.protocol - Explicit protocol override (skips protocolFlag)
25011
+ * @param params.amount - Payment amount
25012
+ * @param params.memo - Payment memo
25013
+ * @param params.paymentRequirements - X402 payment requirements
25014
+ * @param params.challenge - MPP challenge object
25015
+ * @returns AuthorizeResult with protocol and opaque credential
25016
+ */
25017
+ async authorize(params) {
25018
+ const { account, userId, destination } = params;
25019
+ // Determine protocol
25020
+ const protocol = params.protocol
25021
+ ?? (this.protocolFlag ? this.protocolFlag(userId, destination) : 'atxp');
25022
+ // Delegate to the account's authorize method
25023
+ return account.authorize({
25024
+ protocol,
25025
+ amount: params.amount,
25026
+ destination,
25027
+ memo: params.memo,
25028
+ paymentRequirements: params.paymentRequirements,
25029
+ challenge: params.challenge,
25030
+ });
25031
+ }
25032
+ }
25033
+
25034
+ function isX402Challenge(obj) {
25035
+ if (typeof obj !== 'object' || obj === null)
25036
+ return false;
25037
+ const candidate = obj;
25038
+ return (typeof candidate.x402Version !== 'undefined' &&
25039
+ Array.isArray(candidate.accepts));
25040
+ }
25041
+ /**
25042
+ * Protocol handler for X402 payment challenges.
25043
+ *
25044
+ * Detects HTTP 402 responses with x402Version in the JSON body.
25045
+ * Creates payment headers using the x402 library and retries the request.
25046
+ */
25047
+ class X402ProtocolHandler {
25048
+ constructor(config) {
25049
+ this.protocol = 'x402';
25050
+ this.accountsServer = config?.accountsServer ?? 'https://accounts.atxp.ai';
25051
+ }
25052
+ async canHandle(response) {
25053
+ if (response.status !== 402)
25054
+ return false;
25055
+ try {
25056
+ const cloned = response.clone();
25057
+ const body = await cloned.text();
25058
+ const parsed = JSON.parse(body);
25059
+ return isX402Challenge(parsed);
25060
+ }
25061
+ catch {
25062
+ return false;
25063
+ }
25064
+ }
25065
+ async handlePaymentChallenge(response, originalRequest, config) {
25066
+ const { account, logger, fetchFn, approvePayment, onPayment, onPaymentFailure } = config;
25067
+ const responseBody = await response.text();
25068
+ let paymentChallenge;
25069
+ try {
25070
+ paymentChallenge = JSON.parse(responseBody);
25071
+ }
25072
+ catch {
25073
+ logger.error('X402: failed to parse challenge body');
25074
+ return null;
25075
+ }
25076
+ if (!isX402Challenge(paymentChallenge)) {
25077
+ return null;
25078
+ }
25079
+ try {
25080
+ const { selectPaymentRequirements } = await import('x402/client');
25081
+ const selectedPaymentRequirements = selectPaymentRequirements(paymentChallenge.accepts, undefined, 'exact');
25082
+ if (!selectedPaymentRequirements) {
25083
+ logger.info('X402: no suitable payment option found');
25084
+ return this.reconstructResponse(responseBody, response);
25085
+ }
25086
+ const amountInUsdc = Number(selectedPaymentRequirements.maxAmountRequired) / (10 ** 6);
25087
+ const network = selectedPaymentRequirements.network;
25088
+ logger.debug(`X402: payment required: ${amountInUsdc} USDC on ${network} to ${selectedPaymentRequirements.payTo}`);
25089
+ const url = typeof originalRequest.url === 'string' ? originalRequest.url : originalRequest.url.toString();
25090
+ const accountId = await account.getAccountId();
25091
+ const prospectivePayment = {
25092
+ accountId,
25093
+ resourceUrl: url,
25094
+ resourceName: selectedPaymentRequirements.description || url,
25095
+ currency: 'USDC',
25096
+ amount: new bignumber_js.BigNumber(amountInUsdc),
25097
+ iss: selectedPaymentRequirements.payTo
25098
+ };
25099
+ const approved = await approvePayment(prospectivePayment);
25100
+ if (!approved) {
25101
+ logger.info('X402: payment not approved');
25102
+ const error = new Error('Payment not approved');
25103
+ await onPaymentFailure({
25104
+ payment: prospectivePayment,
25105
+ error,
25106
+ attemptedNetworks: [network],
25107
+ failureReasons: new Map([[network, error]]),
25108
+ retryable: true,
25109
+ timestamp: new Date()
25110
+ });
25111
+ return this.reconstructResponse(responseBody, response);
25112
+ }
25113
+ // Authorize via account.authorize() — ATXPAccount calls the accounts
25114
+ // service, BaseAccount signs locally. No fallback — each account type
25115
+ // handles authorization according to its capabilities.
25116
+ const client = new PaymentClient({
25117
+ accountsServer: this.accountsServer,
25118
+ logger,
25119
+ fetchFn,
25120
+ });
25121
+ const authorizeResult = await client.authorize({
25122
+ account,
25123
+ userId: accountId,
25124
+ destination: url,
25125
+ protocol: 'x402',
25126
+ paymentRequirements: selectedPaymentRequirements,
25127
+ });
25128
+ const paymentHeader = authorizeResult.credential;
25129
+ // Retry with X-PAYMENT header
25130
+ const retryHeaders = buildPaymentHeaders({ protocol: 'x402', credential: paymentHeader }, originalRequest.init?.headers);
25131
+ const retryInit = { ...originalRequest.init, headers: retryHeaders };
25132
+ logger.info('X402: retrying request with X-PAYMENT header');
25133
+ const retryResponse = await fetchFn(originalRequest.url, retryInit);
25134
+ if (retryResponse.ok) {
25135
+ logger.info('X402: payment accepted');
25136
+ await onPayment({
25137
+ payment: prospectivePayment,
25138
+ transactionHash: paymentHeader.substring(0, 66),
25139
+ network
25140
+ });
25141
+ }
25142
+ else {
25143
+ logger.warn(`X402: request failed after payment with status ${retryResponse.status}`);
25144
+ const error = new Error(`Request failed with status ${retryResponse.status}`);
25145
+ await onPaymentFailure({
25146
+ payment: prospectivePayment,
25147
+ error,
25148
+ attemptedNetworks: [network],
25149
+ failureReasons: new Map([[network, error]]),
25150
+ retryable: false,
25151
+ timestamp: new Date()
25152
+ });
25153
+ }
25154
+ return retryResponse;
25155
+ }
25156
+ catch (error) {
25157
+ logger.error(`X402: failed to handle payment challenge: ${error}`);
25158
+ if (isX402Challenge(paymentChallenge) && paymentChallenge.accepts[0]) {
25159
+ const firstOption = paymentChallenge.accepts[0];
25160
+ const amount = firstOption.maxAmountRequired ? Number(firstOption.maxAmountRequired) / (10 ** 6) : 0;
25161
+ const url = typeof originalRequest.url === 'string' ? originalRequest.url : originalRequest.url.toString();
25162
+ const accountId = await account.getAccountId();
25163
+ const errorNetwork = firstOption.network || 'unknown';
25164
+ const typedError = error;
25165
+ const isRetryable = typedError instanceof ATXPPaymentError ? typedError.retryable : true;
25166
+ await onPaymentFailure({
25167
+ payment: {
25168
+ accountId,
25169
+ resourceUrl: url,
25170
+ resourceName: firstOption.description || url,
25171
+ currency: 'USDC',
25172
+ amount: new bignumber_js.BigNumber(amount),
25173
+ iss: firstOption.payTo || ''
25174
+ },
25175
+ error: typedError,
25176
+ attemptedNetworks: [errorNetwork],
25177
+ failureReasons: new Map([[errorNetwork, typedError]]),
25178
+ retryable: isRetryable,
25179
+ timestamp: new Date()
25180
+ });
25181
+ }
25182
+ return this.reconstructResponse(responseBody, response);
25183
+ }
25184
+ }
25185
+ reconstructResponse(body, original) {
25186
+ return new Response(body, {
25187
+ status: original.status,
25188
+ statusText: original.statusText,
25189
+ headers: original.headers
25190
+ });
25191
+ }
25192
+ }
25193
+
25194
+ /**
25195
+ * Protocol handler for ATXP-MCP payment challenges.
25196
+ *
25197
+ * Detects JSON-RPC errors with code -30402 (PAYMENT_REQUIRED_ERROR_CODE) in
25198
+ * MCP responses (both SSE and JSON formats).
25199
+ */
25200
+ class ATXPProtocolHandler {
25201
+ constructor() {
25202
+ this.protocol = 'atxp';
25203
+ }
25204
+ async canHandle(response) {
25205
+ // ATXP-MCP challenges are embedded in MCP JSON-RPC responses,
25206
+ // not at the HTTP level. We detect them by looking for error code -30402.
25207
+ try {
25208
+ const cloned = response.clone();
25209
+ const body = await cloned.text();
25210
+ if (body.length === 0)
25211
+ return false;
25212
+ const paymentRequests = await this.extractPaymentRequests(body);
25213
+ return paymentRequests.length > 0;
25214
+ }
25215
+ catch {
25216
+ return false;
25217
+ }
25218
+ }
25219
+ async handlePaymentChallenge(response, _originalRequest, config) {
25220
+ const { logger } = config;
25221
+ try {
25222
+ const body = await response.clone().text();
25223
+ const paymentRequests = await this.extractPaymentRequests(body);
25224
+ if (paymentRequests.length === 0) {
25225
+ return null;
25226
+ }
25227
+ // ATXP-MCP payment challenges are handled through the fetcher's
25228
+ // checkForATXPResponse → handlePaymentRequestError flow, not at
25229
+ // the HTTP protocol handler level. Return null so the fetcher's
25230
+ // existing MCP payment flow picks it up.
25231
+ // This handler's role in the strategy pattern is detection via
25232
+ // canHandle() for protocol selection (e.g., protocolFlag), not
25233
+ // direct payment handling.
25234
+ if (paymentRequests.length > 1) {
25235
+ logger.warn(`ATXP: multiple payment requirements found in MCP response. ` +
25236
+ `The client does not support multiple payment requirements. ` +
25237
+ `${paymentRequests.map(pr => pr.url).join(', ')}`);
25238
+ }
25239
+ else {
25240
+ logger.info(`ATXP: payment requirement found in MCP response - ${paymentRequests[0].url}`);
25241
+ }
25242
+ return null;
25243
+ }
25244
+ catch (error) {
25245
+ logger.error(`ATXP: error checking for payment requirements: ${error}`);
25246
+ return null;
25247
+ }
25248
+ }
25249
+ async extractPaymentRequests(body) {
25250
+ try {
25251
+ if (common.isSSEResponse(body)) {
25252
+ const messages = await common.parseMcpMessages(body);
25253
+ return messages.flatMap(message => common.parsePaymentRequests(message)).filter((pr) => pr !== null);
25254
+ }
25255
+ else {
25256
+ const json = JSON.parse(body);
25257
+ const messages = await common.parseMcpMessages(json);
25258
+ return messages.flatMap(message => common.parsePaymentRequests(message)).filter((pr) => pr !== null);
25259
+ }
25260
+ }
25261
+ catch {
25262
+ return [];
25263
+ }
25264
+ }
25265
+ }
25266
+
25267
+ /**
25268
+ * Protocol handler for MPP (Machine Payments Protocol) payment challenges.
25269
+ *
25270
+ * Detects MPP challenges in two forms:
25271
+ * 1. HTTP level: HTTP 402 with WWW-Authenticate: Payment header
25272
+ * 2. MCP level: JSON-RPC error with code -32042 containing MPP data
25273
+ *
25274
+ * Handles the challenge by calling /authorize/mpp on the accounts service
25275
+ * and retrying with an Authorization: Payment header.
25276
+ */
25277
+ class MPPProtocolHandler {
25278
+ constructor(config) {
25279
+ this.protocol = 'mpp';
25280
+ this.accountsServer = config?.accountsServer ?? 'https://accounts.atxp.ai';
25281
+ }
25282
+ async canHandle(response) {
25283
+ if (mpp.hasMPPChallenge(response))
25284
+ return true;
25285
+ return mpp.hasMPPMCPError(response);
25286
+ }
25287
+ async handlePaymentChallenge(response, originalRequest, config) {
25288
+ const { account, logger, approvePayment } = config;
25289
+ // Extract the challenge and body text from the response
25290
+ const extracted = await this.extractChallenge(response, logger);
25291
+ if (!extracted) {
25292
+ logger.error('MPP: failed to extract challenge from response');
25293
+ return null;
25294
+ }
25295
+ const { challenge, bodyText } = extracted;
25296
+ const url = typeof originalRequest.url === 'string'
25297
+ ? originalRequest.url
25298
+ : originalRequest.url.toString();
25299
+ // Build prospective payment for approval
25300
+ const accountId = await account.getAccountId();
25301
+ const prospectivePayment = this.buildProspectivePayment(challenge, url, accountId);
25302
+ // Ask for approval
25303
+ const approved = await approvePayment(prospectivePayment);
25304
+ if (!approved) {
25305
+ logger.info('MPP: payment not approved');
25306
+ await this.reportFailure(config, prospectivePayment, new Error('Payment not approved'), challenge.network, true);
25307
+ return this.reconstructResponse(bodyText, response);
25308
+ }
25309
+ return this.authorizeAndRetry(challenge, prospectivePayment, originalRequest, config, bodyText, response);
25310
+ }
25311
+ /**
25312
+ * Extract MPP challenge from response - tries HTTP header first, then MCP error body.
25313
+ * Returns both the challenge and the body text to avoid double-consumption.
25314
+ */
25315
+ async extractChallenge(response, logger) {
25316
+ // Read body once upfront to avoid double consumption (response.text() can only be called once)
25317
+ let bodyText = '';
25318
+ try {
25319
+ bodyText = await response.text();
25320
+ }
25321
+ catch {
25322
+ // Body may not be available
25323
+ }
25324
+ // Try HTTP header first
25325
+ const header = response.headers.get('WWW-Authenticate');
25326
+ if (header) {
25327
+ const challenge = mpp.parseMPPHeader(header);
25328
+ if (challenge) {
25329
+ logger.debug('MPP: parsed challenge from WWW-Authenticate header');
25330
+ return { challenge, bodyText };
25331
+ }
25332
+ }
25333
+ // Try MCP error body (use clone since response body may have been consumed above)
25334
+ try {
25335
+ const parsed = JSON.parse(bodyText);
25336
+ if (typeof parsed === 'object' &&
25337
+ parsed !== null &&
25338
+ typeof parsed.error === 'object' &&
25339
+ parsed.error !== null &&
25340
+ parsed.error.code === mpp.MPP_ERROR_CODE) {
25341
+ const challenge = mpp.parseMPPFromMCPError(parsed.error.data);
25342
+ if (challenge) {
25343
+ logger.debug('MPP: parsed challenge from MCP error body');
25344
+ return { challenge, bodyText };
25345
+ }
25346
+ }
25347
+ }
25348
+ catch {
25349
+ // Not JSON or malformed
25350
+ }
25351
+ return null;
25352
+ }
25353
+ /**
25354
+ * Build a ProspectivePayment from an MPP challenge.
25355
+ */
25356
+ buildProspectivePayment(challenge, url, accountId) {
25357
+ const amountNum = Number(challenge.amount) / (10 ** 6);
25358
+ return {
25359
+ accountId,
25360
+ resourceUrl: url,
25361
+ resourceName: url,
25362
+ currency: challenge.currency,
25363
+ amount: new bignumber_js.BigNumber(amountNum),
25364
+ iss: challenge.recipient,
25365
+ };
25366
+ }
25367
+ /**
25368
+ * Report a payment failure via the onPaymentFailure callback.
25369
+ */
25370
+ async reportFailure(config, payment, error, network, retryable) {
25371
+ await config.onPaymentFailure({
25372
+ payment,
25373
+ error,
25374
+ attemptedNetworks: [network],
25375
+ failureReasons: new Map([[network, error]]),
25376
+ retryable,
25377
+ timestamp: new Date(),
25378
+ });
25379
+ }
25380
+ /**
25381
+ * Call /authorize/mpp on accounts service and retry the original request with the credential.
25382
+ */
25383
+ async authorizeAndRetry(challenge, prospectivePayment, originalRequest, config, bodyText, originalResponse) {
25384
+ const { account, logger, fetchFn, onPayment } = config;
25385
+ try {
25386
+ logger.debug('MPP: calling /authorize/mpp on accounts service');
25387
+ const client = new PaymentClient({
25388
+ accountsServer: this.accountsServer,
25389
+ logger,
25390
+ fetchFn,
25391
+ });
25392
+ const accountId = await account.getAccountId();
25393
+ let authorizeResult;
25394
+ try {
25395
+ authorizeResult = await client.authorize({
25396
+ account,
25397
+ userId: accountId,
25398
+ destination: typeof originalRequest.url === 'string' ? originalRequest.url : originalRequest.url.toString(),
25399
+ protocol: 'mpp',
25400
+ challenge,
25401
+ });
25402
+ }
25403
+ catch (authorizeError) {
25404
+ // AuthorizationError = server rejected the request (HTTP error from accounts)
25405
+ // Other errors = data validation or network failure
25406
+ if (authorizeError instanceof common.AuthorizationError) {
25407
+ logger.debug(`MPP: /authorize/mpp rejected (${authorizeError.statusCode}), returning original response`);
25408
+ return this.reconstructResponse(bodyText, originalResponse);
25409
+ }
25410
+ throw authorizeError;
25411
+ }
25412
+ const retryHeaders = buildPaymentHeaders(authorizeResult, originalRequest.init?.headers);
25413
+ const retryInit = { ...originalRequest.init, headers: retryHeaders };
25414
+ logger.info('MPP: retrying request with Authorization: Payment header');
25415
+ const retryResponse = await fetchFn(originalRequest.url, retryInit);
25416
+ if (retryResponse.ok) {
25417
+ logger.info('MPP: payment accepted');
25418
+ await onPayment({ payment: prospectivePayment, transactionHash: challenge.id, network: challenge.network });
25419
+ }
25420
+ else {
25421
+ logger.warn(`MPP: request failed after payment with status ${retryResponse.status}`);
25422
+ await this.reportFailure(config, prospectivePayment, new Error(`Request failed with status ${retryResponse.status}`), challenge.network, false);
25423
+ }
25424
+ return retryResponse;
25425
+ }
25426
+ catch (error) {
25427
+ logger.error(`MPP: failed to handle payment challenge: ${error}`);
25428
+ const cause = error instanceof Error ? error : new Error(String(error));
25429
+ await this.reportFailure(config, prospectivePayment, cause, challenge.network, true);
25430
+ return this.reconstructResponse(bodyText, originalResponse);
25431
+ }
25432
+ }
25433
+ reconstructResponse(body, original) {
25434
+ return new Response(body || null, {
25435
+ status: original.status,
25436
+ statusText: original.statusText,
25437
+ headers: original.headers,
25438
+ });
25439
+ }
25440
+ }
25441
+
24891
25442
  Object.defineProperty(exports, "ATXPAccount", {
24892
25443
  enumerable: true,
24893
25444
  get: function () { return common.ATXPAccount; }
@@ -24895,14 +25446,17 @@ Object.defineProperty(exports, "ATXPAccount", {
24895
25446
  exports.ATXPDestinationMaker = ATXPDestinationMaker;
24896
25447
  exports.ATXPLocalAccount = ATXPLocalAccount;
24897
25448
  exports.ATXPPaymentError = ATXPPaymentError;
25449
+ exports.ATXPProtocolHandler = ATXPProtocolHandler;
24898
25450
  exports.DEFAULT_CLIENT_CONFIG = DEFAULT_CLIENT_CONFIG;
24899
25451
  exports.GasEstimationError = GasEstimationError;
24900
25452
  exports.InsufficientFundsError = InsufficientFundsError;
25453
+ exports.MPPProtocolHandler = MPPProtocolHandler;
24901
25454
  exports.OAuthAuthenticationRequiredError = OAuthAuthenticationRequiredError;
24902
25455
  exports.OAuthClient = OAuthClient;
24903
25456
  exports.POLYGON_AMOY = POLYGON_AMOY;
24904
25457
  exports.POLYGON_MAINNET = POLYGON_MAINNET;
24905
25458
  exports.PassthroughDestinationMaker = PassthroughDestinationMaker;
25459
+ exports.PaymentClient = PaymentClient;
24906
25460
  exports.PaymentExpiredError = PaymentExpiredError;
24907
25461
  exports.PaymentNetworkError = PaymentNetworkError;
24908
25462
  exports.PaymentServerError = PaymentServerError;
@@ -24916,9 +25470,11 @@ exports.UnsupportedCurrencyError = UnsupportedCurrencyError;
24916
25470
  exports.UserRejectedError = UserRejectedError;
24917
25471
  exports.WORLD_CHAIN_MAINNET = WORLD_CHAIN_MAINNET;
24918
25472
  exports.WORLD_CHAIN_SEPOLIA = WORLD_CHAIN_SEPOLIA;
25473
+ exports.X402ProtocolHandler = X402ProtocolHandler;
24919
25474
  exports.atxpClient = atxpClient;
24920
25475
  exports.atxpFetch = atxpFetch;
24921
25476
  exports.buildClientConfig = buildClientConfig;
25477
+ exports.buildPaymentHeaders = buildPaymentHeaders;
24922
25478
  exports.buildStreamableTransport = buildStreamableTransport;
24923
25479
  exports.getPolygonAmoyWithRPC = getPolygonAmoyWithRPC;
24924
25480
  exports.getPolygonByChainId = getPolygonByChainId;