mppx 0.14.1 → 0.16.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 (105) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +1 -1
  3. package/dist/Expires.js +1 -1
  4. package/dist/Store.d.ts +2 -2
  5. package/dist/Store.js +13 -3
  6. package/dist/cli/cli.js +137 -131
  7. package/dist/cli/plugins/index.js +11 -11
  8. package/dist/cli/validate/payment.d.ts +12 -0
  9. package/dist/cli/validate/payment.js +113 -14
  10. package/dist/client/Mppx.d.ts +13 -3
  11. package/dist/client/Mppx.js +47 -22
  12. package/dist/client/internal/Fetch.d.ts +11 -0
  13. package/dist/client/internal/Fetch.js +46 -8
  14. package/dist/client/internal/ProofRealm.d.ts +21 -0
  15. package/dist/client/internal/ProofRealm.js +55 -0
  16. package/dist/{attestation → extensions/identity}/Constants.d.ts +2 -2
  17. package/dist/{attestation → extensions/identity}/Constants.js +2 -2
  18. package/dist/extensions/identity/Types.d.ts +21 -0
  19. package/dist/extensions/identity/index.d.ts +7 -0
  20. package/dist/extensions/identity/index.js +6 -0
  21. package/dist/{attestation → extensions/identity}/internal/HttpMessageSignature.d.ts +3 -3
  22. package/dist/{attestation → extensions/identity}/internal/HttpMessageSignature.js +12 -10
  23. package/dist/extensions/identity/internal/SigningContext.d.ts +6 -0
  24. package/dist/{attestation → extensions/identity}/internal/SigningContext.js +3 -3
  25. package/dist/extensions/identity/internal/Verification.d.ts +9 -0
  26. package/dist/extensions/identity/internal/Verification.js +10 -0
  27. package/dist/{tap → extensions/identity/tap}/Constants.js +1 -1
  28. package/dist/extensions/identity/tap/client/index.d.ts +32 -0
  29. package/dist/{tap → extensions/identity/tap}/client/index.js +13 -9
  30. package/dist/extensions/identity/tap/server/index.d.ts +27 -0
  31. package/dist/{tap → extensions/identity/tap}/server/index.js +12 -4
  32. package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/Constants.js +1 -1
  33. package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/client/index.d.ts +6 -5
  34. package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/client/index.js +5 -5
  35. package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/server/index.d.ts +6 -5
  36. package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/server/index.js +6 -4
  37. package/dist/identity/Client.d.ts +34 -0
  38. package/dist/identity/Client.js +133 -0
  39. package/dist/identity/Server.d.ts +46 -0
  40. package/dist/identity/Server.js +145 -0
  41. package/dist/identity/Types.d.ts +90 -0
  42. package/dist/identity/Types.js +2 -0
  43. package/dist/{attestation → identity}/index.d.ts +2 -2
  44. package/dist/{attestation → identity}/index.js +1 -2
  45. package/dist/identity/internal/SigningContext.d.ts +6 -0
  46. package/dist/identity/internal/SigningContext.js +11 -0
  47. package/dist/integrations/x402/Evm.d.ts +2 -0
  48. package/dist/internal/version.d.ts +2 -2
  49. package/dist/internal/version.js +1 -1
  50. package/dist/mcp/client/McpClient.d.ts +5 -0
  51. package/dist/mcp/client/McpClient.js +2 -0
  52. package/dist/middlewares/express.js +18 -6
  53. package/dist/proxy/Proxy.js +10 -5
  54. package/dist/proxy/Service.d.ts +42 -4
  55. package/dist/proxy/Service.js +21 -5
  56. package/dist/server/Mppx.d.ts +32 -8
  57. package/dist/server/Mppx.js +144 -54
  58. package/dist/stripe/server/internal/html.gen.js +1 -1
  59. package/dist/tempo/Methods.d.ts +5 -0
  60. package/dist/tempo/Methods.js +1 -0
  61. package/dist/tempo/client/Subscription.d.ts +5 -0
  62. package/dist/tempo/internal/charge.js +2 -1
  63. package/dist/tempo/internal/fee-payer.d.ts +0 -2
  64. package/dist/tempo/internal/fee-payer.js +0 -4
  65. package/dist/tempo/server/Charge.js +4 -0
  66. package/dist/tempo/server/Methods.d.ts +26 -0
  67. package/dist/tempo/server/Subscription.d.ts +5 -0
  68. package/dist/tempo/server/Subscription.js +8 -5
  69. package/dist/tempo/server/internal/html.gen.js +1 -1
  70. package/dist/tempo/session/client/Runtime.d.ts +2 -0
  71. package/dist/tempo/session/client/Session.js +1 -1
  72. package/dist/tempo/session/client/SessionManager.d.ts +6 -0
  73. package/dist/tempo/session/client/SessionManager.js +14 -7
  74. package/dist/tempo/session/client/Transports.d.ts +12 -3
  75. package/dist/tempo/session/client/Transports.js +37 -11
  76. package/dist/tempo/session/precompile/Chain.js +0 -3
  77. package/dist/tempo/session/server/Ws.d.ts +5 -2
  78. package/dist/tempo/session/server/Ws.js +11 -2
  79. package/dist/tempo/subscription/Store.d.ts +7 -1
  80. package/dist/tempo/subscription/Store.js +66 -13
  81. package/dist/tempo/subscription/Types.d.ts +2 -0
  82. package/package.json +15 -11
  83. package/dist/attestation/Client.d.ts +0 -17
  84. package/dist/attestation/Client.js +0 -28
  85. package/dist/attestation/Server.d.ts +0 -4
  86. package/dist/attestation/Server.js +0 -8
  87. package/dist/attestation/Types.d.ts +0 -76
  88. package/dist/attestation/internal/SigningContext.d.ts +0 -4
  89. package/dist/tap/client/index.d.ts +0 -26
  90. package/dist/tap/server/index.d.ts +0 -20
  91. /package/dist/{attestation → extensions/identity}/Types.js +0 -0
  92. /package/dist/{tap → extensions/identity/tap}/Constants.d.ts +0 -0
  93. /package/dist/{tap → extensions/identity/tap}/Types.d.ts +0 -0
  94. /package/dist/{tap → extensions/identity/tap}/Types.js +0 -0
  95. /package/dist/{tap → extensions/identity/tap}/index.d.ts +0 -0
  96. /package/dist/{tap → extensions/identity/tap}/index.js +0 -0
  97. /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/Constants.d.ts +0 -0
  98. /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/Types.d.ts +0 -0
  99. /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/Types.js +0 -0
  100. /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/index.d.ts +0 -0
  101. /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/index.js +0 -0
  102. /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/internal/JwkThumbprint.d.ts +0 -0
  103. /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/internal/JwkThumbprint.js +0 -0
  104. /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/internal/SignatureAgent.d.ts +0 -0
  105. /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/internal/SignatureAgent.js +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,47 @@
1
1
  # mppx
2
2
 
3
+ ## 0.16.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 793aa2b: Replaced request attestation with per-route identity. Breaking changes:
8
+
9
+ - `mppx/attestation` became `mppx/identity`. Server `Mppx.create({ attestation })` was removed (passing it now throws); routes take `identity: [verifier, ...]` (all required), for example `mppx.charge({ amount, identity: [tap] })`, and the result exposes `result.identity.get(verifier)`.
10
+ - Added `Identity.anyOf(...)` to accept one of several protocols. Every member runs, so each presented proof is consumed.
11
+ - Verifiers return `missing`, `invalid` or `valid` (`unverified` became `invalid` with code 403), may set their own status `code` and `headers`, and declare a `stage`: `request` (checked before any 402) or `payment` (adds headers to the 402 and is checked against the answered challenge and credential before settlement). Identity rejections are returned on the non-200 branch as a 401/403 response.
12
+ - A composed route can include at most one offer with payment-stage identity. Payment-stage identity is not supported on methods with a preflight hook.
13
+ - Client `attestation` signer maps became `identity: [prover, ...]`. Provers have a required origin `scope`, receive the payment challenge in `prove(request, context)`, and can prepare proofs or request one resend with `retry(response, context)`. Requests that carry proofs do not follow redirects.
14
+ - TAP and Web Bot Auth moved to `mppx/extensions/identity/tap` and `mppx/extensions/identity/web-bot-auth`; `Client.signer` became `Client.prover`.
15
+
16
+ ### Patch Changes
17
+
18
+ - e555544: Fixed client payment events masking original errors when a response body had already been consumed or locked.
19
+ - 81a95e1: Fixed multi-method intent handlers rejecting every route option but `identity` when the methods' options can't be inferred, such as `mppx.charge({ amount })` with `stripe.create(...).defaultMethods()`.
20
+ - c31d932: Added optional `summary` and `requestBody` to proxy endpoint definitions, emitted in the proxy's generated `/openapi.json`. Free routes can carry them as `{ free: true, summary, requestBody }`. An endpoint that is neither a payment handler, `true` nor `{ free: true }` (such as `{ pay: undefined }`) now fails closed with an error instead of being proxied.
21
+ - dd2c3e3: Refused to sign a zero-amount Tempo proof when the Challenge realm differs from the hostname of the server that sent it, so another origin can't relay a Challenge and replay the proof at its realm. To allow a server whose realm isn't its hostname, pin the realm to its origin with `trustedTempoProofRealms` on `Mppx.create` and `Fetch.from`, such as `{ 'https://api.example.com': 'MPP Payment' }`. Without an origin (`McpClient.wrap`, or `Mppx.create` with `Transport.mcp()`), these proofs are refused unless `trustedTempoProofRealms` lists the realm. The `mppx` CLI and `sessionManager` bootstrap apply the same check, and `sessionManager` takes `trustedTempoProofRealms` too. This matches mpp-rs, mpp-go and pympp.
22
+ - d78de2c: Fixed missing subscription access-key address indexes when reusing existing keys.
23
+ - 909f3db: Fixed Tempo session top-up, voucher retry, close and bootstrap requests to send the credential in the header the challenge selects (such as `Payment-Authorization` with `requiresAuth`) instead of always using `Authorization`. These requests keep the paid request's own `Authorization` header and no longer follow redirects, so the credential can't reach another origin.
24
+ - 464df9a: Fixed Tempo charge verification rejecting split amounts written with leading zeros by comparing them as integers.
25
+ - 9008513: Removed the `allowKeyAuthorization` fee-payer policy: sponsored transactions may now carry a `keyAuthorization`, so an access key can open a fee-sponsored session (or pay a sponsored charge) in the transaction that installs it. Sponsor exposure stays bounded by `maxGas` and `maxTotalFee`.
26
+ - 4cfd556: Fixed `mppx validate` failing Tempo session responses that stream over SSE: when the receipt arrives in the event stream instead of the `Payment-Receipt` header, validation checks the `payment-receipt` event in the stream instead of requiring the header, and fails when the stream has none.
27
+ - 510d1ed: Fixed `mppx validate` failing valid SSE sessions: the in-stream `payment-receipt` event is now read from the server's own stream, which the session client strips, and lone-CR line endings are accepted.
28
+ - e6d8ffc: Fixed `mppx validate` buffering SSE session streams in memory and hanging when the session client cancelled one: in-stream receipts are now read as the client consumes the stream instead of from an unread copy.
29
+ - 464df9a: Raised the zod dependency to ^4.6.7 to pick up the fix for GHSA-mrhx-c7xc-m3w5.
30
+
31
+ ## 0.15.0
32
+
33
+ ### Minor Changes
34
+
35
+ - 4f8790f: Fixed implicit TLS bypass for local hostnames, discarded Express payment responses, expiry-crossing replay claims, standalone realm validation, and WebSocket metering from stale challenge prices. Bounded anonymous subscription challenge keys and retained billing keys only after successful payment submission.
36
+
37
+ Standalone credential APIs now require a configured matching realm or trusted route context. Custom WebSocket routes must return the canonical challenge or set an explicit `amount`. The CLI no longer pays when `--insecure` is enabled. Anonymous subscription setup carries an authenticated encrypted key token in the challenge, using one persistent sealing key instead of per-request pending state. Challenge expiry remains enforced before activation. Previously persisted abandoned keys are not automatically removed.
38
+
39
+ Preserved successful charge receipts when a challenge expired during settlement, preventing a fresh payment challenge after funds moved.
40
+
41
+ Preserved Express JSON replacers, spacing, and escaping before payment response wrapping. Preserved HTTPS hostnames for certificate validation; local HTTPS names use the system resolver, while plain HTTP localhost subdomains retain the loopback shortcut.
42
+
43
+ Selected the key bound to a new subscription challenge before a previously retained billing key, allowing an expired subscription to be activated again for the same payer.
44
+
3
45
  ## 0.14.1
4
46
 
5
47
  ### Patch Changes
package/README.md CHANGED
@@ -71,8 +71,8 @@ Generate `MPP_SECRET_KEY` with at least 32 bytes, for example: `openssl rand -ba
71
71
  ### Client
72
72
 
73
73
  ```ts
74
- import { privateKeyToAccount } from 'viem/accounts'
75
74
  import { Mppx, tempo } from 'mppx/client'
75
+ import { privateKeyToAccount } from 'viem/accounts'
76
76
 
77
77
  Mppx.create({
78
78
  methods: [tempo({ account: privateKeyToAccount('0x...') })],
package/dist/Expires.js CHANGED
@@ -16,7 +16,7 @@ export function assert(expires, challengeId) {
16
16
  ...(challengeId && { id: challengeId }),
17
17
  reason: 'malformed expires timestamp',
18
18
  });
19
- if (new Date(expires) < new Date())
19
+ if (Date.parse(expires) <= Date.now())
20
20
  throw new PaymentExpiredError({ expires });
21
21
  }
22
22
  /** Returns an ISO 8601 datetime string `n` days from now. */
package/dist/Store.d.ts CHANGED
@@ -28,7 +28,7 @@ export type Change<value, result> = {
28
28
  * must be synchronous and free of side effects.
29
29
  */
30
30
  export type Update<itemMap extends StoreItemMap = StoreItemMap> = <key extends keyof itemMap & string, result>(key: key, fn: (current: itemMap[key] | null) => Change<itemMap[key], result>) => Promise<result>;
31
- /** Attempts to atomically record first use of a replay key through its expiry. */
31
+ /** Attempts to atomically record first use of a replay key. Must reject expires <= Date.now() inside the atomic operation. */
32
32
  export type TryClaim<itemMap extends StoreItemMap = StoreItemMap> = <key extends keyof itemMap & string>(key: key, expires: number) => MaybePromise<boolean>;
33
33
  /** Persisted marker used by the {@link tryClaim} fallback. */
34
34
  export type ReplayMarker = {
@@ -68,7 +68,7 @@ export type AtomicStore<itemMap extends StoreItemMap = StoreItemMap> = Store<ite
68
68
  * Attempts to atomically record first use of a replay key through its expiry.
69
69
  *
70
70
  * Returns `true` when this call recorded the key and `false` when it was already
71
- * recorded and unexpired. `expires` is a Unix timestamp in milliseconds.
71
+ * recorded and unexpired, or the proposed expiry has passed. `expires` is a Unix timestamp in milliseconds.
72
72
  * Uses the adapter's optimized primitive when available and otherwise falls
73
73
  * back to {@link AtomicStore.update}.
74
74
  */
package/dist/Store.js CHANGED
@@ -19,14 +19,22 @@ import { Json } from 'ox';
19
19
  * Attempts to atomically record first use of a replay key through its expiry.
20
20
  *
21
21
  * Returns `true` when this call recorded the key and `false` when it was already
22
- * recorded and unexpired. `expires` is a Unix timestamp in milliseconds.
22
+ * recorded and unexpired, or the proposed expiry has passed. `expires` is a Unix timestamp in milliseconds.
23
23
  * Uses the adapter's optimized primitive when available and otherwise falls
24
24
  * back to {@link AtomicStore.update}.
25
25
  */
26
26
  export function tryClaim(store, key, expires) {
27
- if (store.tryClaim)
28
- return store.tryClaim(key, expires);
27
+ if (!Number.isFinite(expires) || expires <= Date.now())
28
+ return false;
29
+ if (store.tryClaim) {
30
+ const result = store.tryClaim(key, expires);
31
+ return typeof result === 'boolean'
32
+ ? result && expires > Date.now()
33
+ : result.then((claimed) => claimed && expires > Date.now());
34
+ }
29
35
  return store.update(key, (current) => {
36
+ if (!Number.isFinite(expires) || expires <= Date.now())
37
+ return { op: 'noop', result: false };
30
38
  if (current !== null && (!isReplayMarker(current) || current.expires > Date.now()))
31
39
  return { op: 'noop', result: false };
32
40
  return {
@@ -144,6 +152,8 @@ export function memory(options) {
144
152
  return change.result;
145
153
  },
146
154
  tryClaim(key, expires) {
155
+ if (!Number.isFinite(expires) || expires <= Date.now())
156
+ return false;
147
157
  const current = store.has(key) ? Json.parse(store.get(key)) : null;
148
158
  if (current !== null && (!isReplayMarker(current) || current.expires > Date.now()))
149
159
  return false;