mppx 0.13.4 → 0.13.5

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 (40) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/Errors.d.ts +63 -2
  3. package/dist/Errors.js +52 -2
  4. package/dist/Method.d.ts +10 -0
  5. package/dist/cli/cli.js +133 -133
  6. package/dist/cli/plugins/index.js +12 -12
  7. package/dist/client/Mppx.d.ts +2 -0
  8. package/dist/client/Mppx.js +8 -10
  9. package/dist/client/internal/Fetch.js +107 -8
  10. package/dist/client/internal/MethodResponse.d.ts +6 -0
  11. package/dist/client/internal/MethodResponse.js +16 -0
  12. package/dist/client/internal/protocols/Mcp.js +36 -5
  13. package/dist/evm/server/Charge.js +3 -1
  14. package/dist/internal/version.d.ts +2 -2
  15. package/dist/internal/version.js +1 -1
  16. package/dist/internal/warnOnce.d.ts +3 -0
  17. package/dist/internal/warnOnce.js +9 -0
  18. package/dist/server/Mppx.d.ts +5 -0
  19. package/dist/server/Mppx.js +14 -13
  20. package/dist/server/Transport.js +10 -0
  21. package/dist/tempo/Attribution.d.ts +24 -9
  22. package/dist/tempo/Attribution.js +25 -5
  23. package/dist/tempo/client/Charge.d.ts +116 -10
  24. package/dist/tempo/client/Charge.js +116 -22
  25. package/dist/tempo/client/Methods.d.ts +88 -9
  26. package/dist/tempo/index.d.ts +1 -0
  27. package/dist/tempo/index.js +1 -0
  28. package/dist/tempo/internal/fee-payer.js +4 -0
  29. package/dist/tempo/server/Charge.d.ts +11 -0
  30. package/dist/tempo/server/Charge.js +160 -29
  31. package/dist/tempo/server/Methods.js +16 -4
  32. package/dist/tempo/server/Subscription.js +2 -1
  33. package/dist/tempo/server/internal/fallback-store.d.ts +9 -0
  34. package/dist/tempo/server/internal/fallback-store.js +13 -0
  35. package/dist/tempo/server/internal/html.gen.js +1 -1
  36. package/dist/tempo/session/precompile/Chain.d.ts +2 -0
  37. package/dist/tempo/session/precompile/Chain.js +3 -1
  38. package/dist/tempo/session/server/Session.js +10 -11
  39. package/dist/tempo/session/server/Settlement.js +11 -3
  40. package/package.json +1 -1
@@ -155,6 +155,8 @@ export type ReadbackRetryOptions = {
155
155
  retries?: number | undefined;
156
156
  /** Delay between attempts, in milliseconds. @default 250 */
157
157
  delayMs?: number | undefined;
158
+ /** Whether a failed attempt may be retried. @default every failure */
159
+ shouldRetry?: ((error: unknown) => boolean) | undefined;
158
160
  };
159
161
  /**
160
162
  * Retry an on-chain readback that is pinned to the block containing a just-sent
@@ -169,7 +169,7 @@ export async function getChannelStatesBatch(client, channelIds, escrow = tip20Ch
169
169
  * verification even though the open transaction succeeded.
170
170
  */
171
171
  export async function readbackWithRetry(read, options = {}) {
172
- const { retries = 5, delayMs = 250 } = options;
172
+ const { retries = 5, delayMs = 250, shouldRetry = () => true } = options;
173
173
  let lastError;
174
174
  for (let attempt = 0; attempt <= retries; attempt++) {
175
175
  try {
@@ -177,6 +177,8 @@ export async function readbackWithRetry(read, options = {}) {
177
177
  }
178
178
  catch (error) {
179
179
  lastError = error;
180
+ if (!shouldRetry(error))
181
+ break;
180
182
  if (attempt < retries)
181
183
  await new Promise((resolve) => setTimeout(resolve, delayMs));
182
184
  }
@@ -13,12 +13,12 @@ import * as Errors from '../../../Errors.js';
13
13
  import * as Expires from '../../../Expires.js';
14
14
  import * as Method from '../../../Method.js';
15
15
  import * as ChallengeMeta from '../../../server/internal/challengeMeta.js';
16
- import * as Store from '../../../Store.js';
17
16
  import * as Client from '../../../viem/Client.js';
18
17
  import * as Account from '../../internal/account.js';
19
18
  import * as defaults from '../../internal/defaults.js';
20
19
  import * as Methods from '../../Methods.js';
21
20
  import * as ChargeServer from '../../server/Charge.js';
21
+ import { fallbackStore } from '../../server/internal/fallback-store.js';
22
22
  import * as Transport from '../../server/internal/transport.js';
23
23
  import { tip20ChannelEscrow } from '../precompile/Protocol.js';
24
24
  import { deserializeSnapshot as deserializeSessionSnapshot, serializeSnapshot as serializeSessionSnapshot, } from '../Snapshot.js';
@@ -66,17 +66,14 @@ function createBootstrapChallenge(parameters) {
66
66
  });
67
67
  }
68
68
  function respondBootstrapChallenge(challenge, error) {
69
- const headers = new Headers({
70
- [Constants.Headers.wwwAuthenticate]: Challenge.serialize(challenge),
71
- 'Cache-Control': 'no-store',
72
- });
69
+ const headers = new Headers({ 'Cache-Control': 'no-store' });
70
+ // A 403 is final: omit the challenge, as `Transport.http()` does.
71
+ if (error?.status !== 403)
72
+ headers.set(Constants.Headers.wwwAuthenticate, Challenge.serialize(challenge));
73
73
  if (!error)
74
74
  return new Response(null, { status: 402, headers });
75
75
  headers.set('Content-Type', 'application/problem+json');
76
- return new Response(JSON.stringify(error.toProblemDetails(challenge.id)), {
77
- status: error.status,
78
- headers,
79
- });
76
+ return new Response(JSON.stringify(error.toProblemDetails(error.status === 403 ? undefined : challenge.id)), { status: error.status, headers });
80
77
  }
81
78
  function assertBootstrapChallengeMatches(expected, actual) {
82
79
  if (actual.method !== Constants.Methods.tempo || actual.intent !== Constants.Intents.charge)
@@ -164,7 +161,9 @@ async function handleBootstrapPreflight(parameters) {
164
161
  });
165
162
  }
166
163
  catch (error) {
167
- return respondBootstrapChallenge(challenge, error instanceof Errors.PaymentError ? error : new Errors.InternalPaymentError());
164
+ return respondBootstrapChallenge(challenge, error instanceof Errors.PaymentError
165
+ ? error
166
+ : new Errors.InternalPaymentError({ cause: error }));
168
167
  }
169
168
  const channelId = await resolveSessionChannelId({
170
169
  capturedRequest: parameters.capturedRequest,
@@ -195,7 +194,7 @@ async function handleBootstrapPreflight(parameters) {
195
194
  /** Creates a server-side TIP20EscrowChannel precompile session payment method. */
196
195
  export function session(p) {
197
196
  const parameters = p;
198
- const { amount, channelStateTtl = 5_000, currency = defaults.resolveCurrency(parameters), decimals = defaults.decimals, operator, store: rawStore = Store.memory(), suggestedDeposit, unitType, } = parameters;
197
+ const { amount, channelStateTtl = 5_000, currency = defaults.resolveCurrency(parameters), decimals = defaults.decimals, operator, store: rawStore = fallbackStore('tempo.session'), suggestedDeposit, unitType, } = parameters;
199
198
  const settlementSchedule = resolveSettlementSchedule(parameters.settlementSchedule, decimals);
200
199
  const onSessionSettlement = parameters.onSessionSettlement;
201
200
  const onSessionSettlementFailure = parameters.onSessionSettlementFailure;
@@ -1,4 +1,4 @@
1
- import { BaseError as viem_BaseError, HttpRequestError, InternalRpcError, isAddress, isAddressEqual, LimitExceededRpcError, parseUnits, ResourceUnavailableRpcError, RpcRequestError, SocketClosedError, TimeoutError, WaitForTransactionReceiptTimeoutError, WebSocketRequestError, zeroAddress, } from 'viem';
1
+ import { BaseError as viem_BaseError, ExecutionRevertedError, HttpRequestError, InternalRpcError, isAddress, isAddressEqual, LimitExceededRpcError, parseUnits, ResourceUnavailableRpcError, RpcRequestError, SocketClosedError, TimeoutError, WaitForTransactionReceiptTimeoutError, WebSocketRequestError, zeroAddress, } from 'viem';
2
2
  import { BadRequestError, ChannelClosedError, ChannelNotFoundError, InsufficientBalanceError, VerificationFailedError, } from '../../../Errors.js';
3
3
  import { isSessionContentRequest } from '../../server/internal/request-body.js';
4
4
  import * as Chain from '../precompile/Chain.js';
@@ -307,7 +307,10 @@ export async function settle(store_, client, channelId_, options) {
307
307
  sender: account?.address,
308
308
  });
309
309
  const amount = uint96(channel.highestVoucher.cumulativeAmount);
310
- const txHash = await Chain.settleOnChain(client, channel.descriptor, amount, channel.highestVoucher.signature, escrow, account
310
+ const { signature } = channel.highestVoucher;
311
+ // A replica behind the open or top-up that admitted this voucher rejects the settle while
312
+ // preparing it (e.g. `ChannelNotFound`); nothing was broadcast, so retry until it catches up.
313
+ const txHash = await Chain.readbackWithRetry(() => Chain.settleOnChain(client, channel.descriptor, amount, signature, escrow, account
311
314
  ? {
312
315
  account,
313
316
  ...(options?.feePayer ? { feePayer: options.feePayer } : {}),
@@ -315,7 +318,7 @@ export async function settle(store_, client, channelId_, options) {
315
318
  ...(options?.feeToken ? { feeToken: options.feeToken } : {}),
316
319
  candidateFeeTokens: options?.candidateFeeTokens ?? [channel.token],
317
320
  }
318
- : undefined);
321
+ : undefined), { shouldRetry: isExecutionRevert });
319
322
  const receipt = await Chain.waitForSuccessfulReceipt(client, txHash);
320
323
  const settled = readSettledReceiptFields(Chain.getChannelEvent(receipt, 'Settled', channelId));
321
324
  const { deltaPaid, newSettled } = settled;
@@ -389,6 +392,11 @@ export function ignoreRetryableSettlementFailure(error) {
389
392
  return undefined;
390
393
  throw error;
391
394
  }
395
+ /** Whether a settlement failed because the node's execution reverted it. */
396
+ function isExecutionRevert(error) {
397
+ return (error instanceof viem_BaseError &&
398
+ Boolean(error.walk((cause) => cause instanceof ExecutionRevertedError)));
399
+ }
392
400
  function isRetryableSettlementFailure(error) {
393
401
  if (error instanceof SettlementLeaseError)
394
402
  return true;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mppx",
3
3
  "type": "module",
4
- "version": "0.13.4",
4
+ "version": "0.13.5",
5
5
  "main": "./dist/index.js",
6
6
  "license": "MIT",
7
7
  "homepage": "https://github.com/wevm/mppx#readme",