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