mppx 0.8.17 → 0.8.18

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 (914) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +15 -0
  3. package/dist/Challenge.js +21 -4
  4. package/dist/bin.js +3 -3
  5. package/dist/cli/THIRD-PARTY-LICENSES.txt +328 -0
  6. package/dist/cli/cli.js +253 -1505
  7. package/dist/cli/plugins/index.js +19 -5
  8. package/dist/cli/plugins/tempo.js +7 -3
  9. package/dist/cli/sessions/Manager.js +7 -4
  10. package/dist/cli/sessions/request.d.ts +2 -0
  11. package/dist/cli/sessions/request.js +18 -0
  12. package/dist/cli/sessions/store.js +28 -14
  13. package/dist/discovery/Discovery.js +2 -1
  14. package/dist/evm/client/Charge.js +8 -0
  15. package/dist/server/core.d.ts +9 -0
  16. package/dist/server/core.js +9 -0
  17. package/dist/stripe/server/Charge.js +2 -4
  18. package/dist/stripe/server/Methods.d.ts +2 -2
  19. package/dist/stripe/server/Methods.js +17 -11
  20. package/dist/stripe/server/internal/html.gen.d.ts +1 -1
  21. package/dist/stripe/server/internal/html.gen.js +1 -1
  22. package/dist/stripe/server/spt.d.ts +8 -0
  23. package/dist/stripe/server/spt.js +8 -0
  24. package/dist/tempo/Methods.d.ts +13 -0
  25. package/dist/tempo/Methods.js +14 -3
  26. package/dist/tempo/client/Charge.d.ts +3 -0
  27. package/dist/tempo/client/Charge.js +31 -9
  28. package/dist/tempo/client/Methods.d.ts +13 -0
  29. package/dist/tempo/internal/defaults.d.ts +11 -0
  30. package/dist/tempo/internal/defaults.js +11 -0
  31. package/dist/tempo/internal/machine-token-charge.d.ts +51 -0
  32. package/dist/tempo/internal/machine-token-charge.js +113 -0
  33. package/dist/tempo/internal/machine-token-session.d.ts +88 -0
  34. package/dist/tempo/internal/machine-token-session.js +173 -0
  35. package/dist/tempo/internal/signature-envelope.d.ts +14 -0
  36. package/dist/tempo/internal/signature-envelope.js +28 -0
  37. package/dist/tempo/legacy/client/ChannelOps.d.ts +2 -2
  38. package/dist/tempo/legacy/client/ChannelOps.js +11 -5
  39. package/dist/tempo/legacy/client/Session.d.ts +13 -1
  40. package/dist/tempo/legacy/client/Session.js +3 -3
  41. package/dist/tempo/legacy/client/SessionManager.d.ts +3 -1
  42. package/dist/tempo/legacy/client/SessionManager.js +2 -0
  43. package/dist/tempo/server/Charge.d.ts +3 -0
  44. package/dist/tempo/server/Charge.js +58 -17
  45. package/dist/tempo/server/Methods.d.ts +21 -4
  46. package/dist/tempo/server/Methods.js +8 -4
  47. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  48. package/dist/tempo/server/internal/html.gen.js +1 -1
  49. package/dist/tempo/session/client/ChannelOps.d.ts +18 -3
  50. package/dist/tempo/session/client/ChannelOps.js +35 -6
  51. package/dist/tempo/session/client/ChannelStore.d.ts +0 -5
  52. package/dist/tempo/session/client/ChannelStore.js +2 -2
  53. package/dist/tempo/session/client/CredentialState.d.ts +17 -2
  54. package/dist/tempo/session/client/CredentialState.js +77 -76
  55. package/dist/tempo/session/client/Runtime.d.ts +4 -2
  56. package/dist/tempo/session/client/Runtime.js +21 -5
  57. package/dist/tempo/session/client/Session.d.ts +15 -3
  58. package/dist/tempo/session/client/Session.js +186 -63
  59. package/dist/tempo/session/client/SessionManager.d.ts +3 -1
  60. package/dist/tempo/session/client/SessionManager.js +35 -11
  61. package/dist/tempo/session/client/Transports.d.ts +2 -0
  62. package/dist/tempo/session/client/Transports.js +12 -2
  63. package/dist/tempo/session/precompile/Chain.d.ts +10 -2
  64. package/dist/tempo/session/precompile/Chain.js +50 -10
  65. package/dist/tempo/session/precompile/Protocol.d.ts +15 -0
  66. package/dist/tempo/session/precompile/Protocol.js +7 -0
  67. package/dist/tempo/session/precompile/Voucher.d.ts +3 -1
  68. package/dist/tempo/session/precompile/Voucher.js +6 -12
  69. package/dist/tempo/session/server/ChannelStore.d.ts +6 -0
  70. package/dist/tempo/session/server/ChannelStore.js +11 -4
  71. package/dist/tempo/session/server/CredentialVerification.d.ts +11 -9
  72. package/dist/tempo/session/server/CredentialVerification.js +185 -109
  73. package/dist/tempo/session/server/MeteredStream.d.ts +1 -1
  74. package/dist/tempo/session/server/MeteredStream.js +5 -4
  75. package/dist/tempo/session/server/RequestState.d.ts +19 -0
  76. package/dist/tempo/session/server/RequestState.js +61 -15
  77. package/dist/tempo/session/server/Session.d.ts +14 -2
  78. package/dist/tempo/session/server/Session.js +37 -6
  79. package/dist/tempo/session/server/Settlement.d.ts +11 -4
  80. package/dist/tempo/session/server/Settlement.js +53 -21
  81. package/dist/x402/client/Exact.d.ts +8 -0
  82. package/dist/x402/client/Exact.js +40 -10
  83. package/dist/x402/server/EvmCharge.d.ts +22 -0
  84. package/dist/x402/server/EvmCharge.js +23 -1
  85. package/package.json +15 -37
  86. package/dist/BodyDigest.d.ts.map +0 -1
  87. package/dist/BodyDigest.js.map +0 -1
  88. package/dist/Challenge.d.ts.map +0 -1
  89. package/dist/Challenge.js.map +0 -1
  90. package/dist/Constants.d.ts.map +0 -1
  91. package/dist/Constants.js.map +0 -1
  92. package/dist/Credential.d.ts.map +0 -1
  93. package/dist/Credential.js.map +0 -1
  94. package/dist/Errors.d.ts.map +0 -1
  95. package/dist/Errors.js.map +0 -1
  96. package/dist/Expires.d.ts.map +0 -1
  97. package/dist/Expires.js.map +0 -1
  98. package/dist/Html.d.ts.map +0 -1
  99. package/dist/Html.js.map +0 -1
  100. package/dist/Mcp.d.ts.map +0 -1
  101. package/dist/Mcp.js.map +0 -1
  102. package/dist/Method.d.ts.map +0 -1
  103. package/dist/Method.js.map +0 -1
  104. package/dist/PaymentRequest.d.ts.map +0 -1
  105. package/dist/PaymentRequest.js.map +0 -1
  106. package/dist/Receipt.d.ts.map +0 -1
  107. package/dist/Receipt.js.map +0 -1
  108. package/dist/Store.d.ts.map +0 -1
  109. package/dist/Store.js.map +0 -1
  110. package/dist/attestation/Client.d.ts.map +0 -1
  111. package/dist/attestation/Client.js.map +0 -1
  112. package/dist/attestation/Constants.d.ts.map +0 -1
  113. package/dist/attestation/Constants.js.map +0 -1
  114. package/dist/attestation/Server.d.ts.map +0 -1
  115. package/dist/attestation/Server.js.map +0 -1
  116. package/dist/attestation/Types.d.ts.map +0 -1
  117. package/dist/attestation/Types.js.map +0 -1
  118. package/dist/attestation/index.d.ts.map +0 -1
  119. package/dist/attestation/index.js.map +0 -1
  120. package/dist/attestation/internal/HttpMessageSignature.d.ts.map +0 -1
  121. package/dist/attestation/internal/HttpMessageSignature.js.map +0 -1
  122. package/dist/attestation/internal/SigningContext.d.ts.map +0 -1
  123. package/dist/attestation/internal/SigningContext.js.map +0 -1
  124. package/dist/bin.d.ts.map +0 -1
  125. package/dist/bin.js.map +0 -1
  126. package/dist/cli/account.d.ts.map +0 -1
  127. package/dist/cli/account.js.map +0 -1
  128. package/dist/cli/cli.d.ts.map +0 -1
  129. package/dist/cli/cli.js.map +0 -1
  130. package/dist/cli/config.d.ts.map +0 -1
  131. package/dist/cli/config.js.map +0 -1
  132. package/dist/cli/internal.d.ts.map +0 -1
  133. package/dist/cli/internal.js.map +0 -1
  134. package/dist/cli/plugins/evm.d.ts.map +0 -1
  135. package/dist/cli/plugins/evm.js.map +0 -1
  136. package/dist/cli/plugins/index.d.ts.map +0 -1
  137. package/dist/cli/plugins/index.js.map +0 -1
  138. package/dist/cli/plugins/plugin.d.ts.map +0 -1
  139. package/dist/cli/plugins/plugin.js.map +0 -1
  140. package/dist/cli/plugins/stripe.d.ts.map +0 -1
  141. package/dist/cli/plugins/stripe.js.map +0 -1
  142. package/dist/cli/plugins/tempo.d.ts.map +0 -1
  143. package/dist/cli/plugins/tempo.js.map +0 -1
  144. package/dist/cli/sessions/Manager.d.ts.map +0 -1
  145. package/dist/cli/sessions/Manager.js.map +0 -1
  146. package/dist/cli/sessions/commands.d.ts.map +0 -1
  147. package/dist/cli/sessions/commands.js.map +0 -1
  148. package/dist/cli/sessions/request.d.ts.map +0 -1
  149. package/dist/cli/sessions/request.js.map +0 -1
  150. package/dist/cli/sessions/store.d.ts.map +0 -1
  151. package/dist/cli/sessions/store.js.map +0 -1
  152. package/dist/cli/utils.d.ts.map +0 -1
  153. package/dist/cli/utils.js.map +0 -1
  154. package/dist/cli/validate/challenge.d.ts.map +0 -1
  155. package/dist/cli/validate/challenge.js.map +0 -1
  156. package/dist/cli/validate/discovery.d.ts.map +0 -1
  157. package/dist/cli/validate/discovery.js.map +0 -1
  158. package/dist/cli/validate/helpers.d.ts.map +0 -1
  159. package/dist/cli/validate/helpers.js.map +0 -1
  160. package/dist/cli/validate/index.d.ts.map +0 -1
  161. package/dist/cli/validate/index.js.map +0 -1
  162. package/dist/cli/validate/messages.d.ts.map +0 -1
  163. package/dist/cli/validate/messages.js.map +0 -1
  164. package/dist/cli/validate/payment.d.ts.map +0 -1
  165. package/dist/cli/validate/payment.js.map +0 -1
  166. package/dist/client/Methods.d.ts.map +0 -1
  167. package/dist/client/Methods.js.map +0 -1
  168. package/dist/client/Mppx.d.ts.map +0 -1
  169. package/dist/client/Mppx.js.map +0 -1
  170. package/dist/client/Transport.d.ts.map +0 -1
  171. package/dist/client/Transport.js.map +0 -1
  172. package/dist/client/index.d.ts.map +0 -1
  173. package/dist/client/index.js.map +0 -1
  174. package/dist/client/internal/Fetch.d.ts.map +0 -1
  175. package/dist/client/internal/Fetch.js.map +0 -1
  176. package/dist/client/internal/MethodChallenge.d.ts.map +0 -1
  177. package/dist/client/internal/MethodChallenge.js.map +0 -1
  178. package/dist/client/internal/MethodResponse.d.ts.map +0 -1
  179. package/dist/client/internal/MethodResponse.js.map +0 -1
  180. package/dist/client/internal/protocols/Mcp.d.ts.map +0 -1
  181. package/dist/client/internal/protocols/Mcp.js.map +0 -1
  182. package/dist/client/internal/protocols/Mpp.d.ts.map +0 -1
  183. package/dist/client/internal/protocols/Mpp.js.map +0 -1
  184. package/dist/client/internal/protocols/Protocol.d.ts.map +0 -1
  185. package/dist/client/internal/protocols/Protocol.js.map +0 -1
  186. package/dist/client/internal/protocols/Shared.d.ts.map +0 -1
  187. package/dist/client/internal/protocols/Shared.js.map +0 -1
  188. package/dist/client/internal/protocols/X402.d.ts.map +0 -1
  189. package/dist/client/internal/protocols/X402.js.map +0 -1
  190. package/dist/client/node.d.ts.map +0 -1
  191. package/dist/client/node.js.map +0 -1
  192. package/dist/discovery/Discovery.d.ts.map +0 -1
  193. package/dist/discovery/Discovery.js.map +0 -1
  194. package/dist/discovery/OpenApi.d.ts.map +0 -1
  195. package/dist/discovery/OpenApi.js.map +0 -1
  196. package/dist/discovery/Validate.d.ts.map +0 -1
  197. package/dist/discovery/Validate.js.map +0 -1
  198. package/dist/discovery/index.d.ts.map +0 -1
  199. package/dist/discovery/index.js.map +0 -1
  200. package/dist/discovery/internal/Metadata.d.ts.map +0 -1
  201. package/dist/discovery/internal/Metadata.js.map +0 -1
  202. package/dist/evm/Assets.d.ts.map +0 -1
  203. package/dist/evm/Assets.js.map +0 -1
  204. package/dist/evm/Chains.d.ts.map +0 -1
  205. package/dist/evm/Chains.js.map +0 -1
  206. package/dist/evm/Methods.d.ts.map +0 -1
  207. package/dist/evm/Methods.js.map +0 -1
  208. package/dist/evm/Types.d.ts.map +0 -1
  209. package/dist/evm/Types.js.map +0 -1
  210. package/dist/evm/client/Charge.d.ts.map +0 -1
  211. package/dist/evm/client/Charge.js.map +0 -1
  212. package/dist/evm/client/Methods.d.ts.map +0 -1
  213. package/dist/evm/client/Methods.js.map +0 -1
  214. package/dist/evm/client/index.d.ts.map +0 -1
  215. package/dist/evm/client/index.js.map +0 -1
  216. package/dist/evm/index.d.ts.map +0 -1
  217. package/dist/evm/index.js.map +0 -1
  218. package/dist/evm/server/Charge.d.ts.map +0 -1
  219. package/dist/evm/server/Charge.js.map +0 -1
  220. package/dist/evm/server/Methods.d.ts.map +0 -1
  221. package/dist/evm/server/Methods.js.map +0 -1
  222. package/dist/evm/server/index.d.ts.map +0 -1
  223. package/dist/evm/server/index.js.map +0 -1
  224. package/dist/index.d.ts.map +0 -1
  225. package/dist/index.js.map +0 -1
  226. package/dist/internal/AcceptPayment.d.ts.map +0 -1
  227. package/dist/internal/AcceptPayment.js.map +0 -1
  228. package/dist/internal/HeaderCodec.d.ts.map +0 -1
  229. package/dist/internal/HeaderCodec.js.map +0 -1
  230. package/dist/internal/constantTimeEqual.d.ts.map +0 -1
  231. package/dist/internal/constantTimeEqual.js.map +0 -1
  232. package/dist/internal/env.d.ts.map +0 -1
  233. package/dist/internal/env.js.map +0 -1
  234. package/dist/internal/types.d.ts.map +0 -1
  235. package/dist/internal/types.js.map +0 -1
  236. package/dist/mcp/client/McpClient.d.ts.map +0 -1
  237. package/dist/mcp/client/McpClient.js.map +0 -1
  238. package/dist/mcp/client/index.d.ts.map +0 -1
  239. package/dist/mcp/client/index.js.map +0 -1
  240. package/dist/mcp/server/Transport.d.ts.map +0 -1
  241. package/dist/mcp/server/Transport.js.map +0 -1
  242. package/dist/mcp/server/index.d.ts.map +0 -1
  243. package/dist/mcp/server/index.js.map +0 -1
  244. package/dist/middlewares/elysia.d.ts.map +0 -1
  245. package/dist/middlewares/elysia.js.map +0 -1
  246. package/dist/middlewares/express.d.ts.map +0 -1
  247. package/dist/middlewares/express.js.map +0 -1
  248. package/dist/middlewares/hono.d.ts.map +0 -1
  249. package/dist/middlewares/hono.js.map +0 -1
  250. package/dist/middlewares/internal/mppx.d.ts.map +0 -1
  251. package/dist/middlewares/internal/mppx.js.map +0 -1
  252. package/dist/middlewares/nextjs.d.ts.map +0 -1
  253. package/dist/middlewares/nextjs.js.map +0 -1
  254. package/dist/proxy/Proxy.d.ts.map +0 -1
  255. package/dist/proxy/Proxy.js.map +0 -1
  256. package/dist/proxy/Service.d.ts.map +0 -1
  257. package/dist/proxy/Service.js.map +0 -1
  258. package/dist/proxy/index.d.ts.map +0 -1
  259. package/dist/proxy/index.js.map +0 -1
  260. package/dist/proxy/internal/Headers.d.ts.map +0 -1
  261. package/dist/proxy/internal/Headers.js.map +0 -1
  262. package/dist/proxy/internal/Route.d.ts.map +0 -1
  263. package/dist/proxy/internal/Route.js.map +0 -1
  264. package/dist/proxy/services/anthropic.d.ts.map +0 -1
  265. package/dist/proxy/services/anthropic.js.map +0 -1
  266. package/dist/proxy/services/openai.d.ts.map +0 -1
  267. package/dist/proxy/services/openai.js.map +0 -1
  268. package/dist/proxy/services/stripe.d.ts.map +0 -1
  269. package/dist/proxy/services/stripe.js.map +0 -1
  270. package/dist/server/Methods.d.ts.map +0 -1
  271. package/dist/server/Methods.js.map +0 -1
  272. package/dist/server/Mppx.d.ts.map +0 -1
  273. package/dist/server/Mppx.js.map +0 -1
  274. package/dist/server/NodeListener.d.ts.map +0 -1
  275. package/dist/server/NodeListener.js.map +0 -1
  276. package/dist/server/Request.d.ts.map +0 -1
  277. package/dist/server/Request.js.map +0 -1
  278. package/dist/server/Response.d.ts.map +0 -1
  279. package/dist/server/Response.js.map +0 -1
  280. package/dist/server/Transport.d.ts.map +0 -1
  281. package/dist/server/Transport.js.map +0 -1
  282. package/dist/server/index.d.ts.map +0 -1
  283. package/dist/server/index.js.map +0 -1
  284. package/dist/server/internal/html/compose.main.gen.d.ts.map +0 -1
  285. package/dist/server/internal/html/compose.main.gen.js.map +0 -1
  286. package/dist/server/internal/html/config.d.ts.map +0 -1
  287. package/dist/server/internal/html/config.js.map +0 -1
  288. package/dist/server/internal/html/constants.d.ts.map +0 -1
  289. package/dist/server/internal/html/constants.js.map +0 -1
  290. package/dist/server/internal/html/serviceWorker.client.d.ts.map +0 -1
  291. package/dist/server/internal/html/serviceWorker.client.js.map +0 -1
  292. package/dist/server/internal/html/serviceWorker.gen.d.ts.map +0 -1
  293. package/dist/server/internal/html/serviceWorker.gen.js.map +0 -1
  294. package/dist/server/internal/scope.d.ts.map +0 -1
  295. package/dist/server/internal/scope.js.map +0 -1
  296. package/dist/stripe/Methods.d.ts.map +0 -1
  297. package/dist/stripe/Methods.js.map +0 -1
  298. package/dist/stripe/client/Charge.d.ts.map +0 -1
  299. package/dist/stripe/client/Charge.js.map +0 -1
  300. package/dist/stripe/client/Methods.d.ts.map +0 -1
  301. package/dist/stripe/client/Methods.js.map +0 -1
  302. package/dist/stripe/client/index.d.ts.map +0 -1
  303. package/dist/stripe/client/index.js.map +0 -1
  304. package/dist/stripe/index.d.ts.map +0 -1
  305. package/dist/stripe/index.js.map +0 -1
  306. package/dist/stripe/internal/constants.d.ts.map +0 -1
  307. package/dist/stripe/internal/constants.js.map +0 -1
  308. package/dist/stripe/internal/types.d.ts.map +0 -1
  309. package/dist/stripe/internal/types.js.map +0 -1
  310. package/dist/stripe/server/Charge.d.ts.map +0 -1
  311. package/dist/stripe/server/Charge.js.map +0 -1
  312. package/dist/stripe/server/Methods.d.ts.map +0 -1
  313. package/dist/stripe/server/Methods.js.map +0 -1
  314. package/dist/stripe/server/index.d.ts.map +0 -1
  315. package/dist/stripe/server/index.js.map +0 -1
  316. package/dist/stripe/server/internal/deposit-address.d.ts.map +0 -1
  317. package/dist/stripe/server/internal/deposit-address.js.map +0 -1
  318. package/dist/stripe/server/internal/html/types.d.ts.map +0 -1
  319. package/dist/stripe/server/internal/html/types.js.map +0 -1
  320. package/dist/stripe/server/internal/html.gen.d.ts.map +0 -1
  321. package/dist/stripe/server/internal/html.gen.js.map +0 -1
  322. package/dist/stripe/server/internal/record-payment.d.ts.map +0 -1
  323. package/dist/stripe/server/internal/record-payment.js.map +0 -1
  324. package/dist/stripe/server/internal/request.d.ts.map +0 -1
  325. package/dist/stripe/server/internal/request.js.map +0 -1
  326. package/dist/tap/Constants.d.ts.map +0 -1
  327. package/dist/tap/Constants.js.map +0 -1
  328. package/dist/tap/Types.d.ts.map +0 -1
  329. package/dist/tap/Types.js.map +0 -1
  330. package/dist/tap/client/index.d.ts.map +0 -1
  331. package/dist/tap/client/index.js.map +0 -1
  332. package/dist/tap/index.d.ts.map +0 -1
  333. package/dist/tap/index.js.map +0 -1
  334. package/dist/tap/server/index.d.ts.map +0 -1
  335. package/dist/tap/server/index.js.map +0 -1
  336. package/dist/tempo/Attribution.d.ts.map +0 -1
  337. package/dist/tempo/Attribution.js.map +0 -1
  338. package/dist/tempo/Methods.d.ts.map +0 -1
  339. package/dist/tempo/Methods.js.map +0 -1
  340. package/dist/tempo/Proof.d.ts.map +0 -1
  341. package/dist/tempo/Proof.js.map +0 -1
  342. package/dist/tempo/client/Charge.d.ts.map +0 -1
  343. package/dist/tempo/client/Charge.js.map +0 -1
  344. package/dist/tempo/client/Methods.d.ts.map +0 -1
  345. package/dist/tempo/client/Methods.js.map +0 -1
  346. package/dist/tempo/client/ResolveAccount.d.ts.map +0 -1
  347. package/dist/tempo/client/ResolveAccount.js.map +0 -1
  348. package/dist/tempo/client/Subscription.d.ts.map +0 -1
  349. package/dist/tempo/client/Subscription.js.map +0 -1
  350. package/dist/tempo/client/index.d.ts.map +0 -1
  351. package/dist/tempo/client/index.js.map +0 -1
  352. package/dist/tempo/index.d.ts.map +0 -1
  353. package/dist/tempo/index.js.map +0 -1
  354. package/dist/tempo/internal/account.d.ts.map +0 -1
  355. package/dist/tempo/internal/account.js.map +0 -1
  356. package/dist/tempo/internal/address.d.ts.map +0 -1
  357. package/dist/tempo/internal/address.js.map +0 -1
  358. package/dist/tempo/internal/auto-swap.d.ts.map +0 -1
  359. package/dist/tempo/internal/auto-swap.js.map +0 -1
  360. package/dist/tempo/internal/charge.d.ts.map +0 -1
  361. package/dist/tempo/internal/charge.js.map +0 -1
  362. package/dist/tempo/internal/defaults.d.ts.map +0 -1
  363. package/dist/tempo/internal/defaults.js.map +0 -1
  364. package/dist/tempo/internal/fee-payer.d.ts.map +0 -1
  365. package/dist/tempo/internal/fee-payer.js.map +0 -1
  366. package/dist/tempo/internal/fee-token.d.ts.map +0 -1
  367. package/dist/tempo/internal/fee-token.js.map +0 -1
  368. package/dist/tempo/internal/proof.d.ts.map +0 -1
  369. package/dist/tempo/internal/proof.js.map +0 -1
  370. package/dist/tempo/internal/selectors.d.ts.map +0 -1
  371. package/dist/tempo/internal/selectors.js.map +0 -1
  372. package/dist/tempo/internal/types.d.ts.map +0 -1
  373. package/dist/tempo/internal/types.js.map +0 -1
  374. package/dist/tempo/legacy/client/ChannelOps.d.ts.map +0 -1
  375. package/dist/tempo/legacy/client/ChannelOps.js.map +0 -1
  376. package/dist/tempo/legacy/client/Session.d.ts.map +0 -1
  377. package/dist/tempo/legacy/client/Session.js.map +0 -1
  378. package/dist/tempo/legacy/client/SessionManager.d.ts.map +0 -1
  379. package/dist/tempo/legacy/client/SessionManager.js.map +0 -1
  380. package/dist/tempo/legacy/client/index.d.ts.map +0 -1
  381. package/dist/tempo/legacy/client/index.js.map +0 -1
  382. package/dist/tempo/legacy/index.d.ts.map +0 -1
  383. package/dist/tempo/legacy/index.js.map +0 -1
  384. package/dist/tempo/legacy/session/Chain.d.ts.map +0 -1
  385. package/dist/tempo/legacy/session/Chain.js.map +0 -1
  386. package/dist/tempo/legacy/session/Channel.d.ts.map +0 -1
  387. package/dist/tempo/legacy/session/Channel.js.map +0 -1
  388. package/dist/tempo/legacy/session/Types.d.ts.map +0 -1
  389. package/dist/tempo/legacy/session/Types.js.map +0 -1
  390. package/dist/tempo/legacy/session/Voucher.d.ts.map +0 -1
  391. package/dist/tempo/legacy/session/Voucher.js.map +0 -1
  392. package/dist/tempo/legacy/session/escrow.abi.d.ts.map +0 -1
  393. package/dist/tempo/legacy/session/escrow.abi.js.map +0 -1
  394. package/dist/tempo/legacy/session/index.d.ts.map +0 -1
  395. package/dist/tempo/legacy/session/index.js.map +0 -1
  396. package/dist/tempo/server/Charge.d.ts.map +0 -1
  397. package/dist/tempo/server/Charge.js.map +0 -1
  398. package/dist/tempo/server/Methods.d.ts.map +0 -1
  399. package/dist/tempo/server/Methods.js.map +0 -1
  400. package/dist/tempo/server/Relay.d.ts.map +0 -1
  401. package/dist/tempo/server/Relay.js.map +0 -1
  402. package/dist/tempo/server/SponsorBudget.d.ts.map +0 -1
  403. package/dist/tempo/server/SponsorBudget.js.map +0 -1
  404. package/dist/tempo/server/Subscription.d.ts.map +0 -1
  405. package/dist/tempo/server/Subscription.js.map +0 -1
  406. package/dist/tempo/server/index.d.ts.map +0 -1
  407. package/dist/tempo/server/index.js.map +0 -1
  408. package/dist/tempo/server/internal/html.gen.d.ts.map +0 -1
  409. package/dist/tempo/server/internal/html.gen.js.map +0 -1
  410. package/dist/tempo/server/internal/request-body.d.ts.map +0 -1
  411. package/dist/tempo/server/internal/request-body.js.map +0 -1
  412. package/dist/tempo/server/internal/transport.d.ts.map +0 -1
  413. package/dist/tempo/server/internal/transport.js.map +0 -1
  414. package/dist/tempo/session/Snapshot.d.ts.map +0 -1
  415. package/dist/tempo/session/Snapshot.js.map +0 -1
  416. package/dist/tempo/session/client/ChannelOps.d.ts.map +0 -1
  417. package/dist/tempo/session/client/ChannelOps.js.map +0 -1
  418. package/dist/tempo/session/client/ChannelStore.d.ts.map +0 -1
  419. package/dist/tempo/session/client/ChannelStore.js.map +0 -1
  420. package/dist/tempo/session/client/CredentialState.d.ts.map +0 -1
  421. package/dist/tempo/session/client/CredentialState.js.map +0 -1
  422. package/dist/tempo/session/client/ReceiptCoordinator.d.ts.map +0 -1
  423. package/dist/tempo/session/client/ReceiptCoordinator.js.map +0 -1
  424. package/dist/tempo/session/client/Runtime.d.ts.map +0 -1
  425. package/dist/tempo/session/client/Runtime.js.map +0 -1
  426. package/dist/tempo/session/client/Session.d.ts.map +0 -1
  427. package/dist/tempo/session/client/Session.js.map +0 -1
  428. package/dist/tempo/session/client/SessionManager.d.ts.map +0 -1
  429. package/dist/tempo/session/client/SessionManager.js.map +0 -1
  430. package/dist/tempo/session/client/Transports.d.ts.map +0 -1
  431. package/dist/tempo/session/client/Transports.js.map +0 -1
  432. package/dist/tempo/session/client/index.d.ts.map +0 -1
  433. package/dist/tempo/session/client/index.js.map +0 -1
  434. package/dist/tempo/session/client/internal/SessionManager.d.ts.map +0 -1
  435. package/dist/tempo/session/client/internal/SessionManager.js.map +0 -1
  436. package/dist/tempo/session/index.d.ts.map +0 -1
  437. package/dist/tempo/session/index.js.map +0 -1
  438. package/dist/tempo/session/precompile/Chain.d.ts.map +0 -1
  439. package/dist/tempo/session/precompile/Chain.js.map +0 -1
  440. package/dist/tempo/session/precompile/Channel.d.ts.map +0 -1
  441. package/dist/tempo/session/precompile/Channel.js.map +0 -1
  442. package/dist/tempo/session/precompile/Protocol.d.ts.map +0 -1
  443. package/dist/tempo/session/precompile/Protocol.js.map +0 -1
  444. package/dist/tempo/session/precompile/Voucher.d.ts.map +0 -1
  445. package/dist/tempo/session/precompile/Voucher.js.map +0 -1
  446. package/dist/tempo/session/precompile/escrow.abi.d.ts.map +0 -1
  447. package/dist/tempo/session/precompile/escrow.abi.js.map +0 -1
  448. package/dist/tempo/session/precompile/index.d.ts.map +0 -1
  449. package/dist/tempo/session/precompile/index.js.map +0 -1
  450. package/dist/tempo/session/server/ChannelOps.d.ts.map +0 -1
  451. package/dist/tempo/session/server/ChannelOps.js.map +0 -1
  452. package/dist/tempo/session/server/ChannelStore.d.ts.map +0 -1
  453. package/dist/tempo/session/server/ChannelStore.js.map +0 -1
  454. package/dist/tempo/session/server/CredentialVerification.d.ts.map +0 -1
  455. package/dist/tempo/session/server/CredentialVerification.js.map +0 -1
  456. package/dist/tempo/session/server/MeteredStream.d.ts.map +0 -1
  457. package/dist/tempo/session/server/MeteredStream.js.map +0 -1
  458. package/dist/tempo/session/server/RequestState.d.ts.map +0 -1
  459. package/dist/tempo/session/server/RequestState.js.map +0 -1
  460. package/dist/tempo/session/server/Session.d.ts.map +0 -1
  461. package/dist/tempo/session/server/Session.js.map +0 -1
  462. package/dist/tempo/session/server/Settlement.d.ts.map +0 -1
  463. package/dist/tempo/session/server/Settlement.js.map +0 -1
  464. package/dist/tempo/session/server/Sse.d.ts.map +0 -1
  465. package/dist/tempo/session/server/Sse.js.map +0 -1
  466. package/dist/tempo/session/server/Transports.d.ts.map +0 -1
  467. package/dist/tempo/session/server/Transports.js.map +0 -1
  468. package/dist/tempo/session/server/Ws.d.ts.map +0 -1
  469. package/dist/tempo/session/server/Ws.js.map +0 -1
  470. package/dist/tempo/session/server/index.d.ts.map +0 -1
  471. package/dist/tempo/session/server/index.js.map +0 -1
  472. package/dist/tempo/subscription/KeyAuthorization.d.ts.map +0 -1
  473. package/dist/tempo/subscription/KeyAuthorization.js.map +0 -1
  474. package/dist/tempo/subscription/Receipt.d.ts.map +0 -1
  475. package/dist/tempo/subscription/Receipt.js.map +0 -1
  476. package/dist/tempo/subscription/Store.d.ts.map +0 -1
  477. package/dist/tempo/subscription/Store.js.map +0 -1
  478. package/dist/tempo/subscription/Types.d.ts.map +0 -1
  479. package/dist/tempo/subscription/Types.js.map +0 -1
  480. package/dist/tempo/subscription/index.d.ts.map +0 -1
  481. package/dist/tempo/subscription/index.js.map +0 -1
  482. package/dist/validation/core.d.ts.map +0 -1
  483. package/dist/validation/core.js.map +0 -1
  484. package/dist/validation/index.d.ts.map +0 -1
  485. package/dist/validation/index.js.map +0 -1
  486. package/dist/viem/Account.d.ts.map +0 -1
  487. package/dist/viem/Account.js.map +0 -1
  488. package/dist/viem/Client.d.ts.map +0 -1
  489. package/dist/viem/Client.js.map +0 -1
  490. package/dist/web-bot-auth/Constants.d.ts.map +0 -1
  491. package/dist/web-bot-auth/Constants.js.map +0 -1
  492. package/dist/web-bot-auth/Types.d.ts.map +0 -1
  493. package/dist/web-bot-auth/Types.js.map +0 -1
  494. package/dist/web-bot-auth/client/index.d.ts.map +0 -1
  495. package/dist/web-bot-auth/client/index.js.map +0 -1
  496. package/dist/web-bot-auth/index.d.ts.map +0 -1
  497. package/dist/web-bot-auth/index.js.map +0 -1
  498. package/dist/web-bot-auth/internal/JwkThumbprint.d.ts.map +0 -1
  499. package/dist/web-bot-auth/internal/JwkThumbprint.js.map +0 -1
  500. package/dist/web-bot-auth/internal/SignatureAgent.d.ts.map +0 -1
  501. package/dist/web-bot-auth/internal/SignatureAgent.js.map +0 -1
  502. package/dist/web-bot-auth/server/index.d.ts.map +0 -1
  503. package/dist/web-bot-auth/server/index.js.map +0 -1
  504. package/dist/x402/Assets.d.ts.map +0 -1
  505. package/dist/x402/Assets.js.map +0 -1
  506. package/dist/x402/Header.d.ts.map +0 -1
  507. package/dist/x402/Header.js.map +0 -1
  508. package/dist/x402/Types.d.ts.map +0 -1
  509. package/dist/x402/Types.js.map +0 -1
  510. package/dist/x402/client/Exact.d.ts.map +0 -1
  511. package/dist/x402/client/Exact.js.map +0 -1
  512. package/dist/x402/index.d.ts.map +0 -1
  513. package/dist/x402/index.js.map +0 -1
  514. package/dist/x402/internal/ChallengeBrand.d.ts.map +0 -1
  515. package/dist/x402/internal/ChallengeBrand.js.map +0 -1
  516. package/dist/x402/internal/RouteBinding.d.ts.map +0 -1
  517. package/dist/x402/internal/RouteBinding.js.map +0 -1
  518. package/dist/x402/server/EvmCharge.d.ts.map +0 -1
  519. package/dist/x402/server/EvmCharge.js.map +0 -1
  520. package/dist/x402/server/Facilitator.d.ts.map +0 -1
  521. package/dist/x402/server/Facilitator.js.map +0 -1
  522. package/dist/zod.d.ts.map +0 -1
  523. package/dist/zod.js.map +0 -1
  524. package/src/BodyDigest.test.ts +0 -43
  525. package/src/BodyDigest.ts +0 -55
  526. package/src/Challenge.fuzz.test.ts +0 -154
  527. package/src/Challenge.test-d.ts +0 -82
  528. package/src/Challenge.test.ts +0 -1060
  529. package/src/Challenge.ts +0 -687
  530. package/src/Constants.ts +0 -58
  531. package/src/Credential.fuzz.test.ts +0 -62
  532. package/src/Credential.test.ts +0 -378
  533. package/src/Credential.ts +0 -217
  534. package/src/Errors.test.ts +0 -520
  535. package/src/Errors.ts +0 -448
  536. package/src/Expires.test.ts +0 -112
  537. package/src/Expires.ts +0 -59
  538. package/src/Html.ts +0 -67
  539. package/src/Mcp.ts +0 -85
  540. package/src/Method.test.ts +0 -177
  541. package/src/Method.ts +0 -631
  542. package/src/PaymentRequest.test.ts +0 -147
  543. package/src/PaymentRequest.ts +0 -108
  544. package/src/Receipt.test.ts +0 -151
  545. package/src/Receipt.ts +0 -146
  546. package/src/Store.test-d.ts +0 -211
  547. package/src/Store.test.ts +0 -407
  548. package/src/Store.ts +0 -397
  549. package/src/attestation/Attestation.integration.test.ts +0 -208
  550. package/src/attestation/Client.test.ts +0 -83
  551. package/src/attestation/Client.ts +0 -34
  552. package/src/attestation/Constants.ts +0 -15
  553. package/src/attestation/Server.test.ts +0 -60
  554. package/src/attestation/Server.ts +0 -12
  555. package/src/attestation/Types.test-d.ts +0 -54
  556. package/src/attestation/Types.ts +0 -77
  557. package/src/attestation/index.ts +0 -5
  558. package/src/attestation/internal/HttpMessageSignature.test.ts +0 -101
  559. package/src/attestation/internal/HttpMessageSignature.ts +0 -429
  560. package/src/attestation/internal/SigningContext.ts +0 -13
  561. package/src/bin.js +0 -2
  562. package/src/bin.ts +0 -4
  563. package/src/cli/account.ts +0 -269
  564. package/src/cli/cli.test.ts +0 -2221
  565. package/src/cli/cli.ts +0 -1650
  566. package/src/cli/config.test.ts +0 -89
  567. package/src/cli/config.ts +0 -50
  568. package/src/cli/internal.ts +0 -114
  569. package/src/cli/mcp.test.ts +0 -262
  570. package/src/cli/plugins/evm.ts +0 -55
  571. package/src/cli/plugins/index.ts +0 -4
  572. package/src/cli/plugins/plugin.ts +0 -84
  573. package/src/cli/plugins/stripe.ts +0 -174
  574. package/src/cli/plugins/tempo.ts +0 -1102
  575. package/src/cli/sessions/Manager.test.ts +0 -249
  576. package/src/cli/sessions/Manager.ts +0 -93
  577. package/src/cli/sessions/commands.ts +0 -444
  578. package/src/cli/sessions/request.test.ts +0 -51
  579. package/src/cli/sessions/request.ts +0 -353
  580. package/src/cli/sessions/store.test.ts +0 -581
  581. package/src/cli/sessions/store.ts +0 -940
  582. package/src/cli/utils.test.ts +0 -156
  583. package/src/cli/utils.ts +0 -394
  584. package/src/cli/validate/challenge.ts +0 -565
  585. package/src/cli/validate/discovery.test.ts +0 -435
  586. package/src/cli/validate/discovery.ts +0 -221
  587. package/src/cli/validate/helpers.ts +0 -166
  588. package/src/cli/validate/index.ts +0 -229
  589. package/src/cli/validate/messages.ts +0 -7
  590. package/src/cli/validate/payment.ts +0 -774
  591. package/src/cli/validate.test.ts +0 -843
  592. package/src/client/Methods.ts +0 -8
  593. package/src/client/Mppx.test-d.ts +0 -287
  594. package/src/client/Mppx.test.ts +0 -1065
  595. package/src/client/Mppx.ts +0 -487
  596. package/src/client/Transport.test.ts +0 -586
  597. package/src/client/Transport.ts +0 -170
  598. package/src/client/index.ts +0 -29
  599. package/src/client/internal/Fetch.browser.test.ts +0 -197
  600. package/src/client/internal/Fetch.test-d.ts +0 -109
  601. package/src/client/internal/Fetch.test.ts +0 -2336
  602. package/src/client/internal/Fetch.ts +0 -1075
  603. package/src/client/internal/MethodChallenge.ts +0 -35
  604. package/src/client/internal/MethodResponse.ts +0 -67
  605. package/src/client/internal/protocols/Mcp.test.ts +0 -220
  606. package/src/client/internal/protocols/Mcp.ts +0 -186
  607. package/src/client/internal/protocols/Mpp.ts +0 -21
  608. package/src/client/internal/protocols/Protocol.ts +0 -10
  609. package/src/client/internal/protocols/Shared.ts +0 -25
  610. package/src/client/internal/protocols/X402.ts +0 -46
  611. package/src/client/node.test.ts +0 -115
  612. package/src/client/node.ts +0 -247
  613. package/src/discovery/Discovery.test.ts +0 -214
  614. package/src/discovery/Discovery.ts +0 -105
  615. package/src/discovery/OpenApi.test.ts +0 -623
  616. package/src/discovery/OpenApi.ts +0 -192
  617. package/src/discovery/Validate.test.ts +0 -305
  618. package/src/discovery/Validate.ts +0 -77
  619. package/src/discovery/index.ts +0 -3
  620. package/src/discovery/internal/Metadata.ts +0 -51
  621. package/src/env.d.ts +0 -12
  622. package/src/evm/Assets.ts +0 -1
  623. package/src/evm/Chains.ts +0 -11
  624. package/src/evm/Methods.ts +0 -44
  625. package/src/evm/PublicInterface.test-d.ts +0 -134
  626. package/src/evm/Types.ts +0 -140
  627. package/src/evm/client/Charge.test.ts +0 -347
  628. package/src/evm/client/Charge.ts +0 -187
  629. package/src/evm/client/Methods.ts +0 -19
  630. package/src/evm/client/index.ts +0 -6
  631. package/src/evm/index.ts +0 -16
  632. package/src/evm/server/Charge.test.ts +0 -339
  633. package/src/evm/server/Charge.ts +0 -295
  634. package/src/evm/server/Methods.ts +0 -22
  635. package/src/evm/server/index.ts +0 -6
  636. package/src/index.ts +0 -14
  637. package/src/internal/AcceptPayment.test.ts +0 -347
  638. package/src/internal/AcceptPayment.ts +0 -366
  639. package/src/internal/HeaderCodec.ts +0 -36
  640. package/src/internal/changeset.test.ts +0 -106
  641. package/src/internal/constantTimeEqual.test.ts +0 -47
  642. package/src/internal/constantTimeEqual.ts +0 -10
  643. package/src/internal/env.test.ts +0 -42
  644. package/src/internal/env.ts +0 -49
  645. package/src/internal/types.test-d.ts +0 -21
  646. package/src/internal/types.ts +0 -479
  647. package/src/mcp/client/McpClient.integration.test.ts +0 -639
  648. package/src/mcp/client/McpClient.test-d.ts +0 -144
  649. package/src/mcp/client/McpClient.test.ts +0 -463
  650. package/src/mcp/client/McpClient.ts +0 -307
  651. package/src/mcp/client/McpClient.unit.test.ts +0 -135
  652. package/src/mcp/client/index.ts +0 -2
  653. package/src/mcp/server/Transport.test.ts +0 -191
  654. package/src/mcp/server/Transport.ts +0 -111
  655. package/src/mcp/server/index.ts +0 -1
  656. package/src/middlewares/elysia.test.ts +0 -285
  657. package/src/middlewares/elysia.ts +0 -116
  658. package/src/middlewares/express.test.ts +0 -361
  659. package/src/middlewares/express.ts +0 -140
  660. package/src/middlewares/hono.test.ts +0 -473
  661. package/src/middlewares/hono.ts +0 -153
  662. package/src/middlewares/internal/mppx.test.ts +0 -177
  663. package/src/middlewares/internal/mppx.ts +0 -67
  664. package/src/middlewares/nextjs.test.ts +0 -359
  665. package/src/middlewares/nextjs.ts +0 -107
  666. package/src/proxy/Proxy.test.ts +0 -1038
  667. package/src/proxy/Proxy.ts +0 -483
  668. package/src/proxy/Proxy.upstreamError.test.ts +0 -364
  669. package/src/proxy/Service.test.ts +0 -252
  670. package/src/proxy/Service.ts +0 -280
  671. package/src/proxy/index.ts +0 -6
  672. package/src/proxy/internal/Headers.test.ts +0 -139
  673. package/src/proxy/internal/Headers.ts +0 -66
  674. package/src/proxy/internal/Route.test.ts +0 -217
  675. package/src/proxy/internal/Route.ts +0 -64
  676. package/src/proxy/services/anthropic.test.ts +0 -132
  677. package/src/proxy/services/anthropic.ts +0 -53
  678. package/src/proxy/services/hooks.test-d.ts +0 -14
  679. package/src/proxy/services/hooks.test.ts +0 -44
  680. package/src/proxy/services/openai.test.ts +0 -157
  681. package/src/proxy/services/openai.ts +0 -60
  682. package/src/proxy/services/stripe.test.ts +0 -175
  683. package/src/proxy/services/stripe.ts +0 -59
  684. package/src/server/Methods.test-d.ts +0 -88
  685. package/src/server/Methods.test.ts +0 -69
  686. package/src/server/Methods.ts +0 -3
  687. package/src/server/Mppx.authorize.test.ts +0 -210
  688. package/src/server/Mppx.test-d.ts +0 -494
  689. package/src/server/Mppx.test.ts +0 -6711
  690. package/src/server/Mppx.ts +0 -2919
  691. package/src/server/NodeListener.test.ts +0 -293
  692. package/src/server/NodeListener.ts +0 -45
  693. package/src/server/Request.test.ts +0 -255
  694. package/src/server/Request.ts +0 -155
  695. package/src/server/Response.test.ts +0 -32
  696. package/src/server/Response.ts +0 -45
  697. package/src/server/Transport.test.ts +0 -598
  698. package/src/server/Transport.ts +0 -320
  699. package/src/server/index.ts +0 -9
  700. package/src/server/internal/html/compose.main.gen.ts +0 -2
  701. package/src/server/internal/html/compose.main.ts +0 -88
  702. package/src/server/internal/html/config.ts +0 -672
  703. package/src/server/internal/html/constants.ts +0 -28
  704. package/src/server/internal/html/serviceWorker.client.ts +0 -28
  705. package/src/server/internal/html/serviceWorker.gen.ts +0 -2
  706. package/src/server/internal/html/serviceWorker.ts +0 -27
  707. package/src/server/internal/html/tsconfig.compose.json +0 -8
  708. package/src/server/internal/html/tsconfig.worker.client.json +0 -8
  709. package/src/server/internal/html/tsconfig.worker.json +0 -8
  710. package/src/server/internal/scope.ts +0 -43
  711. package/src/stripe/Charge.integration.test.ts +0 -329
  712. package/src/stripe/Methods.test.ts +0 -62
  713. package/src/stripe/Methods.ts +0 -44
  714. package/src/stripe/client/Charge.test.ts +0 -205
  715. package/src/stripe/client/Charge.ts +0 -132
  716. package/src/stripe/client/Methods.ts +0 -37
  717. package/src/stripe/client/index.ts +0 -2
  718. package/src/stripe/index.ts +0 -1
  719. package/src/stripe/internal/constants.ts +0 -7
  720. package/src/stripe/internal/types.ts +0 -47
  721. package/src/stripe/server/Charge.test-d.ts +0 -81
  722. package/src/stripe/server/Charge.test.ts +0 -700
  723. package/src/stripe/server/Charge.ts +0 -487
  724. package/src/stripe/server/Methods.test-d.ts +0 -157
  725. package/src/stripe/server/Methods.test.ts +0 -403
  726. package/src/stripe/server/Methods.ts +0 -441
  727. package/src/stripe/server/index.ts +0 -2
  728. package/src/stripe/server/internal/deposit-address.ts +0 -57
  729. package/src/stripe/server/internal/html/main.ts +0 -165
  730. package/src/stripe/server/internal/html/node_modules/.bin/mppx +0 -22
  731. package/src/stripe/server/internal/html/node_modules/.bin/mppx.src +0 -22
  732. package/src/stripe/server/internal/html/package.json +0 -9
  733. package/src/stripe/server/internal/html/stripe-js-pure.d.ts +0 -7
  734. package/src/stripe/server/internal/html/tsconfig.json +0 -8
  735. package/src/stripe/server/internal/html/types.ts +0 -5
  736. package/src/stripe/server/internal/html.gen.ts +0 -2
  737. package/src/stripe/server/internal/record-payment.ts +0 -66
  738. package/src/stripe/server/internal/request.ts +0 -49
  739. package/src/tap/Constants.ts +0 -32
  740. package/src/tap/Tap.test.ts +0 -125
  741. package/src/tap/Types.ts +0 -11
  742. package/src/tap/client/index.ts +0 -54
  743. package/src/tap/index.ts +0 -4
  744. package/src/tap/server/index.ts +0 -48
  745. package/src/tempo/AccessKeyAuthorization.test.ts +0 -141
  746. package/src/tempo/Attribution.test.ts +0 -294
  747. package/src/tempo/Attribution.ts +0 -185
  748. package/src/tempo/Methods.test.ts +0 -425
  749. package/src/tempo/Methods.ts +0 -367
  750. package/src/tempo/Proof.conformance.test.ts +0 -146
  751. package/src/tempo/Proof.test-d.ts +0 -32
  752. package/src/tempo/Proof.test.ts +0 -40
  753. package/src/tempo/Proof.ts +0 -69
  754. package/src/tempo/PublicExports.test-d.ts +0 -130
  755. package/src/tempo/Subscription.integration.test.ts +0 -591
  756. package/src/tempo/client/Charge.test.ts +0 -501
  757. package/src/tempo/client/Charge.ts +0 -271
  758. package/src/tempo/client/Methods.ts +0 -45
  759. package/src/tempo/client/ResolveAccount.ts +0 -46
  760. package/src/tempo/client/Subscription.test.ts +0 -180
  761. package/src/tempo/client/Subscription.ts +0 -156
  762. package/src/tempo/client/index.ts +0 -17
  763. package/src/tempo/index.ts +0 -5
  764. package/src/tempo/internal/account.ts +0 -56
  765. package/src/tempo/internal/address.test.ts +0 -42
  766. package/src/tempo/internal/address.ts +0 -6
  767. package/src/tempo/internal/auto-swap.test.ts +0 -198
  768. package/src/tempo/internal/auto-swap.ts +0 -145
  769. package/src/tempo/internal/charge.test.ts +0 -66
  770. package/src/tempo/internal/charge.ts +0 -43
  771. package/src/tempo/internal/defaults.test.ts +0 -95
  772. package/src/tempo/internal/defaults.ts +0 -53
  773. package/src/tempo/internal/fee-payer.test.ts +0 -1177
  774. package/src/tempo/internal/fee-payer.ts +0 -808
  775. package/src/tempo/internal/fee-token.test.ts +0 -165
  776. package/src/tempo/internal/fee-token.ts +0 -72
  777. package/src/tempo/internal/proof.test.ts +0 -97
  778. package/src/tempo/internal/proof.ts +0 -87
  779. package/src/tempo/internal/selectors.ts +0 -10
  780. package/src/tempo/internal/types.ts +0 -19
  781. package/src/tempo/legacy/AccessKeyAuthorization.test.ts +0 -162
  782. package/src/tempo/legacy/README.md +0 -9
  783. package/src/tempo/legacy/client/ChannelOps.test.ts +0 -379
  784. package/src/tempo/legacy/client/ChannelOps.ts +0 -271
  785. package/src/tempo/legacy/client/Session.test.ts +0 -800
  786. package/src/tempo/legacy/client/Session.ts +0 -397
  787. package/src/tempo/legacy/client/SessionManager.test.ts +0 -468
  788. package/src/tempo/legacy/client/SessionManager.ts +0 -909
  789. package/src/tempo/legacy/client/index.ts +0 -6
  790. package/src/tempo/legacy/index.ts +0 -4
  791. package/src/tempo/legacy/session/Chain.test.ts +0 -1349
  792. package/src/tempo/legacy/session/Chain.ts +0 -830
  793. package/src/tempo/legacy/session/Channel.test.ts +0 -109
  794. package/src/tempo/legacy/session/Channel.ts +0 -51
  795. package/src/tempo/legacy/session/Types.ts +0 -91
  796. package/src/tempo/legacy/session/Voucher.test.ts +0 -410
  797. package/src/tempo/legacy/session/Voucher.ts +0 -183
  798. package/src/tempo/legacy/session/escrow.abi.ts +0 -760
  799. package/src/tempo/legacy/session/index.ts +0 -8
  800. package/src/tempo/server/AtomicStore.test-d.ts +0 -63
  801. package/src/tempo/server/Charge.test.ts +0 -6056
  802. package/src/tempo/server/Charge.ts +0 -1379
  803. package/src/tempo/server/Methods.ts +0 -86
  804. package/src/tempo/server/Relay.test.ts +0 -677
  805. package/src/tempo/server/Relay.ts +0 -297
  806. package/src/tempo/server/SponsorBudget.test.ts +0 -154
  807. package/src/tempo/server/SponsorBudget.ts +0 -219
  808. package/src/tempo/server/Sse.test.ts +0 -321
  809. package/src/tempo/server/Subscription.test.ts +0 -1944
  810. package/src/tempo/server/Subscription.ts +0 -1336
  811. package/src/tempo/server/index.ts +0 -12
  812. package/src/tempo/server/internal/html/main.ts +0 -115
  813. package/src/tempo/server/internal/html/node_modules/.bin/mppx +0 -22
  814. package/src/tempo/server/internal/html/node_modules/.bin/mppx.src +0 -22
  815. package/src/tempo/server/internal/html/package.json +0 -10
  816. package/src/tempo/server/internal/html/tsconfig.json +0 -8
  817. package/src/tempo/server/internal/html.gen.ts +0 -2
  818. package/src/tempo/server/internal/request-body.test.ts +0 -210
  819. package/src/tempo/server/internal/request-body.ts +0 -61
  820. package/src/tempo/server/internal/transport.test.ts +0 -922
  821. package/src/tempo/server/internal/transport.ts +0 -322
  822. package/src/tempo/session/README.md +0 -320
  823. package/src/tempo/session/Snapshot.test.ts +0 -41
  824. package/src/tempo/session/Snapshot.ts +0 -95
  825. package/src/tempo/session/client/ChannelOps.test.ts +0 -218
  826. package/src/tempo/session/client/ChannelOps.ts +0 -348
  827. package/src/tempo/session/client/ChannelStore.ts +0 -111
  828. package/src/tempo/session/client/CredentialState.test.ts +0 -789
  829. package/src/tempo/session/client/CredentialState.ts +0 -942
  830. package/src/tempo/session/client/ReceiptCoordinator.ts +0 -95
  831. package/src/tempo/session/client/Runtime.test.ts +0 -1104
  832. package/src/tempo/session/client/Runtime.ts +0 -1008
  833. package/src/tempo/session/client/Session.test.ts +0 -1355
  834. package/src/tempo/session/client/Session.ts +0 -420
  835. package/src/tempo/session/client/SessionManager.test.ts +0 -1899
  836. package/src/tempo/session/client/SessionManager.ts +0 -996
  837. package/src/tempo/session/client/Transports.test.ts +0 -1141
  838. package/src/tempo/session/client/Transports.ts +0 -1534
  839. package/src/tempo/session/client/index.ts +0 -40
  840. package/src/tempo/session/client/internal/SessionManager.ts +0 -36
  841. package/src/tempo/session/index.ts +0 -7
  842. package/src/tempo/session/precompile/Chain.integration.test.ts +0 -339
  843. package/src/tempo/session/precompile/Chain.test.ts +0 -1641
  844. package/src/tempo/session/precompile/Chain.ts +0 -1269
  845. package/src/tempo/session/precompile/Channel.test.ts +0 -147
  846. package/src/tempo/session/precompile/Channel.ts +0 -108
  847. package/src/tempo/session/precompile/Protocol.test.ts +0 -358
  848. package/src/tempo/session/precompile/Protocol.ts +0 -520
  849. package/src/tempo/session/precompile/Voucher.test.ts +0 -354
  850. package/src/tempo/session/precompile/Voucher.ts +0 -162
  851. package/src/tempo/session/precompile/escrow.abi.ts +0 -226
  852. package/src/tempo/session/precompile/index.ts +0 -33
  853. package/src/tempo/session/server/ChannelOps.test.ts +0 -129
  854. package/src/tempo/session/server/ChannelOps.ts +0 -157
  855. package/src/tempo/session/server/ChannelStore.test.ts +0 -1093
  856. package/src/tempo/session/server/ChannelStore.ts +0 -856
  857. package/src/tempo/session/server/CredentialVerification.test.ts +0 -299
  858. package/src/tempo/session/server/CredentialVerification.ts +0 -1087
  859. package/src/tempo/session/server/MeteredStream.ts +0 -99
  860. package/src/tempo/session/server/RequestState.test.ts +0 -574
  861. package/src/tempo/session/server/RequestState.ts +0 -507
  862. package/src/tempo/session/server/Session.integration.test.ts +0 -449
  863. package/src/tempo/session/server/Session.test.ts +0 -4247
  864. package/src/tempo/session/server/Session.ts +0 -669
  865. package/src/tempo/session/server/Settlement.test.ts +0 -345
  866. package/src/tempo/session/server/Settlement.ts +0 -527
  867. package/src/tempo/session/server/Sse.fuzz.test.ts +0 -138
  868. package/src/tempo/session/server/Sse.test.ts +0 -634
  869. package/src/tempo/session/server/Sse.ts +0 -261
  870. package/src/tempo/session/server/Transports.test.ts +0 -346
  871. package/src/tempo/session/server/Transports.ts +0 -256
  872. package/src/tempo/session/server/Ws.test.ts +0 -765
  873. package/src/tempo/session/server/Ws.ts +0 -391
  874. package/src/tempo/session/server/index.ts +0 -12
  875. package/src/tempo/subscription/KeyAuthorization.test.ts +0 -282
  876. package/src/tempo/subscription/KeyAuthorization.ts +0 -396
  877. package/src/tempo/subscription/Receipt.ts +0 -28
  878. package/src/tempo/subscription/Store.test.ts +0 -609
  879. package/src/tempo/subscription/Store.ts +0 -449
  880. package/src/tempo/subscription/Types.ts +0 -68
  881. package/src/tempo/subscription/index.ts +0 -23
  882. package/src/tsconfig.json +0 -10
  883. package/src/validation/core.ts +0 -386
  884. package/src/validation/index.ts +0 -11
  885. package/src/viem/Account.test.ts +0 -72
  886. package/src/viem/Account.ts +0 -30
  887. package/src/viem/Client.test.ts +0 -366
  888. package/src/viem/Client.ts +0 -93
  889. package/src/web-bot-auth/Constants.ts +0 -24
  890. package/src/web-bot-auth/Types.ts +0 -9
  891. package/src/web-bot-auth/WebBotAuth.test.ts +0 -182
  892. package/src/web-bot-auth/client/index.ts +0 -84
  893. package/src/web-bot-auth/index.ts +0 -6
  894. package/src/web-bot-auth/internal/JwkThumbprint.ts +0 -21
  895. package/src/web-bot-auth/internal/SignatureAgent.ts +0 -18
  896. package/src/web-bot-auth/server/index.ts +0 -113
  897. package/src/x402/Assets.test.ts +0 -253
  898. package/src/x402/Assets.ts +0 -221
  899. package/src/x402/Exact.e2e.test.ts +0 -488
  900. package/src/x402/Exact.localnet.test.ts +0 -475
  901. package/src/x402/Header.test.ts +0 -73
  902. package/src/x402/Header.ts +0 -79
  903. package/src/x402/PublicInterface.test-d.ts +0 -39
  904. package/src/x402/Types.ts +0 -248
  905. package/src/x402/client/Exact.test.ts +0 -309
  906. package/src/x402/client/Exact.ts +0 -186
  907. package/src/x402/index.ts +0 -6
  908. package/src/x402/internal/ChallengeBrand.ts +0 -14
  909. package/src/x402/internal/RouteBinding.ts +0 -18
  910. package/src/x402/server/EvmCharge.ts +0 -410
  911. package/src/x402/server/Facilitator.test.ts +0 -111
  912. package/src/x402/server/Facilitator.ts +0 -56
  913. package/src/zod.test.ts +0 -169
  914. package/src/zod.ts +0 -71
@@ -1,4247 +0,0 @@
1
- import * as node_http from 'node:http'
2
-
3
- import { serve } from '@hono/node-server'
4
- import { Hono } from 'hono'
5
- import { Challenge, Constants, Credential } from 'mppx'
6
- import { Mppx as Mppx_server, tempo as tempo_server } from 'mppx/server'
7
- import { Secp256k1 } from 'ox'
8
- import { TxEnvelopeTempo } from 'ox/tempo'
9
- import {
10
- type Address,
11
- createClient,
12
- custom,
13
- defineChain,
14
- encodeAbiParameters,
15
- encodeEventTopics,
16
- encodeFunctionData,
17
- encodeFunctionResult,
18
- type Hex,
19
- maxUint256,
20
- zeroAddress,
21
- } from 'viem'
22
- import { privateKeyToAccount } from 'viem/accounts'
23
- import { Transaction } from 'viem/tempo'
24
- import { describe, expect, test, vi } from 'vp/test'
25
- import { WebSocket, WebSocketServer } from 'ws'
26
- import * as Http from '~test/Http.js'
27
-
28
- import * as NodeRequest from '../../../server/Request.js'
29
- import * as Store from '../../../Store.js'
30
- import { charge as clientCharge } from '../../client/Charge.js'
31
- import * as Methods from '../../Methods.js'
32
- import * as ClientOps from '../client/ChannelOps.js'
33
- import { sessionManager as precompileSessionManager } from '../client/SessionManager.js'
34
- import * as Channel from '../precompile/Channel.js'
35
- import { escrowAbi } from '../precompile/escrow.abi.js'
36
- import { tip20ChannelEscrow } from '../precompile/Protocol.js'
37
- import { deserializeSessionReceipt } from '../precompile/Protocol.js'
38
- import type { SessionReceipt } from '../precompile/Protocol.js'
39
- import type { SessionCredentialPayload } from '../precompile/Protocol.js'
40
- import * as Types from '../precompile/Protocol.js'
41
- import * as Voucher from '../precompile/Voucher.js'
42
- import * as ChannelStore from './ChannelStore.js'
43
- import { charge, session, type ResolveSessionChannelId } from './Session.js'
44
- import * as TempoWs from './Ws.js'
45
-
46
- const payerPrivateKey = '0xac0974bec39a17e36ba6a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'
47
- const payer = privateKeyToAccount(payerPrivateKey)
48
- const wrongPayer = privateKeyToAccount(
49
- '0x59c6995e998f97a5a0044966f094538a009d74290f5811cfba6a6b4d238ff944',
50
- )
51
- const chainId = 42431
52
- const payee = '0x0000000000000000000000000000000000000002' as Address
53
- const token = '0x0000000000000000000000000000000000000003' as Address
54
- const wrongTarget = '0x0000000000000000000000000000000000000004' as Address
55
- const testChain = defineChain({
56
- id: chainId,
57
- name: 'Tempo Test',
58
- nativeCurrency: { name: 'Tempo', symbol: 'TEMPO', decimals: 18 },
59
- rpcUrls: { default: { http: ['http://localhost'] } },
60
- })
61
-
62
- type RpcCall = { method: string; params?: unknown }
63
- type ChainState = { settled: bigint; deposit: bigint; closeRequestedAt: number }
64
- type SessionRequest = ReturnType<typeof Methods.session.schema.request.parse>
65
-
66
- function channelStore(store: Store.Store | Store.AtomicStore): ChannelStore.ChannelStore {
67
- return ChannelStore.fromStore(store)
68
- }
69
-
70
- function createSigningClient(account = payer) {
71
- return createClient({
72
- account,
73
- chain: testChain,
74
- transport: custom({
75
- async request(args) {
76
- if (args.method === 'eth_chainId') return `0x${chainId.toString(16)}`
77
- if (args.method === 'eth_getTransactionCount') return '0x0'
78
- if (args.method === 'eth_estimateGas') return '0x5208'
79
- if (args.method === 'eth_maxPriorityFeePerGas') return '0x1'
80
- if (args.method === 'eth_getBlockByNumber') return { baseFeePerGas: '0x1' }
81
- throw new Error(`unexpected signing rpc request: ${args.method}`)
82
- },
83
- }),
84
- })
85
- }
86
-
87
- function createServerClient(
88
- calls: RpcCall[] = [],
89
- account: typeof payer | null = payer,
90
- _eventChannelId: Hex = `0x${'00'.repeat(32)}` as Hex,
91
- options: {
92
- descriptor?: Channel.ChannelDescriptor
93
- receipt?:
94
- | Record<string, unknown>
95
- | (() => Record<string, unknown> | Promise<Record<string, unknown>>)
96
- sentReceipt?:
97
- | Record<string, unknown>
98
- | (() => Record<string, unknown> | Promise<Record<string, unknown>>)
99
- state?: ChainState | (() => ChainState | Promise<ChainState>)
100
- } = {},
101
- ) {
102
- let sentTransaction = false
103
- return createClient({
104
- ...(account ? { account } : {}),
105
- chain: testChain,
106
- transport: custom({
107
- async request(args) {
108
- calls.push(args)
109
- if (args.method === 'eth_chainId') return `0x${chainId.toString(16)}`
110
- if (args.method === 'eth_getTransactionCount') return '0x0'
111
- if (args.method === 'eth_estimateGas') return '0x5208'
112
- if (args.method === 'eth_maxPriorityFeePerGas') return '0x1'
113
- if (args.method === 'eth_getBlockByNumber') return { baseFeePerGas: '0x1' }
114
- if (args.method === 'eth_sendRawTransaction') {
115
- sentTransaction = true
116
- return `0x${'aa'.repeat(32)}`
117
- }
118
- if (args.method === 'eth_getTransactionReceipt') {
119
- const receipt = sentTransaction
120
- ? (options.sentReceipt ?? options.receipt)
121
- : options.receipt
122
- return typeof receipt === 'function' ? receipt() : (receipt ?? null)
123
- }
124
- if (args.method === 'eth_sendTransaction') {
125
- sentTransaction = true
126
- return `0x${'bb'.repeat(32)}`
127
- }
128
- if (args.method === 'eth_call') {
129
- const configuredState =
130
- typeof options.state === 'function' ? await options.state() : options.state
131
- const state = configuredState ?? { settled: 100n, deposit: 1_000n, closeRequestedAt: 0 }
132
- const data = (args.params as [{ data?: Hex }])[0].data
133
- const getChannelSelector = options.descriptor
134
- ? encodeFunctionData({
135
- abi: escrowAbi,
136
- functionName: 'getChannel',
137
- args: [options.descriptor],
138
- }).slice(0, 10)
139
- : undefined
140
- if (data && getChannelSelector && data.slice(0, 10) === getChannelSelector)
141
- return encodeFunctionResult({
142
- abi: escrowAbi,
143
- functionName: 'getChannel',
144
- result: { descriptor: options.descriptor!, state },
145
- })
146
- return encodeFunctionResult({
147
- abi: escrowAbi,
148
- functionName: 'getChannelState',
149
- result: state,
150
- })
151
- }
152
- throw new Error(`unexpected rpc request: ${args.method}`)
153
- },
154
- }),
155
- })
156
- }
157
-
158
- function createStateClient(
159
- account: typeof payer | null = payer,
160
- state: ChainState = {
161
- settled: 0n,
162
- deposit: 1_000n,
163
- closeRequestedAt: 0,
164
- },
165
- ) {
166
- return createClient({
167
- ...(account ? { account } : {}),
168
- chain: testChain,
169
- transport: custom({
170
- async request(args) {
171
- if (args.method === 'eth_chainId') return `0x${chainId.toString(16)}`
172
- if (args.method === 'eth_call')
173
- return encodeFunctionResult({
174
- abi: escrowAbi,
175
- functionName: 'getChannelState',
176
- result: state,
177
- })
178
- if (args.method === 'eth_getTransactionCount') return '0x0'
179
- if (args.method === 'eth_estimateGas') return '0x5208'
180
- if (args.method === 'eth_maxPriorityFeePerGas') return '0x1'
181
- if (args.method === 'eth_getBlockByNumber') return { baseFeePerGas: '0x1' }
182
- throw new Error(`unexpected rpc request: ${args.method}`)
183
- },
184
- }),
185
- })
186
- }
187
-
188
- function createServer(parameters: Partial<session.Parameters> = {}) {
189
- const rawStore = Store.memory()
190
- const rpcCalls: RpcCall[] = []
191
- const serverClient = createServerClient(rpcCalls)
192
- const method = session({
193
- amount: '1',
194
- chainId,
195
- currency: token,
196
- decimals: 0,
197
- recipient: payee,
198
- store: rawStore,
199
- unitType: 'request',
200
- getClient: () => serverClient,
201
- ...parameters,
202
- })
203
- return { method, store: channelStore(rawStore), rpcCalls }
204
- }
205
-
206
- function makeChallenge(
207
- channelId?: Hex,
208
- options: { operator?: Address | undefined } = {},
209
- ): Challenge.Challenge<SessionRequest, 'session', 'tempo'> {
210
- return {
211
- id: 'challenge-id',
212
- realm: 'api.example.com',
213
- method: 'tempo',
214
- intent: 'session',
215
- request: makeRequest(channelId, options),
216
- } as Challenge.Challenge<SessionRequest, 'session', 'tempo'>
217
- }
218
-
219
- function makeRequest(
220
- channelId?: Hex,
221
- options: { operator?: Address | undefined } = {},
222
- ): SessionRequest {
223
- return {
224
- amount: '100',
225
- currency: token,
226
- recipient: payee,
227
- unitType: 'request',
228
- methodDetails: {
229
- chainId,
230
- escrowContract: tip20ChannelEscrow,
231
- ...(channelId && { channelId }),
232
- ...(options.operator ? { operator: options.operator } : {}),
233
- },
234
- }
235
- }
236
-
237
- type VerifyRequest = Parameters<NonNullable<ReturnType<typeof session>['verify']>>[0]['request']
238
-
239
- function verifyRequest(
240
- channelId?: Hex,
241
- options: { operator?: Address | undefined } = {},
242
- ): VerifyRequest {
243
- // `verify()` receives the HMAC-bound canonical challenge request. The public
244
- // request schema input still includes parse-only fields like `decimals`, so
245
- // keep this test bridge in one place instead of casting every verification.
246
- return makeRequest(channelId, options) as unknown as VerifyRequest
247
- }
248
-
249
- function verifyRequestWithFeePayer(channelId: Hex, feePayer: typeof payer): VerifyRequest {
250
- const request = makeRequest(channelId)
251
- return {
252
- ...request,
253
- feePayer,
254
- methodDetails: {
255
- ...request.methodDetails,
256
- feePayer: true,
257
- },
258
- } as unknown as VerifyRequest
259
- }
260
-
261
- function sourceFor(account = payer): string {
262
- return `did:pkh:eip155:${chainId}:${account.address}`
263
- }
264
-
265
- function voucherCredential(payload: SessionCredentialPayload, channelId: Hex) {
266
- return {
267
- challenge: makeChallenge(channelId),
268
- payload,
269
- source: sourceFor(),
270
- }
271
- }
272
-
273
- let saltCounter = 0
274
-
275
- async function createOpenPayload(
276
- parameters: {
277
- deposit?: bigint | undefined
278
- initialAmount?: bigint | undefined
279
- escrow?: Address | undefined
280
- account?: typeof payer | undefined
281
- operator?: Address | undefined
282
- authorizedSigner?: Address | undefined
283
- } = {},
284
- ): Promise<Extract<SessionCredentialPayload, { action: 'open' }>> {
285
- const account = parameters.account ?? payer
286
- const escrow = parameters.escrow ?? tip20ChannelEscrow
287
- const initialAmount = Types.uint96(parameters.initialAmount ?? 100n)
288
- const deposit = Types.uint96(parameters.deposit ?? 1_000n)
289
- const salt = `0x${(++saltCounter).toString(16).padStart(64, '0')}` as Hex
290
- const operator = parameters.operator ?? zeroAddress
291
- const authorizedSigner = parameters.authorizedSigner ?? account.address
292
- const data = encodeFunctionData({
293
- abi: escrowAbi,
294
- functionName: 'open',
295
- args: [payee, operator, token, deposit, salt, authorizedSigner],
296
- })
297
- const signingClient = createSigningClient(account)
298
- const transaction = (await Transaction.serialize({
299
- chainId,
300
- calls: [{ to: escrow, data }],
301
- feeToken: token,
302
- nonce: 0,
303
- })) as Hex
304
- const expiringNonceHash = Channel.computeExpiringNonceHash(
305
- Transaction.deserialize(
306
- transaction as Transaction.TransactionSerializedTempo,
307
- ) as Channel.ExpiringNonceTransaction,
308
- { sender: account.address },
309
- )
310
- const descriptor = {
311
- payer: account.address,
312
- payee,
313
- operator,
314
- token,
315
- salt,
316
- authorizedSigner,
317
- expiringNonceHash,
318
- } satisfies Channel.ChannelDescriptor
319
- const channelId = Channel.computeId({ ...descriptor, chainId, escrow })
320
- const signature = await Voucher.signVoucher(
321
- signingClient,
322
- account,
323
- { channelId, cumulativeAmount: initialAmount },
324
- escrow,
325
- chainId,
326
- )
327
- return {
328
- action: 'open',
329
- type: 'transaction',
330
- channelId,
331
- transaction,
332
- signature,
333
- descriptor,
334
- cumulativeAmount: initialAmount.toString(),
335
- authorizedSigner: descriptor.authorizedSigner,
336
- }
337
- }
338
-
339
- function sponsorTransaction(
340
- serializedTransaction: Hex,
341
- options: { gas?: bigint | undefined } = {},
342
- ): Hex {
343
- const transaction = Transaction.deserialize(
344
- serializedTransaction as Transaction.TransactionSerializedTempo,
345
- )
346
- const envelope = TxEnvelopeTempo.from({
347
- ...transaction,
348
- feePayerSignature: null,
349
- gas: options.gas ?? 100_000n,
350
- maxFeePerGas: 1n,
351
- maxPriorityFeePerGas: 1n,
352
- nonceKey: maxUint256,
353
- validBefore: Math.floor(Date.now() / 1_000) + 600,
354
- } as never)
355
- return TxEnvelopeTempo.serialize(envelope, {
356
- signature: Secp256k1.sign({
357
- payload: TxEnvelopeTempo.getSignPayload(envelope),
358
- privateKey: payerPrivateKey,
359
- }),
360
- }) as Hex
361
- }
362
-
363
- async function createSponsoredOpenPayload(): Promise<
364
- Extract<SessionCredentialPayload, { action: 'open' }>
365
- > {
366
- const payload = await createOpenPayload()
367
- const transaction = sponsorTransaction(payload.transaction)
368
- const signed = Transaction.deserialize(transaction as Transaction.TransactionSerializedTempo)
369
- const expiringNonceHash = Channel.computeExpiringNonceHash(
370
- Channel.transactionForExpiringNonceHash({ feePayer: true, transaction: signed }),
371
- { sender: payer.address },
372
- )
373
- const descriptor = { ...payload.descriptor, expiringNonceHash }
374
- const channelId = Channel.computeId({ ...descriptor, chainId, escrow: tip20ChannelEscrow })
375
- const signature = await Voucher.signVoucher(
376
- createSigningClient(),
377
- payer,
378
- { channelId, cumulativeAmount: BigInt(payload.cumulativeAmount) },
379
- tip20ChannelEscrow,
380
- chainId,
381
- )
382
- return { ...payload, channelId, transaction, signature, descriptor }
383
- }
384
-
385
- function transactionReceipt(logs: readonly Record<string, unknown>[]) {
386
- return {
387
- blockHash: `0x${'01'.repeat(32)}`,
388
- blockNumber: '0x1',
389
- contractAddress: null,
390
- cumulativeGasUsed: '0x1',
391
- effectiveGasPrice: '0x1',
392
- from: payer.address,
393
- gasUsed: '0x1',
394
- logs,
395
- logsBloom: `0x${'00'.repeat(256)}`,
396
- status: '0x1',
397
- to: tip20ChannelEscrow,
398
- transactionHash: `0x${'aa'.repeat(32)}`,
399
- transactionIndex: '0x0',
400
- type: '0x76',
401
- }
402
- }
403
-
404
- function openedLog(
405
- payload: Extract<SessionCredentialPayload, { action: 'open' }>,
406
- deposit = 1_000n,
407
- ) {
408
- return {
409
- address: tip20ChannelEscrow,
410
- data: encodeAbiParameters(
411
- [
412
- { type: 'address' },
413
- { type: 'address' },
414
- { type: 'address' },
415
- { type: 'bytes32' },
416
- { type: 'bytes32' },
417
- { type: 'uint96' },
418
- ],
419
- [
420
- payload.descriptor.operator,
421
- payload.descriptor.token,
422
- payload.descriptor.authorizedSigner,
423
- payload.descriptor.salt,
424
- payload.descriptor.expiringNonceHash,
425
- deposit,
426
- ],
427
- ),
428
- topics: encodeEventTopics({
429
- abi: escrowAbi,
430
- eventName: 'ChannelOpened',
431
- args: {
432
- channelId: payload.channelId,
433
- payer: payload.descriptor.payer,
434
- payee: payload.descriptor.payee,
435
- },
436
- }),
437
- }
438
- }
439
-
440
- function settledLog(channelId: Hex, newSettled: bigint) {
441
- return {
442
- address: tip20ChannelEscrow,
443
- data: encodeAbiParameters(
444
- [{ type: 'uint96' }, { type: 'uint96' }, { type: 'uint96' }],
445
- [newSettled, newSettled, newSettled],
446
- ),
447
- topics: encodeEventTopics({
448
- abi: escrowAbi,
449
- eventName: 'Settled',
450
- args: { channelId, payer: payer.address, payee: payer.address },
451
- }),
452
- }
453
- }
454
-
455
- function closedLog(channelId: Hex, settledToPayee: bigint, refundedToPayer: bigint) {
456
- return {
457
- address: tip20ChannelEscrow,
458
- data: encodeAbiParameters(
459
- [{ type: 'uint96' }, { type: 'uint96' }],
460
- [settledToPayee, refundedToPayer],
461
- ),
462
- topics: encodeEventTopics({
463
- abi: escrowAbi,
464
- eventName: 'ChannelClosed',
465
- args: { channelId, payer: payer.address, payee: payer.address },
466
- }),
467
- }
468
- }
469
-
470
- function topUpLog(
471
- payload: Extract<SessionCredentialPayload, { action: 'topUp' }>,
472
- newDeposit: bigint,
473
- ) {
474
- return {
475
- address: tip20ChannelEscrow,
476
- data: encodeAbiParameters([{ type: 'uint96' }, { type: 'uint96' }], [1_000n, newDeposit]),
477
- topics: encodeEventTopics({
478
- abi: escrowAbi,
479
- eventName: 'TopUp',
480
- args: { channelId: payload.channelId, payer: payer.address, payee },
481
- }),
482
- }
483
- }
484
-
485
- async function createTopUpPayload(
486
- descriptor: Channel.ChannelDescriptor,
487
- additionalDeposit = 500n,
488
- ): Promise<Extract<SessionCredentialPayload, { action: 'topUp' }>> {
489
- const data = encodeFunctionData({
490
- abi: escrowAbi,
491
- functionName: 'topUp',
492
- args: [descriptor, Types.uint96(additionalDeposit)],
493
- })
494
- const transaction = (await Transaction.serialize({
495
- chainId,
496
- calls: [{ to: tip20ChannelEscrow, data }],
497
- feeToken: token,
498
- nonce: 0,
499
- })) as Hex
500
- const channelId = Channel.computeId({ ...descriptor, chainId, escrow: tip20ChannelEscrow })
501
- return {
502
- action: 'topUp',
503
- type: 'transaction',
504
- channelId,
505
- descriptor,
506
- transaction,
507
- additionalDeposit: additionalDeposit.toString(),
508
- }
509
- }
510
-
511
- async function persistPrecompileChannel(
512
- store: ChannelStore.ChannelStore,
513
- payload: Extract<SessionCredentialPayload, { action: 'open' }>,
514
- overrides: Partial<ChannelStore.State> = {},
515
- ) {
516
- await store.updateChannel(payload.channelId, () => ({
517
- backend: 'precompile',
518
- channelId: payload.channelId,
519
- chainId,
520
- escrowContract: tip20ChannelEscrow,
521
- closeRequestedAt: 0n,
522
- payer: payload.descriptor.payer,
523
- payee,
524
- token,
525
- authorizedSigner: payload.descriptor.authorizedSigner,
526
- deposit: 1_000n,
527
- settledOnChain: 0n,
528
- highestVoucherAmount: BigInt(payload.cumulativeAmount),
529
- highestVoucher: {
530
- channelId: payload.channelId,
531
- cumulativeAmount: BigInt(payload.cumulativeAmount),
532
- signature: payload.signature,
533
- },
534
- spent: 0n,
535
- units: 0,
536
- finalized: false,
537
- createdAt: new Date(0).toISOString(),
538
- descriptor: payload.descriptor,
539
- operator: payload.descriptor.operator,
540
- salt: payload.descriptor.salt,
541
- expiringNonceHash: payload.descriptor.expiringNonceHash,
542
- ...overrides,
543
- }))
544
- }
545
-
546
- describe('precompile server session unit guardrails', () => {
547
- test('request marks TIP-1034 session challenges', async () => {
548
- const { method } = createServer()
549
-
550
- const challengeRequest = await method.request!({
551
- credential: null,
552
- request: {
553
- amount: '1',
554
- currency: token,
555
- decimals: 0,
556
- recipient: payee,
557
- unitType: 'request',
558
- },
559
- } as never)
560
-
561
- expect(challengeRequest.sessionProtocol).toBe(Constants.SessionProtocols.v2)
562
- })
563
-
564
- test('request normalizes fee-payer to boolean for challenge issuance and account for verification', async () => {
565
- const { method } = createServer({ feePayer: wrongPayer })
566
-
567
- const challengeRequest = await method.request!({
568
- credential: null,
569
- request: {
570
- amount: '1',
571
- currency: token,
572
- decimals: 0,
573
- recipient: payee,
574
- unitType: 'request',
575
- },
576
- } as never)
577
- expect(challengeRequest.feePayer).toBe(true)
578
-
579
- const verificationRequest = await method.request!({
580
- credential: { challenge: {}, payload: {} } as never,
581
- request: {
582
- amount: '1',
583
- currency: token,
584
- decimals: 0,
585
- feePayer: payer,
586
- recipient: payee,
587
- unitType: 'request',
588
- },
589
- } as never)
590
- expect(verificationRequest.feePayer).toBe(payer)
591
- })
592
-
593
- test('request allows callers to explicitly disable precompile fee-payer', async () => {
594
- const { method } = createServer({ feePayer: wrongPayer })
595
-
596
- const challengeRequest = await method.request!({
597
- credential: null,
598
- request: {
599
- amount: '1',
600
- currency: token,
601
- decimals: 0,
602
- feePayer: false,
603
- recipient: payee,
604
- unitType: 'request',
605
- },
606
- } as never)
607
- expect(challengeRequest.feePayer).toBeUndefined()
608
-
609
- const verificationRequest = await method.request!({
610
- credential: { challenge: {}, payload: {} } as never,
611
- request: {
612
- amount: '1',
613
- currency: token,
614
- decimals: 0,
615
- feePayer: false,
616
- recipient: payee,
617
- unitType: 'request',
618
- },
619
- } as never)
620
- expect(verificationRequest.feePayer).toBe(false)
621
- })
622
-
623
- test('request returns a server session snapshot for a known precompile channel', async () => {
624
- const { method, store } = createServer()
625
- const openPayload = await createOpenPayload({ initialAmount: 100n })
626
- const highestVoucher = await ClientOps.createVoucherPayload(
627
- createSigningClient(),
628
- payer,
629
- openPayload.descriptor,
630
- Types.uint96(250n),
631
- chainId,
632
- )
633
- if (highestVoucher.action !== 'voucher') throw new Error('expected voucher payload')
634
- await persistPrecompileChannel(store, openPayload, {
635
- highestVoucherAmount: 250n,
636
- highestVoucher: {
637
- channelId: openPayload.channelId,
638
- cumulativeAmount: 250n,
639
- signature: highestVoucher.signature,
640
- },
641
- spent: 200n,
642
- units: 2,
643
- })
644
-
645
- const request = await method.request!({
646
- credential: {
647
- challenge: {},
648
- payload: {},
649
- } as never,
650
- request: {
651
- amount: '1',
652
- channelId: openPayload.channelId,
653
- currency: token,
654
- decimals: 0,
655
- recipient: payee,
656
- unitType: 'request',
657
- },
658
- } as never)
659
-
660
- expect(request.sessionSnapshot).toEqual({
661
- acceptedCumulative: '250',
662
- chainId,
663
- channelId: openPayload.channelId,
664
- closeRequestedAt: undefined,
665
- deposit: '1000',
666
- descriptor: openPayload.descriptor,
667
- escrow: tip20ChannelEscrow,
668
- highestVoucher: {
669
- channelId: openPayload.channelId,
670
- cumulativeAmount: '250',
671
- signature: highestVoucher.signature,
672
- },
673
- requiredCumulative: '201',
674
- settled: '0',
675
- spent: '200',
676
- units: 2,
677
- })
678
- })
679
-
680
- test('request can resolve a server session snapshot from request identity', async () => {
681
- const openPayload = await createOpenPayload({ initialAmount: 100n })
682
- const highestVoucher = await ClientOps.createVoucherPayload(
683
- createSigningClient(),
684
- payer,
685
- openPayload.descriptor,
686
- Types.uint96(250n),
687
- chainId,
688
- )
689
- if (highestVoucher.action !== 'voucher') throw new Error('expected voucher payload')
690
- const { method, store } = createServer({
691
- resolveChannelId({ request, credential, paymentRequest, store: hookStore }) {
692
- expect(request?.headers.get('cookie')).toBe('sid=session-1')
693
- expect(credential).toBeNull()
694
- expect(paymentRequest.unitType).toBe('request')
695
- expect(hookStore).toBe(store)
696
- return openPayload.channelId
697
- },
698
- })
699
- await persistPrecompileChannel(store, openPayload, {
700
- highestVoucherAmount: 250n,
701
- highestVoucher: {
702
- channelId: openPayload.channelId,
703
- cumulativeAmount: 250n,
704
- signature: highestVoucher.signature,
705
- },
706
- spent: 200n,
707
- units: 2,
708
- })
709
-
710
- const request = await method.request!({
711
- capturedRequest: {
712
- hasBody: false,
713
- headers: new Headers({ cookie: 'sid=session-1' }),
714
- method: 'GET',
715
- url: new URL('https://api.example.com/resource'),
716
- },
717
- credential: null,
718
- request: {
719
- amount: '1',
720
- currency: token,
721
- decimals: 0,
722
- recipient: payee,
723
- unitType: 'request',
724
- },
725
- } as never)
726
-
727
- expect(request.sessionSnapshot).toMatchObject({
728
- channelId: openPayload.channelId,
729
- descriptor: openPayload.descriptor,
730
- requiredCumulative: '201',
731
- spent: '200',
732
- })
733
- })
734
-
735
- test('request prefers explicit channel ID over custom channel resolver', async () => {
736
- const openPayload = await createOpenPayload({ initialAmount: 100n })
737
- const { method, store } = createServer({
738
- resolveChannelId() {
739
- throw new Error('unexpected resolver call')
740
- },
741
- })
742
- await persistPrecompileChannel(store, openPayload)
743
-
744
- const request = await method.request!({
745
- credential: null,
746
- request: {
747
- amount: '1',
748
- channelId: openPayload.channelId,
749
- currency: token,
750
- decimals: 0,
751
- recipient: payee,
752
- unitType: 'request',
753
- },
754
- } as never)
755
-
756
- expect(request.sessionSnapshot?.channelId).toBe(openPayload.channelId)
757
- })
758
-
759
- test('request uses zero effective amount for non-content snapshot hints', async () => {
760
- const { method, store } = createServer()
761
- const openPayload = await createOpenPayload({ initialAmount: 100n })
762
- const highestVoucher = await ClientOps.createVoucherPayload(
763
- createSigningClient(),
764
- payer,
765
- openPayload.descriptor,
766
- Types.uint96(250n),
767
- chainId,
768
- )
769
- if (highestVoucher.action !== 'voucher') throw new Error('expected voucher payload')
770
- await persistPrecompileChannel(store, openPayload, {
771
- highestVoucherAmount: 250n,
772
- highestVoucher: {
773
- channelId: openPayload.channelId,
774
- cumulativeAmount: 250n,
775
- signature: highestVoucher.signature,
776
- },
777
- spent: 250n,
778
- units: 2,
779
- })
780
-
781
- const request = await method.request!({
782
- capturedRequest: {
783
- hasBody: false,
784
- headers: new Headers(),
785
- method: 'HEAD',
786
- url: new URL('https://api.example.com/resource'),
787
- },
788
- credential: {
789
- challenge: {},
790
- payload: {},
791
- } as never,
792
- request: {
793
- amount: '1',
794
- channelId: openPayload.channelId,
795
- currency: token,
796
- decimals: 0,
797
- recipient: payee,
798
- unitType: 'request',
799
- },
800
- } as never)
801
-
802
- expect(request.sessionSnapshot).toMatchObject({
803
- acceptedCumulative: '250',
804
- requiredCumulative: '250',
805
- spent: '250',
806
- })
807
- })
808
-
809
- test('request throws when resolved precompile client chain mismatches requested chain', async () => {
810
- const { method } = createServer({ chainId: 1 })
811
-
812
- await expect(
813
- method.request!({
814
- credential: null,
815
- request: {
816
- amount: '1',
817
- chainId: 1,
818
- currency: token,
819
- decimals: 0,
820
- recipient: payee,
821
- unitType: 'request',
822
- },
823
- } as never),
824
- ).rejects.toThrow('Client not configured with chainId 1.')
825
- })
826
-
827
- test('fully validates a session credential without broadcasting or changing channel state', async () => {
828
- const { method, rpcCalls, store } = createServer()
829
- const openPayload = await createOpenPayload()
830
- await persistPrecompileChannel(store, openPayload)
831
- const payload = await ClientOps.createVoucherPayload(
832
- createSigningClient(),
833
- payer,
834
- openPayload.descriptor,
835
- Types.uint96(200n),
836
- chainId,
837
- )
838
-
839
- const validation = await method.validate!({
840
- credential: {
841
- challenge: makeChallenge(openPayload.channelId),
842
- payload,
843
- source: sourceFor(),
844
- },
845
- request: verifyRequest(openPayload.channelId),
846
- })
847
-
848
- await method.validate!({
849
- credential: {
850
- challenge: makeChallenge(openPayload.channelId),
851
- payload,
852
- source: sourceFor(),
853
- },
854
- request: verifyRequest(openPayload.channelId),
855
- })
856
-
857
- expect(validation.details).toEqual({ action: 'voucher', channelId: openPayload.channelId })
858
- expect(await store.getChannel(openPayload.channelId)).toMatchObject({
859
- highestVoucherAmount: 100n,
860
- spent: 0n,
861
- units: 0,
862
- })
863
- expect(rpcCalls.map(({ method }) => method)).toEqual(['eth_call'])
864
- expect(rpcCalls.map(({ method }) => method)).not.toContain('eth_sendRawTransaction')
865
- expect(rpcCalls.map(({ method }) => method)).not.toContain('eth_sendRawTransactionSync')
866
- })
867
-
868
- test('simulates unsponsored open and top-up credentials during validation', async () => {
869
- const { method, rpcCalls, store } = createServer()
870
- const openPayload = await createOpenPayload()
871
-
872
- await method.validate!({
873
- credential: { challenge: makeChallenge(openPayload.channelId), payload: openPayload },
874
- request: verifyRequest(openPayload.channelId),
875
- })
876
- await persistPrecompileChannel(store, openPayload)
877
- const topUpPayload = await createTopUpPayload(openPayload.descriptor, 100n)
878
-
879
- await method.validate!({
880
- credential: { challenge: makeChallenge(topUpPayload.channelId), payload: topUpPayload },
881
- request: verifyRequest(topUpPayload.channelId),
882
- })
883
-
884
- expect(rpcCalls.map(({ method }) => method)).toEqual(['eth_call', 'eth_call', 'eth_call'])
885
- expect(await store.getChannel(openPayload.channelId)).toMatchObject({
886
- deposit: 1_000n,
887
- highestVoucherAmount: 100n,
888
- })
889
- })
890
-
891
- test('does not simulate sponsored credentials during validation but checks top-up state', async () => {
892
- const { method, rpcCalls, store } = createServer({ feePayer: payer })
893
- const openPayload = await createSponsoredOpenPayload()
894
-
895
- await method.validate!({
896
- credential: { challenge: makeChallenge(openPayload.channelId), payload: openPayload },
897
- request: verifyRequestWithFeePayer(openPayload.channelId, payer),
898
- })
899
- await persistPrecompileChannel(store, openPayload)
900
- const topUpPayload = await createTopUpPayload(openPayload.descriptor, 100n)
901
- topUpPayload.transaction = sponsorTransaction(topUpPayload.transaction)
902
-
903
- await method.validate!({
904
- credential: { challenge: makeChallenge(topUpPayload.channelId), payload: topUpPayload },
905
- request: verifyRequestWithFeePayer(topUpPayload.channelId, payer),
906
- })
907
-
908
- expect(rpcCalls.map(({ method }) => method)).toEqual(['eth_call'])
909
- expect(await store.getChannel(openPayload.channelId)).toMatchObject({
910
- deposit: 1_000n,
911
- highestVoucherAmount: 100n,
912
- })
913
- })
914
-
915
- test('rejects sponsored credentials that exceed the fee-payer policy during validation', async () => {
916
- const { method, rpcCalls } = createServer({
917
- feePayer: payer,
918
- feePayerPolicy: { maxGas: 1n },
919
- })
920
- const payload = await createSponsoredOpenPayload()
921
-
922
- payload.transaction = sponsorTransaction(payload.transaction, { gas: 2n })
923
-
924
- await expect(
925
- method.validate!({
926
- credential: { challenge: makeChallenge(payload.channelId), payload },
927
- request: verifyRequestWithFeePayer(payload.channelId, payer),
928
- }),
929
- ).rejects.toThrow(/fee-sponsored transaction gas exceeds sponsor policy/)
930
- expect(rpcCalls).toEqual([])
931
- })
932
-
933
- test('validates close credentials without marking the channel pending', async () => {
934
- const { method, rpcCalls, store } = createServer()
935
- const openPayload = await createOpenPayload()
936
- await persistPrecompileChannel(store, openPayload, { payee: payer.address })
937
- const payload = await ClientOps.createClosePayload(
938
- createSigningClient(),
939
- payer,
940
- openPayload.descriptor,
941
- Types.uint96(100n),
942
- chainId,
943
- )
944
-
945
- await method.validate!({
946
- credential: { challenge: makeChallenge(openPayload.channelId), payload },
947
- request: verifyRequest(openPayload.channelId),
948
- })
949
-
950
- expect((await store.getChannel(openPayload.channelId))?.closeRequestedAt).toBe(0n)
951
- expect(rpcCalls.map(({ method }) => method)).toEqual(['eth_call'])
952
- })
953
-
954
- test('rejects open transactions targeting the wrong address', async () => {
955
- const { method } = createServer()
956
- const payload = await createOpenPayload({ escrow: wrongTarget })
957
-
958
- await expect(
959
- method.verify({
960
- credential: { challenge: makeChallenge(payload.channelId), payload },
961
- request: verifyRequest(payload.channelId),
962
- }),
963
- ).rejects.toThrow(/descriptor does not match channelId|wrong address/)
964
- })
965
-
966
- test('rejects smuggled extra calls in open transactions', async () => {
967
- const { method } = createServer()
968
- const payload = await createOpenPayload()
969
- const tampered = await createOpenPayload()
970
-
971
- // Reuse a valid descriptor/signature, but submit a transaction whose calls
972
- // do not correspond to that descriptor. This exercises the same one-call /
973
- // smuggling guard as legacy server session tests without requiring a live
974
- // chain-backed precompile.
975
- const smuggled = { ...payload, transaction: tampered.transaction }
976
-
977
- await expect(
978
- method.verify({
979
- credential: {
980
- challenge: makeChallenge(payload.channelId),
981
- payload: smuggled,
982
- },
983
- request: verifyRequest(payload.channelId),
984
- }),
985
- ).rejects.toThrow(/does not match/)
986
- })
987
-
988
- test('rejects descriptors that do not match the challenge channel ID', async () => {
989
- const { method } = createServer()
990
- const payload = await createOpenPayload()
991
- const badDescriptor = {
992
- ...payload.descriptor,
993
- token: '0x0000000000000000000000000000000000000005' as Address,
994
- }
995
-
996
- await expect(
997
- method.verify({
998
- credential: {
999
- challenge: makeChallenge(payload.channelId),
1000
- payload: { ...payload, descriptor: badDescriptor },
1001
- },
1002
- request: verifyRequest(payload.channelId),
1003
- }),
1004
- ).rejects.toThrow(/descriptor does not match channelId/)
1005
- })
1006
-
1007
- test('rejects invalid initial voucher signatures', async () => {
1008
- const { method } = createServer()
1009
- const payload = await createOpenPayload()
1010
- const badSignaturePayload = {
1011
- ...payload,
1012
- signature: (await createOpenPayload({ account: wrongPayer })).signature,
1013
- }
1014
-
1015
- await expect(
1016
- method.verify({
1017
- credential: {
1018
- challenge: makeChallenge(payload.channelId),
1019
- payload: badSignaturePayload,
1020
- },
1021
- request: verifyRequest(payload.channelId),
1022
- }),
1023
- ).rejects.toThrow(/invalid voucher signature/)
1024
- })
1025
-
1026
- test('rejects missing precompile descriptors with a verification error', async () => {
1027
- const { method } = createServer()
1028
- const payload = await createOpenPayload()
1029
- const { descriptor: _descriptor, ...payloadWithoutDescriptor } = payload
1030
-
1031
- await expect(
1032
- method.verify({
1033
- credential: {
1034
- challenge: makeChallenge(payload.channelId),
1035
- payload: payloadWithoutDescriptor,
1036
- },
1037
- request: verifyRequest(payload.channelId),
1038
- }),
1039
- ).rejects.toThrow(/descriptor required for TIP-1034 session action/)
1040
- })
1041
-
1042
- test('rejects uint96 overflow in credential amount parsing', async () => {
1043
- const { method } = createServer()
1044
- const payload = await createOpenPayload()
1045
-
1046
- await expect(
1047
- method.verify({
1048
- credential: {
1049
- challenge: makeChallenge(payload.channelId),
1050
- payload: { ...payload, cumulativeAmount: (1n << 96n).toString() },
1051
- },
1052
- request: verifyRequest(payload.channelId),
1053
- }),
1054
- ).rejects.toThrow(/outside uint96 bounds/)
1055
- })
1056
-
1057
- test('rejects settle when no account is available', async () => {
1058
- const { store } = createServer()
1059
- const openPayload = await createOpenPayload()
1060
- await persistPrecompileChannel(store, openPayload)
1061
-
1062
- const { settle } = await import('./Session.js')
1063
- await expect(
1064
- settle(store, createServerClient([], null), openPayload.channelId),
1065
- ).rejects.toThrow(/no account available/)
1066
- })
1067
-
1068
- test('rejects settle when sender is not the channel payee or operator', async () => {
1069
- const { store } = createServer()
1070
- const openPayload = await createOpenPayload()
1071
- await persistPrecompileChannel(store, openPayload)
1072
-
1073
- const { settle } = await import('./Session.js')
1074
- await expect(
1075
- settle(store, createServerClient([], wrongPayer), openPayload.channelId),
1076
- ).rejects.toThrow(/tx sender .* is not the channel payee/)
1077
- })
1078
-
1079
- test('accepts settle sender matching a nonzero precompile operator', async () => {
1080
- const { store } = createServer()
1081
- const openPayload = await createOpenPayload({
1082
- operator: wrongPayer.address,
1083
- })
1084
- await persistPrecompileChannel(store, openPayload)
1085
-
1086
- const { settle } = await import('./Session.js')
1087
- const client = createClient({
1088
- account: wrongPayer,
1089
- chain: testChain,
1090
- transport: custom({
1091
- async request(args) {
1092
- if (args.method === 'eth_chainId') return `0x${chainId.toString(16)}`
1093
- throw new Error(`unexpected rpc request: ${args.method}`)
1094
- },
1095
- }),
1096
- })
1097
- await expect(settle(store, client, openPayload.channelId)).rejects.toThrow(
1098
- /eth_getTransactionCount/,
1099
- )
1100
- })
1101
-
1102
- test('precompile settle fee payer options still enforce payee sender policy', async () => {
1103
- const { store } = createServer()
1104
- const openPayload = await createOpenPayload()
1105
- await persistPrecompileChannel(store, openPayload)
1106
-
1107
- const { settle } = await import('./Session.js')
1108
- await expect(
1109
- settle(store, createServerClient([], payer), openPayload.channelId, {
1110
- feePayer: wrongPayer,
1111
- }),
1112
- ).rejects.toThrow(/tx sender .* is not the channel payee/)
1113
- })
1114
-
1115
- test('accepts precompile settle fee token options', async () => {
1116
- const { store } = createServer()
1117
- const openPayload = await createOpenPayload()
1118
- await persistPrecompileChannel(store, openPayload, {
1119
- payee: payer.address,
1120
- })
1121
- const client = createClient({
1122
- account: payer,
1123
- chain: testChain,
1124
- transport: custom({
1125
- async request(args) {
1126
- if (args.method === 'eth_chainId') return `0x${chainId.toString(16)}`
1127
- if (args.method === 'eth_sendTransaction') throw new Error('sent fee-token settle')
1128
- throw new Error(`unexpected rpc request: ${args.method}`)
1129
- },
1130
- }),
1131
- })
1132
-
1133
- const { settle } = await import('./Session.js')
1134
- await expect(
1135
- settle(store, client, openPayload.channelId, {
1136
- feeToken: token,
1137
- }),
1138
- ).rejects.toThrow(/eth_getTransactionCount/)
1139
- })
1140
-
1141
- test('accepts settle account override matching the channel payee', async () => {
1142
- const { store } = createServer()
1143
- const openPayload = await createOpenPayload()
1144
- await persistPrecompileChannel(store, openPayload, {
1145
- payee: wrongPayer.address,
1146
- })
1147
- const client = createClient({
1148
- account: payer,
1149
- chain: testChain,
1150
- transport: custom({
1151
- async request(args) {
1152
- if (args.method === 'eth_sendTransaction') throw new Error('sent settle transaction')
1153
- if (args.method === 'eth_chainId') return `0x${chainId.toString(16)}`
1154
- throw new Error(`unexpected rpc request: ${args.method}`)
1155
- },
1156
- }),
1157
- })
1158
-
1159
- const { settle } = await import('./Session.js')
1160
- await expect(
1161
- settle(store, client, openPayload.channelId, {
1162
- account: wrongPayer,
1163
- }),
1164
- ).rejects.toThrow(/eth_getTransactionCount/)
1165
- })
1166
-
1167
- test('rejects precompile settle fee-payer policy violations', async () => {
1168
- const { store } = createServer()
1169
- const openPayload = await createOpenPayload()
1170
- await persistPrecompileChannel(store, openPayload, {
1171
- payee: payer.address,
1172
- })
1173
-
1174
- const { settle } = await import('./Session.js')
1175
- await expect(
1176
- settle(store, createServerClient([], payer), openPayload.channelId, {
1177
- feePayer: wrongPayer,
1178
- feePayerPolicy: { maxGas: 1n },
1179
- feeToken: token,
1180
- }),
1181
- ).rejects.toThrow(/fee-payer policy maxGas exceeded/)
1182
- })
1183
-
1184
- test('rejects close voucher below local spent', async () => {
1185
- const rawStore = Store.memory()
1186
- const store = channelStore(rawStore)
1187
- const openPayload = await createOpenPayload()
1188
- await persistPrecompileChannel(store, openPayload, {
1189
- payee: payer.address,
1190
- spent: 150n,
1191
- })
1192
- const method = session({
1193
- account: payer,
1194
- amount: '1',
1195
- chainId,
1196
- currency: token,
1197
- decimals: 0,
1198
- recipient: payee,
1199
- store: rawStore,
1200
- unitType: 'request',
1201
- getClient: () => createStateClient(payer),
1202
- })
1203
- const payload = await ClientOps.createClosePayload(
1204
- createSigningClient(),
1205
- payer,
1206
- openPayload.descriptor,
1207
- Types.uint96(100n),
1208
- chainId,
1209
- )
1210
-
1211
- await expect(
1212
- method.verify({
1213
- credential: {
1214
- challenge: makeChallenge(openPayload.channelId),
1215
- payload,
1216
- },
1217
- request: verifyRequest(openPayload.channelId),
1218
- }),
1219
- ).rejects.toThrow(/close voucher amount must be >= 150 \(spent\)/)
1220
- })
1221
-
1222
- test('rejects close voucher below on-chain settled', async () => {
1223
- const rawStore = Store.memory()
1224
- const store = channelStore(rawStore)
1225
- const openPayload = await createOpenPayload()
1226
- await persistPrecompileChannel(store, openPayload, {
1227
- payee: payer.address,
1228
- })
1229
- const method = session({
1230
- account: payer,
1231
- amount: '1',
1232
- chainId,
1233
- currency: token,
1234
- decimals: 0,
1235
- recipient: payee,
1236
- store: rawStore,
1237
- unitType: 'request',
1238
- getClient: () =>
1239
- createStateClient(payer, {
1240
- settled: 100n,
1241
- deposit: 1_000n,
1242
- closeRequestedAt: 0,
1243
- }),
1244
- })
1245
- const payload = await ClientOps.createClosePayload(
1246
- createSigningClient(),
1247
- payer,
1248
- openPayload.descriptor,
1249
- Types.uint96(99n),
1250
- chainId,
1251
- )
1252
-
1253
- await expect(
1254
- method.verify({
1255
- credential: {
1256
- challenge: makeChallenge(openPayload.channelId),
1257
- payload,
1258
- },
1259
- request: verifyRequest(openPayload.channelId),
1260
- }),
1261
- ).rejects.toThrow(/close voucher amount must be >= 100 \(on-chain settled\)/)
1262
- })
1263
-
1264
- test('rejects close capture exceeding on-chain precompile deposit', async () => {
1265
- const rawStore = Store.memory()
1266
- const store = channelStore(rawStore)
1267
- const openPayload = await createOpenPayload()
1268
- await persistPrecompileChannel(store, openPayload, {
1269
- payee: payer.address,
1270
- spent: 100n,
1271
- })
1272
- const method = session({
1273
- account: payer,
1274
- amount: '1',
1275
- chainId,
1276
- currency: token,
1277
- decimals: 0,
1278
- recipient: payee,
1279
- store: rawStore,
1280
- unitType: 'request',
1281
- getClient: () =>
1282
- createStateClient(payer, {
1283
- settled: 0n,
1284
- deposit: 99n,
1285
- closeRequestedAt: 0,
1286
- }),
1287
- })
1288
- const payload = await ClientOps.createClosePayload(
1289
- createSigningClient(),
1290
- payer,
1291
- openPayload.descriptor,
1292
- Types.uint96(100n),
1293
- chainId,
1294
- )
1295
-
1296
- await expect(
1297
- method.verify({
1298
- credential: {
1299
- challenge: makeChallenge(openPayload.channelId),
1300
- payload,
1301
- },
1302
- request: verifyRequest(openPayload.channelId),
1303
- }),
1304
- ).rejects.toThrow(/close capture amount exceeds on-chain deposit/)
1305
- })
1306
-
1307
- test('rejects close for locally finalized and pending precompile channels', async () => {
1308
- const rawStore = Store.memory()
1309
- const store = channelStore(rawStore)
1310
- const openPayload = await createOpenPayload()
1311
- const payload = await ClientOps.createClosePayload(
1312
- createSigningClient(),
1313
- payer,
1314
- openPayload.descriptor,
1315
- Types.uint96(100n),
1316
- chainId,
1317
- )
1318
- const method = session({
1319
- account: payer,
1320
- amount: '1',
1321
- chainId,
1322
- currency: token,
1323
- decimals: 0,
1324
- recipient: payee,
1325
- store: rawStore,
1326
- unitType: 'request',
1327
- getClient: () => createStateClient(payer),
1328
- })
1329
-
1330
- await persistPrecompileChannel(store, openPayload, {
1331
- finalized: true,
1332
- payee: payer.address,
1333
- })
1334
- await expect(
1335
- method.verify({
1336
- credential: {
1337
- challenge: makeChallenge(openPayload.channelId),
1338
- payload,
1339
- },
1340
- request: verifyRequest(openPayload.channelId),
1341
- }),
1342
- ).rejects.toThrow(/channel is already finalized/)
1343
-
1344
- await persistPrecompileChannel(store, openPayload, {
1345
- closeRequestedAt: 1n,
1346
- payee: payer.address,
1347
- })
1348
- await expect(
1349
- method.verify({
1350
- credential: {
1351
- challenge: makeChallenge(openPayload.channelId),
1352
- payload,
1353
- },
1354
- request: verifyRequest(openPayload.channelId),
1355
- }),
1356
- ).rejects.toThrow(/channel has a pending close request/)
1357
- })
1358
-
1359
- test('accepts valid precompile open with voucher and stores state', async () => {
1360
- const rawStore = Store.memory()
1361
- const store = channelStore(rawStore)
1362
- const openPayload = await createOpenPayload({ initialAmount: 100n })
1363
- const method = session({
1364
- amount: '1',
1365
- chainId,
1366
- currency: token,
1367
- decimals: 0,
1368
- recipient: payee,
1369
- store: rawStore,
1370
- unitType: 'request',
1371
- getClient: () =>
1372
- createServerClient([], payer, openPayload.channelId, {
1373
- descriptor: openPayload.descriptor,
1374
- receipt: transactionReceipt([openedLog(openPayload)]),
1375
- state: { settled: 0n, deposit: 1_000n, closeRequestedAt: 0 },
1376
- }),
1377
- })
1378
-
1379
- const receipt = (await method.verify({
1380
- credential: {
1381
- challenge: makeChallenge(openPayload.channelId),
1382
- payload: openPayload,
1383
- },
1384
- request: verifyRequest(openPayload.channelId),
1385
- })) as SessionReceipt
1386
-
1387
- const stored = await store.getChannel(openPayload.channelId)
1388
- expect(receipt.acceptedCumulative).toBe('100')
1389
- expect(stored?.backend).toBe('precompile')
1390
- expect(stored?.deposit).toBe(1_000n)
1391
- expect(stored?.highestVoucherAmount).toBe(100n)
1392
- if (!stored || !ChannelStore.isPrecompileState(stored))
1393
- throw new Error('expected precompile state')
1394
- expect(stored.descriptor).toEqual(openPayload.descriptor)
1395
- })
1396
-
1397
- test('reopening existing precompile channel with higher voucher updates highest voucher only', async () => {
1398
- const rawStore = Store.memory()
1399
- const store = channelStore(rawStore)
1400
- const openPayload = await createOpenPayload({ initialAmount: 100n })
1401
- await persistPrecompileChannel(store, openPayload, {
1402
- highestVoucherAmount: 100n,
1403
- spent: 75n,
1404
- units: 3,
1405
- })
1406
- const reopenPayload = { ...openPayload, cumulativeAmount: '250' }
1407
- reopenPayload.signature = await Voucher.signVoucher(
1408
- createSigningClient(),
1409
- payer,
1410
- { channelId: openPayload.channelId, cumulativeAmount: 250n },
1411
- tip20ChannelEscrow,
1412
- chainId,
1413
- )
1414
- const method = session({
1415
- amount: '1',
1416
- chainId,
1417
- currency: token,
1418
- decimals: 0,
1419
- recipient: payee,
1420
- store: rawStore,
1421
- unitType: 'request',
1422
- getClient: () =>
1423
- createServerClient([], payer, openPayload.channelId, {
1424
- descriptor: openPayload.descriptor,
1425
- receipt: transactionReceipt([openedLog(openPayload)]),
1426
- state: { settled: 0n, deposit: 1_000n, closeRequestedAt: 0 },
1427
- }),
1428
- })
1429
-
1430
- const receipt = (await method.verify({
1431
- credential: {
1432
- challenge: makeChallenge(openPayload.channelId),
1433
- payload: reopenPayload,
1434
- },
1435
- request: verifyRequest(openPayload.channelId),
1436
- })) as SessionReceipt
1437
-
1438
- const stored = await store.getChannel(openPayload.channelId)
1439
- expect(receipt.acceptedCumulative).toBe('250')
1440
- expect(receipt.spent).toBe('75')
1441
- expect(receipt.units).toBe(3)
1442
- expect(stored?.highestVoucherAmount).toBe(250n)
1443
- expect(stored?.spent).toBe(75n)
1444
- expect(stored?.units).toBe(3)
1445
- })
1446
-
1447
- test('reopening existing precompile channel with same voucher preserves accounting', async () => {
1448
- const rawStore = Store.memory()
1449
- const store = channelStore(rawStore)
1450
- const openPayload = await createOpenPayload({ initialAmount: 100n })
1451
- await persistPrecompileChannel(store, openPayload, {
1452
- highestVoucherAmount: 100n,
1453
- spent: 75n,
1454
- units: 3,
1455
- })
1456
- const method = session({
1457
- amount: '1',
1458
- chainId,
1459
- currency: token,
1460
- decimals: 0,
1461
- recipient: payee,
1462
- store: rawStore,
1463
- unitType: 'request',
1464
- getClient: () =>
1465
- createServerClient([], payer, openPayload.channelId, {
1466
- descriptor: openPayload.descriptor,
1467
- receipt: transactionReceipt([openedLog(openPayload)]),
1468
- state: { settled: 0n, deposit: 1_000n, closeRequestedAt: 0 },
1469
- }),
1470
- })
1471
-
1472
- const receipt = (await method.verify({
1473
- credential: {
1474
- challenge: makeChallenge(openPayload.channelId),
1475
- payload: openPayload,
1476
- },
1477
- request: verifyRequest(openPayload.channelId),
1478
- })) as SessionReceipt
1479
-
1480
- const stored = await store.getChannel(openPayload.channelId)
1481
- expect(receipt.acceptedCumulative).toBe('100')
1482
- expect(receipt.spent).toBe('75')
1483
- expect(receipt.units).toBe(3)
1484
- expect(stored?.highestVoucherAmount).toBe(100n)
1485
- expect(stored?.spent).toBe(75n)
1486
- expect(stored?.units).toBe(3)
1487
- })
1488
-
1489
- test('case-variant precompile channelId does not reset open accounting', async () => {
1490
- const rawStore = Store.memory()
1491
- const store = channelStore(rawStore)
1492
- const openPayload = await createOpenPayload({ initialAmount: 100n })
1493
- await persistPrecompileChannel(store, openPayload, { spent: 75n, units: 3 })
1494
- const mixedCaseChannelId = openPayload.channelId.replace(/[a-f]/g, (char) =>
1495
- char.toUpperCase(),
1496
- ) as Hex
1497
- const method = session({
1498
- amount: '1',
1499
- chainId,
1500
- currency: token,
1501
- decimals: 0,
1502
- recipient: payee,
1503
- store: rawStore,
1504
- unitType: 'request',
1505
- getClient: () =>
1506
- createServerClient([], payer, openPayload.channelId, {
1507
- descriptor: openPayload.descriptor,
1508
- receipt: transactionReceipt([openedLog(openPayload)]),
1509
- state: { settled: 0n, deposit: 1_000n, closeRequestedAt: 0 },
1510
- }),
1511
- })
1512
-
1513
- const receipt = (await method.verify({
1514
- credential: {
1515
- challenge: makeChallenge(mixedCaseChannelId),
1516
- payload: { ...openPayload, channelId: mixedCaseChannelId },
1517
- },
1518
- request: verifyRequest(mixedCaseChannelId),
1519
- })) as SessionReceipt
1520
-
1521
- const stored = await store.getChannel(openPayload.channelId)
1522
- expect(receipt.spent).toBe('75')
1523
- expect(receipt.units).toBe(3)
1524
- expect(stored?.spent).toBe(75n)
1525
- expect(stored?.units).toBe(3)
1526
- })
1527
-
1528
- test('uses payer as precompile voucher signer when authorized signer is zero', async () => {
1529
- const rawStore = Store.memory()
1530
- const store = channelStore(rawStore)
1531
- const openPayload = await createOpenPayload({
1532
- authorizedSigner: zeroAddress,
1533
- initialAmount: 100n,
1534
- })
1535
- expect(openPayload.descriptor.authorizedSigner).toBe(zeroAddress)
1536
- const method = session({
1537
- amount: '1',
1538
- chainId,
1539
- currency: token,
1540
- decimals: 0,
1541
- recipient: payee,
1542
- store: rawStore,
1543
- unitType: 'request',
1544
- getClient: () =>
1545
- createServerClient([], payer, openPayload.channelId, {
1546
- descriptor: openPayload.descriptor,
1547
- receipt: transactionReceipt([openedLog(openPayload)]),
1548
- state: { settled: 0n, deposit: 1_000n, closeRequestedAt: 0 },
1549
- }),
1550
- })
1551
-
1552
- const receipt = (await method.verify({
1553
- credential: {
1554
- challenge: makeChallenge(openPayload.channelId),
1555
- payload: openPayload,
1556
- },
1557
- request: verifyRequest(openPayload.channelId),
1558
- })) as SessionReceipt
1559
-
1560
- const stored = await store.getChannel(openPayload.channelId)
1561
- expect(receipt.acceptedCumulative).toBe('100')
1562
- expect(stored?.authorizedSigner).toBe(payer.address)
1563
- })
1564
-
1565
- test('accepts precompile top-up and preserves spent accounting', async () => {
1566
- const rawStore = Store.memory()
1567
- const store = channelStore(rawStore)
1568
- const openPayload = await createOpenPayload({ initialAmount: 100n })
1569
- await persistPrecompileChannel(store, openPayload, {
1570
- deposit: 1_000n,
1571
- spent: 125n,
1572
- units: 4,
1573
- })
1574
- const topUpPayload = await createTopUpPayload(openPayload.descriptor, 500n)
1575
- const method = session({
1576
- amount: '1',
1577
- chainId,
1578
- currency: token,
1579
- decimals: 0,
1580
- recipient: payee,
1581
- store: rawStore,
1582
- unitType: 'request',
1583
- getClient: () =>
1584
- createServerClient([], payer, topUpPayload.channelId, {
1585
- receipt: transactionReceipt([topUpLog(topUpPayload, 1_500n)]),
1586
- state: { settled: 0n, deposit: 1_500n, closeRequestedAt: 0 },
1587
- }),
1588
- })
1589
-
1590
- const receipt = (await method.verify({
1591
- credential: {
1592
- challenge: makeChallenge(openPayload.channelId),
1593
- payload: topUpPayload,
1594
- },
1595
- request: verifyRequest(openPayload.channelId),
1596
- })) as SessionReceipt
1597
-
1598
- const stored = await store.getChannel(openPayload.channelId)
1599
- expect(receipt.spent).toBe('125')
1600
- expect(receipt.units).toBe(4)
1601
- expect(stored?.deposit).toBe(1_500n)
1602
- expect(stored?.spent).toBe(125n)
1603
- expect(stored?.units).toBe(4)
1604
- })
1605
-
1606
- test('rejects top-up validation when on-chain state has pending close', async () => {
1607
- const rawStore = Store.memory()
1608
- const store = channelStore(rawStore)
1609
- const openPayload = await createOpenPayload({ initialAmount: 100n })
1610
- await persistPrecompileChannel(store, openPayload, { deposit: 1_000n })
1611
- const topUpPayload = await createTopUpPayload(openPayload.descriptor, 500n)
1612
- const method = session({
1613
- amount: '1',
1614
- chainId,
1615
- currency: token,
1616
- decimals: 0,
1617
- recipient: payee,
1618
- store: rawStore,
1619
- unitType: 'request',
1620
- getClient: () =>
1621
- createServerClient([], payer, topUpPayload.channelId, {
1622
- receipt: transactionReceipt([topUpLog(topUpPayload, 1_500n)]),
1623
- state: { settled: 0n, deposit: 1_500n, closeRequestedAt: 1 },
1624
- }),
1625
- })
1626
-
1627
- await expect(
1628
- method.validate!({
1629
- credential: {
1630
- challenge: makeChallenge(openPayload.channelId),
1631
- payload: topUpPayload,
1632
- },
1633
- request: verifyRequest(openPayload.channelId),
1634
- }),
1635
- ).rejects.toThrow(/pending close request/)
1636
- })
1637
-
1638
- test('rejects precompile top-up on unknown channel', async () => {
1639
- const { method } = createServer()
1640
- const openPayload = await createOpenPayload({ initialAmount: 100n })
1641
- const topUpPayload = await createTopUpPayload(openPayload.descriptor, 500n)
1642
-
1643
- await expect(
1644
- method.verify({
1645
- credential: {
1646
- challenge: makeChallenge(openPayload.channelId),
1647
- payload: topUpPayload,
1648
- },
1649
- request: verifyRequest(openPayload.channelId),
1650
- }),
1651
- ).rejects.toThrow(/channel not found/)
1652
- })
1653
-
1654
- test('rejects precompile top-up descriptor mismatches', async () => {
1655
- const { method, store } = createServer()
1656
- const openPayload = await createOpenPayload({ initialAmount: 100n })
1657
- await persistPrecompileChannel(store, openPayload)
1658
- const badDescriptor = { ...openPayload.descriptor, payee: wrongTarget }
1659
- const topUpPayload = await createTopUpPayload(badDescriptor, 500n)
1660
-
1661
- await expect(
1662
- method.verify({
1663
- credential: {
1664
- challenge: makeChallenge(openPayload.channelId),
1665
- payload: { ...topUpPayload, channelId: openPayload.channelId },
1666
- },
1667
- request: verifyRequest(openPayload.channelId),
1668
- }),
1669
- ).rejects.toThrow(/descriptor does not match/)
1670
- })
1671
-
1672
- test('accepts increasing precompile voucher and stores accounting state', async () => {
1673
- const { method, store } = createServer({ channelStateTtl: Number.MAX_SAFE_INTEGER })
1674
- const openPayload = await createOpenPayload({ initialAmount: 100n })
1675
- await persistPrecompileChannel(store, openPayload, {
1676
- highestVoucherAmount: 100n,
1677
- spent: 100n,
1678
- units: 1,
1679
- })
1680
- const voucher = await ClientOps.createVoucherPayload(
1681
- createSigningClient(),
1682
- payer,
1683
- openPayload.descriptor,
1684
- Types.uint96(250n),
1685
- chainId,
1686
- )
1687
-
1688
- const receipt = (await method.verify({
1689
- credential: voucherCredential(voucher, openPayload.channelId),
1690
- request: verifyRequest(openPayload.channelId),
1691
- })) as SessionReceipt
1692
-
1693
- const stored = await store.getChannel(openPayload.channelId)
1694
- expect(receipt.acceptedCumulative).toBe('250')
1695
- expect(receipt.spent).toBe('100')
1696
- expect(receipt.units).toBe(1)
1697
- expect(stored?.highestVoucherAmount).toBe(250n)
1698
- expect(stored?.spent).toBe(100n)
1699
- expect(stored?.units).toBe(1)
1700
- })
1701
-
1702
- test('accepts exact precompile voucher replay idempotently', async () => {
1703
- const { method, store } = createServer({ channelStateTtl: Number.MAX_SAFE_INTEGER })
1704
- const openPayload = await createOpenPayload({ initialAmount: 100n })
1705
- const voucher = await ClientOps.createVoucherPayload(
1706
- createSigningClient(),
1707
- payer,
1708
- openPayload.descriptor,
1709
- Types.uint96(250n),
1710
- chainId,
1711
- )
1712
- if (voucher.action !== 'voucher') throw new Error('expected voucher payload')
1713
- await persistPrecompileChannel(store, openPayload, {
1714
- highestVoucherAmount: 250n,
1715
- highestVoucher: {
1716
- channelId: openPayload.channelId,
1717
- cumulativeAmount: 250n,
1718
- signature: voucher.signature,
1719
- },
1720
- spent: 250n,
1721
- units: 2,
1722
- })
1723
-
1724
- const receipt = (await method.verify({
1725
- credential: voucherCredential(voucher, openPayload.channelId),
1726
- request: verifyRequest(openPayload.channelId),
1727
- })) as SessionReceipt
1728
-
1729
- const stored = await store.getChannel(openPayload.channelId)
1730
- expect(receipt.acceptedCumulative).toBe('250')
1731
- expect(receipt.spent).toBe('250')
1732
- expect(receipt.units).toBe(2)
1733
- expect(stored?.highestVoucherAmount).toBe(250n)
1734
- expect(stored?.units).toBe(2)
1735
- })
1736
-
1737
- test('rejects lower precompile voucher replay', async () => {
1738
- const { method, store } = createServer({ channelStateTtl: Number.MAX_SAFE_INTEGER })
1739
- const openPayload = await createOpenPayload({ initialAmount: 100n })
1740
- await persistPrecompileChannel(store, openPayload, {
1741
- highestVoucherAmount: 500n,
1742
- spent: 500n,
1743
- units: 5,
1744
- })
1745
- const voucher = await ClientOps.createVoucherPayload(
1746
- createSigningClient(),
1747
- payer,
1748
- openPayload.descriptor,
1749
- Types.uint96(250n),
1750
- chainId,
1751
- )
1752
-
1753
- await expect(
1754
- method.verify({
1755
- credential: voucherCredential(voucher, openPayload.channelId),
1756
- request: verifyRequest(openPayload.channelId),
1757
- }),
1758
- ).rejects.toThrow(
1759
- /strictly greater than highest accepted voucher|non-increasing voucher|voucher replay/,
1760
- )
1761
- })
1762
-
1763
- test('rejects precompile voucher below minVoucherDelta', async () => {
1764
- const { method, store } = createServer({
1765
- channelStateTtl: Number.MAX_SAFE_INTEGER,
1766
- minVoucherDelta: '200',
1767
- })
1768
- const openPayload = await createOpenPayload({ initialAmount: 100n })
1769
- await persistPrecompileChannel(store, openPayload, { highestVoucherAmount: 100n })
1770
- const voucher = await ClientOps.createVoucherPayload(
1771
- createSigningClient(),
1772
- payer,
1773
- openPayload.descriptor,
1774
- Types.uint96(250n),
1775
- chainId,
1776
- )
1777
-
1778
- await expect(
1779
- method.verify({
1780
- credential: voucherCredential(voucher, openPayload.channelId),
1781
- request: verifyRequest(openPayload.channelId),
1782
- }),
1783
- ).rejects.toThrow(/voucher delta 150 below minimum 200/)
1784
- })
1785
-
1786
- test('rejects idempotent precompile voucher replay after on-chain settlement catches up', async () => {
1787
- const rawStore = Store.memory()
1788
- const store = channelStore(rawStore)
1789
- const openPayload = await createOpenPayload({ initialAmount: 100n })
1790
- await persistPrecompileChannel(store, openPayload, {
1791
- highestVoucherAmount: 500n,
1792
- settledOnChain: 500n,
1793
- spent: 500n,
1794
- units: 10,
1795
- })
1796
- const voucher = await ClientOps.createVoucherPayload(
1797
- createSigningClient(),
1798
- payer,
1799
- openPayload.descriptor,
1800
- Types.uint96(500n),
1801
- chainId,
1802
- )
1803
- const method = session({
1804
- amount: '1',
1805
- chainId,
1806
- currency: token,
1807
- decimals: 0,
1808
- recipient: payee,
1809
- store: rawStore,
1810
- unitType: 'request',
1811
- getClient: () =>
1812
- createStateClient(payer, { settled: 500n, deposit: 1_000n, closeRequestedAt: 0 }),
1813
- })
1814
-
1815
- await expect(
1816
- method.verify({
1817
- credential: voucherCredential(voucher, openPayload.channelId),
1818
- request: verifyRequest(openPayload.channelId),
1819
- }),
1820
- ).rejects.toThrow(/below on-chain settled amount/)
1821
- })
1822
-
1823
- test('rejects stale or hijacked precompile voucher signatures', async () => {
1824
- const { method, store } = createServer({ channelStateTtl: Number.MAX_SAFE_INTEGER })
1825
- const openPayload = await createOpenPayload({ initialAmount: 100n })
1826
- await persistPrecompileChannel(store, openPayload, { highestVoucherAmount: 100n })
1827
- const signature = await Voucher.signVoucher(
1828
- createSigningClient(wrongPayer),
1829
- wrongPayer,
1830
- { channelId: openPayload.channelId, cumulativeAmount: 250n },
1831
- tip20ChannelEscrow,
1832
- chainId,
1833
- )
1834
-
1835
- await expect(
1836
- method.verify({
1837
- credential: voucherCredential(
1838
- {
1839
- action: 'voucher',
1840
- channelId: openPayload.channelId,
1841
- cumulativeAmount: '250',
1842
- descriptor: openPayload.descriptor,
1843
- signature,
1844
- },
1845
- openPayload.channelId,
1846
- ),
1847
- request: verifyRequest(openPayload.channelId),
1848
- }),
1849
- ).rejects.toThrow(/invalid voucher signature/)
1850
- })
1851
-
1852
- test('rejects precompile voucher exceeding deposit', async () => {
1853
- const { method, store } = createServer({ channelStateTtl: Number.MAX_SAFE_INTEGER })
1854
- const openPayload = await createOpenPayload({ initialAmount: 100n })
1855
- await persistPrecompileChannel(store, openPayload, { deposit: 300n })
1856
- const voucher = await ClientOps.createVoucherPayload(
1857
- createSigningClient(),
1858
- payer,
1859
- openPayload.descriptor,
1860
- Types.uint96(350n),
1861
- chainId,
1862
- )
1863
-
1864
- await expect(
1865
- method.verify({
1866
- credential: voucherCredential(voucher, openPayload.channelId),
1867
- request: verifyRequest(openPayload.channelId),
1868
- }),
1869
- ).rejects.toThrow(/exceeds.*deposit|insufficient channel deposit/)
1870
- })
1871
-
1872
- test('rejects precompile voucher when on-chain state has pending close', async () => {
1873
- const rawStore = Store.memory()
1874
- const store = channelStore(rawStore)
1875
- const openPayload = await createOpenPayload({ initialAmount: 100n })
1876
- await persistPrecompileChannel(store, openPayload, { closeRequestedAt: 0n })
1877
- const voucher = await ClientOps.createVoucherPayload(
1878
- createSigningClient(),
1879
- payer,
1880
- openPayload.descriptor,
1881
- Types.uint96(250n),
1882
- chainId,
1883
- )
1884
- const method = session({
1885
- amount: '1',
1886
- chainId,
1887
- channelStateTtl: 0,
1888
- currency: token,
1889
- decimals: 0,
1890
- recipient: payee,
1891
- store: rawStore,
1892
- unitType: 'request',
1893
- getClient: () =>
1894
- createStateClient(payer, { settled: 0n, deposit: 1_000n, closeRequestedAt: 1 }),
1895
- })
1896
-
1897
- await expect(
1898
- method.verify({
1899
- credential: voucherCredential(voucher, openPayload.channelId),
1900
- request: verifyRequest(openPayload.channelId),
1901
- }),
1902
- ).rejects.toThrow(/pending close request/)
1903
- })
1904
-
1905
- test('rejects precompile voucher when on-chain deposit is zero', async () => {
1906
- const rawStore = Store.memory()
1907
- const store = channelStore(rawStore)
1908
- const openPayload = await createOpenPayload({ initialAmount: 100n })
1909
- await persistPrecompileChannel(store, openPayload, { deposit: 1_000n })
1910
- const voucher = await ClientOps.createVoucherPayload(
1911
- createSigningClient(),
1912
- payer,
1913
- openPayload.descriptor,
1914
- Types.uint96(250n),
1915
- chainId,
1916
- )
1917
- const method = session({
1918
- amount: '1',
1919
- chainId,
1920
- channelStateTtl: 0,
1921
- currency: token,
1922
- decimals: 0,
1923
- recipient: payee,
1924
- store: rawStore,
1925
- unitType: 'request',
1926
- getClient: () => createStateClient(payer, { settled: 0n, deposit: 0n, closeRequestedAt: 0 }),
1927
- })
1928
-
1929
- await expect(
1930
- method.verify({
1931
- credential: voucherCredential(voucher, openPayload.channelId),
1932
- request: verifyRequest(openPayload.channelId),
1933
- }),
1934
- ).rejects.toThrow(/deposit is zero|channel deposit is zero|not found/)
1935
- })
1936
-
1937
- test('rejects precompile voucher on unknown channel', async () => {
1938
- const { method } = createServer({ channelStateTtl: Number.MAX_SAFE_INTEGER })
1939
- const openPayload = await createOpenPayload({ initialAmount: 100n })
1940
- const voucher = await ClientOps.createVoucherPayload(
1941
- createSigningClient(),
1942
- payer,
1943
- openPayload.descriptor,
1944
- Types.uint96(250n),
1945
- chainId,
1946
- )
1947
-
1948
- await expect(
1949
- method.verify({
1950
- credential: {
1951
- challenge: makeChallenge(openPayload.channelId),
1952
- payload: voucher,
1953
- },
1954
- request: verifyRequest(openPayload.channelId),
1955
- }),
1956
- ).rejects.toThrow(/unknown channel|not found/)
1957
- })
1958
-
1959
- describe('respond', () => {
1960
- function respond(action: SessionCredentialPayload['action'], input: Request) {
1961
- const { method } = createServer()
1962
- return method.respond!({
1963
- credential: {
1964
- challenge: makeChallenge(`0x${'01'.repeat(32)}` as Hex),
1965
- payload: { action },
1966
- },
1967
- input,
1968
- } as never)
1969
- }
1970
-
1971
- test('returns 204 for close management requests', () => {
1972
- const result = respond('close', new Request('http://localhost', { method: 'GET' }))
1973
- expect(result).toBeInstanceOf(Response)
1974
- expect((result as Response).status).toBe(204)
1975
- })
1976
-
1977
- test('returns 204 for top-up management requests', () => {
1978
- const result = respond('topUp', new Request('http://localhost', { method: 'POST' }))
1979
- expect(result).toBeInstanceOf(Response)
1980
- expect((result as Response).status).toBe(204)
1981
- })
1982
-
1983
- test('returns 204 for open POST management requests', () => {
1984
- const result = respond('open', new Request('http://localhost', { method: 'POST' }))
1985
- expect(result).toBeInstanceOf(Response)
1986
- expect((result as Response).status).toBe(204)
1987
- })
1988
-
1989
- test('returns 204 for voucher POST management requests', () => {
1990
- const result = respond('voucher', new Request('http://localhost', { method: 'POST' }))
1991
- expect(result).toBeInstanceOf(Response)
1992
- expect((result as Response).status).toBe(204)
1993
- })
1994
-
1995
- test('lets open and voucher GET content requests through', () => {
1996
- expect(respond('open', new Request('http://localhost', { method: 'GET' }))).toBeUndefined()
1997
- expect(respond('voucher', new Request('http://localhost', { method: 'GET' }))).toBeUndefined()
1998
- })
1999
-
2000
- test('lets open and voucher POST content requests with bodies through', () => {
2001
- expect(
2002
- respond(
2003
- 'open',
2004
- new Request('http://localhost', { method: 'POST', headers: { 'content-length': '1' } }),
2005
- ),
2006
- ).toBeUndefined()
2007
- expect(
2008
- respond(
2009
- 'voucher',
2010
- new Request('http://localhost', {
2011
- method: 'POST',
2012
- headers: { 'transfer-encoding': 'chunked' },
2013
- }),
2014
- ),
2015
- ).toBeUndefined()
2016
- })
2017
-
2018
- test('returns 204 for voucher POST with content-length zero', () => {
2019
- const result = respond(
2020
- 'voucher',
2021
- new Request('http://localhost', { method: 'POST', headers: { 'content-length': '0' } }),
2022
- )
2023
- expect(result).toBeInstanceOf(Response)
2024
- expect((result as Response).status).toBe(204)
2025
- })
2026
- })
2027
-
2028
- describe('default HTTP auto-billing', () => {
2029
- function createRoute(rawStore: Store.AtomicStore) {
2030
- return Mppx_server.create({
2031
- methods: [
2032
- tempo_server.session({
2033
- amount: '1',
2034
- chainId,
2035
- currency: token,
2036
- decimals: 0,
2037
- recipient: payee,
2038
- store: rawStore,
2039
- unitType: 'request',
2040
- getClient: () => createStateClient(payer),
2041
- }),
2042
- ],
2043
- realm: 'api.example.com',
2044
- secretKey: 'test-secret-key-test-secret-key-32',
2045
- }).session({ amount: '1', decimals: 0, unitType: 'request' })
2046
- }
2047
-
2048
- test('GET content flow charges once and rejects same-voucher replay', async () => {
2049
- const rawStore = Store.memory()
2050
- const store = channelStore(rawStore)
2051
- const openPayload = await createOpenPayload({ initialAmount: 1n })
2052
- await persistPrecompileChannel(store, openPayload, {
2053
- highestVoucherAmount: 1n,
2054
- spent: 0n,
2055
- units: 0,
2056
- })
2057
- const route = createRoute(rawStore)
2058
- const voucher = await ClientOps.createVoucherPayload(
2059
- createSigningClient(),
2060
- payer,
2061
- openPayload.descriptor,
2062
- Types.uint96(1n),
2063
- chainId,
2064
- )
2065
-
2066
- const serve = async (request: Request) => {
2067
- const result = await route(request)
2068
- if (result.status === 402) return result.challenge
2069
- return result.withReceipt(new Response('paid-content'))
2070
- }
2071
-
2072
- const first = await route(new Request('https://api.example.com/resource'))
2073
- expect(first.status).toBe(402)
2074
- if (first.status !== 402) throw new Error('expected challenge')
2075
-
2076
- const paid = await serve(
2077
- new Request('https://api.example.com/resource', {
2078
- headers: {
2079
- Authorization: Credential.serialize({
2080
- challenge: Challenge.fromResponse(first.challenge),
2081
- payload: voucher,
2082
- source: sourceFor(),
2083
- }),
2084
- },
2085
- }),
2086
- )
2087
- expect(paid.status).toBe(200)
2088
- expect(await paid.text()).toBe('paid-content')
2089
- const receipt = deserializeSessionReceipt(paid.headers.get('Payment-Receipt') as string)
2090
- expect(receipt.acceptedCumulative).toBe('1')
2091
- expect(receipt.spent).toBe('1')
2092
- expect(receipt.units).toBe(1)
2093
-
2094
- const replayChallenge = await route(new Request('https://api.example.com/resource'))
2095
- expect(replayChallenge.status).toBe(402)
2096
- if (replayChallenge.status !== 402) throw new Error('expected challenge')
2097
-
2098
- const replay = await serve(
2099
- new Request('https://api.example.com/resource', {
2100
- headers: {
2101
- Authorization: Credential.serialize({
2102
- challenge: Challenge.fromResponse(replayChallenge.challenge),
2103
- payload: voucher,
2104
- source: sourceFor(),
2105
- }),
2106
- },
2107
- }),
2108
- )
2109
- expect(replay.status).toBe(402)
2110
- expect(replay.headers.get('Payment-Receipt')).toBeNull()
2111
- })
2112
-
2113
- test('POST content flow charges once and rejects same-voucher replay', async () => {
2114
- const rawStore = Store.memory()
2115
- const store = channelStore(rawStore)
2116
- const openPayload = await createOpenPayload({ initialAmount: 1n })
2117
- await persistPrecompileChannel(store, openPayload)
2118
- const route = createRoute(rawStore)
2119
- const voucher = await ClientOps.createVoucherPayload(
2120
- createSigningClient(),
2121
- payer,
2122
- openPayload.descriptor,
2123
- Types.uint96(1n),
2124
- chainId,
2125
- )
2126
- const makeRequest = (authorization?: string) =>
2127
- new Request('https://api.example.com/resource', {
2128
- method: 'POST',
2129
- body: '{}',
2130
- headers: {
2131
- 'content-length': '2',
2132
- 'content-type': 'application/json',
2133
- ...(authorization ? { Authorization: authorization } : {}),
2134
- },
2135
- })
2136
-
2137
- const first = await route(makeRequest())
2138
- expect(first.status).toBe(402)
2139
- if (first.status !== 402) throw new Error('expected challenge')
2140
-
2141
- const result = await route(
2142
- makeRequest(
2143
- Credential.serialize({
2144
- challenge: Challenge.fromResponse(first.challenge),
2145
- payload: voucher,
2146
- source: sourceFor(),
2147
- }),
2148
- ),
2149
- )
2150
- expect(result.status).toBe(200)
2151
- if (result.status !== 200) throw new Error('expected paid response')
2152
- const paid = result.withReceipt(new Response('paid-content'))
2153
- const receipt = deserializeSessionReceipt(paid.headers.get('Payment-Receipt') as string)
2154
- expect(receipt.spent).toBe('1')
2155
- expect(receipt.units).toBe(1)
2156
-
2157
- const replayChallenge = await route(makeRequest())
2158
- expect(replayChallenge.status).toBe(402)
2159
- if (replayChallenge.status !== 402) throw new Error('expected challenge')
2160
- const replay = await route(
2161
- makeRequest(
2162
- Credential.serialize({
2163
- challenge: Challenge.fromResponse(replayChallenge.challenge),
2164
- payload: voucher,
2165
- source: sourceFor(),
2166
- }),
2167
- ),
2168
- )
2169
- expect(replay.status).toBe(402)
2170
- if (replay.status !== 402) throw new Error('expected challenge')
2171
- expect(replay.challenge.headers.get('Payment-Receipt')).toBeNull()
2172
- })
2173
-
2174
- test('verification errors do not include Payment-Receipt', async () => {
2175
- const rawStore = Store.memory()
2176
- const store = channelStore(rawStore)
2177
- const openPayload = await createOpenPayload({ initialAmount: 100n })
2178
- await persistPrecompileChannel(store, openPayload)
2179
- const route = createRoute(rawStore)
2180
- const first = await route(new Request('https://api.example.com/resource'))
2181
- expect(first.status).toBe(402)
2182
- if (first.status !== 402) throw new Error('expected challenge')
2183
-
2184
- const failed = await route(
2185
- new Request('https://api.example.com/resource', {
2186
- headers: {
2187
- Authorization: Credential.serialize({
2188
- challenge: Challenge.fromResponse(first.challenge),
2189
- payload: {
2190
- action: 'voucher',
2191
- channelId: openPayload.channelId,
2192
- cumulativeAmount: '100',
2193
- descriptor: openPayload.descriptor,
2194
- signature: (await createOpenPayload({ account: wrongPayer })).signature,
2195
- },
2196
- source: sourceFor(),
2197
- }),
2198
- },
2199
- }),
2200
- )
2201
-
2202
- expect(failed.status).toBe(402)
2203
- if (failed.status !== 402) throw new Error('expected challenge')
2204
- expect(failed.challenge.headers.get('Payment-Receipt')).toBeNull()
2205
- })
2206
- })
2207
-
2208
- describe('same-route bootstrap', () => {
2209
- function createBootstrapRoute(parameters: {
2210
- rawStore: Store.AtomicStore
2211
- recipient?: Address | undefined
2212
- resolveChannelId?: ResolveSessionChannelId | undefined
2213
- }) {
2214
- return Mppx_server.create({
2215
- methods: [
2216
- tempo_server.session({
2217
- amount: '1',
2218
- bootstrap: true,
2219
- chainId,
2220
- currency: token,
2221
- decimals: 0,
2222
- getClient: () => createStateClient(payer),
2223
- recipient: parameters.recipient ?? payee,
2224
- resolveChannelId: parameters.resolveChannelId,
2225
- store: parameters.rawStore,
2226
- unitType: 'request',
2227
- }),
2228
- ],
2229
- realm: 'api.example.com',
2230
- secretKey: 'test-secret-key-test-secret-key-32',
2231
- }).session({ amount: '1', decimals: 0, unitType: 'request' })
2232
- }
2233
-
2234
- async function createBootstrapCredential(response: Response) {
2235
- const challenge = Challenge.fromResponse(response)
2236
- const method = clientCharge({
2237
- account: payer,
2238
- getClient: () => createSigningClient(),
2239
- })
2240
- return method.createCredential({ challenge: challenge as never, context: {} })
2241
- }
2242
-
2243
- async function bootstrapResponse(
2244
- route: ReturnType<typeof createBootstrapRoute>,
2245
- authorization?: string,
2246
- ) {
2247
- const result = await route(
2248
- new Request('https://api.example.com/resource', {
2249
- method: 'HEAD',
2250
- ...(authorization ? { headers: { Authorization: authorization } } : {}),
2251
- }),
2252
- )
2253
- if (result.status === 402) return result.challenge
2254
- return result.withReceipt(new Response(null, { status: 500 }))
2255
- }
2256
-
2257
- test('HEAD without auth emits a zero-amount tempo charge challenge', async () => {
2258
- const route = createBootstrapRoute({ rawStore: Store.memory() })
2259
-
2260
- const response = await bootstrapResponse(route)
2261
- const challenge = Challenge.fromResponse(response)
2262
-
2263
- expect(response.status).toBe(402)
2264
- expect(challenge.method).toBe('tempo')
2265
- expect(challenge.intent).toBe('charge')
2266
- expect(challenge.request.amount).toBe('0')
2267
- })
2268
-
2269
- test('valid proof resolves channel by source and returns snapshot headers', async () => {
2270
- const rawStore = Store.memory()
2271
- const store = channelStore(rawStore)
2272
- const openPayload = await createOpenPayload({ initialAmount: 1n })
2273
- const highestVoucher = await ClientOps.createVoucherPayload(
2274
- createSigningClient(),
2275
- payer,
2276
- openPayload.descriptor,
2277
- Types.uint96(5n),
2278
- chainId,
2279
- )
2280
- if (highestVoucher.action !== 'voucher') throw new Error('expected voucher payload')
2281
- await persistPrecompileChannel(store, openPayload, {
2282
- highestVoucherAmount: 5n,
2283
- highestVoucher: {
2284
- channelId: openPayload.channelId,
2285
- cumulativeAmount: 5n,
2286
- signature: highestVoucher.signature,
2287
- },
2288
- spent: 3n,
2289
- units: 3,
2290
- })
2291
- const resolveChannelId = vi.fn(({ source }) => {
2292
- expect(source).toContain(payer.address)
2293
- return openPayload.channelId
2294
- })
2295
- const route = createBootstrapRoute({ rawStore, resolveChannelId })
2296
- const challengeResponse = await bootstrapResponse(route)
2297
- const authorization = await createBootstrapCredential(challengeResponse)
2298
-
2299
- const response = await bootstrapResponse(route, authorization)
2300
-
2301
- expect(response.status).toBe(204)
2302
- expect(resolveChannelId).toHaveBeenCalledOnce()
2303
- expect(response.headers.get(Constants.Headers.paymentSession)).toBe(openPayload.channelId)
2304
- const snapshot = session.deserializeSnapshot(
2305
- response.headers.get(Constants.Headers.paymentSessionSnapshot)!,
2306
- )
2307
- expect(snapshot).toMatchObject({
2308
- channelId: openPayload.channelId,
2309
- acceptedCumulative: '5',
2310
- highestVoucher: {
2311
- channelId: openPayload.channelId,
2312
- cumulativeAmount: '5',
2313
- },
2314
- requiredCumulative: '3',
2315
- spent: '3',
2316
- })
2317
- })
2318
-
2319
- test('valid proof with no resolved channel returns empty 204', async () => {
2320
- const rawStore = Store.memory()
2321
- const route = createBootstrapRoute({
2322
- rawStore,
2323
- resolveChannelId: () => undefined,
2324
- })
2325
- const authorization = await createBootstrapCredential(await bootstrapResponse(route))
2326
-
2327
- const response = await bootstrapResponse(route, authorization)
2328
-
2329
- expect(response.status).toBe(204)
2330
- expect(response.headers.get(Constants.Headers.paymentSession)).toBeNull()
2331
- expect(response.headers.get(Constants.Headers.paymentSessionSnapshot)).toBeNull()
2332
- })
2333
-
2334
- test('valid proof does not return snapshots for channels with different payment fields', async () => {
2335
- const rawStore = Store.memory()
2336
- const store = channelStore(rawStore)
2337
- const openPayload = await createOpenPayload({ initialAmount: 1n })
2338
- await persistPrecompileChannel(store, openPayload)
2339
- const route = createBootstrapRoute({
2340
- rawStore,
2341
- recipient: '0x0000000000000000000000000000000000000004',
2342
- resolveChannelId: () => openPayload.channelId,
2343
- })
2344
- const authorization = await createBootstrapCredential(await bootstrapResponse(route))
2345
-
2346
- const response = await bootstrapResponse(route, authorization)
2347
-
2348
- expect(response.status).toBe(204)
2349
- expect(response.headers.get(Constants.Headers.paymentSession)).toBeNull()
2350
- expect(response.headers.get(Constants.Headers.paymentSessionSnapshot)).toBeNull()
2351
- })
2352
-
2353
- test('replayed proof is rejected before channel resolution', async () => {
2354
- const rawStore = Store.memory()
2355
- const resolveChannelId = vi.fn(() => undefined)
2356
- const route = createBootstrapRoute({ rawStore, resolveChannelId })
2357
- const authorization = await createBootstrapCredential(await bootstrapResponse(route))
2358
-
2359
- expect((await bootstrapResponse(route, authorization)).status).toBe(204)
2360
- const replay = await bootstrapResponse(route, authorization)
2361
-
2362
- expect(replay.status).toBe(402)
2363
- expect(resolveChannelId).toHaveBeenCalledOnce()
2364
- })
2365
- })
2366
-
2367
- describe('SSE parity', () => {
2368
- function createManagedSseFetch(
2369
- options: {
2370
- amount?: string
2371
- chunkDelayMs?: number
2372
- maxDeposit?: bigint
2373
- onSessionSettlement?: session.Parameters['onSessionSettlement']
2374
- settlementSchedule?: session.Parameters['settlementSchedule']
2375
- unitType?: 'request' | 'token'
2376
- } = {},
2377
- ) {
2378
- const rawStore = Store.memory()
2379
- let activeChannelId: Hex | undefined
2380
- let currentPayload: SessionCredentialPayload | undefined
2381
- let voucherPosts = 0
2382
- const amount = options.amount ?? '1'
2383
- const maxDeposit = options.maxDeposit ?? 3n
2384
- let deposit = maxDeposit
2385
- let settled = 0n
2386
- const unitType = options.unitType ?? 'token'
2387
- const route = Mppx_server.create({
2388
- methods: [
2389
- tempo_server.session({
2390
- amount,
2391
- chainId,
2392
- currency: token,
2393
- decimals: 0,
2394
- recipient: payer.address,
2395
- onSessionSettlement: options.onSessionSettlement,
2396
- settlementSchedule: options.settlementSchedule,
2397
- sse: true,
2398
- store: rawStore,
2399
- unitType,
2400
- getClient: () => {
2401
- const payload = currentPayload
2402
- if (payload?.action === 'open') {
2403
- return createServerClient([], payer, payload.channelId, {
2404
- descriptor: payload.descriptor,
2405
- receipt: transactionReceipt([openedLog(payload, maxDeposit)]),
2406
- state: { settled: 0n, deposit: maxDeposit, closeRequestedAt: 0 },
2407
- })
2408
- }
2409
- if (payload?.action === 'close') {
2410
- return createServerClient([], payer, payload.channelId, {
2411
- receipt: transactionReceipt([
2412
- closedLog(payload.channelId, BigInt(payload.cumulativeAmount), 0n),
2413
- ]),
2414
- state: { settled, deposit, closeRequestedAt: 0 },
2415
- })
2416
- }
2417
- if (payload?.action === 'topUp') {
2418
- deposit += BigInt(payload.additionalDeposit)
2419
- return createServerClient([], payer, payload.channelId, {
2420
- receipt: transactionReceipt([topUpLog(payload, deposit)]),
2421
- state: { settled, deposit, closeRequestedAt: 0 },
2422
- })
2423
- }
2424
- return createServerClient([], payer, undefined, {
2425
- sentReceipt: async () => {
2426
- if (!activeChannelId) throw new Error('missing channel context')
2427
- const channel = await channelStore(rawStore).getChannel(activeChannelId)
2428
- settled = channel?.highestVoucherAmount ?? settled
2429
- return transactionReceipt([settledLog(activeChannelId, settled)])
2430
- },
2431
- state: () => ({ settled, deposit, closeRequestedAt: 0 }),
2432
- })
2433
- },
2434
- }),
2435
- ],
2436
- realm: 'api.example.com',
2437
- secretKey: 'test-secret-key-test-secret-key-32',
2438
- }).session({ amount, decimals: 0, suggestedDeposit: maxDeposit.toString(), unitType })
2439
-
2440
- const handle = async (request: Request) => {
2441
- currentPayload = undefined
2442
- if (request.headers.has('Authorization')) {
2443
- try {
2444
- currentPayload = Credential.fromRequest<SessionCredentialPayload>(request).payload
2445
- activeChannelId = currentPayload.channelId
2446
- if (currentPayload.action === 'voucher') voucherPosts++
2447
- } catch {}
2448
- }
2449
-
2450
- const result = await route(request)
2451
- if (result.status === 402) return result.challenge
2452
- if (request.method === 'POST') return result.withReceipt()
2453
-
2454
- if (request.headers.get('Accept')?.includes('text/event-stream')) {
2455
- if (unitType === 'request') {
2456
- const encoder = new TextEncoder()
2457
- return result.withReceipt(
2458
- new Response(
2459
- new ReadableStream({
2460
- start(controller) {
2461
- controller.enqueue(encoder.encode('event: message\ndata: chunk-1\n\n'))
2462
- controller.enqueue(encoder.encode('event: message\ndata: chunk-2\n\n'))
2463
- controller.enqueue(encoder.encode('event: message\ndata: chunk-3\n\n'))
2464
- controller.close()
2465
- },
2466
- }),
2467
- { headers: { 'Content-Type': 'text/event-stream; charset=utf-8' } },
2468
- ),
2469
- )
2470
- }
2471
-
2472
- currentPayload = undefined
2473
- return result.withReceipt(async function* (stream) {
2474
- await stream.charge()
2475
- yield 'chunk-1'
2476
- if (options.chunkDelayMs)
2477
- await new Promise((resolve) => setTimeout(resolve, options.chunkDelayMs))
2478
- await stream.charge()
2479
- yield 'chunk-2'
2480
- await stream.charge()
2481
- yield 'chunk-3'
2482
- })
2483
- }
2484
-
2485
- return result.withReceipt(new Response('ok'))
2486
- }
2487
-
2488
- const fetch = async (input: RequestInfo | URL, init?: RequestInit) =>
2489
- handle(new Request(input, init))
2490
-
2491
- return {
2492
- fetch,
2493
- handle,
2494
- rawStore,
2495
- get voucherPosts() {
2496
- return voucherPosts
2497
- },
2498
- }
2499
- }
2500
-
2501
- test('open -> stream -> need-voucher -> resume -> close', async () => {
2502
- const harness = createManagedSseFetch({ maxDeposit: 3n })
2503
- const manager = precompileSessionManager({
2504
- account: payer,
2505
- client: createSigningClient(),
2506
- decimals: 0,
2507
- fetch: harness.fetch,
2508
- maxDeposit: '3',
2509
- })
2510
-
2511
- const chunks: string[] = []
2512
- const stream = await manager.sse('https://api.example.com/stream')
2513
- for await (const chunk of stream) chunks.push(chunk)
2514
-
2515
- expect(chunks).toEqual(['chunk-1', 'chunk-2', 'chunk-3'])
2516
- expect(harness.voucherPosts).toBeGreaterThan(0)
2517
-
2518
- const closeReceipt = await manager.close()
2519
- expect(closeReceipt?.status).toBe('success')
2520
- expect(closeReceipt?.spent).toBe('3')
2521
-
2522
- const channelId = manager.channelId
2523
- expect(channelId).toBeTruthy()
2524
- const persisted = await channelStore(harness.rawStore).getChannel(channelId!)
2525
- expect(persisted?.finalized).toBe(true)
2526
- })
2527
-
2528
- test.each([
2529
- ['unit', { units: 2 }, 0],
2530
- ['amount', { amount: '2' }, 0],
2531
- ['interval', { intervalMs: 500 }, 750],
2532
- ] as const)(
2533
- 'applies the %s settlement schedule after SSE charges',
2534
- async (_label, settlementSchedule, chunkDelayMs) => {
2535
- const settlements: Array<{ delta: bigint; trigger: string }> = []
2536
- const harness = createManagedSseFetch({
2537
- chunkDelayMs,
2538
- maxDeposit: 3n,
2539
- settlementSchedule,
2540
- onSessionSettlement(context) {
2541
- settlements.push({ delta: context.delta, trigger: context.trigger })
2542
- },
2543
- })
2544
- const manager = precompileSessionManager({
2545
- account: payer,
2546
- client: createSigningClient(),
2547
- decimals: 0,
2548
- fetch: harness.fetch,
2549
- maxDeposit: '3',
2550
- })
2551
-
2552
- const chunks: string[] = []
2553
- for await (const chunk of await manager.sse('https://api.example.com/stream')) {
2554
- chunks.push(chunk)
2555
- }
2556
-
2557
- expect(chunks).toEqual(['chunk-1', 'chunk-2', 'chunk-3'])
2558
- expect(harness.voucherPosts).toBeGreaterThan(0)
2559
- expect(settlements).toEqual([{ delta: 2n, trigger: 'scheduled' }])
2560
- const persisted = await channelStore(harness.rawStore).getChannel(manager.channelId!)
2561
- expect(persisted?.lastSettlementUnits).toBe(2)
2562
-
2563
- await manager.close()
2564
- expect(settlements).toEqual([
2565
- { delta: 2n, trigger: 'scheduled' },
2566
- { delta: 1n, trigger: 'close' },
2567
- ])
2568
- },
2569
- )
2570
-
2571
- test('handles empty management POST streams through a Node adapter', async () => {
2572
- const harness = createManagedSseFetch({ maxDeposit: 1n })
2573
- const managementPosts: Array<{
2574
- action: SessionCredentialPayload['action']
2575
- contentLength: string | null
2576
- hasBody: boolean
2577
- hasReceipt: boolean
2578
- status: number
2579
- }> = []
2580
- const app = new Hono()
2581
- app.all('*', async (c) => {
2582
- const request = c.req.raw
2583
- const payload = request.headers.has('Authorization')
2584
- ? Credential.fromRequest<SessionCredentialPayload>(request).payload
2585
- : undefined
2586
- const response = await harness.handle(request)
2587
- if (request.method === 'POST' && payload)
2588
- managementPosts.push({
2589
- action: payload.action,
2590
- contentLength: request.headers.get('content-length'),
2591
- hasBody: request.body !== null,
2592
- hasReceipt: response.headers.has(Constants.Headers.paymentReceipt),
2593
- status: response.status,
2594
- })
2595
- return response
2596
- })
2597
- const server = serve({ fetch: app.fetch, port: 0 })
2598
- await new Promise<void>((resolve) => server.once('listening', resolve))
2599
- const address = server.address()
2600
- if (!address || typeof address === 'string') throw new Error('expected TCP address')
2601
-
2602
- try {
2603
- const fetch: typeof globalThis.fetch = async (input, init) => {
2604
- const response = await globalThis.fetch(input, init)
2605
- return new Response(response.body, response)
2606
- }
2607
- const manager = precompileSessionManager({
2608
- account: payer,
2609
- client: createSigningClient(),
2610
- decimals: 0,
2611
- fetch,
2612
- maxDeposit: '3',
2613
- })
2614
-
2615
- const chunks: string[] = []
2616
- const stream = await manager.sse(`http://localhost:${address.port}/stream`)
2617
- for await (const chunk of stream) chunks.push(chunk)
2618
-
2619
- expect(chunks).toEqual(['chunk-1', 'chunk-2', 'chunk-3'])
2620
- const closeReceipt = await manager.close()
2621
- expect(closeReceipt?.spent).toBe('3')
2622
- expect(managementPosts).toEqual(
2623
- expect.arrayContaining([
2624
- {
2625
- action: 'topUp',
2626
- contentLength: '0',
2627
- hasBody: true,
2628
- hasReceipt: true,
2629
- status: 204,
2630
- },
2631
- {
2632
- action: 'voucher',
2633
- contentLength: '0',
2634
- hasBody: true,
2635
- hasReceipt: true,
2636
- status: 204,
2637
- },
2638
- {
2639
- action: 'close',
2640
- contentLength: '0',
2641
- hasBody: true,
2642
- hasReceipt: true,
2643
- status: 204,
2644
- },
2645
- ]),
2646
- )
2647
- expect(managementPosts.every(({ status }) => status === 204)).toBe(true)
2648
-
2649
- const persisted = await channelStore(harness.rawStore).getChannel(manager.channelId!)
2650
- expect(persisted?.finalized).toBe(true)
2651
- } finally {
2652
- server.close()
2653
- }
2654
- })
2655
-
2656
- test('unitType=request auto-metered SSE responses charge once across the stream', async () => {
2657
- const harness = createManagedSseFetch({ maxDeposit: 1n, unitType: 'request' })
2658
- const manager = precompileSessionManager({
2659
- account: payer,
2660
- client: createSigningClient(),
2661
- decimals: 0,
2662
- fetch: harness.fetch,
2663
- maxDeposit: '1',
2664
- })
2665
-
2666
- const chunks: string[] = []
2667
- const stream = await manager.sse('https://api.example.com/stream')
2668
- for await (const chunk of stream) chunks.push(chunk)
2669
-
2670
- expect(chunks).toEqual(['chunk-1', 'chunk-2', 'chunk-3'])
2671
- expect(harness.voucherPosts).toBe(0)
2672
-
2673
- const closeReceipt = await manager.close()
2674
- expect(closeReceipt?.status).toBe('success')
2675
- expect(closeReceipt?.spent).toBe('1')
2676
- })
2677
- })
2678
-
2679
- describe('WebSocket parity', () => {
2680
- async function createManagedWsHarness(
2681
- options: {
2682
- challengeTtlMs?: number
2683
- maxDeposit?: bigint
2684
- onSessionSettlement?: session.Parameters['onSessionSettlement']
2685
- settlementSchedule?: session.Parameters['settlementSchedule']
2686
- } = {},
2687
- ) {
2688
- const rawStore = Store.memory()
2689
- let activeChannelId: Hex | undefined
2690
- let challengeRequests = 0
2691
- let currentPayload: SessionCredentialPayload | undefined
2692
- let settled = 0n
2693
- let voucherPosts = 0
2694
- const maxDeposit = options.maxDeposit ?? 3n
2695
- const payment = Mppx_server.create({
2696
- methods: [
2697
- tempo_server.session({
2698
- amount: '1',
2699
- chainId,
2700
- currency: token,
2701
- decimals: 0,
2702
- onSessionSettlement: options.onSessionSettlement,
2703
- recipient: payer.address,
2704
- settlementSchedule: options.settlementSchedule,
2705
- store: rawStore,
2706
- unitType: 'token',
2707
- getClient: () => {
2708
- const payload = currentPayload
2709
- if (payload?.action === 'open') {
2710
- return createServerClient([], payer, payload.channelId, {
2711
- descriptor: payload.descriptor,
2712
- receipt: transactionReceipt([openedLog(payload, maxDeposit)]),
2713
- state: { settled: 0n, deposit: maxDeposit, closeRequestedAt: 0 },
2714
- })
2715
- }
2716
- if (payload?.action === 'close') {
2717
- return createServerClient([], payer, payload.channelId, {
2718
- receipt: transactionReceipt([
2719
- closedLog(payload.channelId, BigInt(payload.cumulativeAmount), 0n),
2720
- ]),
2721
- state: { settled, deposit: maxDeposit, closeRequestedAt: 0 },
2722
- })
2723
- }
2724
- return createServerClient([], payer, undefined, {
2725
- sentReceipt: async () => {
2726
- if (!activeChannelId) throw new Error('missing channel context')
2727
- const channel = await channelStore(rawStore).getChannel(activeChannelId)
2728
- settled = channel?.highestVoucherAmount ?? settled
2729
- return transactionReceipt([settledLog(activeChannelId, settled)])
2730
- },
2731
- state: () => ({ settled, deposit: maxDeposit, closeRequestedAt: 0 }),
2732
- })
2733
- },
2734
- }),
2735
- ],
2736
- realm: 'api.example.com',
2737
- secretKey: 'test-secret-key-test-secret-key-32',
2738
- })
2739
-
2740
- const routeOptions = () => ({
2741
- amount: '1',
2742
- decimals: 0,
2743
- suggestedDeposit: maxDeposit.toString(),
2744
- ...(options.challengeTtlMs === undefined
2745
- ? {}
2746
- : { expires: new Date(Date.now() + options.challengeTtlMs) }),
2747
- })
2748
- const route = async (request: Request) => {
2749
- currentPayload = undefined
2750
- if (request.headers.has('Authorization')) {
2751
- try {
2752
- currentPayload = Credential.fromRequest<SessionCredentialPayload>(request).payload
2753
- activeChannelId = currentPayload.channelId
2754
- if (currentPayload.action === 'voucher') voucherPosts++
2755
- } catch {}
2756
- } else challengeRequests++
2757
- const result = await payment.session(routeOptions())(request)
2758
- currentPayload = undefined
2759
- return result
2760
- }
2761
-
2762
- const httpHandler = NodeRequest.toNodeListener(async (request) => {
2763
- const result = await route(request)
2764
- if (result.status === 402) return result.challenge
2765
- return result.withReceipt(new Response('ok'))
2766
- })
2767
-
2768
- const nodeServer = node_http.createServer(httpHandler)
2769
- const wsServer = new WebSocketServer({ noServer: true })
2770
-
2771
- await new Promise<void>((resolve) => nodeServer.listen(0, resolve))
2772
- const { port } = nodeServer.address() as { port: number }
2773
- const server = Http.wrapServer(nodeServer, {
2774
- port,
2775
- url: `http://localhost:${port}`,
2776
- })
2777
-
2778
- nodeServer.on('upgrade', (req, socket, head) => {
2779
- if (req.url !== '/ws') {
2780
- socket.destroy()
2781
- return
2782
- }
2783
- wsServer.handleUpgrade(req, socket, head, (websocket) => {
2784
- wsServer.emit('connection', websocket, req)
2785
- })
2786
- })
2787
-
2788
- return {
2789
- rawStore,
2790
- route,
2791
- serveWebSocket: payment.session.serveWebSocket,
2792
- server,
2793
- wsServer,
2794
- get port() {
2795
- return port
2796
- },
2797
- get challengeRequests() {
2798
- return challengeRequests
2799
- },
2800
- get voucherPosts() {
2801
- return voucherPosts
2802
- },
2803
- close() {
2804
- wsServer.close()
2805
- server.close()
2806
- },
2807
- }
2808
- }
2809
-
2810
- test('refreshes an expired challenge before sending a later websocket voucher', async () => {
2811
- const harness = await createManagedWsHarness({ challengeTtlMs: 2_000, maxDeposit: 2n })
2812
- harness.wsServer.on('connection', (socket: import('ws').WebSocket) => {
2813
- void harness.serveWebSocket({
2814
- socket,
2815
- url: `${harness.server.url}/ws`,
2816
- route: harness.route,
2817
- generate: async function* (stream: TempoWs.SessionController) {
2818
- await stream.charge()
2819
- yield 'chunk-1'
2820
- await new Promise((resolve) => setTimeout(resolve, 2_200))
2821
- await stream.charge()
2822
- yield 'chunk-2'
2823
- },
2824
- })
2825
- })
2826
-
2827
- try {
2828
- const manager = precompileSessionManager({
2829
- account: payer,
2830
- client: createSigningClient(),
2831
- decimals: 0,
2832
- fetch: globalThis.fetch,
2833
- maxDeposit: '2',
2834
- webSocket: WebSocket as never,
2835
- })
2836
- const ws = await manager.ws(`ws://localhost:${harness.port}/ws`)
2837
- const chunks: string[] = []
2838
-
2839
- await new Promise<void>((resolve, reject) => {
2840
- ws.addEventListener('message', (event) => {
2841
- if (typeof event.data === 'string') chunks.push(event.data)
2842
- })
2843
- ws.addEventListener('close', () => resolve(), { once: true })
2844
- ws.addEventListener('error', () => reject(new Error('websocket stream failed')), {
2845
- once: true,
2846
- })
2847
- })
2848
-
2849
- expect(chunks).toEqual(['chunk-1', 'chunk-2'])
2850
- expect(harness.challengeRequests).toBeGreaterThan(1)
2851
- expect(harness.voucherPosts).toBe(1)
2852
- } finally {
2853
- harness.close()
2854
- }
2855
- })
2856
-
2857
- test('open -> stream -> need-voucher -> resume -> close', async () => {
2858
- const harness = await createManagedWsHarness({ maxDeposit: 3n })
2859
- harness.wsServer.on('connection', (socket: import('ws').WebSocket) => {
2860
- void harness.serveWebSocket({
2861
- socket,
2862
- url: `${harness.server.url}/ws`,
2863
- route: harness.route,
2864
- generate: async function* (stream: TempoWs.SessionController) {
2865
- await stream.charge()
2866
- yield 'chunk-1'
2867
- await stream.charge()
2868
- yield 'chunk-2'
2869
- await stream.charge()
2870
- yield 'chunk-3'
2871
- },
2872
- })
2873
- })
2874
-
2875
- try {
2876
- const manager = precompileSessionManager({
2877
- account: payer,
2878
- client: createSigningClient(),
2879
- decimals: 0,
2880
- fetch: globalThis.fetch,
2881
- maxDeposit: '3',
2882
- webSocket: WebSocket as never,
2883
- })
2884
-
2885
- const ws = await manager.ws(`ws://localhost:${harness.port}/ws`)
2886
- const chunks: string[] = []
2887
-
2888
- await new Promise<void>((resolve, reject) => {
2889
- ws.addEventListener('message', (event) => {
2890
- if (typeof event.data !== 'string') return
2891
- chunks.push(event.data)
2892
- })
2893
- ws.addEventListener('close', () => resolve(), { once: true })
2894
- ws.addEventListener('error', () => reject(new Error('websocket stream failed')), {
2895
- once: true,
2896
- })
2897
- })
2898
-
2899
- expect(chunks).toEqual(['chunk-1', 'chunk-2', 'chunk-3'])
2900
- expect(harness.voucherPosts).toBeGreaterThan(0)
2901
-
2902
- const closeReceipt = await manager.close()
2903
- expect(closeReceipt?.status).toBe('success')
2904
- expect(closeReceipt?.spent).toBe('3')
2905
-
2906
- const channelId = manager.channelId
2907
- expect(channelId).toBeTruthy()
2908
- const persisted = await channelStore(harness.rawStore).getChannel(channelId!)
2909
- expect(persisted?.finalized).toBe(true)
2910
- } finally {
2911
- harness.close()
2912
- }
2913
- })
2914
-
2915
- test.each([
2916
- ['unit', { units: 2 }, 0],
2917
- ['amount', { amount: '2' }, 0],
2918
- ['interval', { intervalMs: 500 }, 750],
2919
- ] as const)(
2920
- 'applies the %s settlement schedule after websocket charges',
2921
- async (_label, settlementSchedule, chunkDelayMs) => {
2922
- const settlements: Array<{ delta: bigint; trigger: string }> = []
2923
- const harness = await createManagedWsHarness({
2924
- maxDeposit: 3n,
2925
- settlementSchedule,
2926
- onSessionSettlement(context) {
2927
- settlements.push({ delta: context.delta, trigger: context.trigger })
2928
- },
2929
- })
2930
- harness.wsServer.on('connection', (socket: import('ws').WebSocket) => {
2931
- void harness.serveWebSocket({
2932
- socket,
2933
- url: `${harness.server.url}/ws`,
2934
- route: harness.route,
2935
- generate: async function* (stream: TempoWs.SessionController) {
2936
- for (const chunk of ['chunk-1', 'chunk-2', 'chunk-3']) {
2937
- if (chunk === 'chunk-2' && chunkDelayMs)
2938
- await new Promise((resolve) => setTimeout(resolve, chunkDelayMs))
2939
- await stream.charge()
2940
- yield chunk
2941
- }
2942
- },
2943
- })
2944
- })
2945
-
2946
- try {
2947
- const manager = precompileSessionManager({
2948
- account: payer,
2949
- client: createSigningClient(),
2950
- decimals: 0,
2951
- fetch: globalThis.fetch,
2952
- maxDeposit: '3',
2953
- webSocket: WebSocket as never,
2954
- })
2955
- const ws = await manager.ws(`ws://localhost:${harness.port}/ws`)
2956
- const chunks: string[] = []
2957
-
2958
- await new Promise<void>((resolve, reject) => {
2959
- ws.addEventListener('message', (event) => {
2960
- if (typeof event.data === 'string') chunks.push(event.data)
2961
- })
2962
- ws.addEventListener('close', () => resolve(), { once: true })
2963
- ws.addEventListener('error', () => reject(new Error('websocket stream failed')), {
2964
- once: true,
2965
- })
2966
- })
2967
-
2968
- expect(chunks).toEqual(['chunk-1', 'chunk-2', 'chunk-3'])
2969
- expect(harness.voucherPosts).toBeGreaterThan(0)
2970
- expect(settlements).toEqual([{ delta: 2n, trigger: 'scheduled' }])
2971
- await manager.close()
2972
- expect(settlements).toEqual([
2973
- { delta: 2n, trigger: 'scheduled' },
2974
- { delta: 1n, trigger: 'close' },
2975
- ])
2976
- const persisted = await channelStore(harness.rawStore).getChannel(manager.channelId!)
2977
- expect(persisted?.lastSettlementUnits).toBe(2)
2978
- } finally {
2979
- harness.close()
2980
- }
2981
- },
2982
- )
2983
-
2984
- test('treats control-shaped application payloads as content', async () => {
2985
- const harness = await createManagedWsHarness({ maxDeposit: 1n })
2986
- const controlLookingChunk = JSON.stringify({
2987
- mpp: 'payment-need-voucher',
2988
- data: {
2989
- channelId: `0x${'aa'.repeat(32)}`,
2990
- requiredCumulative: '9',
2991
- acceptedCumulative: '0',
2992
- deposit: '9',
2993
- },
2994
- })
2995
- harness.wsServer.on('connection', (socket: import('ws').WebSocket) => {
2996
- void harness.serveWebSocket({
2997
- socket,
2998
- url: `${harness.server.url}/ws`,
2999
- route: harness.route,
3000
- generate: async function* (stream: TempoWs.SessionController) {
3001
- await stream.charge()
3002
- yield controlLookingChunk
3003
- },
3004
- })
3005
- })
3006
-
3007
- try {
3008
- const manager = precompileSessionManager({
3009
- account: payer,
3010
- client: createSigningClient(),
3011
- decimals: 0,
3012
- fetch: globalThis.fetch,
3013
- maxDeposit: '1',
3014
- webSocket: WebSocket as never,
3015
- })
3016
-
3017
- const ws = await manager.ws(`ws://localhost:${harness.port}/ws`)
3018
- const chunks: string[] = []
3019
-
3020
- await new Promise<void>((resolve, reject) => {
3021
- ws.addEventListener('message', (event) => {
3022
- if (typeof event.data !== 'string') return
3023
- chunks.push(event.data)
3024
- })
3025
- ws.addEventListener('close', () => resolve(), { once: true })
3026
- ws.addEventListener('error', () => reject(new Error('websocket stream failed')), {
3027
- once: true,
3028
- })
3029
- })
3030
-
3031
- expect(chunks).toEqual([controlLookingChunk])
3032
- expect(harness.voucherPosts).toBe(0)
3033
-
3034
- const closeReceipt = await manager.close()
3035
- expect(closeReceipt?.status).toBe('success')
3036
- expect(closeReceipt?.spent).toBe('1')
3037
- } finally {
3038
- harness.close()
3039
- }
3040
- })
3041
-
3042
- test('refuses websocket voucher requests beyond local maxDeposit', async () => {
3043
- const harness = await createManagedWsHarness({ maxDeposit: 1n })
3044
- harness.wsServer.on('connection', (socket: import('ws').WebSocket) => {
3045
- void harness.serveWebSocket({
3046
- socket,
3047
- url: `${harness.server.url}/ws`,
3048
- route: harness.route,
3049
- generate: async function* (stream: TempoWs.SessionController) {
3050
- await stream.charge()
3051
- yield 'chunk-1'
3052
- await stream.charge()
3053
- yield 'chunk-2'
3054
- },
3055
- })
3056
- })
3057
-
3058
- try {
3059
- const manager = precompileSessionManager({
3060
- account: payer,
3061
- client: createSigningClient(),
3062
- decimals: 0,
3063
- fetch: globalThis.fetch,
3064
- maxDeposit: '1',
3065
- webSocket: WebSocket as never,
3066
- })
3067
-
3068
- const ws = await manager.ws(`ws://localhost:${harness.port}/ws`)
3069
- const closeEvent = await new Promise<{ code: number; reason: string }>(
3070
- (resolve, reject) => {
3071
- ws.addEventListener(
3072
- 'close',
3073
- (event) => resolve({ code: event.code, reason: event.reason }),
3074
- { once: true },
3075
- )
3076
- ws.addEventListener('error', () => reject(new Error('websocket stream failed')), {
3077
- once: true,
3078
- })
3079
- },
3080
- )
3081
-
3082
- expect(closeEvent.code).toBe(3008)
3083
- expect(closeEvent.reason).toBe('requested voucher amount 2 exceeds local maxDeposit 1')
3084
- } finally {
3085
- harness.close()
3086
- }
3087
- })
3088
-
3089
- test('rejects websocket receipts bound to a different channel', async () => {
3090
- const harness = await createManagedWsHarness({ maxDeposit: 1n })
3091
- const wrongChannelId = `0x${'11'.repeat(32)}` as Hex
3092
- harness.wsServer.on('connection', (socket: import('ws').WebSocket) => {
3093
- socket.once('message', (data) => {
3094
- const message = TempoWs.parseMessage(data.toString())
3095
- if (!message || message.mpp !== 'authorization') return
3096
-
3097
- const credential = Credential.deserialize<SessionCredentialPayload>(message.authorization)
3098
- socket.send(
3099
- TempoWs.formatReceiptMessage({
3100
- method: 'tempo',
3101
- intent: 'session',
3102
- status: 'success',
3103
- timestamp: new Date().toISOString(),
3104
- reference: wrongChannelId,
3105
- challengeId: credential.challenge.id,
3106
- channelId: wrongChannelId,
3107
- acceptedCumulative: '1',
3108
- spent: '0',
3109
- units: 0,
3110
- }),
3111
- )
3112
- })
3113
- })
3114
-
3115
- try {
3116
- const manager = precompileSessionManager({
3117
- account: payer,
3118
- client: createSigningClient(),
3119
- decimals: 0,
3120
- fetch: globalThis.fetch,
3121
- maxDeposit: '1',
3122
- webSocket: WebSocket as never,
3123
- })
3124
-
3125
- await expect(manager.ws(`ws://localhost:${harness.port}/ws`)).rejects.toThrow(
3126
- 'received mismatched payment-receipt frame',
3127
- )
3128
- } finally {
3129
- harness.close()
3130
- }
3131
- })
3132
-
3133
- test('rejects close-ready receipts beyond local voucher state', async () => {
3134
- const harness = await createManagedWsHarness({ maxDeposit: 1n })
3135
- harness.wsServer.on('connection', (socket: import('ws').WebSocket) => {
3136
- let openCredential: Credential.Credential<SessionCredentialPayload> | undefined
3137
-
3138
- socket.on('message', (data) => {
3139
- const message = TempoWs.parseMessage(data.toString())
3140
- if (!message) return
3141
-
3142
- if (message.mpp === 'authorization') {
3143
- openCredential = Credential.deserialize<SessionCredentialPayload>(message.authorization)
3144
- const payload = openCredential.payload
3145
- if (payload.action !== 'open') return
3146
- socket.send(
3147
- TempoWs.formatReceiptMessage({
3148
- method: 'tempo',
3149
- intent: 'session',
3150
- status: 'success',
3151
- timestamp: new Date().toISOString(),
3152
- reference: payload.channelId,
3153
- challengeId: openCredential.challenge.id,
3154
- channelId: payload.channelId,
3155
- acceptedCumulative: payload.cumulativeAmount,
3156
- spent: '0',
3157
- units: 0,
3158
- }),
3159
- )
3160
- return
3161
- }
3162
-
3163
- if (message.mpp !== 'payment-close-request' || !openCredential) return
3164
- const payload = openCredential.payload
3165
- socket.send(
3166
- TempoWs.formatCloseReadyMessage({
3167
- method: 'tempo',
3168
- intent: 'session',
3169
- status: 'success',
3170
- timestamp: new Date().toISOString(),
3171
- reference: payload.channelId,
3172
- challengeId: openCredential.challenge.id,
3173
- channelId: payload.channelId,
3174
- acceptedCumulative: '1',
3175
- spent: '9',
3176
- units: 1,
3177
- }),
3178
- )
3179
- })
3180
- })
3181
-
3182
- try {
3183
- const manager = precompileSessionManager({
3184
- account: payer,
3185
- client: createSigningClient(),
3186
- decimals: 0,
3187
- fetch: globalThis.fetch,
3188
- maxDeposit: '1',
3189
- webSocket: WebSocket as never,
3190
- })
3191
-
3192
- await manager.ws(`ws://localhost:${harness.port}/ws`)
3193
- await expect(manager.close()).rejects.toThrow(
3194
- 'received payment-close-ready beyond local voucher state',
3195
- )
3196
- } finally {
3197
- harness.close()
3198
- }
3199
- })
3200
-
3201
- test('fallback close after socket death signs for delivered amount, not full voucher', async () => {
3202
- const harness = await createManagedWsHarness({ maxDeposit: 3n })
3203
- let serverSocket: import('ws').WebSocket | null = null
3204
- harness.wsServer.on('connection', (socket: import('ws').WebSocket) => {
3205
- serverSocket = socket
3206
- void harness.serveWebSocket({
3207
- socket,
3208
- url: `${harness.server.url}/ws`,
3209
- route: harness.route,
3210
- generate: async function* (stream: TempoWs.SessionController) {
3211
- await stream.charge()
3212
- yield 'chunk-1'
3213
- await stream.charge()
3214
- yield 'chunk-2'
3215
- await new Promise((resolve) => setTimeout(resolve, 60_000))
3216
- },
3217
- })
3218
- })
3219
-
3220
- try {
3221
- const manager = precompileSessionManager({
3222
- account: payer,
3223
- client: createSigningClient(),
3224
- decimals: 0,
3225
- fetch: globalThis.fetch,
3226
- maxDeposit: '3',
3227
- webSocket: WebSocket as never,
3228
- })
3229
-
3230
- const ws = await manager.ws(`ws://localhost:${harness.port}/ws`)
3231
- const chunks: string[] = []
3232
-
3233
- await new Promise<void>((resolve) => {
3234
- ws.addEventListener('message', (event) => {
3235
- if (typeof event.data !== 'string') return
3236
- chunks.push(event.data)
3237
- if (chunks.length === 2) serverSocket?.terminate()
3238
- })
3239
- ws.addEventListener('close', () => resolve(), { once: true })
3240
- })
3241
-
3242
- expect(chunks).toEqual(['chunk-1', 'chunk-2'])
3243
-
3244
- const closeReceipt = await manager.close()
3245
- expect(closeReceipt).toBeDefined()
3246
- expect(BigInt(closeReceipt!.spent)).toBeLessThanOrEqual(2n)
3247
- expect(BigInt(closeReceipt!.spent)).toBeGreaterThan(0n)
3248
- expect(BigInt(closeReceipt!.spent)).toBeLessThan(3n)
3249
- } finally {
3250
- harness.close()
3251
- }
3252
- })
3253
-
3254
- test('rejects tx-bearing open receipts replayed during websocket close', async () => {
3255
- const harness = await createManagedWsHarness({ maxDeposit: 1n })
3256
- harness.wsServer.on('connection', (socket: import('ws').WebSocket) => {
3257
- let openCredential: Credential.Credential<SessionCredentialPayload> | undefined
3258
- let openReceipt: SessionReceipt | undefined
3259
-
3260
- socket.on('message', (data) => {
3261
- const message = TempoWs.parseMessage(data.toString())
3262
- if (!message) return
3263
-
3264
- if (message.mpp === 'authorization') {
3265
- const credential = Credential.deserialize<SessionCredentialPayload>(
3266
- message.authorization,
3267
- )
3268
- const payload = credential.payload
3269
- if (payload.action === 'close') {
3270
- if (openReceipt) socket.send(TempoWs.formatReceiptMessage(openReceipt))
3271
- return
3272
- }
3273
- if (payload.action !== 'open') return
3274
-
3275
- openCredential = credential
3276
- openReceipt = {
3277
- method: 'tempo',
3278
- intent: 'session',
3279
- status: 'success',
3280
- timestamp: new Date().toISOString(),
3281
- reference: payload.channelId,
3282
- challengeId: credential.challenge.id,
3283
- channelId: payload.channelId,
3284
- acceptedCumulative: payload.cumulativeAmount,
3285
- spent: '0',
3286
- units: 0,
3287
- txHash: `0x${'12'.repeat(32)}` as Hex,
3288
- }
3289
- socket.send(TempoWs.formatReceiptMessage(openReceipt))
3290
- return
3291
- }
3292
-
3293
- if (message.mpp !== 'payment-close-request' || !openCredential) return
3294
- const payload = openCredential.payload
3295
- socket.send(
3296
- TempoWs.formatCloseReadyMessage({
3297
- method: 'tempo',
3298
- intent: 'session',
3299
- status: 'success',
3300
- timestamp: new Date().toISOString(),
3301
- reference: payload.channelId,
3302
- challengeId: openCredential.challenge.id,
3303
- channelId: payload.channelId,
3304
- acceptedCumulative: '1',
3305
- spent: '0',
3306
- units: 0,
3307
- }),
3308
- )
3309
- })
3310
- })
3311
-
3312
- try {
3313
- const manager = precompileSessionManager({
3314
- account: payer,
3315
- client: createSigningClient(),
3316
- decimals: 0,
3317
- fetch: globalThis.fetch,
3318
- maxDeposit: '1',
3319
- webSocket: WebSocket as never,
3320
- })
3321
-
3322
- await manager.ws(`ws://localhost:${harness.port}/ws`)
3323
- await expect(manager.close()).rejects.toThrow(
3324
- 'received mismatched payment-close receipt frame',
3325
- )
3326
- } finally {
3327
- harness.close()
3328
- }
3329
- })
3330
- })
3331
-
3332
- test('does not let a racing lower voucher regress highest accepted precompile voucher', async () => {
3333
- const openPayload = await createOpenPayload({ initialAmount: 100n })
3334
- const lowerVoucher = await ClientOps.createVoucherPayload(
3335
- createSigningClient(),
3336
- payer,
3337
- openPayload.descriptor,
3338
- Types.uint96(200n),
3339
- chainId,
3340
- )
3341
- const higherVoucher = await ClientOps.createVoucherPayload(
3342
- createSigningClient(),
3343
- payer,
3344
- openPayload.descriptor,
3345
- Types.uint96(500n),
3346
- chainId,
3347
- )
3348
- if (higherVoucher.action !== 'voucher') throw new Error('expected voucher payload')
3349
-
3350
- const seedStore = channelStore(Store.memory())
3351
- await persistPrecompileChannel(seedStore, openPayload, {
3352
- highestVoucherAmount: 100n,
3353
- highestVoucher: {
3354
- channelId: openPayload.channelId,
3355
- cumulativeAmount: 100n,
3356
- signature: openPayload.signature,
3357
- },
3358
- })
3359
- const stale = (await seedStore.getChannel(openPayload.channelId))!
3360
- let stored: ChannelStore.State = {
3361
- ...stale,
3362
- highestVoucherAmount: 500n,
3363
- highestVoucher: {
3364
- channelId: openPayload.channelId,
3365
- cumulativeAmount: 500n,
3366
- signature: higherVoucher.signature,
3367
- },
3368
- }
3369
- const racingStore = {
3370
- async get(_key: string) {
3371
- return stale as never
3372
- },
3373
- async put(_key: string, value: unknown) {
3374
- stored = value as ChannelStore.State
3375
- },
3376
- async delete(_key: string) {},
3377
- async update<result>(
3378
- _key: string,
3379
- fn: (current: unknown | null) => Store.Change<unknown, result>,
3380
- ): Promise<result> {
3381
- const change = fn(stored)
3382
- if (change.op === 'set') stored = change.value as ChannelStore.State
3383
- return change.result
3384
- },
3385
- } as Store.AtomicStore
3386
- const method = session({
3387
- account: payer,
3388
- amount: '1',
3389
- chainId,
3390
- channelStateTtl: Number.MAX_SAFE_INTEGER,
3391
- currency: token,
3392
- decimals: 0,
3393
- recipient: payee,
3394
- store: racingStore,
3395
- unitType: 'request',
3396
- getClient: () => createStateClient(payer),
3397
- })
3398
-
3399
- const receipt = await method.verify({
3400
- credential: voucherCredential(lowerVoucher, openPayload.channelId),
3401
- request: verifyRequest(openPayload.channelId),
3402
- })
3403
-
3404
- expect((receipt as SessionReceipt).acceptedCumulative).toBe('500')
3405
- expect(stored.highestVoucherAmount).toBe(500n)
3406
- expect(stored.highestVoucher?.signature).toBe(higherVoucher.signature)
3407
- })
3408
-
3409
- test('marks pending precompile close before broadcast and restores it when broadcast fails', async () => {
3410
- const rawStore = Store.memory()
3411
- const store = channelStore(rawStore)
3412
- const openPayload = await createOpenPayload()
3413
- await persistPrecompileChannel(store, openPayload, {
3414
- payee: payer.address,
3415
- })
3416
- let observedPending = false
3417
- const method = session({
3418
- account: payer,
3419
- amount: '1',
3420
- chainId,
3421
- currency: token,
3422
- decimals: 0,
3423
- recipient: payee,
3424
- store: rawStore,
3425
- unitType: 'request',
3426
- getClient: () =>
3427
- createClient({
3428
- account: payer,
3429
- chain: testChain,
3430
- transport: custom({
3431
- async request(args) {
3432
- if (args.method === 'eth_chainId') return `0x${chainId.toString(16)}`
3433
- if (args.method === 'eth_call')
3434
- return encodeFunctionResult({
3435
- abi: escrowAbi,
3436
- functionName: 'getChannelState',
3437
- result: { settled: 0n, deposit: 1_000n, closeRequestedAt: 0 },
3438
- })
3439
- if (args.method === 'eth_getTransactionCount') {
3440
- observedPending =
3441
- (await store.getChannel(openPayload.channelId))!.closeRequestedAt !== 0n
3442
- throw new Error('broadcast failed')
3443
- }
3444
- if (args.method === 'eth_estimateGas') return '0x5208'
3445
- if (args.method === 'eth_maxPriorityFeePerGas') return '0x1'
3446
- if (args.method === 'eth_getBlockByNumber') return { baseFeePerGas: '0x1' }
3447
- throw new Error(`unexpected rpc request: ${args.method}`)
3448
- },
3449
- }),
3450
- }),
3451
- })
3452
- const payload = await ClientOps.createClosePayload(
3453
- createSigningClient(),
3454
- payer,
3455
- openPayload.descriptor,
3456
- Types.uint96(100n),
3457
- chainId,
3458
- )
3459
-
3460
- await expect(
3461
- method.verify({
3462
- credential: {
3463
- challenge: makeChallenge(openPayload.channelId),
3464
- payload,
3465
- },
3466
- request: verifyRequest(openPayload.channelId),
3467
- }),
3468
- ).rejects.toThrow(/broadcast failed/)
3469
- expect(observedPending).toBe(true)
3470
- expect((await store.getChannel(openPayload.channelId))!.closeRequestedAt).toBe(0n)
3471
- })
3472
-
3473
- test('precompile settle returns txHash when channel disappears before final write', async () => {
3474
- const rawStore = Store.memory()
3475
- const store = channelStore(rawStore)
3476
- const openPayload = await createOpenPayload({ initialAmount: 100n })
3477
- await persistPrecompileChannel(store, openPayload, { payee: payer.address })
3478
- const client = createServerClient([], payer, openPayload.channelId, {
3479
- receipt: transactionReceipt([settledLog(openPayload.channelId, 100n)]),
3480
- state: { settled: 100n, deposit: 1_000n, closeRequestedAt: 0 },
3481
- })
3482
- let deleted = false
3483
- const disappearingStore = {
3484
- async get(key: string) {
3485
- return rawStore.get(key)
3486
- },
3487
- async put(key: string, value: unknown) {
3488
- return rawStore.put(key, value)
3489
- },
3490
- async delete(key: string) {
3491
- return rawStore.delete(key)
3492
- },
3493
- async update<result>(
3494
- key: string,
3495
- fn: (current: unknown | null) => Store.Change<unknown, result>,
3496
- ): Promise<result> {
3497
- if (!deleted) {
3498
- deleted = true
3499
- return rawStore.update(key, fn)
3500
- }
3501
- const change = fn(null)
3502
- return change.result
3503
- },
3504
- } as Store.AtomicStore
3505
-
3506
- const { settle } = await import('./Session.js')
3507
- await expect(settle(disappearingStore, client, openPayload.channelId)).resolves.toBe(
3508
- `0x${'aa'.repeat(32)}`,
3509
- )
3510
- })
3511
-
3512
- test('precompile close still returns receipt when channel disappears before final write', async () => {
3513
- const rawStore = Store.memory()
3514
- const store = channelStore(rawStore)
3515
- const openPayload = await createOpenPayload({ initialAmount: 100n })
3516
- await persistPrecompileChannel(store, openPayload, { payee: payer.address, spent: 100n })
3517
- const closeSignature = await Voucher.signVoucher(
3518
- createSigningClient(),
3519
- payer,
3520
- { channelId: openPayload.channelId, cumulativeAmount: 100n },
3521
- tip20ChannelEscrow,
3522
- chainId,
3523
- )
3524
- const method = session({
3525
- account: payer,
3526
- amount: '1',
3527
- chainId,
3528
- currency: token,
3529
- decimals: 0,
3530
- recipient: payee,
3531
- store: rawStore,
3532
- unitType: 'request',
3533
- getClient: () =>
3534
- createServerClient([], payer, openPayload.channelId, {
3535
- receipt: transactionReceipt([closedLog(openPayload.channelId, 100n, 900n)]),
3536
- state: { settled: 0n, deposit: 1_000n, closeRequestedAt: 0 },
3537
- }),
3538
- })
3539
- let deleteBeforeFinalWrite = false
3540
- const originalUpdate = store.updateChannel.bind(store)
3541
- store.updateChannel = (async (channelId, fn) => {
3542
- if (deleteBeforeFinalWrite) return fn(null as never) as never
3543
- const result = await originalUpdate(channelId, fn)
3544
- deleteBeforeFinalWrite = true
3545
- return result
3546
- }) as typeof store.updateChannel
3547
-
3548
- const receipt = (await method.verify({
3549
- credential: {
3550
- challenge: makeChallenge(openPayload.channelId),
3551
- payload: {
3552
- action: 'close',
3553
- channelId: openPayload.channelId,
3554
- cumulativeAmount: '100',
3555
- descriptor: openPayload.descriptor,
3556
- signature: closeSignature,
3557
- },
3558
- },
3559
- request: verifyRequest(openPayload.channelId),
3560
- })) as SessionReceipt
3561
-
3562
- expect(receipt.txHash).toBe(`0x${'aa'.repeat(32)}`)
3563
- expect(receipt.spent).toBe('100')
3564
- })
3565
-
3566
- test('rejects close when precompile channel is finalized on-chain', async () => {
3567
- const rawStore = Store.memory()
3568
- const store = channelStore(rawStore)
3569
- const openPayload = await createOpenPayload({ initialAmount: 100n })
3570
- await persistPrecompileChannel(store, openPayload, { payee: payer.address, spent: 100n })
3571
- const closeSignature = await Voucher.signVoucher(
3572
- createSigningClient(),
3573
- payer,
3574
- { channelId: openPayload.channelId, cumulativeAmount: 100n },
3575
- tip20ChannelEscrow,
3576
- chainId,
3577
- )
3578
- const method = session({
3579
- account: payer,
3580
- amount: '1',
3581
- chainId,
3582
- currency: token,
3583
- decimals: 0,
3584
- recipient: payee,
3585
- store: rawStore,
3586
- unitType: 'request',
3587
- getClient: () => createStateClient(payer, { settled: 0n, deposit: 0n, closeRequestedAt: 0 }),
3588
- })
3589
-
3590
- await expect(
3591
- method.verify({
3592
- credential: {
3593
- challenge: makeChallenge(openPayload.channelId),
3594
- payload: {
3595
- action: 'close',
3596
- channelId: openPayload.channelId,
3597
- cumulativeAmount: '100',
3598
- descriptor: openPayload.descriptor,
3599
- signature: closeSignature,
3600
- },
3601
- },
3602
- request: verifyRequest(openPayload.channelId),
3603
- }),
3604
- ).rejects.toThrow(/channel deposit is zero/)
3605
- })
3606
-
3607
- test('pending precompile close blocks concurrent charges', async () => {
3608
- const { store } = createServer()
3609
- const openPayload = await createOpenPayload({ initialAmount: 100n })
3610
- await persistPrecompileChannel(store, openPayload, {
3611
- closeRequestedAt: 1n,
3612
- highestVoucherAmount: 500n,
3613
- spent: 100n,
3614
- })
3615
-
3616
- await expect(charge(store, openPayload.channelId, 1n)).rejects.toThrow(/pending close request/)
3617
- })
3618
-
3619
- test('rejects server-driven close when no account is available', async () => {
3620
- const rawStore = Store.memory()
3621
- const store = channelStore(rawStore)
3622
- const openPayload = await createOpenPayload()
3623
- await persistPrecompileChannel(store, openPayload)
3624
- const method = session({
3625
- amount: '1',
3626
- chainId,
3627
- currency: token,
3628
- decimals: 0,
3629
- recipient: payee,
3630
- store: rawStore,
3631
- unitType: 'request',
3632
- getClient: () => createStateClient(null),
3633
- })
3634
- const payload = await ClientOps.createClosePayload(
3635
- createSigningClient(),
3636
- payer,
3637
- openPayload.descriptor,
3638
- Types.uint96(100n),
3639
- chainId,
3640
- )
3641
-
3642
- await expect(
3643
- method.verify({
3644
- credential: {
3645
- challenge: makeChallenge(openPayload.channelId),
3646
- payload,
3647
- },
3648
- request: verifyRequest(openPayload.channelId),
3649
- }),
3650
- ).rejects.toThrow(/no account available/)
3651
- })
3652
-
3653
- test('accepts server-driven close account override matching the channel payee', async () => {
3654
- const rawStore = Store.memory()
3655
- const store = channelStore(rawStore)
3656
- const openPayload = await createOpenPayload()
3657
- await persistPrecompileChannel(store, openPayload, {
3658
- payee: wrongPayer.address,
3659
- })
3660
- const method = session({
3661
- account: wrongPayer,
3662
- amount: '1',
3663
- chainId,
3664
- currency: token,
3665
- decimals: 0,
3666
- recipient: payee,
3667
- store: rawStore,
3668
- unitType: 'request',
3669
- getClient: () => createStateClient(payer),
3670
- })
3671
- const payload = await ClientOps.createClosePayload(
3672
- createSigningClient(),
3673
- payer,
3674
- openPayload.descriptor,
3675
- Types.uint96(100n),
3676
- chainId,
3677
- )
3678
-
3679
- await expect(
3680
- method.verify({
3681
- credential: {
3682
- challenge: makeChallenge(openPayload.channelId),
3683
- payload,
3684
- },
3685
- request: verifyRequest(openPayload.channelId),
3686
- }),
3687
- ).rejects.toThrow(/eth_sendRawTransaction/)
3688
- })
3689
-
3690
- test('uses request-specified fee payer account for server-driven precompile close', async () => {
3691
- const rawStore = Store.memory()
3692
- const store = channelStore(rawStore)
3693
- const openPayload = await createOpenPayload()
3694
- await persistPrecompileChannel(store, openPayload, {
3695
- payee: wrongPayer.address,
3696
- })
3697
- const method = session({
3698
- account: wrongPayer,
3699
- amount: '1',
3700
- chainId,
3701
- currency: token,
3702
- decimals: 0,
3703
- feeToken: token,
3704
- recipient: payee,
3705
- store: rawStore,
3706
- unitType: 'request',
3707
- getClient: () => createStateClient(payer),
3708
- })
3709
- const payload = await ClientOps.createClosePayload(
3710
- createSigningClient(),
3711
- payer,
3712
- openPayload.descriptor,
3713
- Types.uint96(100n),
3714
- chainId,
3715
- )
3716
-
3717
- await expect(
3718
- method.verify({
3719
- credential: {
3720
- challenge: makeChallenge(openPayload.channelId),
3721
- payload,
3722
- },
3723
- request: verifyRequestWithFeePayer(openPayload.channelId, payer),
3724
- }),
3725
- ).rejects.toThrow(/eth_sendRawTransaction/)
3726
- })
3727
-
3728
- test('accepts server-driven close sender matching a nonzero precompile operator', async () => {
3729
- const rawStore = Store.memory()
3730
- const store = channelStore(rawStore)
3731
- const openPayload = await createOpenPayload({
3732
- operator: wrongPayer.address,
3733
- })
3734
- await persistPrecompileChannel(store, openPayload)
3735
- const method = session({
3736
- account: wrongPayer,
3737
- amount: '1',
3738
- chainId,
3739
- currency: token,
3740
- decimals: 0,
3741
- recipient: payee,
3742
- operator: wrongPayer.address,
3743
- store: rawStore,
3744
- unitType: 'request',
3745
- getClient: () => createStateClient(payer),
3746
- })
3747
- const payload = await ClientOps.createClosePayload(
3748
- createSigningClient(),
3749
- payer,
3750
- openPayload.descriptor,
3751
- Types.uint96(100n),
3752
- chainId,
3753
- )
3754
-
3755
- await expect(
3756
- method.verify({
3757
- credential: {
3758
- challenge: makeChallenge(openPayload.channelId, { operator: wrongPayer.address }),
3759
- payload,
3760
- },
3761
- request: verifyRequest(openPayload.channelId, { operator: wrongPayer.address }),
3762
- }),
3763
- ).rejects.toThrow(/eth_sendRawTransaction/)
3764
- })
3765
-
3766
- test('rejects server-driven close when sender is not the channel payee or operator', async () => {
3767
- const rawStore = Store.memory()
3768
- const store = channelStore(rawStore)
3769
- const openPayload = await createOpenPayload()
3770
- await persistPrecompileChannel(store, openPayload)
3771
- const method = session({
3772
- amount: '1',
3773
- chainId,
3774
- currency: token,
3775
- decimals: 0,
3776
- recipient: payee,
3777
- store: rawStore,
3778
- unitType: 'request',
3779
- getClient: () => createStateClient(wrongPayer),
3780
- })
3781
- const payload = await ClientOps.createClosePayload(
3782
- createSigningClient(),
3783
- payer,
3784
- openPayload.descriptor,
3785
- Types.uint96(100n),
3786
- chainId,
3787
- )
3788
-
3789
- await expect(
3790
- method.verify({
3791
- credential: {
3792
- challenge: makeChallenge(openPayload.channelId),
3793
- payload,
3794
- },
3795
- request: verifyRequest(openPayload.channelId),
3796
- }),
3797
- ).rejects.toThrow(/tx sender .* is not the channel payee/)
3798
- })
3799
- })
3800
-
3801
- describe('session settlement extensions', () => {
3802
- test.each([
3803
- ['no schedule is configured', undefined],
3804
- ['the configured threshold is not due', { units: 11 }],
3805
- ] as const)('does not resolve a client when %s', async (_label, settlementSchedule) => {
3806
- const rawStore = Store.memory()
3807
- const store = channelStore(rawStore)
3808
- const openPayload = await createOpenPayload()
3809
- let clientResolutions = 0
3810
- await persistPrecompileChannel(store, openPayload, {
3811
- highestVoucherAmount: 500n,
3812
- highestVoucher: {
3813
- channelId: openPayload.channelId,
3814
- cumulativeAmount: 500n,
3815
- signature: '0x1234',
3816
- },
3817
- spent: 500n,
3818
- units: 10,
3819
- })
3820
-
3821
- const payment = Mppx_server.create({
3822
- methods: [
3823
- tempo_server.session({
3824
- account: payer,
3825
- amount: '1',
3826
- chainId,
3827
- currency: token,
3828
- decimals: 0,
3829
- getClient() {
3830
- clientResolutions++
3831
- throw new Error('client should not be resolved')
3832
- },
3833
- recipient: payee,
3834
- settlementSchedule,
3835
- store: rawStore,
3836
- unitType: 'token',
3837
- }),
3838
- ],
3839
- realm: 'api.example.com',
3840
- secretKey: 'test-secret-key-test-secret-key-32',
3841
- })
3842
- const channel = await store.getChannel(openPayload.channelId)
3843
-
3844
- await expect(payment.session.settleScheduled(channel!)).resolves.toBeUndefined()
3845
- expect(clientResolutions).toBe(0)
3846
- })
3847
- })
3848
-
3849
- describe('onSessionSettlement', () => {
3850
- function createSettleClient(channelId: Hex, settledAmount: bigint) {
3851
- return createClient({
3852
- account: payer,
3853
- chain: testChain,
3854
- transport: custom({
3855
- async request(args) {
3856
- if (args.method === 'eth_chainId') return `0x${chainId.toString(16)}`
3857
- if (args.method === 'eth_getTransactionCount') return '0x0'
3858
- if (args.method === 'eth_estimateGas') return '0x5208'
3859
- if (args.method === 'eth_maxPriorityFeePerGas') return '0x1'
3860
- if (args.method === 'eth_getBlockByNumber') return { baseFeePerGas: '0x1' }
3861
- if (args.method === 'eth_sendRawTransaction') return `0x${'cc'.repeat(32)}`
3862
- if (args.method === 'eth_sendTransaction') return `0x${'cc'.repeat(32)}`
3863
- if (args.method === 'eth_getTransactionReceipt')
3864
- return transactionReceipt([settledLog(channelId, settledAmount)])
3865
- if (args.method === 'eth_call') {
3866
- return encodeFunctionResult({
3867
- abi: escrowAbi,
3868
- functionName: 'getChannelState',
3869
- result: { settled: settledAmount, deposit: 1_000n, closeRequestedAt: 0 },
3870
- })
3871
- }
3872
- throw new Error(`unexpected rpc request: ${args.method}`)
3873
- },
3874
- }),
3875
- })
3876
- }
3877
-
3878
- test('fires onSessionSettlement for explicit settle() calls', async () => {
3879
- const events: { trigger: string; txHash: string; amount: bigint; delta: bigint }[] = []
3880
- const store = Store.memory()
3881
- const openPayload = await createOpenPayload()
3882
- await persistPrecompileChannel(channelStore(store), openPayload, {
3883
- payee: payer.address,
3884
- spent: 100n,
3885
- highestVoucherAmount: 100n,
3886
- highestVoucher: {
3887
- channelId: openPayload.channelId,
3888
- cumulativeAmount: 100n,
3889
- signature: '0x1234',
3890
- },
3891
- })
3892
-
3893
- const { settle } = await import('./Session.js')
3894
- const client = createSettleClient(openPayload.channelId, 100n)
3895
- await settle(store, client, openPayload.channelId, {
3896
- onSessionSettlement: (ctx) => {
3897
- events.push({
3898
- trigger: ctx.trigger,
3899
- txHash: ctx.txHash,
3900
- amount: ctx.amount,
3901
- delta: ctx.delta,
3902
- })
3903
- },
3904
- })
3905
-
3906
- expect(events).toHaveLength(1)
3907
- expect(events[0]).toMatchObject({
3908
- trigger: 'settle',
3909
- txHash: `0x${'cc'.repeat(32)}`,
3910
- amount: 100n,
3911
- delta: 100n,
3912
- })
3913
- })
3914
-
3915
- test('fires onSessionSettlement for auto-scheduled settlements', async () => {
3916
- const events: { trigger: string; channelId: string; amount: bigint; delta: bigint }[] = []
3917
- const rawStore = Store.memory()
3918
- const openPayload = await createOpenPayload()
3919
- const store = channelStore(rawStore)
3920
- await persistPrecompileChannel(store, openPayload, {
3921
- payee: payer.address,
3922
- spent: 500n,
3923
- units: 10,
3924
- highestVoucherAmount: 500n,
3925
- highestVoucher: {
3926
- channelId: openPayload.channelId,
3927
- cumulativeAmount: 500n,
3928
- signature: '0x1234',
3929
- },
3930
- })
3931
-
3932
- const { maybeSettleScheduled } = await import('./Settlement.js')
3933
- const client = createSettleClient(openPayload.channelId, 500n)
3934
- const channel = await store.getChannel(openPayload.channelId)
3935
-
3936
- await maybeSettleScheduled({
3937
- account: payer,
3938
- channel: channel!,
3939
- client,
3940
- schedule: { units: 5 },
3941
- store,
3942
- onSessionSettlement: (ctx) => {
3943
- events.push({
3944
- trigger: ctx.trigger,
3945
- channelId: ctx.channelId,
3946
- amount: ctx.amount,
3947
- delta: ctx.delta,
3948
- })
3949
- },
3950
- })
3951
-
3952
- expect(events).toHaveLength(1)
3953
- expect(events[0]).toMatchObject({
3954
- trigger: 'scheduled',
3955
- channelId: openPayload.channelId,
3956
- amount: 500n,
3957
- delta: 500n,
3958
- })
3959
- })
3960
-
3961
- test('delta reflects incremental scheduled settlement with prior on-chain settlement', async () => {
3962
- const events: { trigger: string; amount: bigint; delta: bigint }[] = []
3963
- const rawStore = Store.memory()
3964
- const openPayload = await createOpenPayload()
3965
- const store = channelStore(rawStore)
3966
- await persistPrecompileChannel(store, openPayload, {
3967
- payee: payer.address,
3968
- spent: 500n,
3969
- units: 10,
3970
- settledOnChain: 200n,
3971
- highestVoucherAmount: 500n,
3972
- highestVoucher: {
3973
- channelId: openPayload.channelId,
3974
- cumulativeAmount: 500n,
3975
- signature: '0x1234',
3976
- },
3977
- })
3978
-
3979
- const { maybeSettleScheduled } = await import('./Settlement.js')
3980
- const client = createSettleClient(openPayload.channelId, 500n)
3981
- const channel = await store.getChannel(openPayload.channelId)
3982
-
3983
- await maybeSettleScheduled({
3984
- account: payer,
3985
- channel: channel!,
3986
- client,
3987
- schedule: { units: 5 },
3988
- store,
3989
- onSessionSettlement: (ctx) => {
3990
- events.push({ trigger: ctx.trigger, amount: ctx.amount, delta: ctx.delta })
3991
- },
3992
- })
3993
-
3994
- expect(events).toHaveLength(1)
3995
- expect(events[0]).toMatchObject({
3996
- trigger: 'scheduled',
3997
- amount: 500n,
3998
- delta: 300n,
3999
- })
4000
- })
4001
-
4002
- test('fires onSessionSettlement for cooperative close', async () => {
4003
- const events: { trigger: string; txHash: string; amount: bigint; delta: bigint }[] = []
4004
- const openPayload = await createOpenPayload()
4005
- const closedAmount = 100n
4006
- const refundedAmount = 900n
4007
- const rawStore = Store.memory()
4008
-
4009
- const closeClient = createClient({
4010
- account: payer,
4011
- chain: testChain,
4012
- transport: custom({
4013
- async request(args) {
4014
- if (args.method === 'eth_chainId') return `0x${chainId.toString(16)}`
4015
- if (args.method === 'eth_getTransactionCount') return '0x0'
4016
- if (args.method === 'eth_estimateGas') return '0x5208'
4017
- if (args.method === 'eth_maxPriorityFeePerGas') return '0x1'
4018
- if (args.method === 'eth_getBlockByNumber') return { baseFeePerGas: '0x1' }
4019
- if (args.method === 'eth_sendRawTransaction') return `0x${'dd'.repeat(32)}`
4020
- if (args.method === 'eth_sendTransaction') return `0x${'dd'.repeat(32)}`
4021
- if (args.method === 'eth_getTransactionReceipt')
4022
- return transactionReceipt([
4023
- closedLog(openPayload.channelId, closedAmount, refundedAmount),
4024
- ])
4025
- if (args.method === 'eth_call') {
4026
- return encodeFunctionResult({
4027
- abi: escrowAbi,
4028
- functionName: 'getChannelState',
4029
- result: { settled: 0n, deposit: 1_000n, closeRequestedAt: 0 },
4030
- })
4031
- }
4032
- throw new Error(`unexpected rpc request: ${args.method}`)
4033
- },
4034
- }),
4035
- })
4036
-
4037
- const method = session({
4038
- amount: '1',
4039
- chainId,
4040
- currency: token,
4041
- decimals: 0,
4042
- recipient: payee,
4043
- store: rawStore,
4044
- unitType: 'request',
4045
- getClient: () => closeClient,
4046
- onSessionSettlement: (ctx) => {
4047
- events.push({
4048
- trigger: ctx.trigger,
4049
- txHash: ctx.txHash,
4050
- amount: ctx.amount,
4051
- delta: ctx.delta,
4052
- })
4053
- },
4054
- })
4055
-
4056
- await persistPrecompileChannel(channelStore(rawStore), openPayload, {
4057
- payee: payer.address,
4058
- spent: 100n,
4059
- highestVoucherAmount: 100n,
4060
- highestVoucher: {
4061
- channelId: openPayload.channelId,
4062
- cumulativeAmount: 100n,
4063
- signature: '0x1234',
4064
- },
4065
- })
4066
-
4067
- const closeSignature = await Voucher.signVoucher(
4068
- createSigningClient(),
4069
- payer,
4070
- { channelId: openPayload.channelId, cumulativeAmount: 100n },
4071
- tip20ChannelEscrow,
4072
- chainId,
4073
- )
4074
-
4075
- await method.verify({
4076
- credential: {
4077
- challenge: makeChallenge(openPayload.channelId),
4078
- payload: {
4079
- action: 'close',
4080
- channelId: openPayload.channelId,
4081
- descriptor: openPayload.descriptor,
4082
- cumulativeAmount: '100',
4083
- signature: closeSignature,
4084
- },
4085
- },
4086
- request: verifyRequest(openPayload.channelId),
4087
- })
4088
-
4089
- expect(events).toHaveLength(1)
4090
- expect(events[0]).toMatchObject({
4091
- trigger: 'close',
4092
- txHash: `0x${'dd'.repeat(32)}`,
4093
- amount: closedAmount,
4094
- delta: closedAmount,
4095
- })
4096
- })
4097
-
4098
- test('delta reflects incremental settlement when channel has prior on-chain settlement', async () => {
4099
- const events: { trigger: string; amount: bigint; delta: bigint }[] = []
4100
- const store = Store.memory()
4101
- const openPayload = await createOpenPayload()
4102
- await persistPrecompileChannel(channelStore(store), openPayload, {
4103
- payee: payer.address,
4104
- spent: 300n,
4105
- settledOnChain: 200n,
4106
- highestVoucherAmount: 300n,
4107
- highestVoucher: {
4108
- channelId: openPayload.channelId,
4109
- cumulativeAmount: 300n,
4110
- signature: '0x1234',
4111
- },
4112
- })
4113
-
4114
- const { settle } = await import('./Session.js')
4115
- const client = createSettleClient(openPayload.channelId, 300n)
4116
- await settle(store, client, openPayload.channelId, {
4117
- onSessionSettlement: (ctx) => {
4118
- events.push({ trigger: ctx.trigger, amount: ctx.amount, delta: ctx.delta })
4119
- },
4120
- })
4121
-
4122
- expect(events).toHaveLength(1)
4123
- expect(events[0]).toMatchObject({
4124
- trigger: 'settle',
4125
- amount: 300n,
4126
- delta: 100n,
4127
- })
4128
- })
4129
-
4130
- test('delta reflects incremental close when channel has prior on-chain settlement', async () => {
4131
- const events: { trigger: string; amount: bigint; delta: bigint }[] = []
4132
- const openPayload = await createOpenPayload()
4133
- const priorSettled = 50n
4134
- const closedAmount = 100n
4135
- const refundedAmount = 900n
4136
- const rawStore = Store.memory()
4137
-
4138
- const closeClient = createClient({
4139
- account: payer,
4140
- chain: testChain,
4141
- transport: custom({
4142
- async request(args) {
4143
- if (args.method === 'eth_chainId') return `0x${chainId.toString(16)}`
4144
- if (args.method === 'eth_getTransactionCount') return '0x0'
4145
- if (args.method === 'eth_estimateGas') return '0x5208'
4146
- if (args.method === 'eth_maxPriorityFeePerGas') return '0x1'
4147
- if (args.method === 'eth_getBlockByNumber') return { baseFeePerGas: '0x1' }
4148
- if (args.method === 'eth_sendRawTransaction') return `0x${'dd'.repeat(32)}`
4149
- if (args.method === 'eth_sendTransaction') return `0x${'dd'.repeat(32)}`
4150
- if (args.method === 'eth_getTransactionReceipt')
4151
- return transactionReceipt([
4152
- closedLog(openPayload.channelId, closedAmount, refundedAmount),
4153
- ])
4154
- if (args.method === 'eth_call') {
4155
- return encodeFunctionResult({
4156
- abi: escrowAbi,
4157
- functionName: 'getChannelState',
4158
- result: { settled: priorSettled, deposit: 1_000n, closeRequestedAt: 0 },
4159
- })
4160
- }
4161
- throw new Error(`unexpected rpc request: ${args.method}`)
4162
- },
4163
- }),
4164
- })
4165
-
4166
- const method = session({
4167
- amount: '1',
4168
- chainId,
4169
- currency: token,
4170
- decimals: 0,
4171
- recipient: payee,
4172
- store: rawStore,
4173
- unitType: 'request',
4174
- getClient: () => closeClient,
4175
- onSessionSettlement: (ctx) => {
4176
- events.push({ trigger: ctx.trigger, amount: ctx.amount, delta: ctx.delta })
4177
- },
4178
- })
4179
-
4180
- await persistPrecompileChannel(channelStore(rawStore), openPayload, {
4181
- payee: payer.address,
4182
- spent: 100n,
4183
- settledOnChain: priorSettled,
4184
- highestVoucherAmount: 100n,
4185
- highestVoucher: {
4186
- channelId: openPayload.channelId,
4187
- cumulativeAmount: 100n,
4188
- signature: '0x1234',
4189
- },
4190
- })
4191
-
4192
- const closeSignature = await Voucher.signVoucher(
4193
- createSigningClient(),
4194
- payer,
4195
- { channelId: openPayload.channelId, cumulativeAmount: 100n },
4196
- tip20ChannelEscrow,
4197
- chainId,
4198
- )
4199
-
4200
- await method.verify({
4201
- credential: {
4202
- challenge: makeChallenge(openPayload.channelId),
4203
- payload: {
4204
- action: 'close',
4205
- channelId: openPayload.channelId,
4206
- descriptor: openPayload.descriptor,
4207
- cumulativeAmount: '100',
4208
- signature: closeSignature,
4209
- },
4210
- },
4211
- request: verifyRequest(openPayload.channelId),
4212
- })
4213
-
4214
- expect(events).toHaveLength(1)
4215
- expect(events[0]).toMatchObject({
4216
- trigger: 'close',
4217
- amount: closedAmount,
4218
- delta: closedAmount - priorSettled,
4219
- })
4220
- })
4221
-
4222
- test('isolates errors in onSessionSettlement handler', async () => {
4223
- const store = Store.memory()
4224
- const openPayload = await createOpenPayload()
4225
- await persistPrecompileChannel(channelStore(store), openPayload, {
4226
- payee: payer.address,
4227
- spent: 100n,
4228
- highestVoucherAmount: 100n,
4229
- highestVoucher: {
4230
- channelId: openPayload.channelId,
4231
- cumulativeAmount: 100n,
4232
- signature: '0x1234',
4233
- },
4234
- })
4235
-
4236
- const { settle } = await import('./Session.js')
4237
- const client = createSettleClient(openPayload.channelId, 100n)
4238
-
4239
- await expect(
4240
- settle(store, client, openPayload.channelId, {
4241
- onSessionSettlement: () => {
4242
- throw new Error('observer exploded')
4243
- },
4244
- }),
4245
- ).resolves.toBe(`0x${'cc'.repeat(32)}`)
4246
- })
4247
- })