mppx 0.8.14 → 0.8.17

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 (507) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/dist/Challenge.d.ts.map +1 -1
  3. package/dist/Challenge.js +40 -35
  4. package/dist/Challenge.js.map +1 -1
  5. package/dist/Constants.d.ts +17 -17
  6. package/dist/Constants.d.ts.map +1 -1
  7. package/dist/Credential.d.ts.map +1 -1
  8. package/dist/Errors.d.ts +3 -3
  9. package/dist/Errors.d.ts.map +1 -1
  10. package/dist/Errors.js.map +1 -1
  11. package/dist/Method.d.ts +35 -5
  12. package/dist/Method.d.ts.map +1 -1
  13. package/dist/Method.js +7 -22
  14. package/dist/Method.js.map +1 -1
  15. package/dist/Store.d.ts +19 -0
  16. package/dist/Store.d.ts.map +1 -1
  17. package/dist/Store.js +43 -0
  18. package/dist/Store.js.map +1 -1
  19. package/dist/attestation/Client.d.ts +17 -0
  20. package/dist/attestation/Client.d.ts.map +1 -0
  21. package/dist/attestation/Client.js +28 -0
  22. package/dist/attestation/Client.js.map +1 -0
  23. package/dist/attestation/Constants.d.ts +15 -0
  24. package/dist/attestation/Constants.d.ts.map +1 -0
  25. package/dist/attestation/Constants.js +15 -0
  26. package/dist/attestation/Constants.js.map +1 -0
  27. package/dist/attestation/Server.d.ts +4 -0
  28. package/dist/attestation/Server.d.ts.map +1 -0
  29. package/dist/attestation/Server.js +8 -0
  30. package/dist/attestation/Server.js.map +1 -0
  31. package/dist/attestation/Types.d.ts +76 -0
  32. package/dist/attestation/Types.d.ts.map +1 -0
  33. package/dist/attestation/Types.js +2 -0
  34. package/dist/attestation/Types.js.map +1 -0
  35. package/dist/attestation/index.d.ts +6 -0
  36. package/dist/attestation/index.d.ts.map +1 -0
  37. package/dist/attestation/index.js +6 -0
  38. package/dist/attestation/index.js.map +1 -0
  39. package/dist/attestation/internal/HttpMessageSignature.d.ts +73 -0
  40. package/dist/attestation/internal/HttpMessageSignature.d.ts.map +1 -0
  41. package/dist/attestation/internal/HttpMessageSignature.js +320 -0
  42. package/dist/attestation/internal/HttpMessageSignature.js.map +1 -0
  43. package/dist/attestation/internal/SigningContext.d.ts +4 -0
  44. package/dist/attestation/internal/SigningContext.d.ts.map +1 -0
  45. package/dist/attestation/internal/SigningContext.js +11 -0
  46. package/dist/attestation/internal/SigningContext.js.map +1 -0
  47. package/dist/cli/account.d.ts +14 -14
  48. package/dist/cli/account.d.ts.map +1 -1
  49. package/dist/cli/cli.d.ts +6 -6
  50. package/dist/cli/cli.d.ts.map +1 -1
  51. package/dist/cli/cli.js +1 -0
  52. package/dist/cli/cli.js.map +1 -1
  53. package/dist/cli/plugins/tempo.js.map +1 -1
  54. package/dist/cli/sessions/commands.d.ts +4 -4
  55. package/dist/cli/sessions/commands.d.ts.map +1 -1
  56. package/dist/cli/sessions/commands.js.map +1 -1
  57. package/dist/cli/sessions/store.d.ts.map +1 -1
  58. package/dist/cli/sessions/store.js.map +1 -1
  59. package/dist/cli/utils.d.ts +1 -1
  60. package/dist/cli/utils.d.ts.map +1 -1
  61. package/dist/cli/utils.js.map +1 -1
  62. package/dist/cli/validate/discovery.js.map +1 -1
  63. package/dist/cli/validate/index.d.ts +3 -3
  64. package/dist/cli/validate/index.d.ts.map +1 -1
  65. package/dist/cli/validate/payment.d.ts.map +1 -1
  66. package/dist/cli/validate/payment.js +18 -4
  67. package/dist/cli/validate/payment.js.map +1 -1
  68. package/dist/client/Mppx.d.ts +3 -0
  69. package/dist/client/Mppx.d.ts.map +1 -1
  70. package/dist/client/Mppx.js +13 -5
  71. package/dist/client/Mppx.js.map +1 -1
  72. package/dist/client/Transport.d.ts +3 -5
  73. package/dist/client/Transport.d.ts.map +1 -1
  74. package/dist/client/Transport.js +1 -17
  75. package/dist/client/Transport.js.map +1 -1
  76. package/dist/client/internal/Fetch.d.ts.map +1 -1
  77. package/dist/client/internal/Fetch.js +92 -13
  78. package/dist/client/internal/Fetch.js.map +1 -1
  79. package/dist/client/internal/MethodChallenge.d.ts +2 -0
  80. package/dist/client/internal/MethodChallenge.d.ts.map +1 -1
  81. package/dist/client/internal/MethodChallenge.js +4 -0
  82. package/dist/client/internal/MethodChallenge.js.map +1 -1
  83. package/dist/client/internal/MethodResponse.d.ts +15 -0
  84. package/dist/client/internal/MethodResponse.d.ts.map +1 -1
  85. package/dist/client/internal/MethodResponse.js +10 -0
  86. package/dist/client/internal/MethodResponse.js.map +1 -1
  87. package/dist/client/node.js.map +1 -1
  88. package/dist/discovery/OpenApi.d.ts +3 -5
  89. package/dist/discovery/OpenApi.d.ts.map +1 -1
  90. package/dist/discovery/OpenApi.js +13 -34
  91. package/dist/discovery/OpenApi.js.map +1 -1
  92. package/dist/discovery/internal/Metadata.d.ts +20 -0
  93. package/dist/discovery/internal/Metadata.d.ts.map +1 -0
  94. package/dist/discovery/internal/Metadata.js +36 -0
  95. package/dist/discovery/internal/Metadata.js.map +1 -0
  96. package/dist/evm/Methods.d.ts +11 -11
  97. package/dist/evm/Methods.d.ts.map +1 -1
  98. package/dist/evm/Types.d.ts +17 -17
  99. package/dist/evm/Types.d.ts.map +1 -1
  100. package/dist/evm/client/Charge.d.ts +11 -11
  101. package/dist/evm/client/Methods.d.ts +11 -11
  102. package/dist/evm/server/Charge.d.ts +1 -1
  103. package/dist/evm/server/Charge.d.ts.map +1 -1
  104. package/dist/evm/server/Charge.js +2 -0
  105. package/dist/evm/server/Charge.js.map +1 -1
  106. package/dist/evm/server/Methods.d.ts +11 -11
  107. package/dist/internal/HeaderCodec.d.ts +2 -2
  108. package/dist/internal/HeaderCodec.d.ts.map +1 -1
  109. package/dist/internal/types.d.ts +6 -0
  110. package/dist/internal/types.d.ts.map +1 -1
  111. package/dist/middlewares/elysia.js.map +1 -1
  112. package/dist/middlewares/express.js.map +1 -1
  113. package/dist/middlewares/hono.js.map +1 -1
  114. package/dist/middlewares/nextjs.js.map +1 -1
  115. package/dist/proxy/Proxy.d.ts +2 -0
  116. package/dist/proxy/Proxy.d.ts.map +1 -1
  117. package/dist/proxy/Proxy.js +117 -9
  118. package/dist/proxy/Proxy.js.map +1 -1
  119. package/dist/proxy/Service.d.ts +36 -1
  120. package/dist/proxy/Service.d.ts.map +1 -1
  121. package/dist/proxy/Service.js +9 -8
  122. package/dist/proxy/Service.js.map +1 -1
  123. package/dist/proxy/services/anthropic.d.ts.map +1 -1
  124. package/dist/proxy/services/anthropic.js +1 -0
  125. package/dist/proxy/services/anthropic.js.map +1 -1
  126. package/dist/proxy/services/openai.d.ts.map +1 -1
  127. package/dist/proxy/services/openai.js +1 -0
  128. package/dist/proxy/services/openai.js.map +1 -1
  129. package/dist/proxy/services/stripe.d.ts.map +1 -1
  130. package/dist/proxy/services/stripe.js +1 -0
  131. package/dist/proxy/services/stripe.js.map +1 -1
  132. package/dist/server/Mppx.d.ts +75 -13
  133. package/dist/server/Mppx.d.ts.map +1 -1
  134. package/dist/server/Mppx.js +201 -23
  135. package/dist/server/Mppx.js.map +1 -1
  136. package/dist/server/internal/html/config.d.ts +9 -9
  137. package/dist/server/internal/html/config.d.ts.map +1 -1
  138. package/dist/server/internal/html/constants.d.ts +17 -17
  139. package/dist/server/internal/html/constants.d.ts.map +1 -1
  140. package/dist/stripe/Methods.d.ts +10 -10
  141. package/dist/stripe/Methods.d.ts.map +1 -1
  142. package/dist/stripe/client/Charge.d.ts +10 -10
  143. package/dist/stripe/client/Methods.d.ts +10 -10
  144. package/dist/stripe/internal/constants.d.ts +1 -1
  145. package/dist/stripe/internal/constants.js +1 -1
  146. package/dist/stripe/internal/types.d.ts +1 -0
  147. package/dist/stripe/internal/types.d.ts.map +1 -1
  148. package/dist/stripe/server/Charge.d.ts +11 -11
  149. package/dist/stripe/server/Charge.d.ts.map +1 -1
  150. package/dist/stripe/server/Charge.js +49 -1
  151. package/dist/stripe/server/Charge.js.map +1 -1
  152. package/dist/stripe/server/Methods.d.ts +137 -48
  153. package/dist/stripe/server/Methods.d.ts.map +1 -1
  154. package/dist/stripe/server/Methods.js +259 -4
  155. package/dist/stripe/server/Methods.js.map +1 -1
  156. package/dist/stripe/server/internal/deposit-address.d.ts +13 -0
  157. package/dist/stripe/server/internal/deposit-address.d.ts.map +1 -0
  158. package/dist/stripe/server/internal/deposit-address.js +28 -0
  159. package/dist/stripe/server/internal/deposit-address.js.map +1 -0
  160. package/dist/stripe/server/internal/html.gen.d.ts +1 -1
  161. package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
  162. package/dist/stripe/server/internal/html.gen.js +1 -1
  163. package/dist/stripe/server/internal/html.gen.js.map +1 -1
  164. package/dist/stripe/server/internal/record-payment.d.ts +15 -0
  165. package/dist/stripe/server/internal/record-payment.d.ts.map +1 -0
  166. package/dist/stripe/server/internal/record-payment.js +42 -0
  167. package/dist/stripe/server/internal/record-payment.js.map +1 -0
  168. package/dist/stripe/server/internal/request.d.ts +25 -0
  169. package/dist/stripe/server/internal/request.d.ts.map +1 -0
  170. package/dist/stripe/server/internal/request.js +29 -0
  171. package/dist/stripe/server/internal/request.js.map +1 -0
  172. package/dist/tap/Constants.d.ts +28 -0
  173. package/dist/tap/Constants.d.ts.map +1 -0
  174. package/dist/tap/Constants.js +32 -0
  175. package/dist/tap/Constants.js.map +1 -0
  176. package/dist/tap/Types.d.ts +11 -0
  177. package/dist/tap/Types.d.ts.map +1 -0
  178. package/dist/tap/Types.js +2 -0
  179. package/dist/tap/Types.js.map +1 -0
  180. package/dist/tap/client/index.d.ts +26 -0
  181. package/dist/tap/client/index.d.ts.map +1 -0
  182. package/dist/tap/client/index.js +33 -0
  183. package/dist/tap/client/index.js.map +1 -0
  184. package/dist/tap/index.d.ts +5 -0
  185. package/dist/tap/index.d.ts.map +1 -0
  186. package/dist/tap/index.js +5 -0
  187. package/dist/tap/index.js.map +1 -0
  188. package/dist/tap/server/index.d.ts +20 -0
  189. package/dist/tap/server/index.d.ts.map +1 -0
  190. package/dist/tap/server/index.js +36 -0
  191. package/dist/tap/server/index.js.map +1 -0
  192. package/dist/tempo/Attribution.d.ts +1 -1
  193. package/dist/tempo/Methods.d.ts +60 -60
  194. package/dist/tempo/Methods.d.ts.map +1 -1
  195. package/dist/tempo/Proof.d.ts +16 -16
  196. package/dist/tempo/client/Charge.d.ts +19 -19
  197. package/dist/tempo/client/Charge.d.ts.map +1 -1
  198. package/dist/tempo/client/Charge.js +1 -7
  199. package/dist/tempo/client/Charge.js.map +1 -1
  200. package/dist/tempo/client/Methods.d.ts +38 -38
  201. package/dist/tempo/client/Methods.js.map +1 -1
  202. package/dist/tempo/client/Subscription.d.ts +23 -23
  203. package/dist/tempo/client/Subscription.js.map +1 -1
  204. package/dist/tempo/internal/address.d.ts +2 -1
  205. package/dist/tempo/internal/address.d.ts.map +1 -1
  206. package/dist/tempo/internal/address.js +3 -1
  207. package/dist/tempo/internal/address.js.map +1 -1
  208. package/dist/tempo/internal/auto-swap.d.ts.map +1 -1
  209. package/dist/tempo/internal/auto-swap.js +4 -19
  210. package/dist/tempo/internal/auto-swap.js.map +1 -1
  211. package/dist/tempo/internal/defaults.d.ts +6 -6
  212. package/dist/tempo/internal/defaults.d.ts.map +1 -1
  213. package/dist/tempo/internal/fee-payer.d.ts +12 -12
  214. package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
  215. package/dist/tempo/internal/fee-payer.js.map +1 -1
  216. package/dist/tempo/internal/proof.d.ts +17 -17
  217. package/dist/tempo/internal/proof.d.ts.map +1 -1
  218. package/dist/tempo/legacy/client/Session.d.ts +20 -20
  219. package/dist/tempo/legacy/client/Session.js.map +1 -1
  220. package/dist/tempo/legacy/client/SessionManager.js.map +1 -1
  221. package/dist/tempo/legacy/index.d.ts +1 -3
  222. package/dist/tempo/legacy/index.d.ts.map +1 -1
  223. package/dist/tempo/legacy/index.js +1 -3
  224. package/dist/tempo/legacy/index.js.map +1 -1
  225. package/dist/tempo/legacy/session/escrow.abi.d.ts +374 -374
  226. package/dist/tempo/legacy/session/escrow.abi.d.ts.map +1 -1
  227. package/dist/tempo/server/Charge.d.ts +34 -12
  228. package/dist/tempo/server/Charge.d.ts.map +1 -1
  229. package/dist/tempo/server/Charge.js +44 -39
  230. package/dist/tempo/server/Charge.js.map +1 -1
  231. package/dist/tempo/server/Methods.d.ts +37 -52
  232. package/dist/tempo/server/Methods.d.ts.map +1 -1
  233. package/dist/tempo/server/Methods.js +0 -14
  234. package/dist/tempo/server/Methods.js.map +1 -1
  235. package/dist/tempo/server/Relay.d.ts +14 -5
  236. package/dist/tempo/server/Relay.d.ts.map +1 -1
  237. package/dist/tempo/server/Relay.js +6 -4
  238. package/dist/tempo/server/Relay.js.map +1 -1
  239. package/dist/tempo/server/SponsorBudget.d.ts.map +1 -1
  240. package/dist/tempo/server/SponsorBudget.js +7 -1
  241. package/dist/tempo/server/SponsorBudget.js.map +1 -1
  242. package/dist/tempo/server/Subscription.d.ts +25 -25
  243. package/dist/tempo/server/Subscription.d.ts.map +1 -1
  244. package/dist/tempo/server/Subscription.js +2 -0
  245. package/dist/tempo/server/Subscription.js.map +1 -1
  246. package/dist/tempo/server/index.d.ts +1 -1
  247. package/dist/tempo/server/index.d.ts.map +1 -1
  248. package/dist/tempo/server/index.js +1 -1
  249. package/dist/tempo/server/index.js.map +1 -1
  250. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  251. package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
  252. package/dist/tempo/server/internal/html.gen.js +1 -1
  253. package/dist/tempo/server/internal/html.gen.js.map +1 -1
  254. package/dist/tempo/server/internal/transport.d.ts +2 -0
  255. package/dist/tempo/server/internal/transport.d.ts.map +1 -1
  256. package/dist/tempo/server/internal/transport.js +9 -2
  257. package/dist/tempo/server/internal/transport.js.map +1 -1
  258. package/dist/tempo/session/client/ChannelOps.js.map +1 -1
  259. package/dist/tempo/session/client/CredentialState.d.ts +1 -1
  260. package/dist/tempo/session/client/CredentialState.js.map +1 -1
  261. package/dist/tempo/session/client/ReceiptCoordinator.js.map +1 -1
  262. package/dist/tempo/session/client/Runtime.js.map +1 -1
  263. package/dist/tempo/session/client/Session.d.ts +19 -19
  264. package/dist/tempo/session/client/Session.d.ts.map +1 -1
  265. package/dist/tempo/session/client/Session.js +128 -14
  266. package/dist/tempo/session/client/Session.js.map +1 -1
  267. package/dist/tempo/session/client/SessionManager.d.ts.map +1 -1
  268. package/dist/tempo/session/client/SessionManager.js +62 -20
  269. package/dist/tempo/session/client/SessionManager.js.map +1 -1
  270. package/dist/tempo/session/client/Transports.d.ts +9 -1
  271. package/dist/tempo/session/client/Transports.d.ts.map +1 -1
  272. package/dist/tempo/session/client/Transports.js +36 -13
  273. package/dist/tempo/session/client/Transports.js.map +1 -1
  274. package/dist/tempo/session/precompile/Chain.d.ts +27 -0
  275. package/dist/tempo/session/precompile/Chain.d.ts.map +1 -1
  276. package/dist/tempo/session/precompile/Chain.js +68 -7
  277. package/dist/tempo/session/precompile/Chain.js.map +1 -1
  278. package/dist/tempo/session/precompile/Protocol.js.map +1 -1
  279. package/dist/tempo/session/precompile/Voucher.d.ts +4 -4
  280. package/dist/tempo/session/precompile/Voucher.d.ts.map +1 -1
  281. package/dist/tempo/session/precompile/escrow.abi.d.ts +306 -306
  282. package/dist/tempo/session/precompile/escrow.abi.d.ts.map +1 -1
  283. package/dist/tempo/session/server/ChannelStore.d.ts +9 -0
  284. package/dist/tempo/session/server/ChannelStore.d.ts.map +1 -1
  285. package/dist/tempo/session/server/ChannelStore.js +16 -8
  286. package/dist/tempo/session/server/ChannelStore.js.map +1 -1
  287. package/dist/tempo/session/server/CredentialVerification.d.ts +30 -13
  288. package/dist/tempo/session/server/CredentialVerification.d.ts.map +1 -1
  289. package/dist/tempo/session/server/CredentialVerification.js +207 -13
  290. package/dist/tempo/session/server/CredentialVerification.js.map +1 -1
  291. package/dist/tempo/session/server/MeteredStream.d.ts +3 -0
  292. package/dist/tempo/session/server/MeteredStream.d.ts.map +1 -1
  293. package/dist/tempo/session/server/MeteredStream.js +3 -1
  294. package/dist/tempo/session/server/MeteredStream.js.map +1 -1
  295. package/dist/tempo/session/server/Session.d.ts +32 -21
  296. package/dist/tempo/session/server/Session.d.ts.map +1 -1
  297. package/dist/tempo/session/server/Session.js +116 -53
  298. package/dist/tempo/session/server/Session.js.map +1 -1
  299. package/dist/tempo/session/server/Sse.d.ts +3 -1
  300. package/dist/tempo/session/server/Sse.d.ts.map +1 -1
  301. package/dist/tempo/session/server/Sse.js +2 -1
  302. package/dist/tempo/session/server/Sse.js.map +1 -1
  303. package/dist/tempo/session/server/Transports.d.ts +1 -1
  304. package/dist/tempo/session/server/Transports.d.ts.map +1 -1
  305. package/dist/tempo/session/server/Transports.js +1 -0
  306. package/dist/tempo/session/server/Transports.js.map +1 -1
  307. package/dist/tempo/session/server/Ws.d.ts +6 -1
  308. package/dist/tempo/session/server/Ws.d.ts.map +1 -1
  309. package/dist/tempo/session/server/Ws.js +4 -0
  310. package/dist/tempo/session/server/Ws.js.map +1 -1
  311. package/dist/tempo/subscription/KeyAuthorization.d.ts +38 -866
  312. package/dist/tempo/subscription/KeyAuthorization.d.ts.map +1 -1
  313. package/dist/tempo/subscription/KeyAuthorization.js +14 -3
  314. package/dist/tempo/subscription/KeyAuthorization.js.map +1 -1
  315. package/dist/viem/Account.d.ts.map +1 -1
  316. package/dist/viem/Account.js.map +1 -1
  317. package/dist/viem/Client.d.ts.map +1 -1
  318. package/dist/viem/Client.js +6 -1
  319. package/dist/viem/Client.js.map +1 -1
  320. package/dist/web-bot-auth/Constants.d.ts +20 -0
  321. package/dist/web-bot-auth/Constants.d.ts.map +1 -0
  322. package/dist/web-bot-auth/Constants.js +24 -0
  323. package/dist/web-bot-auth/Constants.js.map +1 -0
  324. package/dist/web-bot-auth/Types.d.ts +10 -0
  325. package/dist/web-bot-auth/Types.d.ts.map +1 -0
  326. package/dist/web-bot-auth/Types.js +2 -0
  327. package/dist/web-bot-auth/Types.js.map +1 -0
  328. package/dist/web-bot-auth/client/index.d.ts +29 -0
  329. package/dist/web-bot-auth/client/index.d.ts.map +1 -0
  330. package/dist/web-bot-auth/client/index.js +64 -0
  331. package/dist/web-bot-auth/client/index.js.map +1 -0
  332. package/dist/web-bot-auth/index.d.ts +7 -0
  333. package/dist/web-bot-auth/index.d.ts.map +1 -0
  334. package/dist/web-bot-auth/index.js +7 -0
  335. package/dist/web-bot-auth/index.js.map +1 -0
  336. package/dist/web-bot-auth/internal/JwkThumbprint.d.ts +5 -0
  337. package/dist/web-bot-auth/internal/JwkThumbprint.d.ts.map +1 -0
  338. package/dist/web-bot-auth/internal/JwkThumbprint.js +20 -0
  339. package/dist/web-bot-auth/internal/JwkThumbprint.js.map +1 -0
  340. package/dist/web-bot-auth/internal/SignatureAgent.d.ts +3 -0
  341. package/dist/web-bot-auth/internal/SignatureAgent.d.ts.map +1 -0
  342. package/dist/web-bot-auth/internal/SignatureAgent.js +18 -0
  343. package/dist/web-bot-auth/internal/SignatureAgent.js.map +1 -0
  344. package/dist/web-bot-auth/server/index.d.ts +35 -0
  345. package/dist/web-bot-auth/server/index.d.ts.map +1 -0
  346. package/dist/web-bot-auth/server/index.js +88 -0
  347. package/dist/web-bot-auth/server/index.js.map +1 -0
  348. package/dist/x402/Assets.d.ts.map +1 -1
  349. package/dist/x402/Types.d.ts +6 -6
  350. package/dist/x402/Types.d.ts.map +1 -1
  351. package/dist/x402/server/EvmCharge.d.ts.map +1 -1
  352. package/dist/x402/server/EvmCharge.js +32 -20
  353. package/dist/x402/server/EvmCharge.js.map +1 -1
  354. package/package.json +20 -4
  355. package/src/Challenge.fuzz.test.ts +80 -47
  356. package/src/Challenge.test.ts +65 -0
  357. package/src/Challenge.ts +39 -35
  358. package/src/Method.ts +82 -68
  359. package/src/Store.test-d.ts +16 -0
  360. package/src/Store.test.ts +50 -0
  361. package/src/Store.ts +68 -0
  362. package/src/attestation/Attestation.integration.test.ts +208 -0
  363. package/src/attestation/Client.test.ts +83 -0
  364. package/src/attestation/Client.ts +34 -0
  365. package/src/attestation/Constants.ts +15 -0
  366. package/src/attestation/Server.test.ts +60 -0
  367. package/src/attestation/Server.ts +12 -0
  368. package/src/attestation/Types.test-d.ts +54 -0
  369. package/src/attestation/Types.ts +77 -0
  370. package/src/attestation/index.ts +5 -0
  371. package/src/attestation/internal/HttpMessageSignature.test.ts +101 -0
  372. package/src/attestation/internal/HttpMessageSignature.ts +429 -0
  373. package/src/attestation/internal/SigningContext.ts +13 -0
  374. package/src/bin.js +2 -0
  375. package/src/cli/cli.ts +1 -0
  376. package/src/cli/validate/payment.ts +18 -4
  377. package/src/cli/validate.test.ts +16 -0
  378. package/src/client/Mppx.test-d.ts +17 -0
  379. package/src/client/Mppx.test.ts +14 -14
  380. package/src/client/Mppx.ts +25 -4
  381. package/src/client/Transport.test.ts +12 -45
  382. package/src/client/Transport.ts +3 -24
  383. package/src/client/internal/Fetch.test.ts +77 -0
  384. package/src/client/internal/Fetch.ts +112 -14
  385. package/src/client/internal/MethodChallenge.ts +5 -0
  386. package/src/client/internal/MethodResponse.ts +28 -0
  387. package/src/discovery/OpenApi.test.ts +170 -0
  388. package/src/discovery/OpenApi.ts +16 -48
  389. package/src/discovery/internal/Metadata.ts +51 -0
  390. package/src/evm/server/Charge.ts +6 -1
  391. package/src/internal/AcceptPayment.test.ts +12 -8
  392. package/src/internal/types.ts +9 -0
  393. package/src/mcp/client/McpClient.integration.test.ts +19 -32
  394. package/src/middlewares/elysia.test.ts +2 -12
  395. package/src/middlewares/express.test.ts +2 -12
  396. package/src/middlewares/hono.test.ts +6 -9
  397. package/src/middlewares/nextjs.test.ts +2 -12
  398. package/src/proxy/Proxy.test.ts +26 -19
  399. package/src/proxy/Proxy.ts +147 -9
  400. package/src/proxy/Proxy.upstreamError.test.ts +364 -0
  401. package/src/proxy/Service.test.ts +73 -3
  402. package/src/proxy/Service.ts +50 -23
  403. package/src/proxy/services/anthropic.ts +1 -0
  404. package/src/proxy/services/hooks.test-d.ts +14 -0
  405. package/src/proxy/services/hooks.test.ts +44 -0
  406. package/src/proxy/services/openai.ts +1 -0
  407. package/src/proxy/services/stripe.ts +1 -0
  408. package/src/server/Methods.test-d.ts +88 -0
  409. package/src/server/Methods.test.ts +69 -0
  410. package/src/server/Mppx.test-d.ts +141 -7
  411. package/src/server/Mppx.test.ts +1017 -119
  412. package/src/server/Mppx.ts +350 -54
  413. package/src/stripe/internal/constants.ts +1 -1
  414. package/src/stripe/internal/types.ts +1 -0
  415. package/src/stripe/server/Charge.test-d.ts +16 -1
  416. package/src/stripe/server/Charge.test.ts +79 -0
  417. package/src/stripe/server/Charge.ts +54 -1
  418. package/src/stripe/server/Methods.test-d.ts +157 -0
  419. package/src/stripe/server/Methods.test.ts +403 -0
  420. package/src/stripe/server/Methods.ts +423 -6
  421. package/src/stripe/server/internal/deposit-address.ts +57 -0
  422. package/src/stripe/server/internal/html/node_modules/.bin/mppx +3 -3
  423. package/src/stripe/server/internal/html/package.json +1 -1
  424. package/src/stripe/server/internal/html.gen.ts +1 -1
  425. package/src/stripe/server/internal/record-payment.ts +66 -0
  426. package/src/stripe/server/internal/request.ts +49 -0
  427. package/src/tap/Constants.ts +32 -0
  428. package/src/tap/Tap.test.ts +125 -0
  429. package/src/tap/Types.ts +11 -0
  430. package/src/tap/client/index.ts +54 -0
  431. package/src/tap/index.ts +4 -0
  432. package/src/tap/server/index.ts +48 -0
  433. package/src/tempo/PublicExports.test-d.ts +3 -3
  434. package/src/tempo/client/Charge.ts +1 -19
  435. package/src/tempo/internal/address.test.ts +42 -0
  436. package/src/tempo/internal/address.ts +3 -3
  437. package/src/tempo/internal/auto-swap.test.ts +4 -4
  438. package/src/tempo/internal/auto-swap.ts +7 -50
  439. package/src/tempo/legacy/README.md +5 -5
  440. package/src/tempo/legacy/index.ts +1 -3
  441. package/src/tempo/server/AtomicStore.test-d.ts +13 -0
  442. package/src/tempo/server/Charge.test.ts +19 -84
  443. package/src/tempo/server/Charge.ts +92 -56
  444. package/src/tempo/server/Methods.ts +0 -20
  445. package/src/tempo/server/Relay.test.ts +161 -7
  446. package/src/tempo/server/Relay.ts +15 -6
  447. package/src/tempo/server/SponsorBudget.test.ts +29 -0
  448. package/src/tempo/server/SponsorBudget.ts +7 -1
  449. package/src/tempo/server/Subscription.ts +4 -1
  450. package/src/tempo/server/index.ts +1 -1
  451. package/src/tempo/server/internal/html/node_modules/.bin/mppx +3 -3
  452. package/src/tempo/server/internal/html/package.json +1 -1
  453. package/src/tempo/server/internal/html.gen.ts +1 -1
  454. package/src/tempo/server/internal/transport.test.ts +22 -3
  455. package/src/tempo/server/internal/transport.ts +15 -3
  456. package/src/tempo/session/README.md +119 -0
  457. package/src/tempo/session/client/Session.test.ts +299 -33
  458. package/src/tempo/session/client/Session.ts +152 -16
  459. package/src/tempo/session/client/SessionManager.test.ts +216 -9
  460. package/src/tempo/session/client/SessionManager.ts +63 -17
  461. package/src/tempo/session/client/Transports.test.ts +22 -0
  462. package/src/tempo/session/client/Transports.ts +58 -22
  463. package/src/tempo/session/precompile/Chain.ts +131 -11
  464. package/src/tempo/session/server/ChannelStore.ts +33 -12
  465. package/src/tempo/session/server/CredentialVerification.test.ts +4 -4
  466. package/src/tempo/session/server/CredentialVerification.ts +340 -28
  467. package/src/tempo/session/server/MeteredStream.ts +5 -1
  468. package/src/tempo/session/server/Session.test.ts +483 -36
  469. package/src/tempo/session/server/Session.ts +159 -60
  470. package/src/tempo/session/server/Sse.test.ts +77 -0
  471. package/src/tempo/session/server/Sse.ts +8 -1
  472. package/src/tempo/session/server/Transports.ts +2 -1
  473. package/src/tempo/session/server/Ws.test.ts +171 -0
  474. package/src/tempo/session/server/Ws.ts +10 -1
  475. package/src/tempo/subscription/KeyAuthorization.test.ts +70 -1
  476. package/src/tempo/subscription/KeyAuthorization.ts +14 -3
  477. package/src/viem/Client.test.ts +18 -1
  478. package/src/viem/Client.ts +7 -1
  479. package/src/web-bot-auth/Constants.ts +24 -0
  480. package/src/web-bot-auth/Types.ts +9 -0
  481. package/src/web-bot-auth/WebBotAuth.test.ts +182 -0
  482. package/src/web-bot-auth/client/index.ts +84 -0
  483. package/src/web-bot-auth/index.ts +6 -0
  484. package/src/web-bot-auth/internal/JwkThumbprint.ts +21 -0
  485. package/src/web-bot-auth/internal/SignatureAgent.ts +18 -0
  486. package/src/web-bot-auth/server/index.ts +113 -0
  487. package/src/x402/Exact.e2e.test.ts +121 -81
  488. package/src/x402/Exact.localnet.test.ts +1 -1
  489. package/src/x402/server/EvmCharge.ts +35 -19
  490. package/dist/tempo/legacy/server/Session.d.ts +0 -208
  491. package/dist/tempo/legacy/server/Session.d.ts.map +0 -1
  492. package/dist/tempo/legacy/server/Session.js +0 -698
  493. package/dist/tempo/legacy/server/Session.js.map +0 -1
  494. package/dist/tempo/legacy/server/index.d.ts +0 -5
  495. package/dist/tempo/legacy/server/index.d.ts.map +0 -1
  496. package/dist/tempo/legacy/server/index.js +0 -5
  497. package/dist/tempo/legacy/server/index.js.map +0 -1
  498. package/dist/tempo/legacy/session/ChannelStore.d.ts +0 -22
  499. package/dist/tempo/legacy/session/ChannelStore.d.ts.map +0 -1
  500. package/dist/tempo/legacy/session/ChannelStore.js +0 -6
  501. package/dist/tempo/legacy/session/ChannelStore.js.map +0 -1
  502. package/src/tempo/legacy/server/Defaults.test-d.ts +0 -9
  503. package/src/tempo/legacy/server/Session.test.ts +0 -6370
  504. package/src/tempo/legacy/server/Session.ts +0 -1024
  505. package/src/tempo/legacy/server/index.ts +0 -4
  506. package/src/tempo/legacy/session/ChannelStore.test.ts +0 -58
  507. package/src/tempo/legacy/session/ChannelStore.ts +0 -39
@@ -738,8 +738,15 @@ describe('sse transport', () => {
738
738
 
739
739
  test('respondReceipt with non-SSE upstream Response still deducts from channel', async () => {
740
740
  const store = memoryStore()
741
+ const settled: Array<{ spent: bigint; units: number }> = []
741
742
  await seedChannel(store, 10000000n)
742
- const transport = sse({ store })
743
+ const transport = sse({
744
+ store,
745
+ async settleCharged(channel) {
746
+ settled.push({ spent: channel.spent, units: channel.units })
747
+ return undefined
748
+ },
749
+ })
743
750
  const request = new Request('https://test.example.com/session', {
744
751
  body: JSON.stringify({ prompt: 'hello' }),
745
752
  headers: makeAuthorizedRequest().headers,
@@ -768,6 +775,7 @@ describe('sse transport', () => {
768
775
  expect(channel!.units).toBe(1)
769
776
  expect(receipt.spent).toBe('1000000')
770
777
  expect(receipt.units).toBe(1)
778
+ expect(settled).toEqual([{ spent: 1000000n, units: 1 }])
771
779
 
772
780
  expect(JSON.parse(body)).toEqual({ content: 'hello' })
773
781
  expect(response.headers.get('Content-Type')).toBe('application/json')
@@ -806,8 +814,18 @@ describe('sse transport', () => {
806
814
 
807
815
  test('respondReceipt with 204 content response still deducts from channel', async () => {
808
816
  const store = memoryStore()
817
+ let resolveSettlement!: (channel: ChannelStore.State) => void
818
+ const settlement = new Promise<ChannelStore.State>((resolve) => {
819
+ resolveSettlement = resolve
820
+ })
809
821
  await seedChannel(store, 10000000n)
810
- const transport = sse({ store })
822
+ const transport = sse({
823
+ store,
824
+ async settleCharged(channel) {
825
+ resolveSettlement(channel)
826
+ return undefined
827
+ },
828
+ })
811
829
  const request = new Request('https://test.example.com/session', {
812
830
  body: JSON.stringify({ prompt: 'hello' }),
813
831
  headers: makeAuthorizedRequest().headers,
@@ -827,11 +845,12 @@ describe('sse transport', () => {
827
845
  expect(await response.text()).toBe('')
828
846
  const receipt = deserializeSessionReceipt(response.headers.get('Payment-Receipt')!)
829
847
 
830
- await Promise.resolve()
848
+ const settled = await settlement
831
849
 
832
850
  const channel = await store.getChannel(channelId)
833
851
  expect(channel!.spent).toBe(1000000n)
834
852
  expect(channel!.units).toBe(1)
853
+ expect(settled).toMatchObject({ spent: 1000000n, units: 1 })
835
854
  expect(receipt.spent).toBe('1000000')
836
855
  expect(receipt.units).toBe(1)
837
856
  })
@@ -12,6 +12,7 @@ import * as Transport from '../../../server/Transport.js'
12
12
  import type { SessionReceipt } from '../../session/precompile/Protocol.js'
13
13
  import { requireSessionCredentialContext } from '../../session/precompile/Protocol.js'
14
14
  import * as ChannelStore from '../../session/server/ChannelStore.js'
15
+ import type { SettleChargedSessionChannel } from '../../session/server/Settlement.js'
15
16
  import * as Sse_core from '../../session/server/Sse.js'
16
17
  import { captureRequestBodyProbe, shouldChargePlainResponse } from './request-body.js'
17
18
 
@@ -48,7 +49,12 @@ function hasPrepaidSessionTick(receipt: SessionReceipt): boolean {
48
49
  * - Auto-detection of upstream SSE responses
49
50
  * - Fallback to standard HTTP receipt handling for plain Response
50
51
  */
51
- export function sse(options: sse.Options & { store: ChannelStore.ChannelStore }): Sse {
52
+ export function sse(
53
+ options: sse.Options & {
54
+ settleCharged?: SettleChargedSessionChannel | undefined
55
+ store: ChannelStore.ChannelStore
56
+ },
57
+ ): Sse {
52
58
  const { pollingInterval, poll } = options
53
59
 
54
60
  // When `poll` is true, strip `waitForUpdate` so the SSE charge loop
@@ -117,6 +123,7 @@ export function sse(options: sse.Options & { store: ChannelStore.ChannelStore })
117
123
  tickCost,
118
124
  pollIntervalMs: pollingInterval,
119
125
  generate,
126
+ onChargeCommitted: options.settleCharged,
120
127
  prepaidUnits: hasPrepaidSessionTick(receipt as SessionReceipt) ? 1 : 0,
121
128
  signal: input.signal,
122
129
  })
@@ -174,20 +181,25 @@ export function sse(options: sse.Options & { store: ChannelStore.ChannelStore })
174
181
  response: response as Response,
175
182
  challengeId: verifiedChallengeId,
176
183
  })
184
+ const chargePlainResponse = async () => {
185
+ const result = await ChannelStore.deductFromChannel(store, channelId, tickCost)
186
+ if (result.ok) await options.settleCharged?.(result.channel)
187
+ return result
188
+ }
177
189
 
178
190
  // Non-SSE response (e.g. upstream returned JSON instead of event-stream).
179
191
  // Need to deduct tickCost so request isn't free.
180
192
  // For null-body statuses, the request shape determines whether the
181
193
  // response is management (no charge) or plain content (charge one tick).
182
194
  if (isNullBodyStatus(chargedResponse.status)) {
183
- void ChannelStore.deductFromChannel(store, channelId, tickCost)
195
+ void chargePlainResponse()
184
196
  return chargedResponse
185
197
  }
186
198
 
187
199
  const stream = new ReadableStream<Uint8Array>({
188
200
  async start(controller) {
189
201
  // deduction completes before consumer reads
190
- const result = await ChannelStore.deductFromChannel(store, channelId, tickCost)
202
+ const result = await chargePlainResponse()
191
203
  if (!result.ok) {
192
204
  controller.error(
193
205
  new Errors.InsufficientBalanceError({
@@ -83,6 +83,13 @@ sequenceDiagram
83
83
  route handler. `open` and `voucher` used on non-billable requests are also
84
84
  management updates.
85
85
 
86
+ When `Fetch.from` creates an automatic `open`, it keeps the channel entry
87
+ provisional until the server accepts the response or acknowledges the open with
88
+ a matching receipt or session snapshot. Rejected or unacknowledged opens are
89
+ discarded so the next request retries `open`; concurrent automatic opens for the
90
+ same payment scope are serialized. Direct credential creation and
91
+ `SessionManager` retain their existing lifecycle ownership.
92
+
86
93
  The current HTTP contract is deliberately pre-handler: voucher acceptance and
87
94
  default request charging occur before the handler runs. A handler failure can
88
95
  therefore follow an accepted voucher and recorded `spent`. Sessions does not
@@ -179,6 +186,118 @@ sequenceDiagram
179
186
  The initial SSE POST is excluded from default HTTP charging to prevent double
180
187
  counting. Stream accounting remains owned by the transport driver.
181
188
 
189
+ ## Server transport configuration
190
+
191
+ A session method owns the account, channel store, verification policy, and
192
+ settlement schedule. Routes add prices and application content. HTTP and SSE
193
+ run through configured route handlers; WebSocket uses a session-bound helper
194
+ because its application messages no longer pass through HTTP.
195
+
196
+ | Server shape | Session option | Content API | Charging |
197
+ | ---------------------- | -------------- | ----------------------------------- | ----------------------------------- |
198
+ | HTTP only | Omit `sse` | `result.withReceipt(Response)` | Once per billable request |
199
+ | SSE only | `sse: true` | `result.withReceipt(AsyncIterable)` | Each `stream.charge()` |
200
+ | WebSocket only | Omit `sse` | `mppx.session.serveWebSocket(...)` | Each `stream.charge()` |
201
+ | HTTP + SSE + WebSocket | `sse: true` | All three APIs | Selected by route and response type |
202
+
203
+ All transports for one session service must share the same configured session
204
+ method and store. `serveWebSocket` is bound to both the store and settlement
205
+ policy, so callers pass neither dependency again. `tempo.Ws.serve` remains the
206
+ low-level adapter for custom integrations.
207
+
208
+ ### Shared method configuration
209
+
210
+ ```ts
211
+ const session = tempo.session({
212
+ account,
213
+ currency,
214
+ getClient: () => client,
215
+ settlementSchedule: { units: 100 },
216
+ store,
217
+ // Include when this instance serves any SSE route.
218
+ sse: true,
219
+ })
220
+
221
+ const mppx = Mppx.create({ methods: [session], secretKey })
222
+ ```
223
+
224
+ `sse: true` enables async-iterable responses without changing ordinary
225
+ `Response` handling, so a mixed server uses one session method.
226
+
227
+ ### HTTP
228
+
229
+ ```ts
230
+ const httpRoute = mppx.session({ amount: '0.01', unitType: 'request' })
231
+ const result = await httpRoute(request)
232
+
233
+ if (result.status === 402) return result.challenge
234
+ return result.withReceipt(Response.json({ ok: true }))
235
+ ```
236
+
237
+ HTTP accounting charges the configured route amount before application content
238
+ runs. A plain `Response` selects normal HTTP handling even when `sse: true`.
239
+
240
+ ### SSE
241
+
242
+ ```ts
243
+ const sseRoute = mppx.session({ amount: '0.001', unitType: 'token' })
244
+ const result = await sseRoute(request)
245
+
246
+ if (result.status === 402) return result.challenge
247
+ return result.withReceipt(async function* (stream) {
248
+ for await (const token of generateTokens()) {
249
+ await stream.charge()
250
+ yield token
251
+ }
252
+ })
253
+ ```
254
+
255
+ SSE requires `sse: true` on the shared session method. The async iterable
256
+ selects SSE framing and transport-owned metering.
257
+
258
+ ### WebSocket
259
+
260
+ ```ts
261
+ const wsRoute = mppx.session({ amount: '0.001', unitType: 'token' })
262
+
263
+ webSocketServer.on('connection', (socket, request) => {
264
+ void mppx.session.serveWebSocket({
265
+ generate: async function* (stream) {
266
+ for await (const token of generateTokens()) {
267
+ await stream.charge()
268
+ yield token
269
+ }
270
+ },
271
+ route: wsRoute,
272
+ socket,
273
+ url: new URL(request.url!, `ws://${request.headers.host}`),
274
+ })
275
+ })
276
+ ```
277
+
278
+ The application still exposes `wsRoute` over HTTP for the initial challenge
279
+ probe. The bound helper reuses the session store and automatic settlement
280
+ schedule for in-band WebSocket vouchers and charges.
281
+
282
+ ### Mixed server
283
+
284
+ ```ts
285
+ const httpRoute = mppx.session({ amount: '0.01', unitType: 'request' })
286
+ const sseRoute = mppx.session({ amount: '0.001', unitType: 'token' })
287
+ const wsRoute = mppx.session({ amount: '0.001', unitType: 'token' })
288
+
289
+ async function handler(request: Request) {
290
+ const pathname = new URL(request.url).pathname
291
+ if (pathname === '/api/data') return serveHttp(httpRoute, request)
292
+ if (pathname === '/api/events') return serveSse(sseRoute, request)
293
+ if (pathname === '/ws') return serveWebSocketProbe(wsRoute, request)
294
+ return new Response('Not found', { status: 404 })
295
+ }
296
+ ```
297
+
298
+ Each route can choose its own price and unit type. Verification, channel state,
299
+ and settlement policy remain shared through the single session method.
300
+
182
301
  ## Extension boundaries
183
302
 
184
303
  | Change | Preserve |
@@ -16,7 +16,7 @@ import { escrowAbi } from '../precompile/escrow.abi.js'
16
16
  import { tip20ChannelEscrow } from '../precompile/Protocol.js'
17
17
  import * as Types from '../precompile/Protocol.js'
18
18
  import * as Voucher from '../precompile/Voucher.js'
19
- import { createChannelStore } from './ChannelStore.js'
19
+ import { channelKey, createChannelStore } from './ChannelStore.js'
20
20
  import { session } from './Session.js'
21
21
 
22
22
  const account = privateKeyToAccount(
@@ -60,6 +60,13 @@ type SessionChallenge = Challenge<
60
60
  typeof Methods.session.name
61
61
  >
62
62
 
63
+ const defaultChannelKey = channelKey({
64
+ chainId,
65
+ escrow: tip20ChannelEscrow,
66
+ payee: descriptor.payee,
67
+ token: descriptor.token,
68
+ })
69
+
63
70
  function makeChallenge(overrides: Partial<SessionChallenge['request']> = {}): SessionChallenge {
64
71
  const request: SessionChallenge['request'] = {
65
72
  amount: '100',
@@ -78,6 +85,83 @@ function makeChallenge(overrides: Partial<SessionChallenge['request']> = {}): Se
78
85
  }
79
86
  }
80
87
 
88
+ function makeSessionChallenge(
89
+ overrides: Partial<SessionChallenge['request']> = {},
90
+ ): SessionChallenge {
91
+ return makeChallenge({
92
+ ...overrides,
93
+ methodDetails: {
94
+ chainId,
95
+ escrowContract: tip20ChannelEscrow,
96
+ sessionProtocol: Constants.SessionProtocols.v2,
97
+ ...overrides.methodDetails,
98
+ },
99
+ })
100
+ }
101
+
102
+ function paymentRequired(challenge: SessionChallenge, headers?: HeadersInit): Response {
103
+ const responseHeaders = new Headers(headers)
104
+ responseHeaders.set(Constants.Headers.wwwAuthenticate, serializeChallenge(challenge))
105
+ return new Response(null, {
106
+ headers: responseHeaders,
107
+ status: 402,
108
+ })
109
+ }
110
+
111
+ function withSnapshot(
112
+ payload: Types.SessionCredentialPayload,
113
+ acceptedCumulative?: string,
114
+ ): SessionChallenge {
115
+ if (payload.action !== 'open') throw new Error('expected open credential')
116
+ return {
117
+ ...makeSessionChallenge({
118
+ methodDetails: {
119
+ chainId,
120
+ escrowContract: tip20ChannelEscrow,
121
+ sessionProtocol: Constants.SessionProtocols.v2,
122
+ sessionSnapshot: {
123
+ acceptedCumulative: acceptedCumulative ?? payload.cumulativeAmount,
124
+ chainId,
125
+ channelId: payload.channelId,
126
+ deposit: '1000',
127
+ descriptor: payload.descriptor,
128
+ escrow: tip20ChannelEscrow,
129
+ requiredCumulative: '200',
130
+ settled: '0',
131
+ spent: payload.cumulativeAmount,
132
+ },
133
+ },
134
+ }),
135
+ id: 'replacement-id',
136
+ }
137
+ }
138
+
139
+ function makeSessionMethod(
140
+ channelStore = createChannelStore(),
141
+ parameters: Partial<session.Parameters> = {},
142
+ ) {
143
+ return session({
144
+ account,
145
+ channelStore,
146
+ decimals: 0,
147
+ getClient: () => client,
148
+ maxDeposit: '1000',
149
+ ...parameters,
150
+ })
151
+ }
152
+
153
+ function makeSessionFetch(
154
+ fetch: typeof globalThis.fetch,
155
+ channelStore = createChannelStore(),
156
+ maxPaymentRetries?: number,
157
+ ) {
158
+ return Fetch.from({
159
+ fetch,
160
+ maxPaymentRetries,
161
+ methods: [makeSessionMethod(channelStore)],
162
+ })
163
+ }
164
+
81
165
  function deserialize(credential: string) {
82
166
  return Credential.deserialize(credential).payload as Types.SessionCredentialPayload
83
167
  }
@@ -233,14 +317,16 @@ describe('precompile client session', () => {
233
317
  header: string | null
234
318
  method: string | undefined
235
319
  }[] = []
320
+ const vouchers: string[] = []
236
321
  let opened: Extract<Types.SessionCredentialPayload, { action: 'open' }> | undefined
322
+ const paymentRequired = () =>
323
+ new Response(null, {
324
+ status: 402,
325
+ headers: { [Constants.Headers.wwwAuthenticate]: serializeChallenge(challenge) },
326
+ })
237
327
  const rawFetch: typeof globalThis.fetch = async (_input, init) => {
238
328
  const authorization = new Headers(init?.headers).get(Constants.Headers.authorization)
239
- if (!authorization)
240
- return new Response(null, {
241
- status: 402,
242
- headers: { [Constants.Headers.wwwAuthenticate]: serializeChallenge(challenge) },
243
- })
329
+ if (!authorization) return paymentRequired()
244
330
 
245
331
  const payload = deserialize(authorization)
246
332
  actions.push(payload.action)
@@ -250,6 +336,8 @@ describe('precompile client session', () => {
250
336
  }
251
337
  if (payload.action === 'topUp') return new Response(null, { status: 204 })
252
338
  if (payload.action !== 'voucher' || !opened) throw new Error('expected open voucher')
339
+ vouchers.push(authorization)
340
+ if (vouchers.length === 1) return paymentRequired()
253
341
 
254
342
  paidRequests.push({
255
343
  body: init?.body ?? null,
@@ -278,11 +366,192 @@ describe('precompile client session', () => {
278
366
  })
279
367
 
280
368
  expect(await response.text()).toBe('paid')
281
- expect(actions).toEqual(['open', 'topUp', 'voucher'])
369
+ expect(actions).toEqual(['open', 'topUp', 'voucher', 'voucher'])
370
+ expect(new Set(vouchers).size).toBe(1)
282
371
  expect(paidRequests).toEqual([{ body: 'request-body', header: 'preserved', method: 'POST' }])
283
372
  })
284
373
 
285
- test('does not top up a stored channel owned by another account', async () => {
374
+ test('retries an open rejected by a replacement challenge', async () => {
375
+ const challenge = makeSessionChallenge({ suggestedDeposit: '1000' })
376
+ const replacement = { ...challenge, id: 'replacement-id' }
377
+ const channelStore = createChannelStore()
378
+ const actions: Types.SessionCredentialPayload['action'][] = []
379
+ let paidRequests = 0
380
+ const fetch = makeSessionFetch(async (_input, init) => {
381
+ const authorization = new Headers(init?.headers).get(Constants.Headers.authorization)
382
+ if (!authorization) return paymentRequired(challenge)
383
+ actions.push(deserialize(authorization).action)
384
+ if (++paidRequests === 1) return paymentRequired(replacement)
385
+ return new Response('rejected', { status: 500 })
386
+ }, channelStore)
387
+
388
+ expect((await fetch('https://example.com/resource')).status).toBe(500)
389
+ expect(actions).toEqual(['open', 'open'])
390
+ expect(await channelStore.get(defaultChannelKey)).toBeUndefined()
391
+ })
392
+
393
+ test('retries an open after the paid request throws', async () => {
394
+ const challenge = makeSessionChallenge({ suggestedDeposit: '1000' })
395
+ const actions: Types.SessionCredentialPayload['action'][] = []
396
+ const fetch = makeSessionFetch(async (_input, init) => {
397
+ const authorization = new Headers(init?.headers).get(Constants.Headers.authorization)
398
+ if (!authorization) return paymentRequired(challenge)
399
+ actions.push(deserialize(authorization).action)
400
+ throw new Error('paid request failed')
401
+ })
402
+
403
+ await expect(fetch('https://example.com/resource')).rejects.toThrow('paid request failed')
404
+ await expect(fetch('https://example.com/resource')).rejects.toThrow('paid request failed')
405
+ expect(actions).toEqual(['open', 'open'])
406
+ })
407
+
408
+ test('keeps an open acknowledged by a payment receipt', async () => {
409
+ const challenge = makeSessionChallenge({ suggestedDeposit: '1000' })
410
+ const channelStore = createChannelStore()
411
+ const actions: Types.SessionCredentialPayload['action'][] = []
412
+ const credentials: string[] = []
413
+ let paidRequests = 0
414
+ const fetch = makeSessionFetch(async (_input, init) => {
415
+ const authorization = new Headers(init?.headers).get(Constants.Headers.authorization)
416
+ if (!authorization) return paymentRequired(challenge)
417
+ const payload = deserialize(authorization)
418
+ actions.push(payload.action)
419
+ credentials.push(authorization)
420
+ if (++paidRequests === 1) return paymentRequired(challenge)
421
+ if (paidRequests > 2) return new Response('rejected', { status: 500 })
422
+ return paymentRequired(challenge, {
423
+ [Constants.Headers.paymentReceipt]: Types.serializeSessionReceipt(
424
+ Types.createSessionReceipt({
425
+ acceptedCumulative: 100n,
426
+ challengeId: challenge.id,
427
+ channelId: payload.channelId,
428
+ spent: 100n,
429
+ }),
430
+ ),
431
+ })
432
+ }, channelStore)
433
+
434
+ expect((await fetch('https://example.com/resource')).status).toBe(500)
435
+ expect(actions).toEqual(['open', 'open', 'voucher'])
436
+ expect(credentials[0]).toBe(credentials[1])
437
+ expect(await channelStore.get(defaultChannelKey)).toMatchObject({ opened: true })
438
+ })
439
+
440
+ test.each([
441
+ [
442
+ 'keeps an open acknowledged by the final session snapshot',
443
+ undefined,
444
+ ['open', 'voucher'],
445
+ true,
446
+ ],
447
+ [
448
+ 'fails closed on malformed acknowledgement snapshots without leaking the open lock',
449
+ 'not-a-number',
450
+ ['open', 'open'],
451
+ undefined,
452
+ ],
453
+ ])('%s', async (_name, acceptedCumulative, expectedActions, expectedOpened) => {
454
+ const challenge = makeSessionChallenge({ suggestedDeposit: '1000' })
455
+ const channelStore = createChannelStore()
456
+ const actions: Types.SessionCredentialPayload['action'][] = []
457
+ let paidRequests = 0
458
+ const fetch = makeSessionFetch(
459
+ async (_input, init) => {
460
+ const authorization = new Headers(init?.headers).get(Constants.Headers.authorization)
461
+ if (!authorization) return paymentRequired(challenge)
462
+ const payload = deserialize(authorization)
463
+ actions.push(payload.action)
464
+ if (++paidRequests > 1) return new Response('rejected', { status: 500 })
465
+ return paymentRequired(withSnapshot(payload, acceptedCumulative))
466
+ },
467
+ channelStore,
468
+ 1,
469
+ )
470
+
471
+ expect((await fetch('https://example.com/resource')).status).toBe(402)
472
+ expect((await fetch('https://example.com/resource')).status).toBe(500)
473
+ expect(actions).toEqual(expectedActions)
474
+ expect((await channelStore.get(defaultChannelKey))?.opened).toBe(expectedOpened)
475
+ })
476
+
477
+ test('keeps an acknowledged open when challenge ordering throws', async () => {
478
+ const challenge = makeSessionChallenge({ suggestedDeposit: '1000' })
479
+ const channelStore = createChannelStore()
480
+ const actions: Types.SessionCredentialPayload['action'][] = []
481
+ let ordered = 0
482
+ let paidRequests = 0
483
+ const fetch = Fetch.from({
484
+ fetch: async (_input, init) => {
485
+ const authorization = new Headers(init?.headers).get(Constants.Headers.authorization)
486
+ if (!authorization) return paymentRequired(challenge)
487
+ const payload = deserialize(authorization)
488
+ actions.push(payload.action)
489
+ if (++paidRequests > 1) return new Response('paid')
490
+ return paymentRequired(withSnapshot(payload))
491
+ },
492
+ methods: [makeSessionMethod(channelStore)],
493
+ orderChallenges(candidates) {
494
+ if (++ordered === 2) throw new Error('ordering failed')
495
+ return candidates
496
+ },
497
+ })
498
+
499
+ await expect(fetch('https://example.com/resource')).rejects.toThrow('ordering failed')
500
+ expect(await (await fetch('https://example.com/resource')).text()).toBe('paid')
501
+ expect(actions).toEqual(['open', 'voucher'])
502
+ expect(await channelStore.get(defaultChannelKey)).toMatchObject({ opened: true })
503
+ })
504
+
505
+ test('serializes concurrent opens against the committed channel', async () => {
506
+ const challenge = makeSessionChallenge()
507
+ const channelStore = createChannelStore()
508
+ const actions: Types.SessionCredentialPayload['action'][] = []
509
+ const vouchers: bigint[] = []
510
+ const firstGate = Promise.withResolvers<void>()
511
+ const firstRequest = Promise.withResolvers<void>()
512
+ const waitersReady = Promise.withResolvers<void>()
513
+ let credentialPlans = 0
514
+ const fetch = Fetch.from({
515
+ fetch: async (input, init) => {
516
+ const authorization = new Headers(init?.headers).get(Constants.Headers.authorization)
517
+ if (!authorization) return paymentRequired(challenge)
518
+ const payload = deserialize(authorization)
519
+ actions.push(payload.action)
520
+ if (payload.action === 'topUp') return new Response(null, { status: 204 })
521
+ if (payload.action === 'voucher') vouchers.push(BigInt(payload.cumulativeAmount))
522
+ if (input.toString().endsWith('/first')) {
523
+ firstRequest.resolve()
524
+ await firstGate.promise
525
+ return new Response('paid')
526
+ }
527
+ return new Response('rejected', { status: 500 })
528
+ },
529
+ methods: [
530
+ makeSessionMethod(channelStore, {
531
+ resolveAccount() {
532
+ if (++credentialPlans === 3) waitersReady.resolve()
533
+ return account
534
+ },
535
+ }),
536
+ ],
537
+ })
538
+
539
+ const first = fetch('https://example.com/first')
540
+ await firstRequest.promise
541
+ const second = fetch('https://example.com/second')
542
+ const third = fetch('https://example.com/third')
543
+ await waitersReady.promise
544
+ firstGate.resolve()
545
+
546
+ expect((await first).status).toBe(200)
547
+ expect((await second).status).toBe(500)
548
+ expect((await third).status).toBe(500)
549
+ expect(actions).toEqual(['open', 'topUp', 'voucher', 'topUp', 'voucher'])
550
+ expect(vouchers).toEqual([200n, 300n])
551
+ expect(await channelStore.get(defaultChannelKey)).toMatchObject({ opened: true })
552
+ })
553
+
554
+ test('replaces a stored channel owned by another account', async () => {
286
555
  const channelStore = createChannelStore()
287
556
  const foreign = '0x0000000000000000000000000000000000000004' as Address
288
557
  await channelStore.set({
@@ -295,29 +564,21 @@ describe('precompile client session', () => {
295
564
  opened: true,
296
565
  })
297
566
  const actions: Types.SessionCredentialPayload['action'][] = []
298
- const challenge = makeChallenge({
299
- methodDetails: {
300
- chainId,
301
- escrowContract: tip20ChannelEscrow,
302
- sessionProtocol: Constants.SessionProtocols.v2,
303
- },
304
- })
305
- const fetch = Fetch.from({
306
- fetch: async (_input, init) => {
307
- const authorization = new Headers(init?.headers).get(Constants.Headers.authorization)
308
- if (!authorization)
309
- return new Response(null, {
310
- status: 402,
311
- headers: { [Constants.Headers.wwwAuthenticate]: serializeChallenge(challenge) },
312
- })
313
- actions.push(deserialize(authorization).action)
314
- return new Response('paid')
315
- },
316
- methods: [session({ account, channelStore, decimals: 0, getClient: () => client })],
317
- })
567
+ const challenge = makeSessionChallenge({ suggestedDeposit: '1000' })
568
+ const fetch = makeSessionFetch(async (_input, init) => {
569
+ const authorization = new Headers(init?.headers).get(Constants.Headers.authorization)
570
+ if (!authorization) return paymentRequired(challenge)
571
+ actions.push(deserialize(authorization).action)
572
+ return new Response('paid')
573
+ }, channelStore)
318
574
 
319
575
  expect(await (await fetch('https://example.com/resource')).text()).toBe('paid')
320
- expect(actions).toEqual(['open'])
576
+ expect(await (await fetch('https://example.com/resource')).text()).toBe('paid')
577
+ expect(actions).toEqual(['open', 'voucher'])
578
+ expect(await channelStore.get(defaultChannelKey)).toMatchObject({
579
+ descriptor: { authorizedSigner: account.address, payer: account.address },
580
+ opened: true,
581
+ })
321
582
  })
322
583
 
323
584
  test('uses a matching server snapshot deposit when checking headroom', async () => {
@@ -519,12 +780,17 @@ describe('precompile client session', () => {
519
780
  expect(openDeposit(payload)).toBe(100n)
520
781
  })
521
782
 
522
- test('rejects open when maxDeposit is below the request amount', async () => {
783
+ test('rejects open above maxDeposit without leaking the Fetch lock', async () => {
523
784
  const method = session({ account, decimals: 0, maxDeposit: '50', getClient: () => client })
785
+ const fetch = Fetch.from({
786
+ fetch: async () => paymentRequired(makeSessionChallenge()),
787
+ methods: [method],
788
+ })
524
789
 
525
- await expect(
526
- method.createCredential({ challenge: makeChallenge(), context: {} }),
527
- ).rejects.toThrow('requested voucher amount 100 exceeds local maxDeposit 50')
790
+ for (let attempt = 0; attempt < 2; attempt++)
791
+ await expect(fetch('https://example.com/resource')).rejects.toThrow(
792
+ 'requested voucher amount 100 exceeds local maxDeposit 50',
793
+ )
528
794
  })
529
795
 
530
796
  test('rejects explicit opening deposits below the request amount', async () => {