mppx 0.8.12 → 0.8.14

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 (266) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +16 -1
  3. package/dist/Errors.d.ts +10 -0
  4. package/dist/Errors.d.ts.map +1 -1
  5. package/dist/Errors.js +11 -2
  6. package/dist/Errors.js.map +1 -1
  7. package/dist/Method.d.ts +105 -3
  8. package/dist/Method.d.ts.map +1 -1
  9. package/dist/Method.js +102 -2
  10. package/dist/Method.js.map +1 -1
  11. package/dist/cli/account.d.ts +88 -0
  12. package/dist/cli/account.d.ts.map +1 -1
  13. package/dist/cli/account.js +39 -9
  14. package/dist/cli/account.js.map +1 -1
  15. package/dist/cli/cli.d.ts +2 -1
  16. package/dist/cli/cli.d.ts.map +1 -1
  17. package/dist/cli/cli.js +59 -2
  18. package/dist/cli/cli.js.map +1 -1
  19. package/dist/cli/sessions/Manager.d.ts +31 -0
  20. package/dist/cli/sessions/Manager.d.ts.map +1 -0
  21. package/dist/cli/sessions/Manager.js +56 -0
  22. package/dist/cli/sessions/Manager.js.map +1 -0
  23. package/dist/cli/sessions/commands.d.ts +36 -0
  24. package/dist/cli/sessions/commands.d.ts.map +1 -0
  25. package/dist/cli/sessions/commands.js +407 -0
  26. package/dist/cli/sessions/commands.js.map +1 -0
  27. package/dist/cli/sessions/request.d.ts +35 -0
  28. package/dist/cli/sessions/request.d.ts.map +1 -0
  29. package/dist/cli/sessions/request.js +271 -0
  30. package/dist/cli/sessions/request.js.map +1 -0
  31. package/dist/cli/sessions/store.d.ts +134 -0
  32. package/dist/cli/sessions/store.d.ts.map +1 -0
  33. package/dist/cli/sessions/store.js +723 -0
  34. package/dist/cli/sessions/store.js.map +1 -0
  35. package/dist/cli/utils.d.ts +2 -0
  36. package/dist/cli/utils.d.ts.map +1 -1
  37. package/dist/cli/utils.js +12 -0
  38. package/dist/cli/utils.js.map +1 -1
  39. package/dist/cli/validate/index.d.ts.map +1 -1
  40. package/dist/cli/validate/index.js +12 -6
  41. package/dist/cli/validate/index.js.map +1 -1
  42. package/dist/cli/validate/messages.d.ts +2 -0
  43. package/dist/cli/validate/messages.d.ts.map +1 -0
  44. package/dist/cli/validate/messages.js +8 -0
  45. package/dist/cli/validate/messages.js.map +1 -0
  46. package/dist/client/internal/Fetch.d.ts.map +1 -1
  47. package/dist/client/internal/Fetch.js +12 -2
  48. package/dist/client/internal/Fetch.js.map +1 -1
  49. package/dist/client/internal/MethodChallenge.d.ts +17 -0
  50. package/dist/client/internal/MethodChallenge.d.ts.map +1 -0
  51. package/dist/client/internal/MethodChallenge.js +11 -0
  52. package/dist/client/internal/MethodChallenge.js.map +1 -0
  53. package/dist/client/node.d.ts +29 -0
  54. package/dist/client/node.d.ts.map +1 -0
  55. package/dist/client/node.js +177 -0
  56. package/dist/client/node.js.map +1 -0
  57. package/dist/internal/types.d.ts +24 -0
  58. package/dist/internal/types.d.ts.map +1 -1
  59. package/dist/server/Mppx.d.ts +23 -4
  60. package/dist/server/Mppx.d.ts.map +1 -1
  61. package/dist/server/Mppx.js +91 -82
  62. package/dist/server/Mppx.js.map +1 -1
  63. package/dist/stripe/server/Charge.d.ts +8 -4
  64. package/dist/stripe/server/Charge.d.ts.map +1 -1
  65. package/dist/stripe/server/Charge.js.map +1 -1
  66. package/dist/stripe/server/internal/html.gen.d.ts +1 -1
  67. package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
  68. package/dist/stripe/server/internal/html.gen.js +1 -1
  69. package/dist/stripe/server/internal/html.gen.js.map +1 -1
  70. package/dist/tempo/client/Methods.d.ts +2 -1
  71. package/dist/tempo/client/Methods.d.ts.map +1 -1
  72. package/dist/tempo/internal/fee-payer.d.ts +1 -0
  73. package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
  74. package/dist/tempo/internal/fee-payer.js +7 -2
  75. package/dist/tempo/internal/fee-payer.js.map +1 -1
  76. package/dist/tempo/internal/types.d.ts +10 -1
  77. package/dist/tempo/internal/types.d.ts.map +1 -1
  78. package/dist/tempo/legacy/client/ChannelOps.d.ts.map +1 -1
  79. package/dist/tempo/legacy/client/ChannelOps.js +2 -1
  80. package/dist/tempo/legacy/client/ChannelOps.js.map +1 -1
  81. package/dist/tempo/legacy/client/Session.d.ts +2 -2
  82. package/dist/tempo/legacy/server/Session.d.ts +3 -3
  83. package/dist/tempo/legacy/server/Session.d.ts.map +1 -1
  84. package/dist/tempo/legacy/server/Session.js.map +1 -1
  85. package/dist/tempo/server/Charge.d.ts +40 -78
  86. package/dist/tempo/server/Charge.d.ts.map +1 -1
  87. package/dist/tempo/server/Charge.js +289 -209
  88. package/dist/tempo/server/Charge.js.map +1 -1
  89. package/dist/tempo/server/Methods.d.ts +13 -23
  90. package/dist/tempo/server/Methods.d.ts.map +1 -1
  91. package/dist/tempo/server/Methods.js +6 -2
  92. package/dist/tempo/server/Methods.js.map +1 -1
  93. package/dist/tempo/server/Relay.d.ts +48 -0
  94. package/dist/tempo/server/Relay.d.ts.map +1 -0
  95. package/dist/tempo/server/Relay.js +177 -0
  96. package/dist/tempo/server/Relay.js.map +1 -0
  97. package/dist/tempo/server/SponsorBudget.d.ts +59 -0
  98. package/dist/tempo/server/SponsorBudget.d.ts.map +1 -0
  99. package/dist/tempo/server/SponsorBudget.js +144 -0
  100. package/dist/tempo/server/SponsorBudget.js.map +1 -0
  101. package/dist/tempo/server/Subscription.d.ts +3 -2
  102. package/dist/tempo/server/Subscription.d.ts.map +1 -1
  103. package/dist/tempo/server/Subscription.js.map +1 -1
  104. package/dist/tempo/server/index.d.ts +1 -1
  105. package/dist/tempo/server/index.d.ts.map +1 -1
  106. package/dist/tempo/server/index.js.map +1 -1
  107. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  108. package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
  109. package/dist/tempo/server/internal/html.gen.js +1 -1
  110. package/dist/tempo/server/internal/html.gen.js.map +1 -1
  111. package/dist/tempo/session/Snapshot.d.ts +9 -0
  112. package/dist/tempo/session/Snapshot.d.ts.map +1 -1
  113. package/dist/tempo/session/Snapshot.js +6 -0
  114. package/dist/tempo/session/Snapshot.js.map +1 -1
  115. package/dist/tempo/session/client/ChannelOps.d.ts +7 -1
  116. package/dist/tempo/session/client/ChannelOps.d.ts.map +1 -1
  117. package/dist/tempo/session/client/ChannelOps.js +17 -3
  118. package/dist/tempo/session/client/ChannelOps.js.map +1 -1
  119. package/dist/tempo/session/client/CredentialState.d.ts +26 -2
  120. package/dist/tempo/session/client/CredentialState.d.ts.map +1 -1
  121. package/dist/tempo/session/client/CredentialState.js +99 -16
  122. package/dist/tempo/session/client/CredentialState.js.map +1 -1
  123. package/dist/tempo/session/client/Runtime.d.ts +8 -0
  124. package/dist/tempo/session/client/Runtime.d.ts.map +1 -1
  125. package/dist/tempo/session/client/Runtime.js +17 -0
  126. package/dist/tempo/session/client/Runtime.js.map +1 -1
  127. package/dist/tempo/session/client/Session.d.ts +16 -6
  128. package/dist/tempo/session/client/Session.d.ts.map +1 -1
  129. package/dist/tempo/session/client/Session.js +93 -36
  130. package/dist/tempo/session/client/Session.js.map +1 -1
  131. package/dist/tempo/session/client/SessionManager.d.ts +10 -3
  132. package/dist/tempo/session/client/SessionManager.d.ts.map +1 -1
  133. package/dist/tempo/session/client/SessionManager.js +176 -50
  134. package/dist/tempo/session/client/SessionManager.js.map +1 -1
  135. package/dist/tempo/session/client/Transports.d.ts +18 -7
  136. package/dist/tempo/session/client/Transports.d.ts.map +1 -1
  137. package/dist/tempo/session/client/Transports.js +81 -33
  138. package/dist/tempo/session/client/Transports.js.map +1 -1
  139. package/dist/tempo/session/client/internal/SessionManager.d.ts +19 -0
  140. package/dist/tempo/session/client/internal/SessionManager.d.ts.map +1 -0
  141. package/dist/tempo/session/client/internal/SessionManager.js +13 -0
  142. package/dist/tempo/session/client/internal/SessionManager.js.map +1 -0
  143. package/dist/tempo/session/precompile/Chain.d.ts.map +1 -1
  144. package/dist/tempo/session/precompile/Chain.js +109 -12
  145. package/dist/tempo/session/precompile/Chain.js.map +1 -1
  146. package/dist/tempo/session/precompile/Channel.d.ts +2 -0
  147. package/dist/tempo/session/precompile/Channel.d.ts.map +1 -1
  148. package/dist/tempo/session/precompile/Channel.js +4 -0
  149. package/dist/tempo/session/precompile/Channel.js.map +1 -1
  150. package/dist/tempo/session/server/CredentialVerification.d.ts +3 -0
  151. package/dist/tempo/session/server/CredentialVerification.d.ts.map +1 -1
  152. package/dist/tempo/session/server/CredentialVerification.js +14 -0
  153. package/dist/tempo/session/server/CredentialVerification.js.map +1 -1
  154. package/dist/tempo/session/server/MeteredStream.d.ts +7 -1
  155. package/dist/tempo/session/server/MeteredStream.d.ts.map +1 -1
  156. package/dist/tempo/session/server/MeteredStream.js +5 -4
  157. package/dist/tempo/session/server/MeteredStream.js.map +1 -1
  158. package/dist/tempo/session/server/RequestState.d.ts +5 -1
  159. package/dist/tempo/session/server/RequestState.d.ts.map +1 -1
  160. package/dist/tempo/session/server/RequestState.js +13 -5
  161. package/dist/tempo/session/server/RequestState.js.map +1 -1
  162. package/dist/tempo/session/server/Session.d.ts +21 -6
  163. package/dist/tempo/session/server/Session.d.ts.map +1 -1
  164. package/dist/tempo/session/server/Session.js +4 -1
  165. package/dist/tempo/session/server/Session.js.map +1 -1
  166. package/dist/tempo/session/server/Settlement.d.ts +20 -0
  167. package/dist/tempo/session/server/Settlement.d.ts.map +1 -1
  168. package/dist/tempo/session/server/Settlement.js +20 -0
  169. package/dist/tempo/session/server/Settlement.js.map +1 -1
  170. package/dist/tempo/session/server/Ws.d.ts +2 -0
  171. package/dist/tempo/session/server/Ws.d.ts.map +1 -1
  172. package/dist/tempo/session/server/Ws.js.map +1 -1
  173. package/dist/tempo/session/server/index.d.ts +1 -1
  174. package/dist/tempo/session/server/index.d.ts.map +1 -1
  175. package/dist/tempo/subscription/KeyAuthorization.d.ts +21 -21
  176. package/dist/validation/core.d.ts.map +1 -1
  177. package/dist/validation/core.js +6 -2
  178. package/dist/validation/core.js.map +1 -1
  179. package/dist/viem/Client.d.ts.map +1 -1
  180. package/dist/viem/Client.js +24 -17
  181. package/dist/viem/Client.js.map +1 -1
  182. package/package.json +6 -1
  183. package/src/Errors.test.ts +23 -0
  184. package/src/Errors.ts +21 -2
  185. package/src/Method.test.ts +102 -1
  186. package/src/Method.ts +241 -5
  187. package/src/cli/account.ts +45 -10
  188. package/src/cli/cli.test.ts +165 -70
  189. package/src/cli/cli.ts +67 -2
  190. package/src/cli/mcp.test.ts +11 -0
  191. package/src/cli/sessions/Manager.test.ts +249 -0
  192. package/src/cli/sessions/Manager.ts +93 -0
  193. package/src/cli/sessions/commands.ts +444 -0
  194. package/src/cli/sessions/request.test.ts +51 -0
  195. package/src/cli/sessions/request.ts +353 -0
  196. package/src/cli/sessions/store.test.ts +581 -0
  197. package/src/cli/sessions/store.ts +940 -0
  198. package/src/cli/utils.test.ts +23 -0
  199. package/src/cli/utils.ts +10 -0
  200. package/src/cli/validate/index.ts +12 -14
  201. package/src/cli/validate/messages.ts +7 -0
  202. package/src/cli/validate.test.ts +38 -5
  203. package/src/client/Mppx.test-d.ts +3 -3
  204. package/src/client/internal/Fetch.ts +16 -7
  205. package/src/client/internal/MethodChallenge.ts +30 -0
  206. package/src/client/node.test.ts +115 -0
  207. package/src/client/node.ts +247 -0
  208. package/src/internal/types.test-d.ts +21 -0
  209. package/src/internal/types.ts +33 -0
  210. package/src/server/Mppx.test-d.ts +44 -0
  211. package/src/server/Mppx.test.ts +205 -0
  212. package/src/server/Mppx.ts +167 -103
  213. package/src/stripe/Methods.test.ts +10 -0
  214. package/src/stripe/server/Charge.test-d.ts +66 -0
  215. package/src/stripe/server/Charge.ts +8 -5
  216. package/src/stripe/server/internal/html.gen.ts +1 -1
  217. package/src/tempo/PublicExports.test-d.ts +20 -0
  218. package/src/tempo/internal/fee-payer.ts +7 -3
  219. package/src/tempo/internal/types.ts +13 -2
  220. package/src/tempo/legacy/client/ChannelOps.test.ts +7 -0
  221. package/src/tempo/legacy/client/ChannelOps.ts +2 -1
  222. package/src/tempo/legacy/server/Defaults.test-d.ts +9 -0
  223. package/src/tempo/legacy/server/Session.ts +6 -5
  224. package/src/tempo/server/Charge.test.ts +337 -63
  225. package/src/tempo/server/Charge.ts +440 -270
  226. package/src/tempo/server/Methods.ts +16 -4
  227. package/src/tempo/server/Relay.test.ts +523 -0
  228. package/src/tempo/server/Relay.ts +288 -0
  229. package/src/tempo/server/SponsorBudget.test.ts +125 -0
  230. package/src/tempo/server/SponsorBudget.ts +213 -0
  231. package/src/tempo/server/Subscription.ts +6 -4
  232. package/src/tempo/server/index.ts +5 -1
  233. package/src/tempo/server/internal/html.gen.ts +1 -1
  234. package/src/tempo/session/README.md +201 -0
  235. package/src/tempo/session/Snapshot.ts +21 -0
  236. package/src/tempo/session/client/ChannelOps.test.ts +55 -0
  237. package/src/tempo/session/client/ChannelOps.ts +21 -3
  238. package/src/tempo/session/client/CredentialState.test.ts +2 -2
  239. package/src/tempo/session/client/CredentialState.ts +159 -16
  240. package/src/tempo/session/client/Runtime.test.ts +12 -0
  241. package/src/tempo/session/client/Runtime.ts +22 -0
  242. package/src/tempo/session/client/Session.test.ts +238 -5
  243. package/src/tempo/session/client/Session.ts +130 -41
  244. package/src/tempo/session/client/SessionManager.test.ts +239 -8
  245. package/src/tempo/session/client/SessionManager.ts +204 -54
  246. package/src/tempo/session/client/Transports.test.ts +181 -8
  247. package/src/tempo/session/client/Transports.ts +127 -45
  248. package/src/tempo/session/client/internal/SessionManager.ts +36 -0
  249. package/src/tempo/session/precompile/Chain.integration.test.ts +19 -1
  250. package/src/tempo/session/precompile/Chain.test.ts +223 -10
  251. package/src/tempo/session/precompile/Chain.ts +119 -12
  252. package/src/tempo/session/precompile/Channel.test.ts +9 -0
  253. package/src/tempo/session/precompile/Channel.ts +5 -0
  254. package/src/tempo/session/server/CredentialVerification.ts +18 -1
  255. package/src/tempo/session/server/MeteredStream.ts +12 -5
  256. package/src/tempo/session/server/RequestState.test.ts +47 -4
  257. package/src/tempo/session/server/RequestState.ts +15 -8
  258. package/src/tempo/session/server/Session.test.ts +461 -0
  259. package/src/tempo/session/server/Session.ts +31 -8
  260. package/src/tempo/session/server/Settlement.ts +45 -0
  261. package/src/tempo/session/server/Ws.test.ts +113 -0
  262. package/src/tempo/session/server/Ws.ts +2 -0
  263. package/src/tempo/session/server/index.ts +2 -0
  264. package/src/validation/core.ts +7 -1
  265. package/src/viem/Client.test.ts +44 -1
  266. package/src/viem/Client.ts +31 -17
@@ -277,6 +277,39 @@ export type ExactPartial<type> = {
277
277
  [key in keyof type]?: type[key] | undefined
278
278
  }
279
279
 
280
+ /**
281
+ * Keys whose values are known to be present and defined.
282
+ *
283
+ * This is useful when a factory accepts partial configuration and its return
284
+ * type needs to distinguish values supplied as defaults from fields that
285
+ * callers must still provide later. Optional and `undefined`-able properties
286
+ * are excluded because their runtime values cannot be assumed to be defaults.
287
+ *
288
+ * @internal
289
+ */
290
+ export type DefinedKeys<type> = {
291
+ [key in keyof type]-?: {} extends Pick<type, key>
292
+ ? never
293
+ : undefined extends type[key]
294
+ ? never
295
+ : key
296
+ }[keyof type]
297
+
298
+ /**
299
+ * Request defaults known to have been configured by a factory caller.
300
+ *
301
+ * Optional and `undefined`-able parameters are omitted: their runtime values
302
+ * are not guaranteed to be usable defaults for a subsequently returned
303
+ * handler. Factories can intersect this type with values they resolve
304
+ * unconditionally at runtime.
305
+ *
306
+ * @internal
307
+ */
308
+ export type ConfiguredDefaults<parameters, defaults> = Pick<
309
+ parameters,
310
+ Extract<DefinedKeys<parameters>, keyof defaults>
311
+ >
312
+
280
313
  /** @internal */
281
314
  export type ExactRequired<type> = {
282
315
  [key in keyof type]-?: Exclude<type[key], undefined>
@@ -223,6 +223,8 @@ describe('Mppx type tests', () => {
223
223
  const mppx = Mppx.create({ methods: [alphaMethod], realm, secretKey })
224
224
 
225
225
  expectTypeOf(mppx.verifyCredential).toBeFunction()
226
+ expectTypeOf(mppx.broadcastCredential).toBeFunction()
227
+ expectTypeOf(mppx.validateCredential).toBeFunction()
226
228
  })
227
229
 
228
230
  test('server events receive typed method context', () => {
@@ -313,4 +315,46 @@ describe('Mppx type tests', () => {
313
315
  }),
314
316
  ).toBeFunction()
315
317
  })
318
+
319
+ test('tempo factories expose resolved currency and decimals as defaults', () => {
320
+ const charge = Mppx.create({ methods: [tempo.charge()], realm, secretKey })
321
+ expectTypeOf(charge.tempo.charge({ amount: '1' })).toBeFunction()
322
+ // @ts-expect-error amount is not supplied by Tempo's runtime defaults.
323
+ void charge.tempo.charge({})
324
+
325
+ const subscription = Mppx.create({
326
+ methods: [
327
+ tempo.subscription({
328
+ recipient: '0x1234567890abcdef1234567890abcdef12345678',
329
+ resolve: async () => null,
330
+ }),
331
+ ],
332
+ realm,
333
+ secretKey,
334
+ })
335
+ expectTypeOf(
336
+ subscription.tempo.subscription({
337
+ amount: '1',
338
+ periodCount: 1n,
339
+ periodUnit: 'day',
340
+ subscriptionExpires: new Date('2026-01-01T00:00:00Z'),
341
+ }),
342
+ ).toBeFunction()
343
+
344
+ const session = Mppx.create({ methods: [tempo.session()], realm, secretKey })
345
+ expectTypeOf(session.tempo.session({ amount: '1', unitType: 'request' })).toBeFunction()
346
+
347
+ const common = Mppx.create({
348
+ methods: [
349
+ tempo.common({
350
+ amount: '1',
351
+ recipient: '0x1234567890abcdef1234567890abcdef12345678',
352
+ }),
353
+ ],
354
+ realm,
355
+ secretKey,
356
+ })
357
+ expectTypeOf(common.tempo.charge({})).toBeFunction()
358
+ expectTypeOf(common.tempo.session({ unitType: 'request' })).toBeFunction()
359
+ })
316
360
  })
@@ -4872,6 +4872,211 @@ describe('verifyCredential', () => {
4872
4872
  expect(verifyArgs).toBeDefined()
4873
4873
  })
4874
4874
 
4875
+ test('validateCredential uses pure method validation without broadcast', async () => {
4876
+ const calls: string[] = []
4877
+ const splitServer = Method.toServer(mockCharge, {
4878
+ async validate({ credential, request }) {
4879
+ calls.push('validate')
4880
+ return {
4881
+ challenge: credential.challenge,
4882
+ credential,
4883
+ details: { token: credential.payload.token },
4884
+ intent: 'charge',
4885
+ method: 'alpha',
4886
+ request,
4887
+ source: credential.source,
4888
+ }
4889
+ },
4890
+ async broadcast() {
4891
+ calls.push('broadcast')
4892
+ return mockReceipt('broadcast')
4893
+ },
4894
+ })
4895
+ const mppx = Mppx.create({ methods: [splitServer], realm, secretKey })
4896
+ const challenge = await mppx.challenge.alpha.charge(challengeOpts)
4897
+ const credential = Credential.from({ challenge, payload: { token: 'valid' } })
4898
+
4899
+ const validation = await mppx.validateCredential(credential)
4900
+
4901
+ expect(validation.details).toEqual({ token: 'valid' })
4902
+ expect(calls).toEqual(['validate'])
4903
+ })
4904
+
4905
+ test('broadcastCredential revalidates and uses method broadcast', async () => {
4906
+ const calls: string[] = []
4907
+ const splitServer = Method.toServer(mockCharge, {
4908
+ async validate({ credential, request }) {
4909
+ calls.push('validate')
4910
+ return {
4911
+ challenge: credential.challenge,
4912
+ credential,
4913
+ details: {},
4914
+ intent: 'charge',
4915
+ method: 'alpha',
4916
+ request,
4917
+ source: credential.source,
4918
+ }
4919
+ },
4920
+ async broadcast() {
4921
+ calls.push('broadcast')
4922
+ return mockReceipt('broadcast')
4923
+ },
4924
+ })
4925
+ const mppx = Mppx.create({ methods: [splitServer], realm, secretKey })
4926
+ const challenge = await mppx.challenge.alpha.charge(challengeOpts)
4927
+ const credential = Credential.from({ challenge, payload: { token: 'valid' } })
4928
+
4929
+ const receipt = await mppx.broadcastCredential(credential)
4930
+
4931
+ expect(receipt.method).toBe('broadcast')
4932
+ expect(calls).toEqual(['validate', 'broadcast'])
4933
+ })
4934
+
4935
+ test('verifyCredential remains a legacy alias for broadcast', async () => {
4936
+ const calls: string[] = []
4937
+ const splitServer = Method.toServer(mockCharge, {
4938
+ async validate({ credential, request }) {
4939
+ calls.push('validate')
4940
+ return {
4941
+ challenge: credential.challenge,
4942
+ credential,
4943
+ details: {},
4944
+ intent: 'charge',
4945
+ method: 'alpha',
4946
+ request,
4947
+ source: credential.source,
4948
+ }
4949
+ },
4950
+ async broadcast() {
4951
+ calls.push('broadcast')
4952
+ return mockReceipt('broadcast')
4953
+ },
4954
+ })
4955
+ const mppx = Mppx.create({ methods: [splitServer], realm, secretKey })
4956
+ const challenge = await mppx.challenge.alpha.charge(challengeOpts)
4957
+ const credential = Credential.from({ challenge, payload: { token: 'valid' } })
4958
+
4959
+ const receipt = await mppx.verifyCredential(credential)
4960
+
4961
+ expect(receipt.method).toBe('broadcast')
4962
+ expect(calls).toEqual(['validate', 'broadcast'])
4963
+ })
4964
+
4965
+ test('validateCredential rejects legacy-only methods without emitting payment failure', async () => {
4966
+ const events: string[] = []
4967
+ const mppx = Mppx.create({ methods: [alphaChargeServer], realm, secretKey })
4968
+ mppx.onPaymentFailed((context) => {
4969
+ events.push(context.error.name)
4970
+ })
4971
+ const challenge = await mppx.challenge.alpha.charge(challengeOpts)
4972
+ const credential = Credential.from({ challenge, payload: { token: 'valid' } })
4973
+
4974
+ await expect(mppx.validateCredential(credential)).rejects.toThrow(
4975
+ 'does not support non-mutating credential validation',
4976
+ )
4977
+
4978
+ expect(events).toEqual([])
4979
+ })
4980
+
4981
+ test('validateCredential enforces supplied route requirements', async () => {
4982
+ const splitServer = Method.toServer(mockCharge, {
4983
+ async validate({ credential, request }) {
4984
+ return {
4985
+ challenge: credential.challenge,
4986
+ credential,
4987
+ details: { amount: request.amount },
4988
+ intent: 'charge',
4989
+ method: 'alpha',
4990
+ request,
4991
+ source: credential.source,
4992
+ }
4993
+ },
4994
+ async broadcast() {
4995
+ return mockReceipt('broadcast')
4996
+ },
4997
+ })
4998
+ const mppx = Mppx.create({ methods: [splitServer], realm, secretKey })
4999
+ const challenge = await mppx.challenge.alpha.charge(challengeOpts)
5000
+ const credential = Credential.from({ challenge, payload: { token: 'valid' } })
5001
+
5002
+ const validation = await mppx.validateCredential(credential, { request: challengeOpts })
5003
+ expect(validation.details).toEqual({ amount: '1000' })
5004
+
5005
+ await expect(
5006
+ mppx.validateCredential(credential, {
5007
+ request: {
5008
+ ...challengeOpts,
5009
+ amount: '2000',
5010
+ },
5011
+ }),
5012
+ ).rejects.toThrow('credential amount does not match this route')
5013
+ })
5014
+
5015
+ test('broadcastCredential emits payment failure when split validation fails', async () => {
5016
+ const calls: string[] = []
5017
+ const events: string[] = []
5018
+ const splitServer = Method.toServer(mockCharge, {
5019
+ async validate() {
5020
+ calls.push('validate')
5021
+ throw new Errors.VerificationFailedError({ reason: 'risk denied' })
5022
+ },
5023
+ async broadcast() {
5024
+ calls.push('broadcast')
5025
+ return mockReceipt('broadcast')
5026
+ },
5027
+ })
5028
+ const mppx = Mppx.create({ methods: [splitServer], realm, secretKey })
5029
+ mppx.onPaymentFailed((context) => {
5030
+ events.push(context.error.name)
5031
+ })
5032
+ const challenge = await mppx.challenge.alpha.charge(challengeOpts)
5033
+ const credential = Credential.from({ challenge, payload: { token: 'valid' } })
5034
+
5035
+ await expect(mppx.broadcastCredential(credential)).rejects.toThrow('risk denied')
5036
+
5037
+ expect(calls).toEqual(['validate'])
5038
+ expect(events).toEqual(['VerificationFailedError'])
5039
+ })
5040
+
5041
+ test('route handlers revalidate before broadcast for split methods', async () => {
5042
+ const calls: string[] = []
5043
+ const splitServer = Method.toServer(mockCharge, {
5044
+ async validate({ credential, request }) {
5045
+ calls.push('validate')
5046
+ return {
5047
+ challenge: credential.challenge,
5048
+ credential,
5049
+ details: {},
5050
+ intent: 'charge',
5051
+ method: 'alpha',
5052
+ request,
5053
+ source: credential.source,
5054
+ }
5055
+ },
5056
+ async broadcast() {
5057
+ calls.push('broadcast')
5058
+ return mockReceipt('broadcast')
5059
+ },
5060
+ })
5061
+ const mppx = Mppx.create({ methods: [splitServer], realm, secretKey })
5062
+ const firstResult = await mppx.charge(challengeOpts)(
5063
+ new Request('https://api.example.com/resource'),
5064
+ )
5065
+ expect(firstResult.status).toBe(402)
5066
+ if (firstResult.status !== 402) throw new Error()
5067
+
5068
+ const challenge = Challenge.fromResponse(firstResult.challenge)
5069
+ const credential = Credential.from({ challenge, payload: { token: 'valid' } })
5070
+ const result = await mppx.charge(challengeOpts)(
5071
+ new Request('https://api.example.com/resource', {
5072
+ headers: { Authorization: Credential.serialize(credential) },
5073
+ }),
5074
+ )
5075
+
5076
+ expect(result.status).toBe(200)
5077
+ expect(calls).toEqual(['validate', 'broadcast'])
5078
+ })
5079
+
4875
5080
  test('verifies a parsed Credential object (charge)', async () => {
4876
5081
  verifyArgs = undefined
4877
5082
  const mppx = Mppx.create({