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,1258 @@
1
+ import {
2
+ createClient,
3
+ custom,
4
+ encodeAbiParameters,
5
+ encodeEventTopics,
6
+ encodeFunctionData,
7
+ encodeFunctionResult,
8
+ erc20Abi,
9
+ maxUint256,
10
+ zeroAddress,
11
+ type Address,
12
+ type Hex,
13
+ } from 'viem'
14
+ import { privateKeyToAccount } from 'viem/accounts'
15
+ import { Transaction } from 'viem/tempo'
16
+ import { describe, expect, test } from 'vp/test'
17
+
18
+ import { VerificationFailedError } from '../../../Errors.js'
19
+ import * as ServerChannelOps from '../server/ChannelOps.js'
20
+ import * as Chain from './Chain.js'
21
+ import * as Channel from './Channel.js'
22
+ import { escrowAbi } from './escrow.abi.js'
23
+ import { tip20ChannelEscrow } from './Protocol.js'
24
+ import * as Types from './Protocol.js'
25
+
26
+ const descriptor = {
27
+ payer: '0x1111111111111111111111111111111111111111',
28
+ payee: '0x2222222222222222222222222222222222222222',
29
+ operator: '0x3333333333333333333333333333333333333333',
30
+ token: '0x4444444444444444444444444444444444444444',
31
+ salt: '0x0000000000000000000000000000000000000000000000000000000000000001',
32
+ authorizedSigner: '0x5555555555555555555555555555555555555555',
33
+ expiringNonceHash: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
34
+ } as const
35
+
36
+ const deposit = Types.uint96(1_000_000n)
37
+ const chainId = 42431
38
+ const txHash = `0x${'ab'.repeat(32)}` as const
39
+ const feePayer = privateKeyToAccount(
40
+ '0x59c6995e998f97a5a0044966f0945389d1fc6e60e7346d6c36c49d32f75b9a1b',
41
+ )
42
+ const mockFeePayer = {
43
+ ...feePayer,
44
+ async signTransaction() {
45
+ return `0x76${'00'.repeat(32)}` as `0x${string}`
46
+ },
47
+ }
48
+
49
+ function createMockClient(
50
+ parameters: {
51
+ channel?: { descriptor: Channel.ChannelDescriptor; state: Chain.ChannelState } | undefined
52
+ receipt?: Record<string, unknown> | null | undefined
53
+ rpcMethods?: string[] | undefined
54
+ } = {},
55
+ ) {
56
+ return createClient({
57
+ account: feePayer,
58
+ chain: { id: chainId } as never,
59
+ transport: custom({
60
+ async request(args) {
61
+ parameters.rpcMethods?.push(args.method)
62
+ if (args.method === 'eth_chainId') return `0x${chainId.toString(16)}`
63
+ if (args.method === 'eth_sendRawTransaction') return txHash
64
+ if (args.method === 'eth_sendRawTransactionSync') return parameters.receipt ?? receipt([])
65
+ if (args.method === 'eth_getTransactionReceipt') return parameters.receipt ?? null
66
+ if (args.method === 'eth_call') {
67
+ const data = (args.params as [{ data?: `0x${string}` }])[0].data
68
+ const channel = parameters.channel
69
+ if (!channel || !data) return '0x'
70
+ const selector = data.slice(0, 10)
71
+ const getChannelSelector = encodeFunctionData({
72
+ abi: escrowAbi,
73
+ functionName: 'getChannel',
74
+ args: [channel.descriptor],
75
+ }).slice(0, 10)
76
+ if (selector === getChannelSelector)
77
+ return encodeFunctionResult({
78
+ abi: escrowAbi,
79
+ functionName: 'getChannel',
80
+ result: { descriptor: channel.descriptor, state: channel.state },
81
+ })
82
+ return encodeFunctionResult({
83
+ abi: escrowAbi,
84
+ functionName: 'getChannelState',
85
+ result: channel.state,
86
+ })
87
+ }
88
+ throw new Error(`unexpected rpc request: ${args.method}`)
89
+ },
90
+ }),
91
+ })
92
+ }
93
+
94
+ function receipt(logs: readonly Record<string, unknown>[]) {
95
+ return {
96
+ blockHash: `0x${'01'.repeat(32)}`,
97
+ blockNumber: '0x1',
98
+ contractAddress: null,
99
+ cumulativeGasUsed: '0x1',
100
+ effectiveGasPrice: '0x1',
101
+ from: descriptor.payer,
102
+ gasUsed: '0x1',
103
+ logs,
104
+ logsBloom: `0x${'00'.repeat(256)}`,
105
+ status: '0x1',
106
+ to: tip20ChannelEscrow,
107
+ transactionHash: txHash,
108
+ transactionIndex: '0x0',
109
+ type: '0x76',
110
+ }
111
+ }
112
+
113
+ function openedLog(parameters: {
114
+ channelId: `0x${string}`
115
+ expiringNonceHash: `0x${string}`
116
+ deposit?: bigint | undefined
117
+ }) {
118
+ return {
119
+ address: tip20ChannelEscrow,
120
+ data: encodeAbiParameters(
121
+ [
122
+ { type: 'address' },
123
+ { type: 'address' },
124
+ { type: 'address' },
125
+ { type: 'bytes32' },
126
+ { type: 'bytes32' },
127
+ { type: 'uint96' },
128
+ ],
129
+ [
130
+ descriptor.operator,
131
+ descriptor.token,
132
+ descriptor.authorizedSigner,
133
+ descriptor.salt,
134
+ parameters.expiringNonceHash,
135
+ parameters.deposit ?? deposit,
136
+ ],
137
+ ),
138
+ topics: encodeEventTopics({
139
+ abi: escrowAbi,
140
+ eventName: 'ChannelOpened',
141
+ args: { channelId: parameters.channelId, payer: descriptor.payer, payee: descriptor.payee },
142
+ }),
143
+ }
144
+ }
145
+
146
+ function topUpLog(parameters: { channelId: `0x${string}`; newDeposit: bigint }) {
147
+ return {
148
+ address: tip20ChannelEscrow,
149
+ data: encodeAbiParameters(
150
+ [{ type: 'uint96' }, { type: 'uint96' }],
151
+ [deposit, parameters.newDeposit],
152
+ ),
153
+ topics: encodeEventTopics({
154
+ abi: escrowAbi,
155
+ eventName: 'TopUp',
156
+ args: { channelId: parameters.channelId, payer: descriptor.payer, payee: descriptor.payee },
157
+ }),
158
+ }
159
+ }
160
+
161
+ async function createSerializedTransaction(parameters: {
162
+ calls: { to: `0x${string}`; data?: `0x${string}` | undefined }[]
163
+ gas?: bigint | undefined
164
+ signed?: boolean | undefined
165
+ }) {
166
+ return (await Transaction.serialize({
167
+ chainId,
168
+ calls: parameters.calls,
169
+ feeToken: descriptor.token,
170
+ nonce: 0,
171
+ ...(parameters.gas !== undefined ? { gas: parameters.gas } : {}),
172
+ ...(parameters.signed
173
+ ? {
174
+ maxFeePerGas: 1n,
175
+ maxPriorityFeePerGas: 1n,
176
+ nonceKey: maxUint256,
177
+ validBefore: Math.floor(Date.now() / 1_000) + 600,
178
+ }
179
+ : {}),
180
+ ...(parameters.signed
181
+ ? {
182
+ signature: {
183
+ r: `0x${'01'.repeat(32)}` as `0x${string}`,
184
+ s: `0x${'02'.repeat(32)}` as `0x${string}`,
185
+ yParity: 0,
186
+ },
187
+ }
188
+ : {}),
189
+ } as never)) as `0x${string}`
190
+ }
191
+
192
+ async function createOpenTransaction(
193
+ parameters: {
194
+ authorizedSigner?: `0x${string}` | undefined
195
+ gas?: bigint | undefined
196
+ operator?: `0x${string}` | undefined
197
+ payee?: `0x${string}` | undefined
198
+ signed?: boolean | undefined
199
+ token?: `0x${string}` | undefined
200
+ to?: `0x${string}` | undefined
201
+ } = {},
202
+ ) {
203
+ const data = encodeFunctionData({
204
+ abi: escrowAbi,
205
+ functionName: 'open',
206
+ args: [
207
+ parameters.payee ?? descriptor.payee,
208
+ parameters.operator ?? descriptor.operator,
209
+ parameters.token ?? descriptor.token,
210
+ deposit,
211
+ descriptor.salt,
212
+ parameters.authorizedSigner ?? descriptor.authorizedSigner,
213
+ ],
214
+ })
215
+ return createSerializedTransaction({
216
+ calls: [{ to: parameters.to ?? tip20ChannelEscrow, data }],
217
+ gas: parameters.gas,
218
+ signed: parameters.signed,
219
+ })
220
+ }
221
+
222
+ async function createTopUpTransaction(
223
+ parameters: {
224
+ additionalDeposit?: bigint | undefined
225
+ descriptor_?: Channel.ChannelDescriptor | undefined
226
+ gas?: bigint | undefined
227
+ signed?: boolean | undefined
228
+ to?: `0x${string}` | undefined
229
+ } = {},
230
+ ) {
231
+ const data = encodeFunctionData({
232
+ abi: escrowAbi,
233
+ functionName: 'topUp',
234
+ args: [
235
+ parameters.descriptor_ ?? descriptor,
236
+ Types.uint96(parameters.additionalDeposit ?? deposit),
237
+ ],
238
+ })
239
+ return createSerializedTransaction({
240
+ calls: [{ to: parameters.to ?? tip20ChannelEscrow, data }],
241
+ gas: parameters.gas,
242
+ signed: parameters.signed,
243
+ })
244
+ }
245
+
246
+ function expectedExpiringNonceHash(serializedTransaction: `0x${string}`) {
247
+ const transaction = Transaction.deserialize(
248
+ serializedTransaction as Transaction.TransactionSerializedTempo,
249
+ )
250
+ return Channel.computeExpiringNonceHash(
251
+ Channel.transactionForExpiringNonceHash({ transaction }),
252
+ { sender: descriptor.payer },
253
+ )
254
+ }
255
+
256
+ describe('precompile open calldata parsing', () => {
257
+ test('parseOpenCall accepts TIP-1034 open calldata', () => {
258
+ const data = encodeFunctionData({
259
+ abi: escrowAbi,
260
+ functionName: 'open',
261
+ args: [
262
+ descriptor.payee,
263
+ descriptor.operator,
264
+ descriptor.token,
265
+ deposit,
266
+ descriptor.salt,
267
+ descriptor.authorizedSigner,
268
+ ],
269
+ })
270
+ const open = ServerChannelOps.parseOpenCall({
271
+ data,
272
+ expected: {
273
+ authorizedSigner: descriptor.authorizedSigner,
274
+ deposit,
275
+ operator: descriptor.operator,
276
+ payee: descriptor.payee,
277
+ token: descriptor.token,
278
+ },
279
+ })
280
+ expect(open).toEqual({
281
+ authorizedSigner: descriptor.authorizedSigner,
282
+ deposit,
283
+ operator: descriptor.operator,
284
+ payee: descriptor.payee,
285
+ salt: descriptor.salt,
286
+ token: descriptor.token,
287
+ })
288
+ })
289
+
290
+ test('parseOpenCall rejects non-open calldata and expected mismatches', () => {
291
+ const approve = encodeFunctionData({
292
+ abi: erc20Abi,
293
+ functionName: 'approve',
294
+ args: [descriptor.payee, deposit],
295
+ })
296
+ expect(() => ServerChannelOps.parseOpenCall({ data: approve })).toThrow(
297
+ 'Expected TIP-1034 open calldata',
298
+ )
299
+
300
+ const data = encodeFunctionData({
301
+ abi: escrowAbi,
302
+ functionName: 'open',
303
+ args: [
304
+ descriptor.payee,
305
+ descriptor.operator,
306
+ descriptor.token,
307
+ deposit,
308
+ descriptor.salt,
309
+ descriptor.authorizedSigner,
310
+ ],
311
+ })
312
+ expect(() =>
313
+ ServerChannelOps.parseOpenCall({
314
+ data,
315
+ expected: { payee: '0xffffffffffffffffffffffffffffffffffffffff' },
316
+ }),
317
+ ).toThrow('payee does not match')
318
+ expect(() =>
319
+ ServerChannelOps.parseOpenCall({
320
+ data,
321
+ expected: { operator: '0xffffffffffffffffffffffffffffffffffffffff' },
322
+ }),
323
+ ).toThrow('operator does not match')
324
+ expect(() =>
325
+ ServerChannelOps.parseOpenCall({
326
+ data,
327
+ expected: { token: '0xffffffffffffffffffffffffffffffffffffffff' },
328
+ }),
329
+ ).toThrow('token does not match')
330
+ expect(() =>
331
+ ServerChannelOps.parseOpenCall({
332
+ data,
333
+ expected: { authorizedSigner: '0xffffffffffffffffffffffffffffffffffffffff' },
334
+ }),
335
+ ).toThrow('authorizedSigner does not match')
336
+ expect(() =>
337
+ ServerChannelOps.parseOpenCall({ data, expected: { deposit: Types.uint96(1n) } }),
338
+ ).toThrow('deposit does not match')
339
+ })
340
+ })
341
+
342
+ describe('precompile broadcastOpenTransaction', () => {
343
+ test('rejects transactions with extra calls', async () => {
344
+ const serializedTransaction = await createSerializedTransaction({
345
+ calls: [
346
+ {
347
+ to: tip20ChannelEscrow,
348
+ data: encodeFunctionData({
349
+ abi: escrowAbi,
350
+ functionName: 'open',
351
+ args: [
352
+ descriptor.payee,
353
+ descriptor.operator,
354
+ descriptor.token,
355
+ deposit,
356
+ descriptor.salt,
357
+ descriptor.authorizedSigner,
358
+ ],
359
+ }),
360
+ },
361
+ {
362
+ to: descriptor.token,
363
+ data: encodeFunctionData({
364
+ abi: erc20Abi,
365
+ functionName: 'approve',
366
+ args: [tip20ChannelEscrow, deposit],
367
+ }),
368
+ },
369
+ ],
370
+ })
371
+
372
+ await expect(
373
+ Chain.broadcastOpenTransaction({
374
+ chainId,
375
+ client: createMockClient(),
376
+ escrowContract: tip20ChannelEscrow,
377
+ expectedAuthorizedSigner: descriptor.authorizedSigner,
378
+ expectedChannelId: `0x${'11'.repeat(32)}`,
379
+ expectedCurrency: descriptor.token,
380
+ expectedExpiringNonceHash: `0x${'aa'.repeat(32)}`,
381
+ expectedOperator: descriptor.operator,
382
+ expectedPayee: descriptor.payee,
383
+ expectedPayer: descriptor.payer,
384
+ serializedTransaction,
385
+ }),
386
+ ).rejects.toThrow('TIP-1034 open transaction must contain exactly one call')
387
+ })
388
+
389
+ test('rejects open transactions targeting the wrong escrow contract', async () => {
390
+ const serializedTransaction = await createOpenTransaction({ to: descriptor.token })
391
+
392
+ await expect(
393
+ Chain.broadcastOpenTransaction({
394
+ chainId,
395
+ client: createMockClient(),
396
+ escrowContract: tip20ChannelEscrow,
397
+ expectedAuthorizedSigner: descriptor.authorizedSigner,
398
+ expectedChannelId: `0x${'11'.repeat(32)}`,
399
+ expectedCurrency: descriptor.token,
400
+ expectedExpiringNonceHash: expectedExpiringNonceHash(serializedTransaction),
401
+ expectedOperator: descriptor.operator,
402
+ expectedPayee: descriptor.payee,
403
+ expectedPayer: descriptor.payer,
404
+ serializedTransaction,
405
+ }),
406
+ ).rejects.toThrow('TIP-1034 open transaction targets the wrong address')
407
+ })
408
+
409
+ test('rejects open transactions missing calldata', async () => {
410
+ const serializedTransaction = await createSerializedTransaction({
411
+ calls: [{ to: tip20ChannelEscrow }],
412
+ })
413
+
414
+ await expect(
415
+ Chain.broadcastOpenTransaction({
416
+ chainId,
417
+ client: createMockClient(),
418
+ escrowContract: tip20ChannelEscrow,
419
+ expectedAuthorizedSigner: descriptor.authorizedSigner,
420
+ expectedChannelId: `0x${'11'.repeat(32)}`,
421
+ expectedCurrency: descriptor.token,
422
+ expectedExpiringNonceHash: expectedExpiringNonceHash(serializedTransaction),
423
+ expectedOperator: descriptor.operator,
424
+ expectedPayee: descriptor.payee,
425
+ expectedPayer: descriptor.payer,
426
+ serializedTransaction,
427
+ }),
428
+ ).rejects.toThrow('TIP-1034 open transaction is missing calldata')
429
+ })
430
+
431
+ test('rejects open calldata mismatches before broadcasting', async () => {
432
+ const serializedTransaction = await createOpenTransaction({ payee: zeroAddress })
433
+
434
+ await expect(
435
+ Chain.broadcastOpenTransaction({
436
+ chainId,
437
+ client: createMockClient(),
438
+ escrowContract: tip20ChannelEscrow,
439
+ expectedAuthorizedSigner: descriptor.authorizedSigner,
440
+ expectedChannelId: `0x${'11'.repeat(32)}`,
441
+ expectedCurrency: descriptor.token,
442
+ expectedExpiringNonceHash: expectedExpiringNonceHash(serializedTransaction),
443
+ expectedOperator: descriptor.operator,
444
+ expectedPayee: descriptor.payee,
445
+ expectedPayer: descriptor.payer,
446
+ serializedTransaction,
447
+ }),
448
+ ).rejects.toThrow('payee does not match')
449
+ })
450
+
451
+ test('fee-payer rejects non-Tempo open transactions', async () => {
452
+ await expect(
453
+ Chain.broadcastOpenTransaction({
454
+ chainId,
455
+ client: createMockClient(),
456
+ escrowContract: tip20ChannelEscrow,
457
+ expectedAuthorizedSigner: descriptor.authorizedSigner,
458
+ expectedChannelId: `0x${'11'.repeat(32)}`,
459
+ expectedCurrency: descriptor.token,
460
+ expectedExpiringNonceHash: `0x${'aa'.repeat(32)}`,
461
+ expectedOperator: descriptor.operator,
462
+ expectedPayee: descriptor.payee,
463
+ expectedPayer: descriptor.payer,
464
+ feePayer: { address: descriptor.payee } as never,
465
+ serializedTransaction: '0x1234',
466
+ }),
467
+ ).rejects.toThrow('Only Tempo (0x76/0x78) transactions are supported')
468
+ })
469
+
470
+ test('fee-payer rejects unsigned open transactions', async () => {
471
+ const serializedTransaction = await createOpenTransaction()
472
+ const expiringNonceHash = expectedExpiringNonceHash(serializedTransaction)
473
+ const expectedDescriptor = { ...descriptor, expiringNonceHash }
474
+ const channelId = Channel.computeId({
475
+ ...expectedDescriptor,
476
+ chainId,
477
+ escrow: tip20ChannelEscrow,
478
+ })
479
+
480
+ await expect(
481
+ Chain.broadcastOpenTransaction({
482
+ chainId,
483
+ client: createMockClient(),
484
+ escrowContract: tip20ChannelEscrow,
485
+ expectedAuthorizedSigner: descriptor.authorizedSigner,
486
+ expectedChannelId: channelId,
487
+ expectedCurrency: descriptor.token,
488
+ expectedExpiringNonceHash: expiringNonceHash,
489
+ expectedOperator: descriptor.operator,
490
+ expectedPayee: descriptor.payee,
491
+ expectedPayer: descriptor.payer,
492
+ feePayer: { address: descriptor.payee } as never,
493
+ serializedTransaction,
494
+ }),
495
+ ).rejects.toThrow('Transaction must be signed by the sender before fee payer co-signing')
496
+ })
497
+
498
+ test('fee-payer rejects open transactions whose gas budget exceeds sponsor policy', async () => {
499
+ const serializedTransaction = await createOpenTransaction({ gas: 2_000_001n, signed: true })
500
+ const transaction = Transaction.deserialize(
501
+ serializedTransaction as Transaction.TransactionSerializedTempo,
502
+ )
503
+ const payer = transaction.from!
504
+ const expiringNonceHash = Channel.computeExpiringNonceHash(
505
+ Channel.transactionForExpiringNonceHash({ feePayer, transaction }),
506
+ { sender: payer },
507
+ )
508
+ const expectedDescriptor = { ...descriptor, payer, expiringNonceHash }
509
+ const channelId = Channel.computeId({
510
+ ...expectedDescriptor,
511
+ chainId,
512
+ escrow: tip20ChannelEscrow,
513
+ })
514
+
515
+ await expect(
516
+ Chain.broadcastOpenTransaction({
517
+ chainId,
518
+ client: createMockClient(),
519
+ escrowContract: tip20ChannelEscrow,
520
+ expectedAuthorizedSigner: descriptor.authorizedSigner,
521
+ expectedChannelId: channelId,
522
+ expectedCurrency: descriptor.token,
523
+ expectedExpiringNonceHash: expiringNonceHash,
524
+ expectedOperator: descriptor.operator,
525
+ expectedPayee: descriptor.payee,
526
+ expectedPayer: payer,
527
+ feePayer,
528
+ feePayerPolicy: { maxGas: 2_000_000n },
529
+ serializedTransaction,
530
+ }),
531
+ ).rejects.toThrow('fee-sponsored transaction gas exceeds sponsor policy')
532
+ })
533
+
534
+ test('fee-payer simulates open before raw broadcast', async () => {
535
+ const rpcMethods: string[] = []
536
+ const serializedTransaction = await createOpenTransaction({ gas: 100_000n, signed: true })
537
+ const transaction = Transaction.deserialize(
538
+ serializedTransaction as Transaction.TransactionSerializedTempo,
539
+ )
540
+ const payer = transaction.from!
541
+ const expiringNonceHash = Channel.computeExpiringNonceHash(
542
+ Channel.transactionForExpiringNonceHash({ feePayer, transaction }),
543
+ { sender: payer },
544
+ )
545
+ const expectedDescriptor = { ...descriptor, payer, expiringNonceHash }
546
+ const channelId = Channel.computeId({
547
+ ...expectedDescriptor,
548
+ chainId,
549
+ escrow: tip20ChannelEscrow,
550
+ })
551
+ const state = { settled: 0n, deposit, closeRequestedAt: 0 }
552
+
553
+ await Chain.broadcastOpenTransaction({
554
+ chainId,
555
+ client: createMockClient({
556
+ channel: { descriptor: expectedDescriptor, state },
557
+ receipt: receipt([openedLog({ channelId, expiringNonceHash })]),
558
+ rpcMethods,
559
+ }),
560
+ escrowContract: tip20ChannelEscrow,
561
+ expectedAuthorizedSigner: descriptor.authorizedSigner,
562
+ expectedChannelId: channelId,
563
+ expectedCurrency: descriptor.token,
564
+ expectedExpiringNonceHash: expiringNonceHash,
565
+ expectedOperator: descriptor.operator,
566
+ expectedPayee: descriptor.payee,
567
+ expectedPayer: payer,
568
+ feePayer: mockFeePayer,
569
+ serializedTransaction,
570
+ })
571
+
572
+ const broadcastIndex = rpcMethods.indexOf('eth_sendRawTransactionSync')
573
+ const simulationIndex = rpcMethods.indexOf('eth_call')
574
+ expect(broadcastIndex).toBeGreaterThan(-1)
575
+ expect(simulationIndex).toBeGreaterThan(-1)
576
+ expect(simulationIndex).toBeLessThan(broadcastIndex)
577
+ })
578
+
579
+ test('rejects expiring nonce hash mismatches before broadcasting', async () => {
580
+ const serializedTransaction = await createOpenTransaction()
581
+
582
+ const wrongExpiringNonceHash = `0x${'bb'.repeat(32)}` as const
583
+ const expectedChannelId = Channel.computeId({
584
+ ...descriptor,
585
+ chainId,
586
+ escrow: tip20ChannelEscrow,
587
+ expiringNonceHash: wrongExpiringNonceHash,
588
+ })
589
+
590
+ await expect(
591
+ Chain.broadcastOpenTransaction({
592
+ chainId,
593
+ client: createMockClient(),
594
+ escrowContract: tip20ChannelEscrow,
595
+ expectedAuthorizedSigner: descriptor.authorizedSigner,
596
+ expectedChannelId,
597
+ expectedCurrency: descriptor.token,
598
+ expectedExpiringNonceHash: wrongExpiringNonceHash,
599
+ expectedOperator: descriptor.operator,
600
+ expectedPayee: descriptor.payee,
601
+ expectedPayer: descriptor.payer,
602
+ serializedTransaction,
603
+ }),
604
+ ).rejects.toThrow('credential expiringNonceHash does not match transaction')
605
+ })
606
+
607
+ test('returns tx hash, descriptor, event fields, and read-back state on success', async () => {
608
+ const serializedTransaction = await createOpenTransaction()
609
+ const expiringNonceHash = expectedExpiringNonceHash(serializedTransaction)
610
+ const expectedDescriptor = { ...descriptor, expiringNonceHash }
611
+ const channelId = Channel.computeId({
612
+ ...expectedDescriptor,
613
+ chainId,
614
+ escrow: tip20ChannelEscrow,
615
+ })
616
+ const state = { settled: 0n, deposit, closeRequestedAt: 0 }
617
+
618
+ const result = await Chain.broadcastOpenTransaction({
619
+ chainId,
620
+ client: createMockClient({
621
+ channel: { descriptor: expectedDescriptor, state },
622
+ receipt: receipt([openedLog({ channelId, expiringNonceHash })]),
623
+ }),
624
+ escrowContract: tip20ChannelEscrow,
625
+ expectedAuthorizedSigner: descriptor.authorizedSigner,
626
+ expectedChannelId: channelId,
627
+ expectedCurrency: descriptor.token,
628
+ expectedExpiringNonceHash: expiringNonceHash,
629
+ expectedOperator: descriptor.operator,
630
+ expectedPayee: descriptor.payee,
631
+ expectedPayer: descriptor.payer,
632
+ serializedTransaction,
633
+ })
634
+
635
+ expect(result).toEqual({
636
+ txHash,
637
+ descriptor: expectedDescriptor,
638
+ state,
639
+ expiringNonceHash,
640
+ openDeposit: deposit,
641
+ })
642
+ })
643
+
644
+ test('rejects ChannelOpened receipt deposit mismatches', async () => {
645
+ const serializedTransaction = await createOpenTransaction()
646
+ const expiringNonceHash = expectedExpiringNonceHash(serializedTransaction)
647
+ const expectedDescriptor = { ...descriptor, expiringNonceHash }
648
+ const channelId = Channel.computeId({
649
+ ...expectedDescriptor,
650
+ chainId,
651
+ escrow: tip20ChannelEscrow,
652
+ })
653
+
654
+ await expect(
655
+ Chain.broadcastOpenTransaction({
656
+ chainId,
657
+ client: createMockClient({
658
+ channel: {
659
+ descriptor: expectedDescriptor,
660
+ state: { settled: 0n, deposit, closeRequestedAt: 0 },
661
+ },
662
+ receipt: receipt([openedLog({ channelId, expiringNonceHash, deposit: deposit + 1n })]),
663
+ }),
664
+ escrowContract: tip20ChannelEscrow,
665
+ expectedAuthorizedSigner: descriptor.authorizedSigner,
666
+ expectedChannelId: channelId,
667
+ expectedCurrency: descriptor.token,
668
+ expectedExpiringNonceHash: expiringNonceHash,
669
+ expectedOperator: descriptor.operator,
670
+ expectedPayee: descriptor.payee,
671
+ expectedPayer: descriptor.payer,
672
+ serializedTransaction,
673
+ }),
674
+ ).rejects.toThrow('ChannelOpened deposit does not match calldata')
675
+ })
676
+ })
677
+
678
+ describe('precompile broadcastTopUpTransaction', () => {
679
+ test('rejects transactions with extra calls', async () => {
680
+ const serializedTransaction = await createSerializedTransaction({
681
+ calls: [
682
+ {
683
+ to: tip20ChannelEscrow,
684
+ data: encodeFunctionData({
685
+ abi: escrowAbi,
686
+ functionName: 'topUp',
687
+ args: [descriptor, deposit],
688
+ }),
689
+ },
690
+ {
691
+ to: descriptor.token,
692
+ data: encodeFunctionData({
693
+ abi: erc20Abi,
694
+ functionName: 'approve',
695
+ args: [tip20ChannelEscrow, deposit],
696
+ }),
697
+ },
698
+ ],
699
+ })
700
+
701
+ await expect(
702
+ Chain.broadcastTopUpTransaction({
703
+ additionalDeposit: deposit,
704
+ chainId,
705
+ client: createMockClient(),
706
+ descriptor,
707
+ escrowContract: tip20ChannelEscrow,
708
+ expectedChannelId: `0x${'11'.repeat(32)}`,
709
+ expectedCurrency: descriptor.token,
710
+ serializedTransaction,
711
+ }),
712
+ ).rejects.toThrow('TIP-1034 topUp transaction must contain exactly one call')
713
+ })
714
+
715
+ test('rejects top-up transactions targeting the wrong escrow contract', async () => {
716
+ const serializedTransaction = await createTopUpTransaction({ to: descriptor.token })
717
+
718
+ await expect(
719
+ Chain.broadcastTopUpTransaction({
720
+ additionalDeposit: deposit,
721
+ chainId,
722
+ client: createMockClient(),
723
+ descriptor,
724
+ escrowContract: tip20ChannelEscrow,
725
+ expectedChannelId: `0x${'11'.repeat(32)}`,
726
+ expectedCurrency: descriptor.token,
727
+ serializedTransaction,
728
+ }),
729
+ ).rejects.toThrow('TIP-1034 topUp transaction targets the wrong address')
730
+ })
731
+
732
+ test('rejects top-up transactions missing calldata', async () => {
733
+ const serializedTransaction = await createSerializedTransaction({
734
+ calls: [{ to: tip20ChannelEscrow }],
735
+ })
736
+
737
+ await expect(
738
+ Chain.broadcastTopUpTransaction({
739
+ additionalDeposit: deposit,
740
+ chainId,
741
+ client: createMockClient(),
742
+ descriptor,
743
+ escrowContract: tip20ChannelEscrow,
744
+ expectedChannelId: `0x${'11'.repeat(32)}`,
745
+ expectedCurrency: descriptor.token,
746
+ serializedTransaction,
747
+ }),
748
+ ).rejects.toThrow('TIP-1034 topUp transaction is missing calldata')
749
+ })
750
+
751
+ test('rejects top-up calldata descriptor mismatches before broadcasting', async () => {
752
+ const serializedTransaction = await createTopUpTransaction({
753
+ descriptor_: { ...descriptor, payee: zeroAddress },
754
+ })
755
+
756
+ await expect(
757
+ Chain.broadcastTopUpTransaction({
758
+ additionalDeposit: deposit,
759
+ chainId,
760
+ client: createMockClient(),
761
+ descriptor,
762
+ escrowContract: tip20ChannelEscrow,
763
+ expectedChannelId: `0x${'11'.repeat(32)}`,
764
+ expectedCurrency: descriptor.token,
765
+ serializedTransaction,
766
+ }),
767
+ ).rejects.toThrow('descriptor does not match')
768
+ })
769
+
770
+ test('fee-payer rejects non-Tempo top-up transactions', async () => {
771
+ await expect(
772
+ Chain.broadcastTopUpTransaction({
773
+ additionalDeposit: deposit,
774
+ chainId,
775
+ client: createMockClient(),
776
+ descriptor,
777
+ escrowContract: tip20ChannelEscrow,
778
+ expectedChannelId: `0x${'11'.repeat(32)}`,
779
+ expectedCurrency: descriptor.token,
780
+ feePayer: { address: descriptor.payee } as never,
781
+ serializedTransaction: '0x1234',
782
+ }),
783
+ ).rejects.toThrow('Only Tempo (0x76/0x78) transactions are supported')
784
+ })
785
+
786
+ test('fee-payer rejects unsigned top-up transactions', async () => {
787
+ const serializedTransaction = await createTopUpTransaction()
788
+
789
+ await expect(
790
+ Chain.broadcastTopUpTransaction({
791
+ additionalDeposit: deposit,
792
+ chainId,
793
+ client: createMockClient(),
794
+ descriptor,
795
+ escrowContract: tip20ChannelEscrow,
796
+ expectedChannelId: Channel.computeId({
797
+ ...descriptor,
798
+ chainId,
799
+ escrow: tip20ChannelEscrow,
800
+ }),
801
+ expectedCurrency: descriptor.token,
802
+ feePayer: { address: descriptor.payee } as never,
803
+ serializedTransaction,
804
+ }),
805
+ ).rejects.toThrow('Transaction must be signed by the sender before fee payer co-signing')
806
+ })
807
+
808
+ test('fee-payer rejects top-up transactions whose gas budget exceeds sponsor policy', async () => {
809
+ const serializedTransaction = await createTopUpTransaction({ gas: 2_000_001n, signed: true })
810
+
811
+ await expect(
812
+ Chain.broadcastTopUpTransaction({
813
+ additionalDeposit: deposit,
814
+ chainId,
815
+ client: createMockClient(),
816
+ descriptor,
817
+ escrowContract: tip20ChannelEscrow,
818
+ expectedChannelId: Channel.computeId({
819
+ ...descriptor,
820
+ chainId,
821
+ escrow: tip20ChannelEscrow,
822
+ }),
823
+ expectedCurrency: descriptor.token,
824
+ feePayer,
825
+ feePayerPolicy: { maxGas: 2_000_000n },
826
+ serializedTransaction,
827
+ }),
828
+ ).rejects.toThrow('fee-sponsored transaction gas exceeds sponsor policy')
829
+ })
830
+
831
+ test('fee-payer simulates top-up before raw broadcast', async () => {
832
+ const rpcMethods: string[] = []
833
+ const serializedTransaction = await createTopUpTransaction({ gas: 100_000n, signed: true })
834
+ const channelId = Channel.computeId({ ...descriptor, chainId, escrow: tip20ChannelEscrow })
835
+ const newDeposit = deposit * 2n
836
+ await Chain.broadcastTopUpTransaction({
837
+ additionalDeposit: deposit,
838
+ chainId,
839
+ client: createMockClient({
840
+ channel: { descriptor, state: { settled: 0n, deposit: newDeposit, closeRequestedAt: 0 } },
841
+ receipt: receipt([topUpLog({ channelId, newDeposit })]),
842
+ rpcMethods,
843
+ }),
844
+ descriptor,
845
+ escrowContract: tip20ChannelEscrow,
846
+ expectedChannelId: channelId,
847
+ expectedCurrency: descriptor.token,
848
+ feePayer: mockFeePayer,
849
+ serializedTransaction,
850
+ })
851
+
852
+ const broadcastIndex = rpcMethods.indexOf('eth_sendRawTransactionSync')
853
+ const simulationIndex = rpcMethods.indexOf('eth_call')
854
+ expect(broadcastIndex).toBeGreaterThan(-1)
855
+ expect(simulationIndex).toBeGreaterThan(-1)
856
+ expect(simulationIndex).toBeLessThan(broadcastIndex)
857
+ })
858
+
859
+ test('rejects top-up calldata amount mismatches before broadcasting', async () => {
860
+ const serializedTransaction = await createTopUpTransaction({ additionalDeposit: 1n })
861
+
862
+ await expect(
863
+ Chain.broadcastTopUpTransaction({
864
+ additionalDeposit: deposit,
865
+ chainId,
866
+ client: createMockClient(),
867
+ descriptor,
868
+ escrowContract: tip20ChannelEscrow,
869
+ expectedChannelId: `0x${'11'.repeat(32)}`,
870
+ expectedCurrency: descriptor.token,
871
+ serializedTransaction,
872
+ }),
873
+ ).rejects.toThrow('topUp deposit does not match credential')
874
+ })
875
+
876
+ test('returns tx hash, new deposit, and read-back state on success', async () => {
877
+ const serializedTransaction = await createTopUpTransaction()
878
+ const channelId = Channel.computeId({ ...descriptor, chainId, escrow: tip20ChannelEscrow })
879
+ const newDeposit = deposit * 2n
880
+ const state = { settled: 0n, deposit: newDeposit, closeRequestedAt: 0 }
881
+
882
+ const result = await Chain.broadcastTopUpTransaction({
883
+ additionalDeposit: deposit,
884
+ chainId,
885
+ client: createMockClient({
886
+ channel: { descriptor, state },
887
+ receipt: receipt([topUpLog({ channelId, newDeposit })]),
888
+ }),
889
+ descriptor,
890
+ escrowContract: tip20ChannelEscrow,
891
+ expectedChannelId: channelId,
892
+ expectedCurrency: descriptor.token,
893
+ serializedTransaction,
894
+ })
895
+
896
+ expect(result).toEqual({ txHash, newDeposit, state })
897
+ })
898
+
899
+ test('rejects top-up receipt/readback deposit mismatches', async () => {
900
+ const serializedTransaction = await createTopUpTransaction()
901
+ const channelId = Channel.computeId({ ...descriptor, chainId, escrow: tip20ChannelEscrow })
902
+
903
+ await expect(
904
+ Chain.broadcastTopUpTransaction({
905
+ additionalDeposit: deposit,
906
+ chainId,
907
+ client: createMockClient({
908
+ channel: { descriptor, state: { settled: 0n, deposit: deposit, closeRequestedAt: 0 } },
909
+ receipt: receipt([topUpLog({ channelId, newDeposit: deposit * 2n })]),
910
+ }),
911
+ descriptor,
912
+ escrowContract: tip20ChannelEscrow,
913
+ expectedChannelId: channelId,
914
+ expectedCurrency: descriptor.token,
915
+ serializedTransaction,
916
+ }),
917
+ ).rejects.toThrow('on-chain channel state does not match topUp receipt')
918
+ })
919
+ })
920
+
921
+ describe('precompile escrowAbi parity', () => {
922
+ test('contains all TIP-1034 functions and events', () => {
923
+ const functions = escrowAbi.filter((item) => item.type === 'function').map((item) => item.name)
924
+ expect(functions).toEqual([
925
+ 'CLOSE_GRACE_PERIOD',
926
+ 'VOUCHER_TYPEHASH',
927
+ 'open',
928
+ 'settle',
929
+ 'topUp',
930
+ 'close',
931
+ 'requestClose',
932
+ 'withdraw',
933
+ 'getChannel',
934
+ 'getChannelState',
935
+ 'getChannelStatesBatch',
936
+ 'computeChannelId',
937
+ 'getVoucherDigest',
938
+ 'domainSeparator',
939
+ ])
940
+
941
+ const events = escrowAbi.filter((item) => item.type === 'event').map((item) => item.name)
942
+ expect(events).toEqual([
943
+ 'ChannelOpened',
944
+ 'Settled',
945
+ 'TopUp',
946
+ 'CloseRequested',
947
+ 'ChannelClosed',
948
+ 'CloseRequestCancelled',
949
+ ])
950
+ })
951
+
952
+ test('keeps ChannelDescriptor component order and ChannelOpened expiringNonceHash', () => {
953
+ const settle = escrowAbi.find((item) => item.type === 'function' && item.name === 'settle')!
954
+ const descriptorInput = settle.inputs[0]
955
+ expect(descriptorInput.components.map((component) => component.name)).toEqual([
956
+ 'payer',
957
+ 'payee',
958
+ 'operator',
959
+ 'token',
960
+ 'salt',
961
+ 'authorizedSigner',
962
+ 'expiringNonceHash',
963
+ ])
964
+
965
+ const opened = escrowAbi.find((item) => item.type === 'event' && item.name === 'ChannelOpened')!
966
+ expect(opened.inputs.map((input) => input.name)).toContain('expiringNonceHash')
967
+ })
968
+ })
969
+
970
+ const receiptValidationChainId = 42431
971
+ const receiptValidationEscrow = '0x4D50500000000000000000000000000000000000' as Address
972
+ const receiptValidationDescriptor = {
973
+ payer: '0x0000000000000000000000000000000000000001' as Address,
974
+ payee: '0x0000000000000000000000000000000000000002' as Address,
975
+ operator: '0x0000000000000000000000000000000000000000' as Address,
976
+ token: '0x20c0000000000000000000000000000000000001' as Address,
977
+ salt: `0x${'11'.repeat(32)}` as Hex,
978
+ authorizedSigner: '0x0000000000000000000000000000000000000001' as Address,
979
+ expiringNonceHash: `0x${'22'.repeat(32)}` as Hex,
980
+ } satisfies Channel.ChannelDescriptor
981
+ const receiptValidationChannelId = Channel.computeId({
982
+ ...receiptValidationDescriptor,
983
+ chainId: receiptValidationChainId,
984
+ escrow: receiptValidationEscrow,
985
+ })
986
+
987
+ describe('ChainReceiptValidation', () => {
988
+ test('reads typed ChannelOpened receipt fields', () => {
989
+ expect(
990
+ Chain.readChannelOpenedReceiptFields({
991
+ args: {
992
+ channelId: receiptValidationChannelId,
993
+ deposit: 100n,
994
+ expiringNonceHash: receiptValidationDescriptor.expiringNonceHash,
995
+ },
996
+ }),
997
+ ).toEqual({
998
+ channelId: receiptValidationChannelId,
999
+ deposit: 100n,
1000
+ expiringNonceHash: receiptValidationDescriptor.expiringNonceHash,
1001
+ })
1002
+ })
1003
+
1004
+ test('reads typed TopUp receipt fields', () => {
1005
+ expect(
1006
+ Chain.readTopUpReceiptFields({
1007
+ args: { channelId: receiptValidationChannelId, newDeposit: 200n },
1008
+ }),
1009
+ ).toEqual({
1010
+ channelId: receiptValidationChannelId,
1011
+ newDeposit: 200n,
1012
+ })
1013
+ })
1014
+
1015
+ test('reads typed settlement receipt fields', () => {
1016
+ expect(Chain.readSettledReceiptFields({ args: { newSettled: 300n } })).toEqual({
1017
+ newSettled: 300n,
1018
+ })
1019
+ })
1020
+
1021
+ test('reads typed ChannelClosed receipt fields', () => {
1022
+ expect(
1023
+ Chain.readChannelClosedReceiptFields({
1024
+ args: {
1025
+ settledToPayee: 250n,
1026
+ refundedToPayer: 50n,
1027
+ },
1028
+ }),
1029
+ ).toEqual({
1030
+ settledToPayee: 250n,
1031
+ refundedToPayer: 50n,
1032
+ })
1033
+ })
1034
+
1035
+ test('rejects malformed receipt event fields', () => {
1036
+ expect(() =>
1037
+ Chain.readChannelOpenedReceiptFields({
1038
+ args: {
1039
+ channelId: receiptValidationChannelId,
1040
+ deposit: '100',
1041
+ expiringNonceHash: receiptValidationDescriptor.expiringNonceHash,
1042
+ },
1043
+ }),
1044
+ ).toThrow('ChannelOpened deposit missing from receipt event')
1045
+
1046
+ expect(() =>
1047
+ Chain.readChannelOpenedReceiptFields({
1048
+ args: {
1049
+ channelId: '0x1234',
1050
+ deposit: 100n,
1051
+ expiringNonceHash: receiptValidationDescriptor.expiringNonceHash,
1052
+ },
1053
+ }),
1054
+ ).toThrow('ChannelOpened channelId missing from receipt event')
1055
+
1056
+ expect(() =>
1057
+ Chain.readChannelOpenedReceiptFields({
1058
+ args: {
1059
+ channelId: receiptValidationChannelId,
1060
+ deposit: 100n,
1061
+ expiringNonceHash: `0x${'zz'.repeat(32)}`,
1062
+ },
1063
+ }),
1064
+ ).toThrow('ChannelOpened expiringNonceHash missing from receipt event')
1065
+
1066
+ expect(() =>
1067
+ Chain.readTopUpReceiptFields({
1068
+ args: {
1069
+ channelId: '0x1234',
1070
+ newDeposit: 200n,
1071
+ },
1072
+ }),
1073
+ ).toThrow('TopUp channelId missing from receipt event')
1074
+
1075
+ expect(() =>
1076
+ Chain.readTopUpReceiptFields({
1077
+ args: {
1078
+ channelId: receiptValidationChannelId,
1079
+ newDeposit: 2n ** 96n,
1080
+ },
1081
+ }),
1082
+ ).toThrow('TopUp newDeposit exceeds uint96 range')
1083
+
1084
+ expect(() => Chain.readSettledReceiptFields({ args: { newSettled: '300' } })).toThrow(
1085
+ 'Settled newSettled missing from receipt event',
1086
+ )
1087
+
1088
+ expect(() =>
1089
+ Chain.readChannelClosedReceiptFields({
1090
+ args: {
1091
+ settledToPayee: 250n,
1092
+ refundedToPayer: 2n ** 96n,
1093
+ },
1094
+ }),
1095
+ ).toThrow('ChannelClosed refundedToPayer exceeds uint96 range')
1096
+ })
1097
+
1098
+ test('accepts a matching ChannelOpened receipt and read-back state', () => {
1099
+ expect(() =>
1100
+ Chain.validateChannelOpenedReceipt({
1101
+ chainId: receiptValidationChainId,
1102
+ descriptor: receiptValidationDescriptor,
1103
+ emittedChannelId: receiptValidationChannelId,
1104
+ emittedDeposit: 100n,
1105
+ emittedExpiringNonceHash: receiptValidationDescriptor.expiringNonceHash,
1106
+ escrow: receiptValidationEscrow,
1107
+ expectedChannelId: receiptValidationChannelId,
1108
+ openDeposit: 100n,
1109
+ }),
1110
+ ).not.toThrow()
1111
+
1112
+ expect(() =>
1113
+ Chain.validateOpenReadbackState({
1114
+ emittedDeposit: 100n,
1115
+ state: { deposit: 100n, settled: 0n, closeRequestedAt: 0 },
1116
+ }),
1117
+ ).not.toThrow()
1118
+ })
1119
+
1120
+ test('rejects ChannelOpened mismatches', () => {
1121
+ const cases = [
1122
+ {
1123
+ parameters: { emittedChannelId: `0x${'ff'.repeat(32)}` as Hex },
1124
+ message: 'ChannelOpened channelId does not match credential',
1125
+ },
1126
+ {
1127
+ parameters: { emittedExpiringNonceHash: `0x${'33'.repeat(32)}` as Hex },
1128
+ message: 'ChannelOpened expiringNonceHash does not match descriptor',
1129
+ },
1130
+ {
1131
+ parameters: { emittedDeposit: 101n },
1132
+ message: 'ChannelOpened deposit does not match calldata',
1133
+ },
1134
+ ] as const
1135
+
1136
+ for (const item of cases) {
1137
+ expect(() =>
1138
+ Chain.validateChannelOpenedReceipt({
1139
+ chainId: receiptValidationChainId,
1140
+ descriptor: receiptValidationDescriptor,
1141
+ emittedChannelId: receiptValidationChannelId,
1142
+ emittedDeposit: 100n,
1143
+ emittedExpiringNonceHash: receiptValidationDescriptor.expiringNonceHash,
1144
+ escrow: receiptValidationEscrow,
1145
+ expectedChannelId: receiptValidationChannelId,
1146
+ openDeposit: 100n,
1147
+ ...item.parameters,
1148
+ }),
1149
+ ).toThrow(item.message)
1150
+ }
1151
+ })
1152
+
1153
+ test('rejects mismatched open read-back state', () => {
1154
+ expect(() =>
1155
+ Chain.validateOpenReadbackState({
1156
+ emittedDeposit: 100n,
1157
+ state: { deposit: 100n, settled: 1n, closeRequestedAt: 0 },
1158
+ }),
1159
+ ).toThrow(VerificationFailedError)
1160
+ })
1161
+
1162
+ test('validates top-up receipt and read-back state', () => {
1163
+ expect(() =>
1164
+ Chain.validateTopUpReceipt({
1165
+ emittedChannelId: receiptValidationChannelId,
1166
+ expectedChannelId: receiptValidationChannelId,
1167
+ }),
1168
+ ).not.toThrow()
1169
+ expect(() =>
1170
+ Chain.validateTopUpReadbackState({
1171
+ newDeposit: 200n,
1172
+ state: { deposit: 200n, settled: 0n, closeRequestedAt: 0 },
1173
+ }),
1174
+ ).not.toThrow()
1175
+ })
1176
+
1177
+ test('rejects top-up receipt and read-back mismatches', () => {
1178
+ expect(() =>
1179
+ Chain.validateTopUpReceipt({
1180
+ emittedChannelId: `0x${'ff'.repeat(32)}` as Hex,
1181
+ expectedChannelId: receiptValidationChannelId,
1182
+ }),
1183
+ ).toThrow('TopUp channelId does not match credential')
1184
+
1185
+ expect(() =>
1186
+ Chain.validateTopUpReadbackState({
1187
+ newDeposit: 200n,
1188
+ state: { deposit: 199n, settled: 0n, closeRequestedAt: 0 },
1189
+ }),
1190
+ ).toThrow('on-chain channel state does not match topUp receipt')
1191
+ })
1192
+ })
1193
+
1194
+ describe('Chain.assertPrecompileFeePayerPolicy', () => {
1195
+ test('allows transactions within configured sponsor limits', () => {
1196
+ expect(() =>
1197
+ Chain.assertPrecompileFeePayerPolicy({
1198
+ prepared: {
1199
+ gas: 100n,
1200
+ maxFeePerGas: 20n,
1201
+ maxPriorityFeePerGas: 5n,
1202
+ },
1203
+ policy: {
1204
+ maxGas: 100n,
1205
+ maxFeePerGas: 20n,
1206
+ maxPriorityFeePerGas: 5n,
1207
+ maxTotalFee: 2_000n,
1208
+ },
1209
+ }),
1210
+ ).not.toThrow()
1211
+ })
1212
+
1213
+ test('allows missing optional policy', () => {
1214
+ expect(() =>
1215
+ Chain.assertPrecompileFeePayerPolicy({
1216
+ prepared: {
1217
+ gas: 100n,
1218
+ maxFeePerGas: 20n,
1219
+ maxPriorityFeePerGas: 5n,
1220
+ },
1221
+ }),
1222
+ ).not.toThrow()
1223
+ })
1224
+
1225
+ test('rejects each exceeded sponsor limit', () => {
1226
+ const cases = [
1227
+ {
1228
+ policy: { maxGas: 99n },
1229
+ reason: 'fee-payer policy maxGas exceeded',
1230
+ },
1231
+ {
1232
+ policy: { maxFeePerGas: 19n },
1233
+ reason: 'fee-payer policy maxFeePerGas exceeded',
1234
+ },
1235
+ {
1236
+ policy: { maxPriorityFeePerGas: 4n },
1237
+ reason: 'fee-payer policy maxPriorityFeePerGas exceeded',
1238
+ },
1239
+ {
1240
+ policy: { maxTotalFee: 1_999n },
1241
+ reason: 'fee-payer policy maxTotalFee exceeded',
1242
+ },
1243
+ ] as const
1244
+
1245
+ for (const item of cases) {
1246
+ expect(() =>
1247
+ Chain.assertPrecompileFeePayerPolicy({
1248
+ prepared: {
1249
+ gas: 100n,
1250
+ maxFeePerGas: 20n,
1251
+ maxPriorityFeePerGas: 5n,
1252
+ },
1253
+ policy: item.policy,
1254
+ }),
1255
+ ).toThrow(item.reason)
1256
+ }
1257
+ })
1258
+ })