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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # mppx
2
2
 
3
+ ## 0.13.5
4
+
5
+ ### Patch Changes
6
+
7
+ - 19315c7: Bounded automatic MCP JSON and SSE payment-challenge inspection to 1 MiB while preserving the original response stream for callers.
8
+ - 9da0e18: Removed retained native response bodies from manual payment event snapshots to prevent unbounded buffering while callers consume streams. Status and headers remained available; non-HTTP transport snapshots were unchanged.
9
+ - 68381a4: Fixed Tempo charge `validBefore` to cover a fractional challenge expiry, so charge transactions stay valid for as long as their challenge.
10
+ - ca6f04b: Exported `Attribution` from `mppx/tempo` so apps can encode, decode, and verify MPP attribution memos on TIP-20 transfers.
11
+ - b085cae: Exposed the verified payer as `payer` on payment success contexts and per-method `onPaymentSuccess` hooks. Tempo charge and EVM charge validation now report the recovered payer address as `payer`.
12
+ - 47beb31: Attached the original error as `cause` on `InternalPaymentError` so payment hooks can inspect unexpected failures, and added `CredentialReplayedError` (a `VerificationFailedError` subclass) for Tempo charge credentials whose payment already settled: replayed transaction hashes and proofs, claims held by a concurrent request, and settled transfers whose memo is bound to another challenge. The HTTP transport and session bootstrap answer it with a final 403 Problem Details response without a `WWW-Authenticate` challenge, so clients do not pay again; the problem type stays `verification-failed`.
13
+ - 7271a85: Recovered confirmed Tempo charges when a synchronous broadcast returned an RPC error after inclusion.
14
+ - 08fa745: Released Tempo charge transaction hash claims when settlement fails after a broadcast attempt, so retrying the same credential re-broadcasts and recovers the receipt instead of being rejected as a replay.
15
+ - 1e692c3: Added `Errors.PaymentOutcomeUnknownError` and emitted it through `payment.failed` when a paid retry returns a 5xx response, carrying the challenge, credential, and the transaction hash for push and unsponsored Tempo charges. The response is still returned and never retried; when `payment.failed` is observed, its body is first buffered up to 64 KiB within 2 seconds so the connection is released, and a cut-off body is marked with `Mppx-Body-Truncated: true`. A thrown paid retry now passes the same error to `payment.failed` with the original error as `cause`, and the original error is still thrown.
16
+ - 74533b0: Required at least 15 seconds of remaining validity before sponsoring and broadcasting transactions.
17
+ - 94162a5: Fixed session settlements failing the charged request when a lagging RPC replica reverted the settle while preparing it (such as `ChannelNotFound` right after an open); `settle()` now retries reverted preparations.
18
+ - 763a187: Shared default sponsor-budget storage across currency offers from tempo.charge and tempo.common, enforcing aggregate exposure limits without changing replay-store policy. Multi-process deployments still require a shared atomic store.
19
+ - 68381a4: Shared one in-flight signing between concurrent Tempo charge requests for the same challenge and signer.
20
+ - 61b4c10: Added `maxAmount` (display units, parsed with `decimals`) and `maxAtomicAmount` (base units) to the `tempo.charge()` client, matching the EVM charge client, plus a `currencies` allowlist that the caps require. Offers that exceed a cap or fail `currencies`, `expectedRecipients`, `expectedChainId`, or `allowedChainIds` are skipped during challenge selection and rejected before signing, and a `maxAmount` that would round is rejected.
21
+ - 29855e8: Bounded synchronous Tempo charge broadcasts by a configurable `confirmationTimeoutMs` deadline (default 30 seconds). The broadcast may use at most two thirds of it, which leaves time for recovery. When the broadcast outcome is unknown, such as a timeout, a lost connection, or an "already known" response, the receipt is now polled until the deadline instead of failing while the transaction is still pending. Polling stops early once an expiring transaction is past its `validBefore`, followed by one final receipt lookup. Definitive RPC rejections still fail immediately.
22
+ - 27213df: Added a one-time warning when Tempo `charge`, `session`, or `subscription` server methods fall back to an in-memory store, since replay protection backed by it only applies within a single instance.
23
+
3
24
  ## 0.13.4
4
25
 
5
26
  ### Patch Changes
package/dist/Errors.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type * as Challenge from './Challenge.js';
1
2
  /** Default hint messages for payment errors. */
2
3
  export declare const Hints: {
3
4
  readonly paymentRequired: 'Use a supported wallet to pay for this resource using one of the supported payment methods returned in the WWW-Authenticate header. See https://mpp.dev/tools/wallet.md';
@@ -24,6 +25,8 @@ export declare abstract class PaymentError extends Error {
24
25
  }
25
26
  export declare namespace PaymentError {
26
27
  type Options = {
28
+ /** Underlying error. Never serialized into Problem Details. */
29
+ cause?: unknown;
27
30
  details?: Record<string, unknown> | undefined;
28
31
  };
29
32
  type ProblemDetails = {
@@ -82,7 +85,7 @@ export declare namespace InvalidChallengeError {
82
85
  * Payment proof is invalid or verification failed.
83
86
  */
84
87
  export declare class VerificationFailedError extends PaymentError {
85
- readonly name = "VerificationFailedError";
88
+ readonly name: string;
86
89
  readonly title = "Verification Failed";
87
90
  readonly type = "https://paymentauth.org/problems/verification-failed";
88
91
  constructor(options?: VerificationFailedError.Options);
@@ -95,15 +98,43 @@ export declare namespace VerificationFailedError {
95
98
  reason?: string;
96
99
  };
97
100
  }
101
+ /**
102
+ * The payment behind a credential is already used or bound elsewhere: the
103
+ * credential was consumed by an earlier request, is being settled by a
104
+ * concurrent one, or its settled transfer is bound to another challenge.
105
+ *
106
+ * Answered with 403 and no challenge (the payment was valid, but policy
107
+ * denies access), since a fresh 402 challenge would invite an auto-paying
108
+ * client to pay again. The problem type stays `verification-failed`.
109
+ */
110
+ export declare class CredentialReplayedError extends VerificationFailedError {
111
+ readonly name = "CredentialReplayedError";
112
+ readonly status = 403;
113
+ constructor(options?: CredentialReplayedError.Options);
114
+ }
115
+ export declare namespace CredentialReplayedError {
116
+ type Options = {
117
+ /** Reason the credential was rejected (e.g., "Transaction hash has already been used"). */
118
+ reason?: string;
119
+ };
120
+ }
98
121
  /**
99
122
  * Payment processing failed because of an unexpected internal error.
123
+ *
124
+ * The response stays generic; the original error is only available on `cause`.
100
125
  */
101
126
  export declare class InternalPaymentError extends PaymentError {
102
127
  readonly name = "InternalPaymentError";
103
128
  readonly title = "Internal Payment Error";
104
129
  readonly status = 500;
105
130
  readonly type = "https://paymentauth.org/problems/internal-payment-error";
106
- constructor();
131
+ constructor(options?: InternalPaymentError.Options);
132
+ }
133
+ export declare namespace InternalPaymentError {
134
+ type Options = {
135
+ /** Original error, for server-side hooks and logging. Never sent to clients. */
136
+ cause?: unknown;
137
+ };
107
138
  }
108
139
  /**
109
140
  * Payment requires additional action (e.g., 3DS authentication).
@@ -135,6 +166,36 @@ export declare namespace PaymentExpiredError {
135
166
  expires?: string;
136
167
  };
137
168
  }
169
+ /**
170
+ * A credential was sent, but the payment result could not be confirmed.
171
+ *
172
+ * Reconcile with `credential` (and `transactionHash` when the method can derive it)
173
+ * instead of retrying, which could pay twice.
174
+ */
175
+ export declare class PaymentOutcomeUnknownError extends PaymentError {
176
+ readonly name = "PaymentOutcomeUnknownError";
177
+ readonly title = "Payment Outcome Unknown";
178
+ readonly type = "https://paymentauth.org/problems/payment-outcome-unknown";
179
+ /** Challenge the credential paid for. */
180
+ readonly challenge: Challenge.Challenge;
181
+ /** Serialized credential sent with the paid request. */
182
+ readonly credential: string;
183
+ /** On-chain hash the payment will have if it settled, when derivable from the credential. */
184
+ readonly transactionHash: string | undefined;
185
+ constructor(options: PaymentOutcomeUnknownError.Options);
186
+ }
187
+ export declare namespace PaymentOutcomeUnknownError {
188
+ type Options = {
189
+ /** Error thrown by the paid request, when it failed without a response. */
190
+ cause?: unknown;
191
+ /** Challenge the credential paid for. */
192
+ challenge: Challenge.Challenge;
193
+ /** Serialized credential sent with the paid request. */
194
+ credential: string;
195
+ /** On-chain hash the payment will have if it settled. */
196
+ transactionHash?: string | undefined;
197
+ };
198
+ }
138
199
  /**
139
200
  * No credential was provided but payment is required.
140
201
  */
package/dist/Errors.js CHANGED
@@ -17,6 +17,13 @@ export class PaymentError extends Error {
17
17
  constructor(message, options = {}) {
18
18
  super(message);
19
19
  this.details = options.details;
20
+ // Defined manually (non-enumerable, like native `cause`) so consumers on pre-ES2022 libs still typecheck.
21
+ if (options.cause !== undefined)
22
+ Object.defineProperty(this, 'cause', {
23
+ configurable: true,
24
+ value: options.cause,
25
+ writable: true,
26
+ });
20
27
  }
21
28
  /** Converts the error to RFC 9457 Problem Details format. */
22
29
  toProblemDetails(challengeId) {
@@ -74,16 +81,35 @@ export class VerificationFailedError extends PaymentError {
74
81
  });
75
82
  }
76
83
  }
84
+ /**
85
+ * The payment behind a credential is already used or bound elsewhere: the
86
+ * credential was consumed by an earlier request, is being settled by a
87
+ * concurrent one, or its settled transfer is bound to another challenge.
88
+ *
89
+ * Answered with 403 and no challenge (the payment was valid, but policy
90
+ * denies access), since a fresh 402 challenge would invite an auto-paying
91
+ * client to pay again. The problem type stays `verification-failed`.
92
+ */
93
+ export class CredentialReplayedError extends VerificationFailedError {
94
+ name = 'CredentialReplayedError';
95
+ status = 403;
96
+ constructor(options = {}) {
97
+ const { reason = 'Credential has already been used' } = options;
98
+ super({ reason });
99
+ }
100
+ }
77
101
  /**
78
102
  * Payment processing failed because of an unexpected internal error.
103
+ *
104
+ * The response stays generic; the original error is only available on `cause`.
79
105
  */
80
106
  export class InternalPaymentError extends PaymentError {
81
107
  name = 'InternalPaymentError';
82
108
  title = 'Internal Payment Error';
83
109
  status = 500;
84
110
  type = 'https://paymentauth.org/problems/internal-payment-error';
85
- constructor() {
86
- super('An internal payment error occurred.');
111
+ constructor(options = {}) {
112
+ super('An internal payment error occurred.', { cause: options.cause });
87
113
  }
88
114
  }
89
115
  /**
@@ -110,6 +136,30 @@ export class PaymentExpiredError extends PaymentError {
110
136
  super(expires ? `Payment expired at ${expires}.` : 'Payment has expired.');
111
137
  }
112
138
  }
139
+ /**
140
+ * A credential was sent, but the payment result could not be confirmed.
141
+ *
142
+ * Reconcile with `credential` (and `transactionHash` when the method can derive it)
143
+ * instead of retrying, which could pay twice.
144
+ */
145
+ export class PaymentOutcomeUnknownError extends PaymentError {
146
+ name = 'PaymentOutcomeUnknownError';
147
+ title = 'Payment Outcome Unknown';
148
+ type = 'https://paymentauth.org/problems/payment-outcome-unknown';
149
+ /** Challenge the credential paid for. */
150
+ challenge;
151
+ /** Serialized credential sent with the paid request. */
152
+ credential;
153
+ /** On-chain hash the payment will have if it settled, when derivable from the credential. */
154
+ transactionHash;
155
+ constructor(options) {
156
+ const { cause, challenge, credential, transactionHash } = options;
157
+ super(`Payment outcome is unknown after sending a credential for challenge "${challenge.id}". Do not blindly retry.`, { cause });
158
+ this.challenge = challenge;
159
+ this.credential = credential;
160
+ this.transactionHash = transactionHash;
161
+ }
162
+ }
113
163
  /**
114
164
  * No credential was provided but payment is required.
115
165
  */
package/dist/Method.d.ts CHANGED
@@ -127,6 +127,14 @@ export type Validation<method extends Method = Method, details = unknown> = Read
127
127
  intent: method['intent'];
128
128
  /** The validated payment method, repeated for convenient dispatch. */
129
129
  method: method['name'];
130
+ /**
131
+ * Payer identity established by method-specific validation, when the method
132
+ * can determine one (for example, the Tempo address that sent the payment).
133
+ *
134
+ * Unlike `source`, this is derived from the verified credential payload.
135
+ * Its format is method-specific.
136
+ */
137
+ payer?: string | undefined;
130
138
  /**
131
139
  * The validated method request after the method's schema transforms.
132
140
  *
@@ -191,6 +199,8 @@ export type OnPaymentSuccessFn<method extends Method> = (parameters: {
191
199
  /** Challenge associated with the successful payment. */
192
200
  challenge?: DeepReadonly<Challenge.Challenge<z.output<method['schema']['request']>, method['intent'], method['name']>> | undefined;
193
201
  input?: globalThis.Request;
202
+ /** Payer identity verified by the method, when available. See {@link Validation.payer}. */
203
+ payer?: string | undefined;
194
204
  receipt: DeepReadonly<Receipt.Receipt>;
195
205
  /** Canonical payment request included in the challenge. */
196
206
  request: DeepReadonly<z.output<method['schema']['request']>>;