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
@@ -3,6 +3,7 @@ import { tempo as tempo_chain } from 'viem/chains'
3
3
 
4
4
  import * as MethodChallenge from '../../../client/internal/MethodChallenge.js'
5
5
  import * as MethodResponse from '../../../client/internal/MethodResponse.js'
6
+ import * as Constants from '../../../Constants.js'
6
7
  import * as Credential from '../../../Credential.js'
7
8
  import * as Method from '../../../Method.js'
8
9
  import * as Account from '../../../viem/Account.js'
@@ -16,6 +17,7 @@ import * as defaults from '../../internal/defaults.js'
16
17
  import * as Methods from '../../Methods.js'
17
18
  import * as Channel from '../precompile/Channel.js'
18
19
  import {
20
+ deserializeSessionReceipt,
19
21
  isEventStream,
20
22
  readSessionChallengeAmount,
21
23
  requireSessionCredentialContext,
@@ -35,6 +37,7 @@ import {
35
37
  } from './CredentialState.js'
36
38
  import { assertWithinMaxDeposit, resolveAutomaticTopUp } from './Runtime.js'
37
39
  import {
40
+ getSessionSnapshot,
38
41
  handleSseNeedVoucher,
39
42
  isTip1034SessionChallenge,
40
43
  postTopUp,
@@ -45,6 +48,67 @@ import {
45
48
 
46
49
  export { sessionContextSchema, type SessionContext } from './CredentialState.js'
47
50
 
51
+ /** Tail promise for each payment scope, used to serialize automatic opens per store. */
52
+ const channelTails = new WeakMap<ChannelStore, Map<string, Promise<void>>>()
53
+
54
+ /** Serializes automatic opens for one payment scope across methods sharing a store. */
55
+ async function lockChannel(store: ChannelStore, key: string) {
56
+ const tails = channelTails.get(store) ?? new Map<string, Promise<void>>()
57
+ channelTails.set(store, tails)
58
+ const previous = tails.get(key)
59
+ let unlock!: () => void
60
+ const current = new Promise<void>((resolve) => {
61
+ unlock = resolve
62
+ })
63
+ tails.set(key, current)
64
+ await previous
65
+ return () => {
66
+ unlock()
67
+ if (tails.get(key) === current) tails.delete(key)
68
+ }
69
+ }
70
+
71
+ function matchesOpen(
72
+ opened: ChannelEntry,
73
+ acknowledgement: { acceptedCumulative: string; channelId: string } | null | undefined,
74
+ ): boolean {
75
+ try {
76
+ return (
77
+ acknowledgement?.channelId.toLowerCase() === opened.channelId.toLowerCase() &&
78
+ BigInt(acknowledgement.acceptedCumulative) === opened.cumulativeAmount
79
+ )
80
+ } catch {
81
+ return false
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Returns whether the server acknowledged an open.
87
+ *
88
+ * Receipts must match the originating challenge, channel, and cumulative amount.
89
+ * Replacement challenges may acknowledge the same channel and amount through a
90
+ * session snapshot. Malformed acknowledgements are ignored.
91
+ */
92
+ function acknowledgesOpen(
93
+ outcome: MethodResponse.AttemptOutcome,
94
+ challenge: TempoSessionChallenge,
95
+ opened: ChannelEntry,
96
+ ): boolean {
97
+ try {
98
+ const receiptHeader = outcome.response?.headers.get(Constants.Headers.paymentReceipt)
99
+ if (receiptHeader) {
100
+ const receipt = deserializeSessionReceipt(receiptHeader)
101
+ if (receipt.challengeId === challenge.id && matchesOpen(opened, receipt)) return true
102
+ }
103
+ } catch {}
104
+ return (
105
+ outcome.challenges?.some((candidate) => {
106
+ if (!isTip1034SessionChallenge(candidate)) return false
107
+ return matchesOpen(opened, getSessionSnapshot(candidate))
108
+ }) ?? false
109
+ )
110
+ }
111
+
48
112
  /**
49
113
  * Creates the low-level TIP-1034 session payment method for use with `Mppx.create()`.
50
114
  *
@@ -100,30 +164,102 @@ export function session(parameters: session.Parameters = {}) {
100
164
  )
101
165
  }
102
166
 
167
+ const resolveCredentialPlan = async (
168
+ resolved: ChallengeContext,
169
+ context: CredentialContext | undefined,
170
+ entry: ChannelEntry | undefined,
171
+ ) =>
172
+ planCredential({
173
+ account: await resolveCredentialAccount(resolved, context, entry),
174
+ entry,
175
+ context,
176
+ decimals,
177
+ maxDeposit,
178
+ resolved,
179
+ })
180
+
103
181
  const method = Method.toClient(Methods.session, {
104
182
  canHandleChallenge: ({ challenge }) => isTip1034SessionChallenge(challenge),
105
183
  context: sessionContextSchema,
106
- async createCredential({ challenge, context }) {
184
+ async createCredential(parameters) {
185
+ const { challenge, context } = parameters
107
186
  const resolved = await resolveChallengeContext({
108
187
  challenge,
109
188
  escrowOverride,
110
189
  getClient,
111
190
  })
112
- const entry = await store.get(resolved.key)
113
- const account = await resolveCredentialAccount(resolved, context, entry)
114
- const payload = await executeCredentialPlan(
115
- planCredential({
116
- account,
117
- entry,
118
- context,
119
- decimals,
120
- maxDeposit,
121
- resolved,
122
- }),
123
- sink,
124
- AutoSwap.resolve(context?.autoSwap ?? autoSwapParameter, AutoSwap.defaultCurrencies),
125
- )
126
- return serializeCredential(challenge, payload, resolved.chainId, account)
191
+ const attempt = MethodResponse.getAttempt(parameters)
192
+ let release = () => {}
193
+ try {
194
+ let entry = await store.get(resolved.key)
195
+ let plan = await resolveCredentialPlan(resolved, context, entry)
196
+ if (attempt && plan.type === 'open') {
197
+ release = await lockChannel(store, resolved.key)
198
+ const nextEntry = await store.get(resolved.key)
199
+ if (nextEntry?.channelId !== entry?.channelId) {
200
+ entry = nextEntry
201
+ if (entry?.opened) {
202
+ await attempt.prepare()
203
+ entry = await store.get(resolved.key)
204
+ }
205
+ plan = await resolveCredentialPlan(resolved, context, entry)
206
+ }
207
+ }
208
+ let pendingOpen: ChannelEntry | undefined
209
+ // Defer opens only when low-level Fetch owns the response lifecycle.
210
+ // SessionManager unregisters this hook and keeps its existing transaction boundary.
211
+ const credentialSink =
212
+ attempt && plan.type === 'open'
213
+ ? {
214
+ store: {
215
+ get: (key: string) => store.get(key),
216
+ async set(next: ChannelEntry) {
217
+ pendingOpen = next
218
+ },
219
+ delete: (key: string) => store.delete(key),
220
+ },
221
+ notifyUpdate() {},
222
+ }
223
+ : sink
224
+ const payload = await executeCredentialPlan(
225
+ plan,
226
+ credentialSink,
227
+ AutoSwap.resolve(context?.autoSwap ?? autoSwapParameter, AutoSwap.defaultCurrencies),
228
+ )
229
+ const credential = await serializeCredential(
230
+ challenge,
231
+ payload,
232
+ resolved.chainId,
233
+ plan.account,
234
+ )
235
+ if (attempt && pendingOpen) {
236
+ const opened = pendingOpen
237
+ attempt.settle = async (outcome) => {
238
+ const accepted =
239
+ outcome.status === 'accepted' || acknowledgesOpen(outcome, challenge, opened)
240
+ if (!accepted && outcome.status === 'pending') return false
241
+ try {
242
+ if (accepted) {
243
+ const current = await store.get(resolved.key)
244
+ if (
245
+ !current?.opened ||
246
+ current.channelId.toLowerCase() !== opened.channelId.toLowerCase()
247
+ ) {
248
+ await store.set(opened)
249
+ sink.notifyUpdate(opened)
250
+ }
251
+ }
252
+ return true
253
+ } finally {
254
+ release()
255
+ }
256
+ }
257
+ } else release()
258
+ return credential
259
+ } catch (error) {
260
+ release()
261
+ throw error
262
+ }
127
263
  },
128
264
  })
129
265
 
@@ -113,6 +113,20 @@ function makeChannelStore(seed: readonly ChannelEntry[] = []) {
113
113
  return { store, set, delete: remove, map }
114
114
  }
115
115
 
116
+ function makeDurableChannelStore() {
117
+ const backend = new Map<string, string>()
118
+ const store = createJsonChannelStore({
119
+ get: (key) => backend.get(key),
120
+ set: (key, value) => {
121
+ backend.set(key, value)
122
+ },
123
+ delete: (key) => {
124
+ backend.delete(key)
125
+ },
126
+ })
127
+ return { backend, store }
128
+ }
129
+
116
130
  function makeChallenge(overrides: Record<string, unknown> = {}): Challenge.Challenge {
117
131
  return Challenge.from({
118
132
  id: challengeId,
@@ -657,7 +671,7 @@ describe('Session', () => {
657
671
  })
658
672
  })
659
673
 
660
- test('rolls back an optimistic open when a replacement challenge does not reference it', async () => {
674
+ test('rolls back optimistic opens across replacement challenges', async () => {
661
675
  const { store, delete: remove } = makeChannelStore()
662
676
  const postedPayloads: SessionCredentialPayload[] = []
663
677
  const challenge = (feePayer: boolean) =>
@@ -669,6 +683,8 @@ describe('Session', () => {
669
683
  feePayer,
670
684
  },
671
685
  })
686
+ const original = challenge(true)
687
+ const replacement = challenge(false)
672
688
  let openCount = 0
673
689
  const mockFetch = vi.fn().mockImplementation((_input, init?: RequestInit) => {
674
690
  const authorization = new Headers(init?.headers).get(Constants.Headers.authorization)
@@ -677,10 +693,11 @@ describe('Session', () => {
677
693
  : undefined
678
694
  if (payload) postedPayloads.push(payload)
679
695
 
680
- if (!payload) return Promise.resolve(make402Response(challenge(true)))
696
+ if (!payload) return Promise.resolve(make402Response(original))
681
697
  if (payload.action === 'open') {
682
698
  openCount++
683
- if (openCount === 1) return Promise.resolve(make402Response(challenge(false)))
699
+ if (openCount === 1) return Promise.resolve(make402Response(replacement))
700
+ if (openCount === 2) return Promise.resolve(make402Response(original))
684
701
  return Promise.resolve(makeOkResponse())
685
702
  }
686
703
  return Promise.resolve(new Response('unexpected voucher', { status: 500 }))
@@ -696,12 +713,33 @@ describe('Session', () => {
696
713
  const response = await s.fetch('https://api.example.com/data')
697
714
 
698
715
  expect(response.status).toBe(200)
699
- // A replacement challenge with no snapshot means the server did not
700
- // acknowledge the optimistic open, so the next credential must open again.
701
- expect(postedPayloads.map((payload) => payload.action)).toEqual(['open', 'open'])
702
- expect(postedPayloads[0]?.channelId).not.toBe(postedPayloads[1]?.channelId)
703
- expect(remove).toHaveBeenCalledOnce()
704
- expect(s.channelId).toBe(postedPayloads[1]?.channelId)
716
+ expect(postedPayloads.map((payload) => payload.action)).toEqual(['open', 'open', 'open'])
717
+ expect(new Set(postedPayloads.map((payload) => payload.channelId)).size).toBe(3)
718
+ expect(remove).toHaveBeenCalledTimes(2)
719
+ expect(s.channelId).toBe(postedPayloads[2]?.channelId)
720
+ })
721
+
722
+ test('reuses prepared voucher state when an identical challenge succeeds', async () => {
723
+ const vouchers: Extract<SessionCredentialPayload, { action: 'voucher' }>[] = []
724
+ const mockFetch = vi.fn().mockImplementation((_input, init?: RequestInit) => {
725
+ const authorization = new Headers(init?.headers).get(Constants.Headers.authorization)
726
+ if (!authorization) return Promise.resolve(make402Response())
727
+ const payload = Credential.deserialize<SessionCredentialPayload>(authorization).payload
728
+ if (payload.action !== 'voucher') throw new Error('expected voucher')
729
+ vouchers.push(payload)
730
+ return Promise.resolve(vouchers.length === 1 ? make402Response() : makeOkResponse())
731
+ })
732
+ const s = sessionManager({
733
+ account,
734
+ client,
735
+ fetch: mockFetch as typeof globalThis.fetch,
736
+ channelStore: makeChannelStore([channelEntry()]).store,
737
+ })
738
+
739
+ expect((await s.fetch('https://api.example.com/data')).status).toBe(200)
740
+ expect(vouchers.map((voucher) => voucher.cumulativeAmount)).toEqual(['2000000', '2000000'])
741
+ expect(s.cumulative).toBe(2_000_000n)
742
+ expect(s.state.status).toBe('active')
705
743
  })
706
744
 
707
745
  test('does not bootstrap when disabled', async () => {
@@ -989,6 +1027,60 @@ describe('Session', () => {
989
1027
  })
990
1028
  })
991
1029
 
1030
+ test('preserves a snapshot-acknowledged open when its voucher fails', async () => {
1031
+ const { backend, store } = makeDurableChannelStore()
1032
+ const payloads: SessionCredentialPayload[] = []
1033
+ const mockFetch = vi.fn().mockImplementation((_input, init?: RequestInit) => {
1034
+ const authorization = new Headers(init?.headers).get(Constants.Headers.authorization)
1035
+ if (!authorization)
1036
+ return Promise.resolve(make402Response(makeChallenge({ suggestedDeposit: '10000000' })))
1037
+ const payload = Credential.deserialize<SessionCredentialPayload>(authorization).payload
1038
+ payloads.push(payload)
1039
+ if (payload.action !== 'open')
1040
+ return Promise.resolve(new Response('failed', { status: 500 }))
1041
+ return Promise.resolve(
1042
+ make402Response(
1043
+ makeChallenge({
1044
+ methodDetails: {
1045
+ escrowContract: tip20ChannelEscrow,
1046
+ chainId: 4217,
1047
+ sessionProtocol: Constants.SessionProtocols.v2,
1048
+ sessionSnapshot: {
1049
+ acceptedCumulative: '1000000',
1050
+ chainId: 4217,
1051
+ channelId: payload.channelId,
1052
+ deposit: '10000000',
1053
+ descriptor: payload.descriptor,
1054
+ escrow: tip20ChannelEscrow,
1055
+ requiredCumulative: '2000000',
1056
+ settled: '0',
1057
+ spent: '1000000',
1058
+ units: 1,
1059
+ },
1060
+ },
1061
+ }),
1062
+ ),
1063
+ )
1064
+ })
1065
+ const s = sessionManager({
1066
+ account,
1067
+ client,
1068
+ fetch: mockFetch as typeof globalThis.fetch,
1069
+ maxDeposit: '10',
1070
+ channelStore: store,
1071
+ })
1072
+
1073
+ expect((await s.fetch('https://api.example.com/data')).status).toBe(500)
1074
+ expect(payloads.map((payload) => payload.action)).toEqual(['open', 'voucher'])
1075
+ expect(s.channelId).toBe(payloads[0]?.channelId)
1076
+ expect(s.cumulative).toBe(1_000_000n)
1077
+ expect(JSON.parse([...backend.values()][0]!)).toMatchObject({
1078
+ channelId: payloads[0]?.channelId,
1079
+ cumulativeAmount: '1000000',
1080
+ opened: true,
1081
+ })
1082
+ })
1083
+
992
1084
  test('preemptively top-ups before signing an HTTP voucher that exceeds deposit', async () => {
993
1085
  const postedPayloads: SessionCredentialPayload[] = []
994
1086
  let challengeCount = 0
@@ -1055,6 +1147,121 @@ describe('Session', () => {
1055
1147
  units: 2,
1056
1148
  })
1057
1149
  })
1150
+
1151
+ test('preserves a successful automatic top-up when the paid retry fails', async () => {
1152
+ const { map, store } = makeChannelStore()
1153
+ const payloads: SessionCredentialPayload[] = []
1154
+ const mockFetch = vi.fn().mockImplementation((_input, init?: RequestInit) => {
1155
+ const authorization = new Headers(init?.headers).get(Constants.Headers.authorization)
1156
+ if (!authorization)
1157
+ return Promise.resolve(make402Response(makeChallenge({ suggestedDeposit: '1000000' })))
1158
+ const payload = Credential.deserialize<SessionCredentialPayload>(authorization).payload
1159
+ payloads.push(payload)
1160
+ if (payload.action === 'open')
1161
+ return Promise.resolve(
1162
+ new Response('ok', {
1163
+ headers: {
1164
+ [Constants.Headers.paymentReceipt]: serializeSessionReceipt(
1165
+ createSessionReceipt({
1166
+ acceptedCumulative: 1_000_000n,
1167
+ challengeId,
1168
+ channelId: payload.channelId,
1169
+ spent: 1_000_000n,
1170
+ units: 3,
1171
+ }),
1172
+ ),
1173
+ },
1174
+ }),
1175
+ )
1176
+ if (payload.action === 'topUp') return Promise.resolve(new Response(null, { status: 204 }))
1177
+ return Promise.resolve(make402Response(makeChallenge({ suggestedDeposit: '1000000' })))
1178
+ })
1179
+ const s = sessionManager({
1180
+ account,
1181
+ client,
1182
+ fetch: mockFetch as typeof globalThis.fetch,
1183
+ maxDeposit: '3',
1184
+ channelStore: store,
1185
+ })
1186
+
1187
+ await s.fetch('https://api.example.com/data')
1188
+ expect((await s.fetch('https://api.example.com/data')).status).toBe(402)
1189
+
1190
+ expect(payloads.map((payload) => payload.action)).toEqual([
1191
+ 'open',
1192
+ 'topUp',
1193
+ 'voucher',
1194
+ 'voucher',
1195
+ 'voucher',
1196
+ ])
1197
+ expect(s.cumulative).toBe(1_000_000n)
1198
+ expect(s.state).toMatchObject({ status: 'active', deposit: '2000000', units: 3 })
1199
+ expect([...map.values()][0]).toMatchObject({
1200
+ cumulativeAmount: 1_000_000n,
1201
+ deposit: 2_000_000n,
1202
+ })
1203
+ })
1204
+
1205
+ test('preserves a topped-up resumed channel in durable storage after payment fails', async () => {
1206
+ const { store } = makeDurableChannelStore()
1207
+ const seeded = channelEntry({ cumulativeAmount: 1_000_000n, deposit: 1_000_000n })
1208
+ await store.set(seeded)
1209
+ const payloads: SessionCredentialPayload[] = []
1210
+ const mockFetch = vi.fn().mockImplementation((_input, init?: RequestInit) => {
1211
+ const authorization = new Headers(init?.headers).get(Constants.Headers.authorization)
1212
+ if (!authorization)
1213
+ return Promise.resolve(make402Response(makeChallenge({ suggestedDeposit: '1000000' })))
1214
+ const payload = Credential.deserialize<SessionCredentialPayload>(authorization).payload
1215
+ payloads.push(payload)
1216
+ return Promise.resolve(
1217
+ payload.action === 'topUp'
1218
+ ? new Response(null, { status: 204 })
1219
+ : new Response('failed', { status: 500 }),
1220
+ )
1221
+ })
1222
+ const s = sessionManager({
1223
+ account,
1224
+ client,
1225
+ fetch: mockFetch as typeof globalThis.fetch,
1226
+ maxDeposit: '3',
1227
+ channelStore: store,
1228
+ })
1229
+
1230
+ expect((await s.fetch('https://api.example.com/data')).status).toBe(500)
1231
+
1232
+ expect(payloads.map((payload) => payload.action)).toEqual(['topUp', 'voucher'])
1233
+ expect(s.cumulative).toBe(1_000_000n)
1234
+ expect(s.state).toMatchObject({ status: 'active', deposit: '2000000' })
1235
+ await expect(store.get(entryKey(seeded))).resolves.toMatchObject({
1236
+ cumulativeAmount: 1_000_000n,
1237
+ deposit: 2_000_000n,
1238
+ opened: true,
1239
+ })
1240
+ })
1241
+
1242
+ test.each([
1243
+ ['new', false],
1244
+ ['replacement', true],
1245
+ ] as const)('removes a failed %s durable channel', async (_kind, seeded) => {
1246
+ const { backend, store } = makeDurableChannelStore()
1247
+ if (seeded) await store.set(channelEntry())
1248
+ const mockFetch = vi.fn().mockImplementation((_input, init?: RequestInit) => {
1249
+ const authorization = new Headers(init?.headers).get(Constants.Headers.authorization)
1250
+ return Promise.resolve(
1251
+ authorization ? new Response('failed', { status: 500 }) : make402Response(),
1252
+ )
1253
+ })
1254
+ const s = sessionManager({
1255
+ account,
1256
+ client,
1257
+ fetch: mockFetch as typeof globalThis.fetch,
1258
+ channelStore: store,
1259
+ })
1260
+
1261
+ expect((await s.fetch('https://api.example.com/data')).status).toBe(500)
1262
+ expect(s.channelId).toBeUndefined()
1263
+ expect(backend.size).toBe(0)
1264
+ })
1058
1265
  })
1059
1266
 
1060
1267
  describe('.topUp()', () => {
@@ -58,6 +58,7 @@ import { applyTopUpResult, resolveManualTopUp, type TopUpRequirement } from './T
58
58
  import {
59
59
  openWebSocketSession,
60
60
  prepareWebSocketSession,
61
+ probeWebSocketSession,
61
62
  type WebSocketDriverOptions,
62
63
  } from './Transports.js'
63
64
 
@@ -243,11 +244,14 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
243
244
 
244
245
  /** Removes a failed channel from candidacy for the rest of this manager's life. */
245
246
  async function ignoreChannel(entry: ChannelEntry) {
247
+ const key = entryKey(entry)
246
248
  ignoredChannelIds.add(entry.channelId)
247
- await Promise.resolve(backing.delete(entryKey(entry))).catch(() => undefined)
249
+ channelUse?.seenExisting.delete(key)
250
+ if (channelUse?.resumed === entry) channelUse.resumed = undefined
251
+ await Promise.resolve(backing.delete(key)).catch(() => undefined)
248
252
  }
249
253
 
250
- async function rollbackCreatedChannelsForRetry() {
254
+ async function rollbackCreatedChannels() {
251
255
  const use = channelUse
252
256
  if (!use?.created.size) return
253
257
  for (const [key, entry] of use.created) {
@@ -255,19 +259,31 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
255
259
  await Promise.resolve(backing.delete(key)).catch(() => undefined)
256
260
  }
257
261
  use.created.clear()
258
- restoreRuntime(use.previous)
262
+ await restoreRuntime(use.committed ?? use.previous)
259
263
  }
260
264
 
261
- function referencesCreatedChannel(challenge: Challenge.Challenge): boolean {
265
+ /** Commits a newly created channel once an authoritative server snapshot references it. */
266
+ function commitReferencedChannel(challenge: Challenge.Challenge): boolean {
262
267
  const snapshot = Constants.getMethodDetail<{ channelId?: unknown }>(
263
268
  challenge.request.methodDetails,
264
269
  Constants.MethodDetailKeys.sessionSnapshot,
265
270
  )
266
271
  if (typeof snapshot?.channelId !== 'string') return false
267
272
  const use = channelUse
268
- if (!use?.created.size) return false
269
- for (const entry of use.created.values()) {
270
- if (entry.channelId.toLowerCase() === snapshot.channelId.toLowerCase()) return true
273
+ if (!use?.created.size || !runtime.channel) return false
274
+ for (const [key, entry] of use.created) {
275
+ if (
276
+ entry.channelId.toLowerCase() !== snapshot.channelId.toLowerCase() ||
277
+ runtime.channel.channelId.toLowerCase() !== snapshot.channelId.toLowerCase()
278
+ )
279
+ continue
280
+ use.created.delete(key)
281
+ use.committed = captureRuntimeStateSnapshot({
282
+ channel: runtime.channel,
283
+ spent: runtime.spent,
284
+ state: runtime.state,
285
+ })
286
+ return true
271
287
  }
272
288
  return false
273
289
  }
@@ -276,6 +292,14 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
276
292
  return dispatchSessionEvent(runtime, event)
277
293
  }
278
294
 
295
+ function activeUnits() {
296
+ const state =
297
+ runtime.state.status === 'active'
298
+ ? runtime.state
299
+ : (channelUse?.committed?.state ?? channelUse?.previous.state)
300
+ return state?.status === 'active' ? state.units : 0
301
+ }
302
+
279
303
  function commitDurableTopUp(entry: ChannelEntry) {
280
304
  const use = channelUse
281
305
  const baseline = use?.committed?.channel?.entry ?? use?.resumed
@@ -312,7 +336,7 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
312
336
  challengeId: runtime.lastChallenge.id,
313
337
  entry,
314
338
  spent: runtime.spent.toString(),
315
- units: 0,
339
+ units: activeUnits(),
316
340
  })
317
341
  }
318
342
  commitDurableTopUp(entry)
@@ -332,11 +356,15 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
332
356
  const use = channelUse
333
357
  const isRepeatedRetryChallenge =
334
358
  use && use.challengesReceived > 0 && challenge.id === runtime.lastChallenge?.id
335
- if (!referencesCreatedChannel(challenge)) {
336
- if (use?.created.size) await rollbackCreatedChannelsForRetry()
337
- else if (isRepeatedRetryChallenge) restoreRuntime(use.previous)
338
- }
359
+ const isSameRetryChallenge =
360
+ isRepeatedRetryChallenge &&
361
+ Challenge.serialize(challenge) === Challenge.serialize(runtime.lastChallenge!)
339
362
  if (use) use.challengesReceived++
363
+ if (isSameRetryChallenge) return undefined
364
+ if (!commitReferencedChannel(challenge)) {
365
+ if (use?.created.size) await rollbackCreatedChannels()
366
+ else if (isRepeatedRetryChallenge) await restoreRuntime(use.committed ?? use.previous)
367
+ }
340
368
  runtime.lastChallenge = challenge
341
369
  dispatch({ type: 'challengeReceived', challengeId: challenge.id })
342
370
  return undefined
@@ -558,7 +586,7 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
558
586
  challengeId: runtime.lastChallenge.id,
559
587
  entry: applied.channel,
560
588
  spent: runtime.spent.toString(),
561
- units: runtime.state.status === 'active' ? runtime.state.units : 0,
589
+ units: activeUnits(),
562
590
  })
563
591
  commitDurableTopUp(applied.channel)
564
592
  }
@@ -589,7 +617,7 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
589
617
  })
590
618
  }
591
619
 
592
- function restoreCumulative(channelId: Hex.Hex, cumulativeAmount: bigint) {
620
+ async function restoreCumulative(channelId: Hex.Hex, cumulativeAmount: bigint) {
593
621
  const restored = restoreCumulativeAuthorization({
594
622
  channel: runtime.channel,
595
623
  channelId,
@@ -606,14 +634,16 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
606
634
  spent: restored.spent,
607
635
  units: restored.units,
608
636
  })
637
+ await backing.set(runtime.channel)
609
638
  }
610
639
  }
611
640
 
612
- function restoreRuntime(snapshot: RuntimeSnapshot) {
641
+ async function restoreRuntime(snapshot: RuntimeSnapshot) {
613
642
  const restored = restoreRuntimeStateSnapshot(snapshot, runtime.channel)
614
643
  runtime.channel = restored.channel
615
644
  runtime.spent = restored.spent
616
645
  runtime.state = restored.state
646
+ if (runtime.channel?.opened) await backing.set(runtime.channel)
617
647
  }
618
648
 
619
649
  function toPaymentResponse(response: Response): PaymentResponse {
@@ -679,7 +709,8 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
679
709
  try {
680
710
  response = await wrappedFetch(input, effectiveInit)
681
711
  } catch (error) {
682
- restoreRuntime(use.committed ?? previous)
712
+ if (use.created.size) await rollbackCreatedChannels()
713
+ else await restoreRuntime(use.committed ?? previous)
683
714
  if (await retryWithoutResumed()) continue
684
715
  throw error
685
716
  }
@@ -706,7 +737,8 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
706
737
  }
707
738
  }
708
739
  if (!attemptedHttpManagement && !paymentResponse.ok && !paymentResponse.receipt) {
709
- restoreRuntime(use.committed ?? previous)
740
+ if (use.created.size) await rollbackCreatedChannels()
741
+ else await restoreRuntime(use.committed ?? previous)
710
742
  if (await retryWithoutResumed()) continue
711
743
  return paymentResponse
712
744
  }
@@ -838,6 +870,20 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
838
870
  setSocketSession(session) {
839
871
  runtime.socketSession = session
840
872
  },
873
+ async refreshChallenge() {
874
+ const refreshed = await probeWebSocketSession({
875
+ fetch: config.fetch,
876
+ input: httpUrl,
877
+ probeInit: requestInitWithSessionHint(
878
+ httpUrl,
879
+ signalInit,
880
+ runtime.channel?.opened ? runtime.channel.channelId : undefined,
881
+ ),
882
+ signal: init?.signal,
883
+ })
884
+ runtime.lastChallenge = refreshed.challenge
885
+ return refreshed.challenge
886
+ },
841
887
  assertVoucherWithinLocalLimit,
842
888
  acceptReceipt: updateSpentFromReceipt,
843
889
  rejectCloseReady: receipts.rejectCloseReady,