mppx 0.6.31 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (477) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/Challenge.d.ts.map +1 -1
  3. package/dist/Challenge.js +9 -7
  4. package/dist/Challenge.js.map +1 -1
  5. package/dist/Constants.d.ts +46 -0
  6. package/dist/Constants.d.ts.map +1 -0
  7. package/dist/Constants.js +46 -0
  8. package/dist/Constants.js.map +1 -0
  9. package/dist/Credential.d.ts.map +1 -1
  10. package/dist/Credential.js +5 -4
  11. package/dist/Credential.js.map +1 -1
  12. package/dist/Method.d.ts +32 -4
  13. package/dist/Method.d.ts.map +1 -1
  14. package/dist/Method.js +5 -2
  15. package/dist/Method.js.map +1 -1
  16. package/dist/Receipt.d.ts.map +1 -1
  17. package/dist/Receipt.js +3 -2
  18. package/dist/Receipt.js.map +1 -1
  19. package/dist/cli/cli.d.ts.map +1 -1
  20. package/dist/cli/cli.js +19 -11
  21. package/dist/cli/cli.js.map +1 -1
  22. package/dist/cli/plugins/tempo.d.ts.map +1 -1
  23. package/dist/cli/plugins/tempo.js +17 -6
  24. package/dist/cli/plugins/tempo.js.map +1 -1
  25. package/dist/cli/utils.d.ts +5 -0
  26. package/dist/cli/utils.d.ts.map +1 -1
  27. package/dist/cli/utils.js +10 -0
  28. package/dist/cli/utils.js.map +1 -1
  29. package/dist/client/Methods.d.ts +5 -2
  30. package/dist/client/Methods.d.ts.map +1 -1
  31. package/dist/client/Methods.js +5 -2
  32. package/dist/client/Methods.js.map +1 -1
  33. package/dist/client/Transport.d.ts.map +1 -1
  34. package/dist/client/Transport.js +4 -5
  35. package/dist/client/Transport.js.map +1 -1
  36. package/dist/client/index.d.ts +2 -1
  37. package/dist/client/index.d.ts.map +1 -1
  38. package/dist/client/index.js +2 -1
  39. package/dist/client/index.js.map +1 -1
  40. package/dist/client/internal/Fetch.d.ts.map +1 -1
  41. package/dist/client/internal/Fetch.js +14 -6
  42. package/dist/client/internal/Fetch.js.map +1 -1
  43. package/dist/evm/server/Methods.d.ts +1 -1
  44. package/dist/evm/server/Methods.d.ts.map +1 -1
  45. package/dist/index.d.ts +1 -0
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +1 -0
  48. package/dist/index.js.map +1 -1
  49. package/dist/internal/AcceptPayment.d.ts +3 -0
  50. package/dist/internal/AcceptPayment.d.ts.map +1 -1
  51. package/dist/internal/AcceptPayment.js +15 -11
  52. package/dist/internal/AcceptPayment.js.map +1 -1
  53. package/dist/mcp-sdk/client/McpClient.d.ts +12 -5
  54. package/dist/mcp-sdk/client/McpClient.d.ts.map +1 -1
  55. package/dist/mcp-sdk/client/McpClient.js +55 -42
  56. package/dist/mcp-sdk/client/McpClient.js.map +1 -1
  57. package/dist/server/Mppx.d.ts +11 -3
  58. package/dist/server/Mppx.d.ts.map +1 -1
  59. package/dist/server/Mppx.js +76 -27
  60. package/dist/server/Mppx.js.map +1 -1
  61. package/dist/server/Response.d.ts.map +1 -1
  62. package/dist/server/Response.js +2 -1
  63. package/dist/server/Response.js.map +1 -1
  64. package/dist/server/Transport.d.ts.map +1 -1
  65. package/dist/server/Transport.js +4 -3
  66. package/dist/server/Transport.js.map +1 -1
  67. package/dist/server/index.d.ts +1 -0
  68. package/dist/server/index.d.ts.map +1 -1
  69. package/dist/server/index.js +1 -0
  70. package/dist/server/index.js.map +1 -1
  71. package/dist/stripe/client/Charge.d.ts +1 -1
  72. package/dist/stripe/client/Charge.d.ts.map +1 -1
  73. package/dist/stripe/client/Charge.js +3 -1
  74. package/dist/stripe/client/Charge.js.map +1 -1
  75. package/dist/stripe/server/Charge.d.ts +1 -1
  76. package/dist/stripe/server/Charge.d.ts.map +1 -1
  77. package/dist/stripe/server/Charge.js +9 -2
  78. package/dist/stripe/server/Charge.js.map +1 -1
  79. package/dist/stripe/server/Methods.d.ts +1 -1
  80. package/dist/stripe/server/Methods.d.ts.map +1 -1
  81. package/dist/stripe/server/internal/html.gen.d.ts +1 -1
  82. package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
  83. package/dist/stripe/server/internal/html.gen.js +1 -1
  84. package/dist/stripe/server/internal/html.gen.js.map +1 -1
  85. package/dist/tempo/Methods.d.ts +18 -0
  86. package/dist/tempo/Methods.d.ts.map +1 -1
  87. package/dist/tempo/Methods.js +16 -1
  88. package/dist/tempo/Methods.js.map +1 -1
  89. package/dist/tempo/client/Charge.d.ts +6 -0
  90. package/dist/tempo/client/Charge.d.ts.map +1 -1
  91. package/dist/tempo/client/Charge.js +9 -2
  92. package/dist/tempo/client/Charge.js.map +1 -1
  93. package/dist/tempo/client/Methods.d.ts +36 -7
  94. package/dist/tempo/client/Methods.d.ts.map +1 -1
  95. package/dist/tempo/client/Methods.js +12 -5
  96. package/dist/tempo/client/Methods.js.map +1 -1
  97. package/dist/tempo/client/index.d.ts +7 -4
  98. package/dist/tempo/client/index.d.ts.map +1 -1
  99. package/dist/tempo/client/index.js +5 -3
  100. package/dist/tempo/client/index.js.map +1 -1
  101. package/dist/tempo/index.d.ts +1 -0
  102. package/dist/tempo/index.d.ts.map +1 -1
  103. package/dist/tempo/index.js +1 -0
  104. package/dist/tempo/index.js.map +1 -1
  105. package/dist/tempo/internal/fee-payer.d.ts +21 -1
  106. package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
  107. package/dist/tempo/internal/fee-payer.js +109 -4
  108. package/dist/tempo/internal/fee-payer.js.map +1 -1
  109. package/dist/tempo/{client → legacy/client}/ChannelOps.d.ts +19 -6
  110. package/dist/tempo/legacy/client/ChannelOps.d.ts.map +1 -0
  111. package/dist/tempo/{client → legacy/client}/ChannelOps.js +9 -3
  112. package/dist/tempo/legacy/client/ChannelOps.js.map +1 -0
  113. package/dist/tempo/{client → legacy/client}/Session.d.ts +23 -4
  114. package/dist/tempo/legacy/client/Session.d.ts.map +1 -0
  115. package/dist/tempo/{client → legacy/client}/Session.js +14 -7
  116. package/dist/tempo/legacy/client/Session.js.map +1 -0
  117. package/dist/tempo/{client → legacy/client}/SessionManager.d.ts +20 -5
  118. package/dist/tempo/legacy/client/SessionManager.d.ts.map +1 -0
  119. package/dist/tempo/{client → legacy/client}/SessionManager.js +20 -16
  120. package/dist/tempo/legacy/client/SessionManager.js.map +1 -0
  121. package/dist/tempo/legacy/client/index.d.ts +7 -0
  122. package/dist/tempo/legacy/client/index.d.ts.map +1 -0
  123. package/dist/tempo/legacy/client/index.js +5 -0
  124. package/dist/tempo/legacy/client/index.js.map +1 -0
  125. package/dist/tempo/legacy/index.d.ts +7 -0
  126. package/dist/tempo/legacy/index.d.ts.map +1 -0
  127. package/dist/tempo/legacy/index.js +7 -0
  128. package/dist/tempo/legacy/index.js.map +1 -0
  129. package/dist/tempo/{server → legacy/server}/Session.d.ts +28 -11
  130. package/dist/tempo/legacy/server/Session.d.ts.map +1 -0
  131. package/dist/tempo/{server → legacy/server}/Session.js +12 -10
  132. package/dist/tempo/legacy/server/Session.js.map +1 -0
  133. package/dist/tempo/legacy/server/index.d.ts +5 -0
  134. package/dist/tempo/legacy/server/index.d.ts.map +1 -0
  135. package/dist/tempo/legacy/server/index.js +5 -0
  136. package/dist/tempo/legacy/server/index.js.map +1 -0
  137. package/dist/tempo/{session → legacy/session}/Chain.d.ts +30 -23
  138. package/dist/tempo/legacy/session/Chain.d.ts.map +1 -0
  139. package/dist/tempo/{session → legacy/session}/Chain.js +12 -11
  140. package/dist/tempo/legacy/session/Chain.js.map +1 -0
  141. package/dist/tempo/{session → legacy/session}/Channel.d.ts +1 -0
  142. package/dist/tempo/legacy/session/Channel.d.ts.map +1 -0
  143. package/dist/tempo/legacy/session/Channel.js.map +1 -0
  144. package/dist/tempo/legacy/session/ChannelStore.d.ts +22 -0
  145. package/dist/tempo/legacy/session/ChannelStore.d.ts.map +1 -0
  146. package/dist/tempo/legacy/session/ChannelStore.js +6 -0
  147. package/dist/tempo/legacy/session/ChannelStore.js.map +1 -0
  148. package/dist/tempo/legacy/session/Types.d.ts +73 -0
  149. package/dist/tempo/legacy/session/Types.d.ts.map +1 -0
  150. package/dist/tempo/legacy/session/Types.js.map +1 -0
  151. package/dist/tempo/{session → legacy/session}/Voucher.d.ts +4 -4
  152. package/dist/tempo/legacy/session/Voucher.d.ts.map +1 -0
  153. package/dist/tempo/{session → legacy/session}/Voucher.js +1 -1
  154. package/dist/tempo/legacy/session/Voucher.js.map +1 -0
  155. package/dist/tempo/{session → legacy/session}/escrow.abi.d.ts +1 -0
  156. package/dist/tempo/{session → legacy/session}/escrow.abi.d.ts.map +1 -1
  157. package/dist/tempo/{session → legacy/session}/escrow.abi.js +1 -0
  158. package/dist/tempo/legacy/session/escrow.abi.js.map +1 -0
  159. package/dist/tempo/legacy/session/index.d.ts +9 -0
  160. package/dist/tempo/legacy/session/index.d.ts.map +1 -0
  161. package/dist/tempo/legacy/session/index.js +9 -0
  162. package/dist/tempo/legacy/session/index.js.map +1 -0
  163. package/dist/tempo/server/Charge.d.ts +1 -1
  164. package/dist/tempo/server/Charge.d.ts.map +1 -1
  165. package/dist/tempo/server/Charge.js +13 -16
  166. package/dist/tempo/server/Charge.js.map +1 -1
  167. package/dist/tempo/server/Methods.d.ts +63 -6
  168. package/dist/tempo/server/Methods.d.ts.map +1 -1
  169. package/dist/tempo/server/Methods.js +36 -8
  170. package/dist/tempo/server/Methods.js.map +1 -1
  171. package/dist/tempo/server/Subscription.d.ts +1 -1
  172. package/dist/tempo/server/Subscription.d.ts.map +1 -1
  173. package/dist/tempo/server/index.d.ts +6 -5
  174. package/dist/tempo/server/index.d.ts.map +1 -1
  175. package/dist/tempo/server/index.js +5 -5
  176. package/dist/tempo/server/index.js.map +1 -1
  177. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  178. package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
  179. package/dist/tempo/server/internal/html.gen.js +1 -1
  180. package/dist/tempo/server/internal/html.gen.js.map +1 -1
  181. package/dist/tempo/server/internal/request-body.d.ts +7 -2
  182. package/dist/tempo/server/internal/request-body.d.ts.map +1 -1
  183. package/dist/tempo/server/internal/request-body.js +20 -3
  184. package/dist/tempo/server/internal/request-body.js.map +1 -1
  185. package/dist/tempo/server/internal/transport.d.ts +8 -4
  186. package/dist/tempo/server/internal/transport.d.ts.map +1 -1
  187. package/dist/tempo/server/internal/transport.js +8 -7
  188. package/dist/tempo/server/internal/transport.js.map +1 -1
  189. package/dist/tempo/session/Snapshot.d.ts +32 -0
  190. package/dist/tempo/session/Snapshot.d.ts.map +1 -0
  191. package/dist/tempo/session/Snapshot.js +37 -0
  192. package/dist/tempo/session/Snapshot.js.map +1 -0
  193. package/dist/tempo/session/client/ChannelOps.d.ts +82 -0
  194. package/dist/tempo/session/client/ChannelOps.d.ts.map +1 -0
  195. package/dist/tempo/session/client/ChannelOps.js +204 -0
  196. package/dist/tempo/session/client/ChannelOps.js.map +1 -0
  197. package/dist/tempo/session/client/CredentialState.d.ts +262 -0
  198. package/dist/tempo/session/client/CredentialState.d.ts.map +1 -0
  199. package/dist/tempo/session/client/CredentialState.js +417 -0
  200. package/dist/tempo/session/client/CredentialState.js.map +1 -0
  201. package/dist/tempo/session/client/ReceiptCoordinator.d.ts +26 -0
  202. package/dist/tempo/session/client/ReceiptCoordinator.d.ts.map +1 -0
  203. package/dist/tempo/session/client/ReceiptCoordinator.js +61 -0
  204. package/dist/tempo/session/client/ReceiptCoordinator.js.map +1 -0
  205. package/dist/tempo/session/client/Runtime.d.ts +464 -0
  206. package/dist/tempo/session/client/Runtime.d.ts.map +1 -0
  207. package/dist/tempo/session/client/Runtime.js +499 -0
  208. package/dist/tempo/session/client/Runtime.js.map +1 -0
  209. package/dist/tempo/session/client/Session.d.ts +132 -0
  210. package/dist/tempo/session/client/Session.d.ts.map +1 -0
  211. package/dist/tempo/session/client/Session.js +55 -0
  212. package/dist/tempo/session/client/Session.js.map +1 -0
  213. package/dist/tempo/session/client/SessionManager.d.ts +120 -0
  214. package/dist/tempo/session/client/SessionManager.d.ts.map +1 -0
  215. package/dist/tempo/session/client/SessionManager.js +627 -0
  216. package/dist/tempo/session/client/SessionManager.js.map +1 -0
  217. package/dist/tempo/session/client/Transports.d.ts +449 -0
  218. package/dist/tempo/session/client/Transports.d.ts.map +1 -0
  219. package/dist/tempo/session/client/Transports.js +721 -0
  220. package/dist/tempo/session/client/Transports.js.map +1 -0
  221. package/dist/tempo/session/client/index.d.ts +12 -0
  222. package/dist/tempo/session/client/index.d.ts.map +1 -0
  223. package/dist/tempo/session/client/index.js +5 -0
  224. package/dist/tempo/session/client/index.js.map +1 -0
  225. package/dist/tempo/session/index.d.ts +7 -8
  226. package/dist/tempo/session/index.d.ts.map +1 -1
  227. package/dist/tempo/session/index.js +7 -8
  228. package/dist/tempo/session/index.js.map +1 -1
  229. package/dist/tempo/session/precompile/Chain.d.ts +319 -0
  230. package/dist/tempo/session/precompile/Chain.d.ts.map +1 -0
  231. package/dist/tempo/session/precompile/Chain.js +492 -0
  232. package/dist/tempo/session/precompile/Chain.js.map +1 -0
  233. package/dist/tempo/session/precompile/Channel.d.ts +46 -0
  234. package/dist/tempo/session/precompile/Channel.d.ts.map +1 -0
  235. package/dist/tempo/session/precompile/Channel.js +56 -0
  236. package/dist/tempo/session/precompile/Channel.js.map +1 -0
  237. package/dist/tempo/session/precompile/Protocol.d.ts +308 -0
  238. package/dist/tempo/session/precompile/Protocol.d.ts.map +1 -0
  239. package/dist/tempo/session/precompile/Protocol.js +264 -0
  240. package/dist/tempo/session/precompile/Protocol.js.map +1 -0
  241. package/dist/tempo/session/precompile/Voucher.d.ts +40 -0
  242. package/dist/tempo/session/precompile/Voucher.d.ts.map +1 -0
  243. package/dist/tempo/session/precompile/Voucher.js +126 -0
  244. package/dist/tempo/session/precompile/Voucher.js.map +1 -0
  245. package/dist/tempo/session/precompile/escrow.abi.d.ts +522 -0
  246. package/dist/tempo/session/precompile/escrow.abi.d.ts.map +1 -0
  247. package/dist/tempo/session/precompile/escrow.abi.js +224 -0
  248. package/dist/tempo/session/precompile/escrow.abi.js.map +1 -0
  249. package/dist/tempo/session/precompile/index.d.ts +24 -0
  250. package/dist/tempo/session/precompile/index.d.ts.map +1 -0
  251. package/dist/tempo/session/precompile/index.js +22 -0
  252. package/dist/tempo/session/precompile/index.js.map +1 -0
  253. package/dist/tempo/session/server/ChannelOps.d.ts +56 -0
  254. package/dist/tempo/session/server/ChannelOps.d.ts.map +1 -0
  255. package/dist/tempo/session/server/ChannelOps.js +91 -0
  256. package/dist/tempo/session/server/ChannelOps.js.map +1 -0
  257. package/dist/tempo/session/server/ChannelStore.d.ts +347 -0
  258. package/dist/tempo/session/server/ChannelStore.d.ts.map +1 -0
  259. package/dist/tempo/session/server/ChannelStore.js +404 -0
  260. package/dist/tempo/session/server/ChannelStore.js.map +1 -0
  261. package/dist/tempo/session/server/CredentialVerification.d.ts +85 -0
  262. package/dist/tempo/session/server/CredentialVerification.d.ts.map +1 -0
  263. package/dist/tempo/session/server/CredentialVerification.js +494 -0
  264. package/dist/tempo/session/server/CredentialVerification.js.map +1 -0
  265. package/dist/tempo/session/server/MeteredStream.d.ts +40 -0
  266. package/dist/tempo/session/server/MeteredStream.d.ts.map +1 -0
  267. package/dist/tempo/session/server/MeteredStream.js +42 -0
  268. package/dist/tempo/session/server/MeteredStream.js.map +1 -0
  269. package/dist/tempo/session/server/RequestState.d.ts +208 -0
  270. package/dist/tempo/session/server/RequestState.d.ts.map +1 -0
  271. package/dist/tempo/session/server/RequestState.js +252 -0
  272. package/dist/tempo/session/server/RequestState.js.map +1 -0
  273. package/dist/tempo/session/server/Session.d.ts +169 -0
  274. package/dist/tempo/session/server/Session.d.ts.map +1 -0
  275. package/dist/tempo/session/server/Session.js +351 -0
  276. package/dist/tempo/session/server/Session.js.map +1 -0
  277. package/dist/tempo/session/server/Settlement.d.ts +185 -0
  278. package/dist/tempo/session/server/Settlement.d.ts.map +1 -0
  279. package/dist/tempo/session/server/Settlement.js +250 -0
  280. package/dist/tempo/session/server/Settlement.js.map +1 -0
  281. package/dist/tempo/session/{Sse.d.ts → server/Sse.d.ts} +9 -56
  282. package/dist/tempo/session/server/Sse.d.ts.map +1 -0
  283. package/dist/tempo/session/server/Sse.js +184 -0
  284. package/dist/tempo/session/server/Sse.js.map +1 -0
  285. package/dist/tempo/session/server/Transports.d.ts +89 -0
  286. package/dist/tempo/session/server/Transports.d.ts.map +1 -0
  287. package/dist/tempo/session/server/Transports.js +149 -0
  288. package/dist/tempo/session/server/Transports.js.map +1 -0
  289. package/dist/tempo/session/server/Ws.d.ts +48 -0
  290. package/dist/tempo/session/server/Ws.d.ts.map +1 -0
  291. package/dist/tempo/session/server/Ws.js +244 -0
  292. package/dist/tempo/session/server/Ws.js.map +1 -0
  293. package/dist/tempo/session/server/index.d.ts +4 -0
  294. package/dist/tempo/session/server/index.d.ts.map +1 -0
  295. package/dist/tempo/session/server/index.js +2 -0
  296. package/dist/tempo/session/server/index.js.map +1 -0
  297. package/package.json +6 -1
  298. package/src/Challenge.ts +9 -7
  299. package/src/Constants.ts +58 -0
  300. package/src/Credential.ts +5 -4
  301. package/src/Method.ts +46 -5
  302. package/src/Receipt.ts +3 -2
  303. package/src/cli/cli.test.ts +23 -28
  304. package/src/cli/cli.ts +23 -10
  305. package/src/cli/mcp.test.ts +21 -7
  306. package/src/cli/plugins/tempo.ts +21 -8
  307. package/src/cli/utils.test.ts +25 -1
  308. package/src/cli/utils.ts +10 -0
  309. package/src/client/Methods.ts +5 -2
  310. package/src/client/Mppx.test-d.ts +10 -0
  311. package/src/client/Mppx.test.ts +75 -0
  312. package/src/client/Transport.ts +4 -5
  313. package/src/client/index.ts +11 -1
  314. package/src/client/internal/Fetch.test.ts +29 -4
  315. package/src/client/internal/Fetch.ts +17 -5
  316. package/src/env.d.ts +1 -1
  317. package/src/index.ts +1 -0
  318. package/src/internal/AcceptPayment.test.ts +61 -0
  319. package/src/internal/AcceptPayment.ts +21 -14
  320. package/src/mcp-sdk/client/McpClient.integration.test.ts +8 -7
  321. package/src/mcp-sdk/client/McpClient.test-d.ts +7 -0
  322. package/src/mcp-sdk/client/McpClient.ts +99 -67
  323. package/src/mcp-sdk/client/McpClient.unit.test.ts +131 -0
  324. package/src/middlewares/elysia.test.ts +8 -4
  325. package/src/middlewares/express.test.ts +8 -4
  326. package/src/middlewares/hono.test.ts +4 -4
  327. package/src/middlewares/nextjs.test.ts +8 -4
  328. package/src/proxy/Proxy.test.ts +8 -8
  329. package/src/server/Mppx.test-d.ts +54 -0
  330. package/src/server/Mppx.test.ts +200 -7
  331. package/src/server/Mppx.ts +487 -406
  332. package/src/server/Response.ts +2 -1
  333. package/src/server/Transport.ts +4 -3
  334. package/src/server/index.ts +1 -0
  335. package/src/stripe/client/Charge.test.ts +20 -5
  336. package/src/stripe/client/Charge.ts +6 -2
  337. package/src/stripe/server/Charge.test.ts +114 -1
  338. package/src/stripe/server/Charge.ts +13 -2
  339. package/src/stripe/server/internal/html.gen.ts +1 -1
  340. package/src/tempo/AccessKeyAuthorization.test.ts +4 -94
  341. package/src/tempo/Methods.test.ts +45 -17
  342. package/src/tempo/Methods.ts +22 -0
  343. package/src/tempo/PublicExports.test-d.ts +105 -0
  344. package/src/tempo/client/Charge.test.ts +85 -0
  345. package/src/tempo/client/Charge.ts +19 -2
  346. package/src/tempo/client/Methods.ts +18 -6
  347. package/src/tempo/client/index.ts +15 -4
  348. package/src/tempo/index.ts +1 -0
  349. package/src/tempo/internal/fee-payer.test.ts +241 -17
  350. package/src/tempo/internal/fee-payer.ts +150 -4
  351. package/src/tempo/internal/fee-token.test.ts +14 -9
  352. package/src/tempo/legacy/AccessKeyAuthorization.test.ts +162 -0
  353. package/src/tempo/legacy/README.md +9 -0
  354. package/src/tempo/{client → legacy/client}/ChannelOps.test.ts +6 -7
  355. package/src/tempo/{client → legacy/client}/ChannelOps.ts +22 -9
  356. package/src/tempo/{client → legacy/client}/Session.test.ts +51 -9
  357. package/src/tempo/{client → legacy/client}/Session.ts +25 -11
  358. package/src/tempo/{client → legacy/client}/SessionManager.test.ts +81 -9
  359. package/src/tempo/{client → legacy/client}/SessionManager.ts +41 -20
  360. package/src/tempo/legacy/client/index.ts +6 -0
  361. package/src/tempo/legacy/index.ts +6 -0
  362. package/src/tempo/{server → legacy/server}/Session.test.ts +45 -45
  363. package/src/tempo/{server → legacy/server}/Session.ts +32 -23
  364. package/src/tempo/legacy/server/index.ts +4 -0
  365. package/src/tempo/{session → legacy/session}/Chain.test.ts +3 -4
  366. package/src/tempo/{session → legacy/session}/Chain.ts +94 -63
  367. package/src/tempo/{session → legacy/session}/Channel.ts +1 -0
  368. package/src/tempo/legacy/session/ChannelStore.test.ts +58 -0
  369. package/src/tempo/legacy/session/ChannelStore.ts +39 -0
  370. package/src/tempo/legacy/session/Types.ts +91 -0
  371. package/src/tempo/{session → legacy/session}/Voucher.ts +12 -8
  372. package/src/tempo/{session → legacy/session}/escrow.abi.ts +1 -0
  373. package/src/tempo/legacy/session/index.ts +8 -0
  374. package/src/tempo/server/AtomicStore.test-d.ts +16 -11
  375. package/src/tempo/server/Charge.test.ts +92 -14
  376. package/src/tempo/server/Charge.ts +18 -16
  377. package/src/tempo/server/Methods.ts +54 -8
  378. package/src/tempo/server/Sse.test.ts +2 -2
  379. package/src/tempo/server/index.ts +6 -5
  380. package/src/tempo/server/internal/html.gen.ts +1 -1
  381. package/src/tempo/server/internal/request-body.test.ts +37 -4
  382. package/src/tempo/server/internal/request-body.ts +25 -6
  383. package/src/tempo/server/internal/transport.test.ts +4 -4
  384. package/src/tempo/server/internal/transport.ts +19 -10
  385. package/src/tempo/session/Snapshot.test.ts +41 -0
  386. package/src/tempo/session/Snapshot.ts +74 -0
  387. package/src/tempo/session/client/ChannelOps.test.ts +163 -0
  388. package/src/tempo/session/client/ChannelOps.ts +344 -0
  389. package/src/tempo/session/client/CredentialState.test.ts +645 -0
  390. package/src/tempo/session/client/CredentialState.ts +814 -0
  391. package/src/tempo/session/client/ReceiptCoordinator.ts +95 -0
  392. package/src/tempo/session/client/Runtime.test.ts +1092 -0
  393. package/src/tempo/session/client/Runtime.ts +986 -0
  394. package/src/tempo/session/client/Session.test.ts +734 -0
  395. package/src/tempo/session/client/Session.ts +97 -0
  396. package/src/tempo/session/client/SessionManager.test.ts +1308 -0
  397. package/src/tempo/session/client/SessionManager.ts +845 -0
  398. package/src/tempo/session/client/Transports.test.ts +837 -0
  399. package/src/tempo/session/client/Transports.ts +1292 -0
  400. package/src/tempo/session/client/index.ts +37 -0
  401. package/src/tempo/session/index.ts +7 -8
  402. package/src/tempo/session/precompile/Chain.integration.test.ts +321 -0
  403. package/src/tempo/session/precompile/Chain.test.ts +1258 -0
  404. package/src/tempo/session/precompile/Chain.ts +979 -0
  405. package/src/tempo/session/precompile/Channel.test.ts +138 -0
  406. package/src/tempo/session/precompile/Channel.ts +103 -0
  407. package/src/tempo/session/precompile/Protocol.test.ts +358 -0
  408. package/src/tempo/session/precompile/Protocol.ts +520 -0
  409. package/src/tempo/session/precompile/Voucher.test.ts +316 -0
  410. package/src/tempo/session/precompile/Voucher.ts +160 -0
  411. package/src/tempo/session/precompile/escrow.abi.ts +226 -0
  412. package/src/tempo/session/precompile/index.ts +33 -0
  413. package/src/tempo/session/server/ChannelOps.test.ts +129 -0
  414. package/src/tempo/session/server/ChannelOps.ts +157 -0
  415. package/src/tempo/session/{ChannelStore.test.ts → server/ChannelStore.test.ts} +536 -29
  416. package/src/tempo/session/server/ChannelStore.ts +835 -0
  417. package/src/tempo/session/server/CredentialVerification.test.ts +146 -0
  418. package/src/tempo/session/server/CredentialVerification.ts +710 -0
  419. package/src/tempo/session/server/MeteredStream.ts +88 -0
  420. package/src/tempo/session/server/RequestState.test.ts +531 -0
  421. package/src/tempo/session/server/RequestState.ts +499 -0
  422. package/src/tempo/session/server/Session.integration.test.ts +444 -0
  423. package/src/tempo/session/server/Session.test.ts +3253 -0
  424. package/src/tempo/session/server/Session.ts +543 -0
  425. package/src/tempo/session/server/Settlement.test.ts +242 -0
  426. package/src/tempo/session/server/Settlement.ts +470 -0
  427. package/src/tempo/session/{Sse.test.ts → server/Sse.test.ts} +37 -3
  428. package/src/tempo/session/server/Sse.ts +256 -0
  429. package/src/tempo/session/server/Transports.test.ts +346 -0
  430. package/src/tempo/session/server/Transports.ts +255 -0
  431. package/src/tempo/session/{Ws.test.ts → server/Ws.test.ts} +4 -4
  432. package/src/tempo/session/server/Ws.ts +384 -0
  433. package/src/tempo/session/server/index.ts +8 -0
  434. package/dist/tempo/client/ChannelOps.d.ts.map +0 -1
  435. package/dist/tempo/client/ChannelOps.js.map +0 -1
  436. package/dist/tempo/client/Session.d.ts.map +0 -1
  437. package/dist/tempo/client/Session.js.map +0 -1
  438. package/dist/tempo/client/SessionManager.d.ts.map +0 -1
  439. package/dist/tempo/client/SessionManager.js.map +0 -1
  440. package/dist/tempo/server/Session.d.ts.map +0 -1
  441. package/dist/tempo/server/Session.js.map +0 -1
  442. package/dist/tempo/session/Chain.d.ts.map +0 -1
  443. package/dist/tempo/session/Chain.js.map +0 -1
  444. package/dist/tempo/session/Channel.d.ts.map +0 -1
  445. package/dist/tempo/session/Channel.js.map +0 -1
  446. package/dist/tempo/session/ChannelStore.d.ts +0 -117
  447. package/dist/tempo/session/ChannelStore.d.ts.map +0 -1
  448. package/dist/tempo/session/ChannelStore.js +0 -172
  449. package/dist/tempo/session/ChannelStore.js.map +0 -1
  450. package/dist/tempo/session/Receipt.d.ts +0 -22
  451. package/dist/tempo/session/Receipt.d.ts.map +0 -1
  452. package/dist/tempo/session/Receipt.js +0 -34
  453. package/dist/tempo/session/Receipt.js.map +0 -1
  454. package/dist/tempo/session/Sse.d.ts.map +0 -1
  455. package/dist/tempo/session/Sse.js +0 -363
  456. package/dist/tempo/session/Sse.js.map +0 -1
  457. package/dist/tempo/session/Types.d.ts +0 -78
  458. package/dist/tempo/session/Types.d.ts.map +0 -1
  459. package/dist/tempo/session/Types.js.map +0 -1
  460. package/dist/tempo/session/Voucher.d.ts.map +0 -1
  461. package/dist/tempo/session/Voucher.js.map +0 -1
  462. package/dist/tempo/session/Ws.d.ts +0 -87
  463. package/dist/tempo/session/Ws.d.ts.map +0 -1
  464. package/dist/tempo/session/Ws.js +0 -443
  465. package/dist/tempo/session/Ws.js.map +0 -1
  466. package/dist/tempo/session/escrow.abi.js.map +0 -1
  467. package/src/tempo/session/ChannelStore.ts +0 -308
  468. package/src/tempo/session/Receipt.test.ts +0 -89
  469. package/src/tempo/session/Receipt.ts +0 -46
  470. package/src/tempo/session/Sse.ts +0 -462
  471. package/src/tempo/session/Types.ts +0 -86
  472. package/src/tempo/session/Ws.ts +0 -576
  473. /package/dist/tempo/{session → legacy/session}/Channel.js +0 -0
  474. /package/dist/tempo/{session → legacy/session}/Types.js +0 -0
  475. /package/src/tempo/{session → legacy/session}/Channel.test.ts +0 -0
  476. /package/src/tempo/{session → legacy/session}/Voucher.test.ts +0 -0
  477. /package/src/tempo/session/{Sse.fuzz.test.ts → server/Sse.fuzz.test.ts} +0 -0
@@ -0,0 +1,1092 @@
1
+ import { Hex } from 'ox'
2
+ import { describe, expect, test, vi } from 'vp/test'
3
+
4
+ import * as Challenge from '../../../Challenge.js'
5
+ import type { ChannelEntry } from '../client/ChannelOps.js'
6
+ import type { SessionContext } from '../client/CredentialState.js'
7
+ import * as Ws from '../precompile/Protocol.js'
8
+ import {
9
+ createSessionReceipt,
10
+ type ChannelDescriptor,
11
+ type SessionReceipt,
12
+ } from '../precompile/Protocol.js'
13
+ import { createSessionReceiptCoordinator } from './ReceiptCoordinator.js'
14
+ import {
15
+ activeStateFromChannel,
16
+ activeStateFromReceipt,
17
+ applySessionReceiptToRuntime,
18
+ assertCloseReadyWithinLocalState,
19
+ assertReceiptWithinLocalState,
20
+ assertVoucherWithinLocalLimit,
21
+ assertWithinMaxDeposit,
22
+ captureRuntimeSnapshot,
23
+ closedStateFromReceipt,
24
+ closeSocketSession,
25
+ createActiveState,
26
+ createSessionManagerRuntime,
27
+ initialState,
28
+ isExpectedCloseReceipt,
29
+ localCloseSpendLimit,
30
+ nextSpentFromReceipt,
31
+ parseManagerAmount,
32
+ reduce,
33
+ resolveCloseTarget,
34
+ resolveNeedVoucherTransition,
35
+ resolveOpeningDeposit,
36
+ restoreCumulativeAuthorization,
37
+ restoreRuntimeSnapshot,
38
+ type CloseTarget,
39
+ type SessionEvent,
40
+ type SessionSnapshot,
41
+ type SessionState,
42
+ } from './Runtime.js'
43
+ import {
44
+ WebSocketReadyState,
45
+ type ActiveSocketSession,
46
+ type TempoSessionChallenge,
47
+ } from './Transports.js'
48
+
49
+ describe('Machine', () => {
50
+ const descriptor = {
51
+ authorizedSigner: '0x0000000000000000000000000000000000000006',
52
+ expiringNonceHash: `0x${'11'.repeat(32)}`,
53
+ operator: '0x0000000000000000000000000000000000000000',
54
+ payee: '0x0000000000000000000000000000000000000002',
55
+ payer: '0x0000000000000000000000000000000000000001',
56
+ salt: `0x${'22'.repeat(32)}`,
57
+ token: '0x0000000000000000000000000000000000000003',
58
+ } as const satisfies ChannelDescriptor
59
+
60
+ const channelId = `0x${'33'.repeat(32)}` as const
61
+ const escrow = '0x4D50500000000000000000000000000000000000' as const
62
+
63
+ const snapshot = {
64
+ acceptedCumulative: '5',
65
+ chainId: 4217,
66
+ channelId,
67
+ deposit: '10',
68
+ descriptor,
69
+ escrow,
70
+ requiredCumulative: '6',
71
+ settled: '0',
72
+ spent: '2',
73
+ units: 2,
74
+ } satisfies SessionSnapshot
75
+
76
+ const channelEntry: ChannelEntry = {
77
+ channelId,
78
+ cumulativeAmount: 5n,
79
+ deposit: 10n,
80
+ descriptor,
81
+ escrow,
82
+ chainId: 4217,
83
+ opened: true,
84
+ }
85
+
86
+ const receipt = {
87
+ acceptedCumulative: '5',
88
+ challengeId: 'challenge-1',
89
+ channelId,
90
+ intent: 'session',
91
+ method: 'tempo',
92
+ reference: channelId,
93
+ spent: '5',
94
+ status: 'success',
95
+ timestamp: new Date(0).toISOString(),
96
+ } as const
97
+
98
+ const states = {
99
+ idle: initialState,
100
+ challenged: { status: 'challenged', challengeId: 'challenge-1' },
101
+ hydrating: { status: 'hydrating', challengeId: 'challenge-1', snapshot },
102
+ opening: { status: 'opening', challengeId: 'challenge-1' },
103
+ active: createActiveState({
104
+ challengeId: 'challenge-1',
105
+ channelId,
106
+ descriptor,
107
+ acceptedCumulative: '5',
108
+ deposit: '10',
109
+ spent: '5',
110
+ units: 1,
111
+ }),
112
+ voucherNeeded: {
113
+ status: 'voucherNeeded',
114
+ challengeId: 'challenge-1',
115
+ channelId,
116
+ descriptor,
117
+ requiredCumulative: '7',
118
+ deposit: '10',
119
+ },
120
+ toppingUp: {
121
+ status: 'toppingUp',
122
+ challengeId: 'challenge-1',
123
+ channelId,
124
+ descriptor,
125
+ deposit: '10',
126
+ },
127
+ settling: { status: 'settling', channelId, descriptor, deposit: '10' },
128
+ closeRequested: { status: 'closeRequested', channelId, descriptor },
129
+ withdrawable: { status: 'withdrawable', channelId, descriptor },
130
+ closing: { status: 'closing', channelId, descriptor },
131
+ closed: { status: 'closed', channelId, descriptor },
132
+ } satisfies Record<SessionState['status'], SessionState>
133
+
134
+ const events = {
135
+ challengeReceived: { type: 'challengeReceived', challengeId: 'challenge-2' },
136
+ challenge: { type: 'challenge', challengeId: 'challenge-2' },
137
+ activated: {
138
+ type: 'activated',
139
+ challengeId: 'challenge-2',
140
+ entry: channelEntry,
141
+ spent: '2',
142
+ units: 2,
143
+ },
144
+ hydrated: { type: 'hydrated', snapshot },
145
+ opened: { type: 'opened', descriptor, receipt, deposit: '10' },
146
+ receiptAccepted: { type: 'receiptAccepted', receipt, entry: channelEntry },
147
+ needVoucher: {
148
+ type: 'needVoucher',
149
+ descriptor,
150
+ event: {
151
+ acceptedCumulative: '5',
152
+ channelId,
153
+ deposit: '10',
154
+ requiredCumulative: '7',
155
+ },
156
+ },
157
+ topUpStarted: { type: 'topUpStarted' },
158
+ voucherAccepted: { type: 'voucherAccepted', receipt },
159
+ settleStarted: { type: 'settleStarted' },
160
+ settled: { type: 'settled', receipt },
161
+ closeRequested: { type: 'closeRequested' },
162
+ withdrawable: { type: 'withdrawable' },
163
+ closeStarted: { type: 'closeStarted' },
164
+ closed: { type: 'closed', receipt },
165
+ } satisfies Record<SessionEvent['type'], SessionEvent>
166
+
167
+ const allowedEvents = {
168
+ idle: ['challengeReceived', 'challenge', 'activated', 'receiptAccepted'],
169
+ challenged: ['challengeReceived', 'activated', 'receiptAccepted'],
170
+ hydrating: ['challengeReceived', 'activated', 'hydrated', 'receiptAccepted'],
171
+ opening: ['challengeReceived', 'activated', 'opened', 'receiptAccepted'],
172
+ active: [
173
+ 'challengeReceived',
174
+ 'challenge',
175
+ 'activated',
176
+ 'receiptAccepted',
177
+ 'needVoucher',
178
+ 'settleStarted',
179
+ 'closeRequested',
180
+ 'closeStarted',
181
+ ],
182
+ voucherNeeded: [
183
+ 'challengeReceived',
184
+ 'activated',
185
+ 'receiptAccepted',
186
+ 'topUpStarted',
187
+ 'voucherAccepted',
188
+ ],
189
+ toppingUp: ['challengeReceived', 'activated', 'receiptAccepted', 'voucherAccepted'],
190
+ settling: ['challengeReceived', 'activated', 'receiptAccepted', 'settled'],
191
+ closeRequested: [
192
+ 'challengeReceived',
193
+ 'activated',
194
+ 'receiptAccepted',
195
+ 'withdrawable',
196
+ 'closeStarted',
197
+ ],
198
+ withdrawable: ['challengeReceived', 'activated', 'receiptAccepted', 'closeStarted', 'closed'],
199
+ closing: ['closed'],
200
+ closed: [],
201
+ } satisfies Record<SessionState['status'], readonly SessionEvent['type'][]>
202
+
203
+ describe('precompile session state machine', () => {
204
+ test('only allows documented state/event transitions', () => {
205
+ for (const [status, state] of Object.entries(states)) {
206
+ const allowed = new Set(allowedEvents[status as SessionState['status']])
207
+
208
+ for (const [eventType, event] of Object.entries(events)) {
209
+ const transition = () => reduce(state, event)
210
+
211
+ if (allowed.has(eventType as SessionEvent['type'])) {
212
+ expect(transition).not.toThrow()
213
+ } else {
214
+ expect(transition).toThrow(`Invalid session transition: ${status} + ${eventType}`)
215
+ }
216
+ }
217
+ }
218
+ })
219
+
220
+ test('hydrates from a server snapshot', () => {
221
+ const challenged = reduce(initialState, {
222
+ type: 'challenge',
223
+ challengeId: 'challenge-1',
224
+ snapshot,
225
+ })
226
+
227
+ expect(challenged.state.status).toBe('hydrating')
228
+ expect(challenged.effects).toEqual([{ type: 'hydrate', snapshot }])
229
+
230
+ const hydrated = reduce(challenged.state, { type: 'hydrated', snapshot })
231
+ expect(hydrated.effects).toEqual([])
232
+ expect(hydrated.state).toEqual({
233
+ status: 'active',
234
+ challengeId: 'challenge-1',
235
+ channelId,
236
+ descriptor,
237
+ acceptedCumulative: '5',
238
+ deposit: '10',
239
+ spent: '2',
240
+ units: 2,
241
+ })
242
+ })
243
+
244
+ test('opens when no snapshot exists', () => {
245
+ const next = reduce(initialState, { type: 'challenge', challengeId: 'challenge-1' })
246
+
247
+ expect(next.state).toEqual({ status: 'opening', challengeId: 'challenge-1' })
248
+ expect(next.effects).toEqual([{ type: 'open' }])
249
+ })
250
+
251
+ test('keeps deposit separate from accepted cumulative after open receipts', () => {
252
+ const opening = reduce(initialState, { type: 'challenge', challengeId: 'challenge-1' })
253
+ const opened = reduce(opening.state, {
254
+ type: 'opened',
255
+ descriptor,
256
+ deposit: '10',
257
+ receipt: {
258
+ acceptedCumulative: '5',
259
+ challengeId: 'challenge-1',
260
+ channelId,
261
+ intent: 'session',
262
+ method: 'tempo',
263
+ reference: channelId,
264
+ spent: '5',
265
+ status: 'success',
266
+ timestamp: new Date(0).toISOString(),
267
+ },
268
+ })
269
+
270
+ expect(opened.state).toMatchObject({
271
+ status: 'active',
272
+ acceptedCumulative: '5',
273
+ deposit: '10',
274
+ spent: '5',
275
+ })
276
+ })
277
+
278
+ test('requests top-up before voucher when required cumulative exceeds deposit', () => {
279
+ const active = reduce(
280
+ reduce(initialState, { type: 'challenge', challengeId: 'challenge-1', snapshot }).state,
281
+ { type: 'hydrated', snapshot },
282
+ ).state
283
+
284
+ const next = reduce(active, {
285
+ type: 'needVoucher',
286
+ descriptor,
287
+ event: {
288
+ acceptedCumulative: '5',
289
+ channelId,
290
+ deposit: '10',
291
+ requiredCumulative: '12',
292
+ },
293
+ })
294
+
295
+ expect(next.state).toEqual({
296
+ status: 'toppingUp',
297
+ challengeId: 'challenge-1',
298
+ channelId,
299
+ descriptor,
300
+ deposit: '10',
301
+ })
302
+ expect(next.effects).toEqual([{ type: 'topUp', channelId, amount: '2' }])
303
+ })
304
+
305
+ test('plans voucher-only need-voucher transitions when deposit has headroom', () => {
306
+ expect(
307
+ resolveNeedVoucherTransition({
308
+ challengeId: 'challenge-1',
309
+ descriptor,
310
+ event: {
311
+ acceptedCumulative: '5',
312
+ channelId,
313
+ deposit: '10',
314
+ requiredCumulative: '7',
315
+ },
316
+ }),
317
+ ).toEqual({
318
+ state: {
319
+ status: 'voucherNeeded',
320
+ challengeId: 'challenge-1',
321
+ channelId,
322
+ descriptor,
323
+ requiredCumulative: '7',
324
+ deposit: '10',
325
+ },
326
+ effects: [{ type: 'voucher' }],
327
+ })
328
+ })
329
+
330
+ test('preserves deposit after settlement receipts', () => {
331
+ const active = reduce(
332
+ reduce(initialState, { type: 'challenge', challengeId: 'challenge-1', snapshot }).state,
333
+ { type: 'hydrated', snapshot },
334
+ ).state
335
+ const settling = reduce(active, { type: 'settleStarted' })
336
+ const settled = reduce(settling.state, {
337
+ type: 'settled',
338
+ receipt: {
339
+ acceptedCumulative: '5',
340
+ challengeId: 'challenge-1',
341
+ channelId,
342
+ intent: 'session',
343
+ method: 'tempo',
344
+ reference: channelId,
345
+ spent: '5',
346
+ status: 'success',
347
+ timestamp: new Date(0).toISOString(),
348
+ },
349
+ })
350
+
351
+ expect(settled.state).toMatchObject({
352
+ status: 'active',
353
+ acceptedCumulative: '5',
354
+ deposit: '10',
355
+ spent: '5',
356
+ })
357
+ })
358
+
359
+ test('rejects invalid transitions', () => {
360
+ expect(() =>
361
+ reduce(initialState, {
362
+ type: 'voucherAccepted',
363
+ receipt: {
364
+ acceptedCumulative: '1',
365
+ challengeId: 'challenge-1',
366
+ channelId,
367
+ intent: 'session',
368
+ method: 'tempo',
369
+ reference: channelId,
370
+ spent: '1',
371
+ status: 'success',
372
+ timestamp: new Date(0).toISOString(),
373
+ },
374
+ }),
375
+ ).toThrow('Invalid session transition')
376
+ })
377
+ })
378
+ })
379
+
380
+ describe('RuntimeState', () => {
381
+ const channelId = '0x0000000000000000000000000000000000000000000000000000000000000001' as Hex.Hex
382
+ const salt = '0x0000000000000000000000000000000000000000000000000000000000000002' as Hex.Hex
383
+ const expiringNonceHash =
384
+ '0x0000000000000000000000000000000000000000000000000000000000000003' as Hex.Hex
385
+
386
+ function channel(overrides: Partial<ChannelEntry> = {}): ChannelEntry {
387
+ return {
388
+ channelId,
389
+ cumulativeAmount: 10n,
390
+ deposit: 20n,
391
+ descriptor: {
392
+ payer: '0x0000000000000000000000000000000000000001',
393
+ payee: '0x0000000000000000000000000000000000000002',
394
+ operator: '0x0000000000000000000000000000000000000000',
395
+ token: '0x20c0000000000000000000000000000000000001',
396
+ salt,
397
+ authorizedSigner: '0x0000000000000000000000000000000000000001',
398
+ expiringNonceHash,
399
+ },
400
+ escrow: '0x4D50500000000000000000000000000000000000',
401
+ chainId: 4217,
402
+ opened: true,
403
+ ...overrides,
404
+ }
405
+ }
406
+
407
+ describe('RuntimeState', () => {
408
+ test('projects cached channel data into active machine state', () => {
409
+ const entry = channel({ cumulativeAmount: 12n, deposit: 30n })
410
+
411
+ expect(
412
+ activeStateFromChannel({
413
+ challengeId: 'challenge-1',
414
+ entry,
415
+ spent: '7',
416
+ units: 2,
417
+ }),
418
+ ).toEqual({
419
+ status: 'active',
420
+ challengeId: 'challenge-1',
421
+ channelId: entry.channelId,
422
+ descriptor: entry.descriptor,
423
+ acceptedCumulative: '12',
424
+ deposit: '30',
425
+ spent: '7',
426
+ units: 2,
427
+ })
428
+ })
429
+
430
+ test('projects receipts without replacing the local deposit boundary', () => {
431
+ const entry = channel({ deposit: 30n })
432
+ const receipt = createSessionReceipt({
433
+ acceptedCumulative: 12n,
434
+ challengeId: 'challenge-1',
435
+ channelId: entry.channelId,
436
+ spent: 8n,
437
+ units: 3,
438
+ })
439
+
440
+ expect(activeStateFromReceipt(receipt, entry)).toMatchObject({
441
+ acceptedCumulative: '12',
442
+ deposit: '30',
443
+ spent: '8',
444
+ units: 3,
445
+ })
446
+ })
447
+
448
+ test('projects final close receipts into closed machine state', () => {
449
+ const entry = channel()
450
+ const receipt = createSessionReceipt({
451
+ acceptedCumulative: 12n,
452
+ challengeId: 'challenge-1',
453
+ channelId: entry.channelId,
454
+ spent: 8n,
455
+ txHash: '0x1234',
456
+ })
457
+
458
+ expect(closedStateFromReceipt(receipt, entry)).toEqual({
459
+ status: 'closed',
460
+ channelId: entry.channelId,
461
+ descriptor: entry.descriptor,
462
+ })
463
+ })
464
+
465
+ test('restores mutable channel fields from a snapshot', () => {
466
+ const entry = channel({ cumulativeAmount: 12n, deposit: 30n, opened: true })
467
+ const snapshot = captureRuntimeSnapshot({ channel: entry, spent: 4n, state: initialState })
468
+
469
+ entry.cumulativeAmount = 99n
470
+ entry.deposit = 100n
471
+ entry.opened = false
472
+
473
+ const restored = restoreRuntimeSnapshot(snapshot, entry)
474
+
475
+ expect(restored).toEqual({ channel: entry, spent: 4n, state: initialState })
476
+ expect(entry.cumulativeAmount).toBe(12n)
477
+ expect(entry.deposit).toBe(30n)
478
+ expect(entry.opened).toBe(true)
479
+ })
480
+
481
+ test('restores a null channel snapshot and marks the current channel closed', () => {
482
+ const current = channel({ opened: true })
483
+ const snapshot = captureRuntimeSnapshot({ channel: null, spent: 0n, state: initialState })
484
+
485
+ const restored = restoreRuntimeSnapshot(snapshot, current)
486
+
487
+ expect(restored).toEqual({ channel: null, spent: 0n, state: initialState })
488
+ expect(current.opened).toBe(false)
489
+ })
490
+
491
+ test('restores cumulative authorization and returns refreshed active state', () => {
492
+ const entry = channel({ cumulativeAmount: 99n, deposit: 150n })
493
+
494
+ const state = restoreCumulativeAuthorization({
495
+ channel: entry,
496
+ channelId: entry.channelId,
497
+ challengeId: 'challenge-1',
498
+ cumulativeAmount: 20n,
499
+ spent: 7n,
500
+ state: activeStateFromChannel({
501
+ challengeId: 'challenge-1',
502
+ entry,
503
+ spent: '7',
504
+ units: 4,
505
+ }),
506
+ })
507
+
508
+ expect(entry.cumulativeAmount).toBe(20n)
509
+ expect(state).toMatchObject({
510
+ status: 'active',
511
+ acceptedCumulative: '20',
512
+ deposit: '150',
513
+ spent: '7',
514
+ units: 4,
515
+ })
516
+ })
517
+
518
+ test('does not restore cumulative authorization for a different channel', () => {
519
+ const entry = channel({ cumulativeAmount: 99n })
520
+
521
+ const state = restoreCumulativeAuthorization({
522
+ channel: entry,
523
+ channelId: '0x0000000000000000000000000000000000000000000000000000000000000099' as Hex.Hex,
524
+ challengeId: 'challenge-1',
525
+ cumulativeAmount: 20n,
526
+ spent: 7n,
527
+ state: initialState,
528
+ })
529
+
530
+ expect(entry.cumulativeAmount).toBe(99n)
531
+ expect(state).toBeUndefined()
532
+ })
533
+
534
+ test('creates idle mutable runtime state for a new manager', () => {
535
+ expect(createSessionManagerRuntime()).toEqual({
536
+ channel: null,
537
+ lastChallenge: null,
538
+ lastUrl: null,
539
+ spent: 0n,
540
+ socketSession: null,
541
+ state: { status: 'idle' },
542
+ })
543
+ })
544
+
545
+ test('applies matching receipts to spent and public active state', () => {
546
+ const runtime = createSessionManagerRuntime()
547
+ runtime.channel = channel({ cumulativeAmount: 100n, deposit: 150n })
548
+ runtime.spent = 10n
549
+
550
+ applySessionReceiptToRuntime({
551
+ maxVoucherCumulative: null,
552
+ receipt: createSessionReceipt({
553
+ acceptedCumulative: 100n,
554
+ challengeId: 'challenge-1',
555
+ channelId,
556
+ spent: 20n,
557
+ units: 3,
558
+ }),
559
+ runtime,
560
+ })
561
+
562
+ expect(runtime.spent).toBe(20n)
563
+ expect(runtime.state).toMatchObject({
564
+ status: 'active',
565
+ challengeId: 'challenge-1',
566
+ channelId,
567
+ acceptedCumulative: '100',
568
+ deposit: '150',
569
+ spent: '20',
570
+ units: 3,
571
+ })
572
+ })
573
+
574
+ test('ignores receipts for other channels', () => {
575
+ const runtime = createSessionManagerRuntime()
576
+ runtime.channel = channel({ cumulativeAmount: 100n, deposit: 150n })
577
+ runtime.spent = 10n
578
+
579
+ applySessionReceiptToRuntime({
580
+ maxVoucherCumulative: null,
581
+ receipt: createSessionReceipt({
582
+ acceptedCumulative: 100n,
583
+ challengeId: 'challenge-1',
584
+ channelId: `0x${'02'.repeat(32)}` as Hex.Hex,
585
+ spent: 20n,
586
+ }),
587
+ runtime,
588
+ })
589
+
590
+ expect(runtime.spent).toBe(10n)
591
+ expect(runtime.state).toEqual({ status: 'idle' })
592
+ })
593
+ })
594
+ })
595
+
596
+ describe('SessionReceiptCoordinator', () => {
597
+ const channelId = '0x0000000000000000000000000000000000000000000000000000000000000001' as Hex.Hex
598
+ const challenge: TempoSessionChallenge = {
599
+ id: 'challenge-1',
600
+ realm: 'test',
601
+ method: 'tempo',
602
+ intent: 'session',
603
+ request: {
604
+ amount: '25',
605
+ currency: 'pathUSD',
606
+ methodDetails: { escrowContract: undefined },
607
+ recipient: 'payee',
608
+ unitType: 'chunk',
609
+ },
610
+ }
611
+
612
+ function receipt(spent: bigint) {
613
+ return createSessionReceipt({
614
+ acceptedCumulative: spent,
615
+ challengeId: challenge.id,
616
+ channelId,
617
+ spent,
618
+ })
619
+ }
620
+
621
+ function socketSession(overrides: Partial<ActiveSocketSession> = {}): ActiveSocketSession {
622
+ return {
623
+ challenge,
624
+ channelId,
625
+ closeReadyReceipt: null,
626
+ deliveredChunks: 0n,
627
+ expectedCloseAmount: null,
628
+ socket: null,
629
+ tickCost: 25n,
630
+ ...overrides,
631
+ }
632
+ }
633
+
634
+ describe('SessionReceiptCoordinator', () => {
635
+ test('waits for the receipt accepted by the predicate', async () => {
636
+ const coordinator = createSessionReceiptCoordinator({ getSocketSession: () => null })
637
+ const expected = receipt(2n)
638
+ const wait = coordinator.waitForReceipt((candidate) => candidate.spent === expected.spent)
639
+
640
+ coordinator.settleReceipt(receipt(1n))
641
+ coordinator.settleReceipt(expected)
642
+
643
+ await expect(wait).resolves.toBe(expected)
644
+ })
645
+
646
+ test('caches matching close-ready receipts on the active socket session', async () => {
647
+ const currentSocket = socketSession()
648
+ const coordinator = createSessionReceiptCoordinator({
649
+ getSocketSession: () => currentSocket,
650
+ })
651
+ const closeReady = receipt(3n)
652
+
653
+ coordinator.settleCloseReady(closeReady)
654
+
655
+ expect(currentSocket.closeReadyReceipt).toBe(closeReady)
656
+ await expect(coordinator.waitForCloseReady()).resolves.toBe(closeReady)
657
+ })
658
+
659
+ test('rejects pending waits', async () => {
660
+ const coordinator = createSessionReceiptCoordinator({ getSocketSession: () => null })
661
+ const wait = coordinator.waitForReceipt()
662
+ const error = new Error('failed')
663
+
664
+ coordinator.rejectReceipt(error)
665
+
666
+ await expect(wait).rejects.toBe(error)
667
+ })
668
+ })
669
+ })
670
+
671
+ describe('CloseAuthorization', () => {
672
+ const channelId = '0x0000000000000000000000000000000000000000000000000000000000000001' as Hex.Hex
673
+
674
+ function challenge(id = 'challenge-1'): TempoSessionChallenge {
675
+ return Challenge.from({
676
+ id,
677
+ method: 'tempo',
678
+ intent: 'session',
679
+ realm: 'test',
680
+ request: {
681
+ amount: '1',
682
+ currency: '0x20c0000000000000000000000000000000000001',
683
+ recipient: '0x0000000000000000000000000000000000000002',
684
+ methodDetails: { chainId: 4217 },
685
+ },
686
+ }) as TempoSessionChallenge
687
+ }
688
+
689
+ function channel(overrides: Partial<ChannelEntry> = {}): ChannelEntry {
690
+ return {
691
+ channelId,
692
+ cumulativeAmount: 100n,
693
+ deposit: 150n,
694
+ descriptor: {
695
+ payer: '0x0000000000000000000000000000000000000001',
696
+ payee: '0x0000000000000000000000000000000000000002',
697
+ operator: '0x0000000000000000000000000000000000000000',
698
+ token: '0x20c0000000000000000000000000000000000001',
699
+ salt: '0x0000000000000000000000000000000000000000000000000000000000000002',
700
+ authorizedSigner: '0x0000000000000000000000000000000000000001',
701
+ expiringNonceHash: '0x0000000000000000000000000000000000000000000000000000000000000003',
702
+ },
703
+ escrow: '0x4D50500000000000000000000000000000000000',
704
+ chainId: 4217,
705
+ opened: true,
706
+ ...overrides,
707
+ }
708
+ }
709
+
710
+ describe('CloseAuthorization', () => {
711
+ test('returns undefined when no local channel is open', () => {
712
+ expect(
713
+ resolveCloseTarget({
714
+ channel: channel({ opened: false }),
715
+ currentSocket: null,
716
+ lastChallenge: challenge(),
717
+ }),
718
+ ).toBeUndefined()
719
+ })
720
+
721
+ test('resolves HTTP close target from local channel and last challenge', () => {
722
+ const target = resolveCloseTarget({
723
+ channel: channel(),
724
+ currentSocket: null,
725
+ lastChallenge: challenge(),
726
+ })
727
+
728
+ expect(target).toMatchObject({ channelId, challenge: { id: 'challenge-1' } })
729
+ })
730
+
731
+ test('requires a challenge for an open channel', () => {
732
+ expect(() =>
733
+ resolveCloseTarget({
734
+ channel: channel(),
735
+ currentSocket: null,
736
+ lastChallenge: null,
737
+ }),
738
+ ).toThrow('Cannot close session: no challenge available.')
739
+ })
740
+
741
+ test('rejects close-ready spend beyond local voucher state', () => {
742
+ expect(localCloseSpendLimit({ cumulativeAmount: 100n, spent: 80n })).toBe(100n)
743
+ expect(localCloseSpendLimit({ cumulativeAmount: 70n, spent: 80n })).toBe(80n)
744
+
745
+ expect(() =>
746
+ assertCloseReadyWithinLocalState({
747
+ cumulativeAmount: 100n,
748
+ readySpent: 101n,
749
+ spent: 80n,
750
+ }),
751
+ ).toThrow('close-ready spent exceeds local voucher state')
752
+ })
753
+
754
+ test('matches only final close receipts with txHash and exact amounts', () => {
755
+ const receipt = createSessionReceipt({
756
+ acceptedCumulative: 80n,
757
+ challengeId: 'challenge-1',
758
+ channelId,
759
+ spent: 80n,
760
+ txHash: '0x1234',
761
+ })
762
+
763
+ expect(
764
+ isExpectedCloseReceipt({
765
+ challengeId: 'challenge-1',
766
+ channelId,
767
+ expectedCloseAmount: '80',
768
+ receipt,
769
+ }),
770
+ ).toBe(true)
771
+ expect(
772
+ isExpectedCloseReceipt({
773
+ challengeId: 'challenge-1',
774
+ channelId,
775
+ expectedCloseAmount: '81',
776
+ receipt,
777
+ }),
778
+ ).toBe(false)
779
+ })
780
+ })
781
+ })
782
+
783
+ describe('LocalAuthorization', () => {
784
+ const channelId = '0x0000000000000000000000000000000000000000000000000000000000000001' as Hex.Hex
785
+
786
+ function channel(overrides: Partial<ChannelEntry> = {}): ChannelEntry {
787
+ return {
788
+ channelId,
789
+ cumulativeAmount: 100n,
790
+ deposit: 150n,
791
+ descriptor: {
792
+ payer: '0x0000000000000000000000000000000000000001',
793
+ payee: '0x0000000000000000000000000000000000000002',
794
+ operator: '0x0000000000000000000000000000000000000000',
795
+ token: '0x20c0000000000000000000000000000000000001',
796
+ salt: '0x0000000000000000000000000000000000000000000000000000000000000002',
797
+ authorizedSigner: '0x0000000000000000000000000000000000000001',
798
+ expiringNonceHash: '0x0000000000000000000000000000000000000000000000000000000000000003',
799
+ },
800
+ escrow: '0x4D50500000000000000000000000000000000000',
801
+ chainId: 4217,
802
+ opened: true,
803
+ ...overrides,
804
+ }
805
+ }
806
+
807
+ describe('LocalAuthorization', () => {
808
+ test('accepts receipts within local cumulative authorization', () => {
809
+ expect(() =>
810
+ assertReceiptWithinLocalState({
811
+ channel: channel({ cumulativeAmount: 100n }),
812
+ maxVoucherCumulative: 100n,
813
+ receipt: createSessionReceipt({
814
+ acceptedCumulative: 100n,
815
+ challengeId: 'challenge-1',
816
+ channelId,
817
+ spent: 80n,
818
+ }),
819
+ }),
820
+ ).not.toThrow()
821
+ })
822
+
823
+ test('rejects receipts that exceed local cumulative authorization', () => {
824
+ expect(() =>
825
+ assertReceiptWithinLocalState({
826
+ channel: channel({ cumulativeAmount: 100n }),
827
+ maxVoucherCumulative: null,
828
+ receipt: createSessionReceipt({
829
+ acceptedCumulative: 101n,
830
+ challengeId: 'challenge-1',
831
+ channelId,
832
+ spent: 80n,
833
+ }),
834
+ }),
835
+ ).toThrow('receipt accepted cumulative exceeds local voucher state')
836
+ })
837
+
838
+ test('rejects receipt spent above accepted cumulative', () => {
839
+ expect(() =>
840
+ assertReceiptWithinLocalState({
841
+ channel: channel({ cumulativeAmount: 100n }),
842
+ maxVoucherCumulative: null,
843
+ receipt: createSessionReceipt({
844
+ acceptedCumulative: 90n,
845
+ challengeId: 'challenge-1',
846
+ channelId,
847
+ spent: 91n,
848
+ }),
849
+ }),
850
+ ).toThrow('receipt spent exceeds accepted cumulative voucher amount')
851
+ })
852
+
853
+ test('keeps locally observed spend monotonic', () => {
854
+ expect(
855
+ nextSpentFromReceipt({
856
+ channel: channel(),
857
+ maxVoucherCumulative: null,
858
+ receipt: createSessionReceipt({
859
+ acceptedCumulative: 100n,
860
+ challengeId: 'challenge-1',
861
+ channelId,
862
+ spent: 80n,
863
+ }),
864
+ spent: 90n,
865
+ }),
866
+ ).toBe(90n)
867
+ })
868
+
869
+ test('ignores receipts for other channels', () => {
870
+ expect(
871
+ nextSpentFromReceipt({
872
+ channel: channel(),
873
+ maxVoucherCumulative: null,
874
+ receipt: createSessionReceipt({
875
+ acceptedCumulative: 100n,
876
+ challengeId: 'challenge-1',
877
+ channelId: `0x${'04'.repeat(32)}` as Hex.Hex,
878
+ spent: 80n,
879
+ }),
880
+ spent: 10n,
881
+ }),
882
+ ).toBe(10n)
883
+ })
884
+
885
+ test('enforces optional maxDeposit authorization cap', () => {
886
+ expect(() =>
887
+ assertVoucherWithinLocalLimit({ cumulativeAmount: 101n, maxVoucherCumulative: 100n }),
888
+ ).toThrow('requested voucher amount 101 exceeds local maxDeposit 100')
889
+ })
890
+
891
+ test('resolves opening deposits from explicit context, server hints, request amount, and local cap', () => {
892
+ expect(
893
+ resolveOpeningDeposit({
894
+ contextDepositRaw: '500',
895
+ maxDeposit: 100n,
896
+ requestAmount: 10n,
897
+ suggestedDepositRaw: '1000',
898
+ }),
899
+ ).toBe(500n)
900
+ expect(() =>
901
+ resolveOpeningDeposit({
902
+ contextDepositRaw: '0',
903
+ maxDeposit: 100n,
904
+ requestAmount: 10n,
905
+ suggestedDepositRaw: '1000',
906
+ }),
907
+ ).toThrow('opening deposit 0 below request amount 10')
908
+ expect(
909
+ resolveOpeningDeposit({
910
+ maxDeposit: 500n,
911
+ requestAmount: 100n,
912
+ suggestedDepositRaw: '1000',
913
+ }),
914
+ ).toBe(500n)
915
+ expect(
916
+ resolveOpeningDeposit({
917
+ maxDeposit: 500n,
918
+ requestAmount: 100n,
919
+ suggestedDepositRaw: '50',
920
+ }),
921
+ ).toBe(100n)
922
+ expect(resolveOpeningDeposit({ maxDeposit: 1000n, requestAmount: 100n })).toBe(100n)
923
+ expect(() => resolveOpeningDeposit({ maxDeposit: 50n, requestAmount: 100n })).toThrow(
924
+ 'requested voucher amount 100 exceeds local maxDeposit 50',
925
+ )
926
+ })
927
+
928
+ test('enforces optional maxDeposit through the compatibility helper', () => {
929
+ expect(() => assertWithinMaxDeposit(101n, 100n)).toThrow(
930
+ 'requested voucher amount 101 exceeds local maxDeposit 100',
931
+ )
932
+ expect(() => assertWithinMaxDeposit(100n, 100n)).not.toThrow()
933
+ expect(() => assertWithinMaxDeposit(101n, undefined)).not.toThrow()
934
+ })
935
+
936
+ test('parses manager amounts from raw bigint or human-readable string', () => {
937
+ expect(parseManagerAmount(5n, 6)).toBe(5n)
938
+ expect(parseManagerAmount('1.25', 6)).toBe(1_250_000n)
939
+ })
940
+ })
941
+ })
942
+
943
+ describe('SocketClose', () => {
944
+ const channelId = '0x0000000000000000000000000000000000000000000000000000000000000001' as Hex.Hex
945
+
946
+ function challenge(id = 'challenge-1'): TempoSessionChallenge {
947
+ return Challenge.from({
948
+ id,
949
+ method: 'tempo',
950
+ intent: 'session',
951
+ realm: 'test',
952
+ request: {
953
+ amount: '10',
954
+ currency: '0x20c0000000000000000000000000000000000001',
955
+ recipient: '0x0000000000000000000000000000000000000002',
956
+ methodDetails: { chainId: 4217 },
957
+ },
958
+ }) as TempoSessionChallenge
959
+ }
960
+
961
+ function channel(overrides: Partial<ChannelEntry> = {}): ChannelEntry {
962
+ return {
963
+ channelId,
964
+ cumulativeAmount: 100n,
965
+ deposit: 100n,
966
+ descriptor: {
967
+ payer: '0x0000000000000000000000000000000000000001',
968
+ payee: '0x0000000000000000000000000000000000000002',
969
+ operator: '0x0000000000000000000000000000000000000000',
970
+ token: '0x20c0000000000000000000000000000000000001',
971
+ salt: '0x0000000000000000000000000000000000000000000000000000000000000002',
972
+ authorizedSigner: '0x0000000000000000000000000000000000000001',
973
+ expiringNonceHash: '0x0000000000000000000000000000000000000000000000000000000000000003',
974
+ },
975
+ escrow: '0x4D50500000000000000000000000000000000000',
976
+ chainId: 4217,
977
+ opened: true,
978
+ ...overrides,
979
+ }
980
+ }
981
+
982
+ function target(overrides: Partial<CloseTarget> = {}): CloseTarget {
983
+ const c = channel()
984
+ return { challenge: challenge(), channel: c, channelId: c.channelId, ...overrides }
985
+ }
986
+
987
+ function receipt(spent: bigint, withTx = false): SessionReceipt {
988
+ return createSessionReceipt({
989
+ acceptedCumulative: spent,
990
+ challengeId: 'challenge-1',
991
+ channelId,
992
+ spent,
993
+ ...(withTx ? { txHash: '0x1234' } : {}),
994
+ })
995
+ }
996
+
997
+ function socketSession(overrides: Partial<ActiveSocketSession> = {}): ActiveSocketSession {
998
+ return {
999
+ challenge: challenge(),
1000
+ channelId,
1001
+ closeReadyReceipt: null,
1002
+ deliveredChunks: 0n,
1003
+ expectedCloseAmount: null,
1004
+ socket: null,
1005
+ tickCost: 10n,
1006
+ ...overrides,
1007
+ }
1008
+ }
1009
+
1010
+ function socket() {
1011
+ return {
1012
+ close: vi.fn(),
1013
+ readyState: WebSocketReadyState.OPEN,
1014
+ send: vi.fn(),
1015
+ } as unknown as WebSocket & { close: ReturnType<typeof vi.fn>; send: ReturnType<typeof vi.fn> }
1016
+ }
1017
+
1018
+ describe('SocketClose', () => {
1019
+ test('requests close-ready, signs that spend, and waits for the final receipt', async () => {
1020
+ const activeSocket = socket()
1021
+ const closeReady = receipt(60n)
1022
+ const finalReceipt = receipt(60n, true)
1023
+ const createSessionCredential = vi.fn(
1024
+ async (_challenge: TempoSessionChallenge, context: SessionContext) => {
1025
+ expect(context).toMatchObject({ action: 'close', cumulativeAmountRaw: '60' })
1026
+ return 'close-credential'
1027
+ },
1028
+ )
1029
+ const currentSocket = socketSession()
1030
+
1031
+ const result = await closeSocketSession({
1032
+ activeSocket,
1033
+ createSessionCredential,
1034
+ currentSocket,
1035
+ spent: 50n,
1036
+ target: target(),
1037
+ waitForCloseReady: async () => closeReady,
1038
+ waitForReceipt: async (predicate) => {
1039
+ expect(currentSocket.expectedCloseAmount).toBe('60')
1040
+ expect(predicate(finalReceipt)).toBe(true)
1041
+ return finalReceipt
1042
+ },
1043
+ })
1044
+
1045
+ expect(result).toBe(finalReceipt)
1046
+ expect(activeSocket.send).toHaveBeenNthCalledWith(1, Ws.formatCloseRequestMessage())
1047
+ expect(activeSocket.send).toHaveBeenNthCalledWith(
1048
+ 2,
1049
+ Ws.formatAuthorizationMessage('close-credential'),
1050
+ )
1051
+ expect(activeSocket.close).toHaveBeenCalledOnce()
1052
+ expect(currentSocket.expectedCloseAmount).toBeNull()
1053
+ })
1054
+
1055
+ test('uses an existing close-ready receipt without sending a close request', async () => {
1056
+ const activeSocket = socket()
1057
+ const finalReceipt = receipt(40n, true)
1058
+ const currentSocket = socketSession({ closeReadyReceipt: receipt(40n) })
1059
+
1060
+ await closeSocketSession({
1061
+ activeSocket,
1062
+ createSessionCredential: async () => 'close-credential',
1063
+ currentSocket,
1064
+ spent: 20n,
1065
+ target: target(),
1066
+ waitForCloseReady: async () => {
1067
+ throw new Error('unexpected close-ready wait')
1068
+ },
1069
+ waitForReceipt: async () => finalReceipt,
1070
+ })
1071
+
1072
+ expect(activeSocket.send).toHaveBeenCalledOnce()
1073
+ expect(activeSocket.send).toHaveBeenCalledWith(
1074
+ Ws.formatAuthorizationMessage('close-credential'),
1075
+ )
1076
+ })
1077
+
1078
+ test('rejects close-ready spend beyond local voucher state', async () => {
1079
+ await expect(
1080
+ closeSocketSession({
1081
+ activeSocket: socket(),
1082
+ createSessionCredential: async () => 'close-credential',
1083
+ currentSocket: socketSession({ closeReadyReceipt: receipt(101n) }),
1084
+ spent: 50n,
1085
+ target: target(),
1086
+ waitForCloseReady: async () => receipt(101n),
1087
+ waitForReceipt: async () => receipt(101n, true),
1088
+ }),
1089
+ ).rejects.toThrow('close-ready spent exceeds local voucher state')
1090
+ })
1091
+ })
1092
+ })