mppx 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (446) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +195 -0
  3. package/dist/BodyDigest.d.ts +42 -0
  4. package/dist/BodyDigest.d.ts.map +1 -0
  5. package/dist/BodyDigest.js +40 -0
  6. package/dist/BodyDigest.js.map +1 -0
  7. package/dist/Challenge.d.ts +271 -0
  8. package/dist/Challenge.d.ts.map +1 -0
  9. package/dist/Challenge.js +291 -0
  10. package/dist/Challenge.js.map +1 -0
  11. package/dist/Credential.d.ts +91 -0
  12. package/dist/Credential.d.ts.map +1 -0
  13. package/dist/Credential.js +122 -0
  14. package/dist/Credential.js.map +1 -0
  15. package/dist/Errors.d.ts +243 -0
  16. package/dist/Errors.d.ts.map +1 -0
  17. package/dist/Errors.js +201 -0
  18. package/dist/Errors.js.map +1 -0
  19. package/dist/Expires.d.ts +15 -0
  20. package/dist/Expires.d.ts.map +1 -0
  21. package/dist/Expires.js +29 -0
  22. package/dist/Expires.js.map +1 -0
  23. package/dist/Intent.d.ts +101 -0
  24. package/dist/Intent.d.ts.map +1 -0
  25. package/dist/Intent.js +83 -0
  26. package/dist/Intent.js.map +1 -0
  27. package/dist/Mcp.d.ts +74 -0
  28. package/dist/Mcp.d.ts.map +1 -0
  29. package/dist/Mcp.js +9 -0
  30. package/dist/Mcp.js.map +1 -0
  31. package/dist/MethodIntent.d.ts +225 -0
  32. package/dist/MethodIntent.d.ts.map +1 -0
  33. package/dist/MethodIntent.js +156 -0
  34. package/dist/MethodIntent.js.map +1 -0
  35. package/dist/PaymentRequest.d.ts +88 -0
  36. package/dist/PaymentRequest.d.ts.map +1 -0
  37. package/dist/PaymentRequest.js +81 -0
  38. package/dist/PaymentRequest.js.map +1 -0
  39. package/dist/Receipt.d.ts +110 -0
  40. package/dist/Receipt.d.ts.map +1 -0
  41. package/dist/Receipt.js +105 -0
  42. package/dist/Receipt.js.map +1 -0
  43. package/dist/Store.d.ts +28 -0
  44. package/dist/Store.d.ts.map +1 -0
  45. package/dist/Store.js +61 -0
  46. package/dist/Store.js.map +1 -0
  47. package/dist/cli.d.ts +3 -0
  48. package/dist/cli.d.ts.map +1 -0
  49. package/dist/cli.js +1219 -0
  50. package/dist/cli.js.map +1 -0
  51. package/dist/client/Methods.d.ts +4 -0
  52. package/dist/client/Methods.d.ts.map +1 -0
  53. package/dist/client/Methods.js +4 -0
  54. package/dist/client/Methods.js.map +1 -0
  55. package/dist/client/Mppx.d.ts +84 -0
  56. package/dist/client/Mppx.d.ts.map +1 -0
  57. package/dist/client/Mppx.js +64 -0
  58. package/dist/client/Mppx.js.map +1 -0
  59. package/dist/client/Transport.d.ts +56 -0
  60. package/dist/client/Transport.d.ts.map +1 -0
  61. package/dist/client/Transport.js +81 -0
  62. package/dist/client/Transport.js.map +1 -0
  63. package/dist/client/index.d.ts +5 -0
  64. package/dist/client/index.d.ts.map +1 -0
  65. package/dist/client/index.js +5 -0
  66. package/dist/client/index.js.map +1 -0
  67. package/dist/client/internal/Fetch.d.ts +85 -0
  68. package/dist/client/internal/Fetch.d.ts.map +1 -0
  69. package/dist/client/internal/Fetch.js +95 -0
  70. package/dist/client/internal/Fetch.js.map +1 -0
  71. package/dist/index.d.ts +13 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +13 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/internal/types.d.ts +302 -0
  76. package/dist/internal/types.d.ts.map +1 -0
  77. package/dist/internal/types.js +2 -0
  78. package/dist/internal/types.js.map +1 -0
  79. package/dist/mcp-sdk/client/McpClient.d.ts +78 -0
  80. package/dist/mcp-sdk/client/McpClient.d.ts.map +1 -0
  81. package/dist/mcp-sdk/client/McpClient.js +98 -0
  82. package/dist/mcp-sdk/client/McpClient.js.map +1 -0
  83. package/dist/mcp-sdk/client/index.d.ts +3 -0
  84. package/dist/mcp-sdk/client/index.d.ts.map +1 -0
  85. package/dist/mcp-sdk/client/index.js +3 -0
  86. package/dist/mcp-sdk/client/index.js.map +1 -0
  87. package/dist/mcp-sdk/server/Transport.d.ts +43 -0
  88. package/dist/mcp-sdk/server/Transport.d.ts.map +1 -0
  89. package/dist/mcp-sdk/server/Transport.js +71 -0
  90. package/dist/mcp-sdk/server/Transport.js.map +1 -0
  91. package/dist/mcp-sdk/server/index.d.ts +2 -0
  92. package/dist/mcp-sdk/server/index.d.ts.map +1 -0
  93. package/dist/mcp-sdk/server/index.js +2 -0
  94. package/dist/mcp-sdk/server/index.js.map +1 -0
  95. package/dist/middlewares/elysia.d.ts +51 -0
  96. package/dist/middlewares/elysia.d.ts.map +1 -0
  97. package/dist/middlewares/elysia.js +59 -0
  98. package/dist/middlewares/elysia.js.map +1 -0
  99. package/dist/middlewares/express.d.ts +46 -0
  100. package/dist/middlewares/express.d.ts.map +1 -0
  101. package/dist/middlewares/express.js +69 -0
  102. package/dist/middlewares/express.js.map +1 -0
  103. package/dist/middlewares/hono.d.ts +46 -0
  104. package/dist/middlewares/hono.d.ts.map +1 -0
  105. package/dist/middlewares/hono.js +57 -0
  106. package/dist/middlewares/hono.js.map +1 -0
  107. package/dist/middlewares/internal/mppx.d.ts +16 -0
  108. package/dist/middlewares/internal/mppx.d.ts.map +1 -0
  109. package/dist/middlewares/internal/mppx.js +16 -0
  110. package/dist/middlewares/internal/mppx.js.map +1 -0
  111. package/dist/middlewares/nextjs.d.ts +45 -0
  112. package/dist/middlewares/nextjs.d.ts.map +1 -0
  113. package/dist/middlewares/nextjs.js +57 -0
  114. package/dist/middlewares/nextjs.js.map +1 -0
  115. package/dist/proxy/Proxy.d.ts +47 -0
  116. package/dist/proxy/Proxy.d.ts.map +1 -0
  117. package/dist/proxy/Proxy.js +126 -0
  118. package/dist/proxy/Proxy.js.map +1 -0
  119. package/dist/proxy/Service.d.ts +100 -0
  120. package/dist/proxy/Service.d.ts.map +1 -0
  121. package/dist/proxy/Service.js +147 -0
  122. package/dist/proxy/Service.js.map +1 -0
  123. package/dist/proxy/index.d.ts +7 -0
  124. package/dist/proxy/index.d.ts.map +1 -0
  125. package/dist/proxy/index.js +7 -0
  126. package/dist/proxy/index.js.map +1 -0
  127. package/dist/proxy/internal/Headers.d.ts +3 -0
  128. package/dist/proxy/internal/Headers.d.ts.map +1 -0
  129. package/dist/proxy/internal/Headers.js +41 -0
  130. package/dist/proxy/internal/Headers.js.map +1 -0
  131. package/dist/proxy/internal/Route.d.ts +14 -0
  132. package/dist/proxy/internal/Route.d.ts.map +1 -0
  133. package/dist/proxy/internal/Route.js +47 -0
  134. package/dist/proxy/internal/Route.js.map +1 -0
  135. package/dist/proxy/services/anthropic.d.ts +29 -0
  136. package/dist/proxy/services/anthropic.d.ts.map +1 -0
  137. package/dist/proxy/services/anthropic.js +30 -0
  138. package/dist/proxy/services/anthropic.js.map +1 -0
  139. package/dist/proxy/services/openai.d.ts +29 -0
  140. package/dist/proxy/services/openai.d.ts.map +1 -0
  141. package/dist/proxy/services/openai.js +30 -0
  142. package/dist/proxy/services/openai.js.map +1 -0
  143. package/dist/proxy/services/stripe.d.ts +29 -0
  144. package/dist/proxy/services/stripe.d.ts.map +1 -0
  145. package/dist/proxy/services/stripe.js +30 -0
  146. package/dist/proxy/services/stripe.js.map +1 -0
  147. package/dist/server/Methods.d.ts +3 -0
  148. package/dist/server/Methods.d.ts.map +1 -0
  149. package/dist/server/Methods.js +3 -0
  150. package/dist/server/Methods.js.map +1 -0
  151. package/dist/server/Mppx.d.ts +116 -0
  152. package/dist/server/Mppx.d.ts.map +1 -0
  153. package/dist/server/Mppx.js +207 -0
  154. package/dist/server/Mppx.js.map +1 -0
  155. package/dist/server/NodeListener.d.ts +3 -0
  156. package/dist/server/NodeListener.d.ts.map +1 -0
  157. package/dist/server/NodeListener.js +3 -0
  158. package/dist/server/NodeListener.js.map +1 -0
  159. package/dist/server/Request.d.ts +24 -0
  160. package/dist/server/Request.d.ts.map +1 -0
  161. package/dist/server/Request.js +26 -0
  162. package/dist/server/Request.js.map +1 -0
  163. package/dist/server/Response.d.ts +10 -0
  164. package/dist/server/Response.d.ts.map +1 -0
  165. package/dist/server/Response.js +15 -0
  166. package/dist/server/Response.js.map +1 -0
  167. package/dist/server/Transport.d.ts +93 -0
  168. package/dist/server/Transport.d.ts.map +1 -0
  169. package/dist/server/Transport.js +132 -0
  170. package/dist/server/Transport.js.map +1 -0
  171. package/dist/server/index.d.ts +9 -0
  172. package/dist/server/index.d.ts.map +1 -0
  173. package/dist/server/index.js +9 -0
  174. package/dist/server/index.js.map +1 -0
  175. package/dist/stripe/Intents.d.ts +54 -0
  176. package/dist/stripe/Intents.d.ts.map +1 -0
  177. package/dist/stripe/Intents.js +27 -0
  178. package/dist/stripe/Intents.js.map +1 -0
  179. package/dist/stripe/client/Charge.d.ts +114 -0
  180. package/dist/stripe/client/Charge.d.ts.map +1 -0
  181. package/dist/stripe/client/Charge.js +77 -0
  182. package/dist/stripe/client/Charge.js.map +1 -0
  183. package/dist/stripe/client/MethodIntents.d.ts +80 -0
  184. package/dist/stripe/client/MethodIntents.d.ts.map +1 -0
  185. package/dist/stripe/client/MethodIntents.js +34 -0
  186. package/dist/stripe/client/MethodIntents.js.map +1 -0
  187. package/dist/stripe/client/index.d.ts +3 -0
  188. package/dist/stripe/client/index.d.ts.map +1 -0
  189. package/dist/stripe/client/index.js +3 -0
  190. package/dist/stripe/client/index.js.map +1 -0
  191. package/dist/stripe/index.d.ts +2 -0
  192. package/dist/stripe/index.d.ts.map +1 -0
  193. package/dist/stripe/index.js +2 -0
  194. package/dist/stripe/index.js.map +1 -0
  195. package/dist/stripe/server/Charge.d.ts +74 -0
  196. package/dist/stripe/server/Charge.d.ts.map +1 -0
  197. package/dist/stripe/server/Charge.js +79 -0
  198. package/dist/stripe/server/Charge.js.map +1 -0
  199. package/dist/stripe/server/MethodIntents.d.ts +65 -0
  200. package/dist/stripe/server/MethodIntents.d.ts.map +1 -0
  201. package/dist/stripe/server/MethodIntents.js +21 -0
  202. package/dist/stripe/server/MethodIntents.js.map +1 -0
  203. package/dist/stripe/server/index.d.ts +3 -0
  204. package/dist/stripe/server/index.d.ts.map +1 -0
  205. package/dist/stripe/server/index.js +3 -0
  206. package/dist/stripe/server/index.js.map +1 -0
  207. package/dist/tempo/Attribution.d.ts +101 -0
  208. package/dist/tempo/Attribution.d.ts.map +1 -0
  209. package/dist/tempo/Attribution.js +124 -0
  210. package/dist/tempo/Attribution.js.map +1 -0
  211. package/dist/tempo/Intents.d.ts +132 -0
  212. package/dist/tempo/Intents.d.ts.map +1 -0
  213. package/dist/tempo/Intents.js +81 -0
  214. package/dist/tempo/Intents.js.map +1 -0
  215. package/dist/tempo/client/ChannelOps.d.ts +54 -0
  216. package/dist/tempo/client/ChannelOps.d.ts.map +1 -0
  217. package/dist/tempo/client/ChannelOps.js +138 -0
  218. package/dist/tempo/client/ChannelOps.js.map +1 -0
  219. package/dist/tempo/client/Charge.d.ts +76 -0
  220. package/dist/tempo/client/Charge.d.ts.map +1 -0
  221. package/dist/tempo/client/Charge.js +69 -0
  222. package/dist/tempo/client/Charge.js.map +1 -0
  223. package/dist/tempo/client/MethodIntents.d.ts +157 -0
  224. package/dist/tempo/client/MethodIntents.d.ts.map +1 -0
  225. package/dist/tempo/client/MethodIntents.js +25 -0
  226. package/dist/tempo/client/MethodIntents.js.map +1 -0
  227. package/dist/tempo/client/Session.d.ts +159 -0
  228. package/dist/tempo/client/Session.d.ts.map +1 -0
  229. package/dist/tempo/client/Session.js +263 -0
  230. package/dist/tempo/client/Session.js.map +1 -0
  231. package/dist/tempo/client/SessionManager.d.ts +62 -0
  232. package/dist/tempo/client/SessionManager.d.ts.map +1 -0
  233. package/dist/tempo/client/SessionManager.js +196 -0
  234. package/dist/tempo/client/SessionManager.js.map +1 -0
  235. package/dist/tempo/client/index.d.ts +6 -0
  236. package/dist/tempo/client/index.d.ts.map +1 -0
  237. package/dist/tempo/client/index.js +5 -0
  238. package/dist/tempo/client/index.js.map +1 -0
  239. package/dist/tempo/index.d.ts +3 -0
  240. package/dist/tempo/index.d.ts.map +1 -0
  241. package/dist/tempo/index.js +3 -0
  242. package/dist/tempo/index.js.map +1 -0
  243. package/dist/tempo/internal/account.d.ts +32 -0
  244. package/dist/tempo/internal/account.d.ts.map +1 -0
  245. package/dist/tempo/internal/account.js +33 -0
  246. package/dist/tempo/internal/account.js.map +1 -0
  247. package/dist/tempo/internal/defaults.d.ts +18 -0
  248. package/dist/tempo/internal/defaults.d.ts.map +1 -0
  249. package/dist/tempo/internal/defaults.js +18 -0
  250. package/dist/tempo/internal/defaults.js.map +1 -0
  251. package/dist/tempo/internal/types.d.ts +11 -0
  252. package/dist/tempo/internal/types.d.ts.map +1 -0
  253. package/dist/tempo/internal/types.js +2 -0
  254. package/dist/tempo/internal/types.js.map +1 -0
  255. package/dist/tempo/server/Charge.d.ts +77 -0
  256. package/dist/tempo/server/Charge.d.ts.map +1 -0
  257. package/dist/tempo/server/Charge.js +228 -0
  258. package/dist/tempo/server/Charge.js.map +1 -0
  259. package/dist/tempo/server/MethodIntents.d.ts +140 -0
  260. package/dist/tempo/server/MethodIntents.d.ts.map +1 -0
  261. package/dist/tempo/server/MethodIntents.js +26 -0
  262. package/dist/tempo/server/MethodIntents.js.map +1 -0
  263. package/dist/tempo/server/Session.d.ts +148 -0
  264. package/dist/tempo/server/Session.d.ts.map +1 -0
  265. package/dist/tempo/server/Session.js +529 -0
  266. package/dist/tempo/server/Session.js.map +1 -0
  267. package/dist/tempo/server/internal/transport.d.ts +47 -0
  268. package/dist/tempo/server/internal/transport.d.ts.map +1 -0
  269. package/dist/tempo/server/internal/transport.js +118 -0
  270. package/dist/tempo/server/internal/transport.js.map +1 -0
  271. package/dist/tempo/stream/Chain.d.ts +52 -0
  272. package/dist/tempo/stream/Chain.d.ts.map +1 -0
  273. package/dist/tempo/stream/Chain.js +215 -0
  274. package/dist/tempo/stream/Chain.js.map +1 -0
  275. package/dist/tempo/stream/Channel.d.ts +26 -0
  276. package/dist/tempo/stream/Channel.d.ts.map +1 -0
  277. package/dist/tempo/stream/Channel.js +27 -0
  278. package/dist/tempo/stream/Channel.js.map +1 -0
  279. package/dist/tempo/stream/ChannelStore.d.ts +103 -0
  280. package/dist/tempo/stream/ChannelStore.d.ts.map +1 -0
  281. package/dist/tempo/stream/ChannelStore.js +100 -0
  282. package/dist/tempo/stream/ChannelStore.js.map +1 -0
  283. package/dist/tempo/stream/Receipt.d.ts +22 -0
  284. package/dist/tempo/stream/Receipt.d.ts.map +1 -0
  285. package/dist/tempo/stream/Receipt.js +34 -0
  286. package/dist/tempo/stream/Receipt.js.map +1 -0
  287. package/dist/tempo/stream/Sse.d.ts +134 -0
  288. package/dist/tempo/stream/Sse.d.ts.map +1 -0
  289. package/dist/tempo/stream/Sse.js +288 -0
  290. package/dist/tempo/stream/Sse.js.map +1 -0
  291. package/dist/tempo/stream/Types.d.ts +78 -0
  292. package/dist/tempo/stream/Types.d.ts.map +1 -0
  293. package/dist/tempo/stream/Types.js +2 -0
  294. package/dist/tempo/stream/Types.js.map +1 -0
  295. package/dist/tempo/stream/Voucher.d.ts +20 -0
  296. package/dist/tempo/stream/Voucher.d.ts.map +1 -0
  297. package/dist/tempo/stream/Voucher.js +98 -0
  298. package/dist/tempo/stream/Voucher.js.map +1 -0
  299. package/dist/tempo/stream/escrow.abi.d.ts +598 -0
  300. package/dist/tempo/stream/escrow.abi.d.ts.map +1 -0
  301. package/dist/tempo/stream/escrow.abi.js +760 -0
  302. package/dist/tempo/stream/escrow.abi.js.map +1 -0
  303. package/dist/tempo/stream/index.d.ts +8 -0
  304. package/dist/tempo/stream/index.d.ts.map +1 -0
  305. package/dist/tempo/stream/index.js +8 -0
  306. package/dist/tempo/stream/index.js.map +1 -0
  307. package/dist/viem/Account.d.ts +12 -0
  308. package/dist/viem/Account.d.ts.map +1 -0
  309. package/dist/viem/Account.js +14 -0
  310. package/dist/viem/Account.js.map +1 -0
  311. package/dist/viem/Client.d.ts +21 -0
  312. package/dist/viem/Client.d.ts.map +1 -0
  313. package/dist/viem/Client.js +19 -0
  314. package/dist/viem/Client.js.map +1 -0
  315. package/dist/zod.d.ts +17 -0
  316. package/dist/zod.d.ts.map +1 -0
  317. package/dist/zod.js +35 -0
  318. package/dist/zod.js.map +1 -0
  319. package/package.json +117 -4
  320. package/src/BodyDigest.test.ts +43 -0
  321. package/src/BodyDigest.ts +53 -0
  322. package/src/Challenge.test-d.ts +81 -0
  323. package/src/Challenge.test.ts +414 -0
  324. package/src/Challenge.ts +429 -0
  325. package/src/Credential.test.ts +227 -0
  326. package/src/Credential.ts +154 -0
  327. package/src/Errors.test.ts +402 -0
  328. package/src/Errors.ts +348 -0
  329. package/src/Expires.ts +34 -0
  330. package/src/Intent.test.ts +180 -0
  331. package/src/Intent.ts +109 -0
  332. package/src/Mcp.ts +81 -0
  333. package/src/MethodIntent.test.ts +303 -0
  334. package/src/MethodIntent.ts +388 -0
  335. package/src/PaymentRequest.test.ts +152 -0
  336. package/src/PaymentRequest.ts +107 -0
  337. package/src/Receipt.test.ts +98 -0
  338. package/src/Receipt.ts +129 -0
  339. package/src/Store.ts +84 -0
  340. package/src/cli.test.ts +542 -0
  341. package/src/cli.ts +1319 -0
  342. package/src/client/Methods.ts +3 -0
  343. package/src/client/Mppx.test-d.ts +90 -0
  344. package/src/client/Mppx.test.ts +468 -0
  345. package/src/client/Mppx.ts +149 -0
  346. package/src/client/Transport.test.ts +283 -0
  347. package/src/client/Transport.ts +115 -0
  348. package/src/client/index.ts +4 -0
  349. package/src/client/internal/Fetch.test-d.ts +57 -0
  350. package/src/client/internal/Fetch.test.ts +281 -0
  351. package/src/client/internal/Fetch.ts +157 -0
  352. package/src/env.d.ts +11 -0
  353. package/src/index.ts +12 -0
  354. package/src/internal/types.ts +403 -0
  355. package/src/mcp-sdk/client/McpClient.test-d.ts +109 -0
  356. package/src/mcp-sdk/client/McpClient.test.ts +219 -0
  357. package/src/mcp-sdk/client/McpClient.ts +187 -0
  358. package/src/mcp-sdk/client/index.ts +2 -0
  359. package/src/mcp-sdk/server/Transport.ts +94 -0
  360. package/src/mcp-sdk/server/index.ts +1 -0
  361. package/src/middlewares/elysia.ts +66 -0
  362. package/src/middlewares/express.test.ts +155 -0
  363. package/src/middlewares/express.ts +82 -0
  364. package/src/middlewares/hono.test.ts +148 -0
  365. package/src/middlewares/hono.ts +62 -0
  366. package/src/middlewares/internal/mppx.ts +30 -0
  367. package/src/middlewares/nextjs.test.ts +164 -0
  368. package/src/middlewares/nextjs.ts +66 -0
  369. package/src/proxy/Proxy.test.ts +472 -0
  370. package/src/proxy/Proxy.ts +175 -0
  371. package/src/proxy/Service.test.ts +125 -0
  372. package/src/proxy/Service.ts +227 -0
  373. package/src/proxy/index.ts +6 -0
  374. package/src/proxy/internal/Headers.test.ts +100 -0
  375. package/src/proxy/internal/Headers.ts +40 -0
  376. package/src/proxy/internal/Route.test.ts +143 -0
  377. package/src/proxy/internal/Route.ts +54 -0
  378. package/src/proxy/services/anthropic.ts +45 -0
  379. package/src/proxy/services/openai.test.ts +97 -0
  380. package/src/proxy/services/openai.ts +48 -0
  381. package/src/proxy/services/stripe.ts +49 -0
  382. package/src/server/Methods.ts +2 -0
  383. package/src/server/Mppx.test-d.ts +343 -0
  384. package/src/server/Mppx.test.ts +342 -0
  385. package/src/server/Mppx.ts +378 -0
  386. package/src/server/NodeListener.test.ts +188 -0
  387. package/src/server/NodeListener.ts +3 -0
  388. package/src/server/Request.test.ts +102 -0
  389. package/src/server/Request.ts +33 -0
  390. package/src/server/Response.test.ts +31 -0
  391. package/src/server/Response.ts +27 -0
  392. package/src/server/Transport.test.ts +294 -0
  393. package/src/server/Transport.ts +222 -0
  394. package/src/server/index.ts +8 -0
  395. package/src/stripe/Charge.integration.test.ts +326 -0
  396. package/src/stripe/Intents.test.ts +52 -0
  397. package/src/stripe/Intents.ts +27 -0
  398. package/src/stripe/client/Charge.ts +119 -0
  399. package/src/stripe/client/MethodIntents.ts +37 -0
  400. package/src/stripe/client/index.ts +2 -0
  401. package/src/stripe/index.ts +1 -0
  402. package/src/stripe/server/Charge.ts +121 -0
  403. package/src/stripe/server/MethodIntents.ts +24 -0
  404. package/src/stripe/server/index.ts +2 -0
  405. package/src/tempo/Attribution.test.ts +187 -0
  406. package/src/tempo/Attribution.ts +156 -0
  407. package/src/tempo/Intents.test.ts +84 -0
  408. package/src/tempo/Intents.ts +93 -0
  409. package/src/tempo/client/ChannelOps.ts +233 -0
  410. package/src/tempo/client/Charge.ts +84 -0
  411. package/src/tempo/client/MethodIntents.ts +28 -0
  412. package/src/tempo/client/Session.ts +369 -0
  413. package/src/tempo/client/SessionManager.test.ts +223 -0
  414. package/src/tempo/client/SessionManager.ts +270 -0
  415. package/src/tempo/client/index.ts +5 -0
  416. package/src/tempo/index.ts +2 -0
  417. package/src/tempo/internal/account.ts +47 -0
  418. package/src/tempo/internal/defaults.ts +20 -0
  419. package/src/tempo/internal/types.ts +8 -0
  420. package/src/tempo/server/Charge.test.ts +847 -0
  421. package/src/tempo/server/Charge.ts +309 -0
  422. package/src/tempo/server/MethodIntents.ts +29 -0
  423. package/src/tempo/server/Session.test.ts +1349 -0
  424. package/src/tempo/server/Session.ts +773 -0
  425. package/src/tempo/server/Sse.test.ts +289 -0
  426. package/src/tempo/server/index.ts +5 -0
  427. package/src/tempo/server/internal/transport.ts +153 -0
  428. package/src/tempo/stream/Chain.ts +333 -0
  429. package/src/tempo/stream/Channel.ts +50 -0
  430. package/src/tempo/stream/ChannelStore.test.ts +473 -0
  431. package/src/tempo/stream/ChannelStore.ts +202 -0
  432. package/src/tempo/stream/Receipt.test.ts +84 -0
  433. package/src/tempo/stream/Receipt.ts +45 -0
  434. package/src/tempo/stream/Sse.test.ts +401 -0
  435. package/src/tempo/stream/Sse.ts +375 -0
  436. package/src/tempo/stream/Types.ts +86 -0
  437. package/src/tempo/stream/Voucher.test.ts +134 -0
  438. package/src/tempo/stream/Voucher.ts +123 -0
  439. package/src/tempo/stream/escrow.abi.ts +759 -0
  440. package/src/tempo/stream/index.ts +7 -0
  441. package/src/tsconfig.json +10 -0
  442. package/src/viem/Account.test.ts +71 -0
  443. package/src/viem/Account.ts +30 -0
  444. package/src/viem/Client.test.ts +58 -0
  445. package/src/viem/Client.ts +33 -0
  446. package/src/zod.ts +47 -0
@@ -0,0 +1,773 @@
1
+ /**
2
+ * Server-side session payment method for request/response flows.
3
+ *
4
+ * Handles the full channel lifecycle (open, voucher, top-up, close) and
5
+ * one-shot settlement. Each incoming request carries a stream credential
6
+ * with a cumulative voucher that the server validates and records.
7
+ *
8
+ * Use `session()` for standard HTTP request/response patterns where each
9
+ * request is a discrete paid unit (for example, a page scrape or API call).
10
+ * For long-lived connections that emit multiple paid events over a single
11
+ * request, use {@link ../stream/Sse} instead.
12
+ */
13
+ import {
14
+ type Address,
15
+ type Hex,
16
+ parseUnits,
17
+ type Account as viem_Account,
18
+ type Client as viem_Client,
19
+ } from 'viem'
20
+ import { tempo as tempo_chain } from 'viem/chains'
21
+ import {
22
+ AmountExceedsDepositError,
23
+ BadRequestError,
24
+ ChannelClosedError,
25
+ ChannelNotFoundError,
26
+ DeltaTooSmallError,
27
+ InsufficientBalanceError,
28
+ InvalidSignatureError,
29
+ VerificationFailedError,
30
+ } from '../../Errors.js'
31
+ import type { Challenge, Credential } from '../../index.js'
32
+ import type { LooseOmit } from '../../internal/types.js'
33
+ import * as MethodIntent from '../../MethodIntent.js'
34
+ import * as Store from '../../Store.js'
35
+ import * as Client from '../../viem/Client.js'
36
+ import * as Intents from '../Intents.js'
37
+ import * as Account from '../internal/account.js'
38
+ import * as defaults from '../internal/defaults.js'
39
+ import type * as types from '../internal/types.js'
40
+ import {
41
+ broadcastOpenTransaction,
42
+ broadcastTopUpTransaction,
43
+ closeOnChain,
44
+ getOnChainChannel,
45
+ type OnChainChannel,
46
+ settleOnChain,
47
+ } from '../stream/Chain.js'
48
+ import * as ChannelStore from '../stream/ChannelStore.js'
49
+ import { createStreamReceipt } from '../stream/Receipt.js'
50
+ import type { SignedVoucher, StreamCredentialPayload, StreamReceipt } from '../stream/Types.js'
51
+ import { parseVoucherFromPayload, verifyVoucher } from '../stream/Voucher.js'
52
+ import * as Transport from './internal/transport.js'
53
+
54
+ /** Challenge methodDetails shape for stream intents. */
55
+ type StreamMethodDetails = {
56
+ escrowContract: Address
57
+ chainId: number
58
+ channelId?: Hex | undefined
59
+ minVoucherDelta?: string | undefined
60
+ feePayer?: boolean | undefined
61
+ }
62
+
63
+ /**
64
+ * Creates a stream payment server using the mppx Method.toServer() pattern.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * import { Mppx, tempo } from 'mppx/server'
69
+ *
70
+ * const mppx = Mppx.create({
71
+ * methods: [
72
+ * tempo.session({
73
+ * store: myStore,
74
+ * recipient: '0x...',
75
+ * currency: '0x...',
76
+ * escrowContract: '0x...',
77
+ * }),
78
+ * ],
79
+ * realm: 'my-app',
80
+ * secretKey: '...',
81
+ * })
82
+ * ```
83
+ */
84
+ export function session<const parameters extends session.Parameters>(p?: parameters) {
85
+ const parameters = p as parameters
86
+ const {
87
+ amount,
88
+ currency,
89
+ decimals = defaults.decimals,
90
+ store: rawStore = Store.memory(),
91
+ suggestedDeposit,
92
+ unitType,
93
+ } = parameters
94
+
95
+ const store = ChannelStore.fromStore(rawStore)
96
+
97
+ const getClient = Client.getResolver({
98
+ chain: tempo_chain,
99
+ getClient: parameters.getClient,
100
+ rpcUrl: defaults.rpcUrl,
101
+ })
102
+ const { account, recipient, feePayer } = Account.resolve(parameters)
103
+
104
+ type Transport = parameters['stream'] extends false | undefined ? undefined : Transport.Sse
105
+ const transport = parameters.stream
106
+ ? Transport.sse({
107
+ store,
108
+ ...(typeof parameters.stream === 'object' ? parameters.stream : undefined),
109
+ })
110
+ : undefined
111
+
112
+ type Defaults = session.DeriveDefaults<parameters>
113
+ return MethodIntent.toServer<typeof Intents.session, Defaults, Transport>(Intents.session, {
114
+ defaults: {
115
+ amount,
116
+ currency,
117
+ decimals,
118
+ recipient,
119
+ suggestedDeposit,
120
+ unitType,
121
+ } as unknown as Defaults,
122
+
123
+ transport: transport as never,
124
+
125
+ // TODO: dedupe `{charge,stream}.request`
126
+ async request({ credential, request }) {
127
+ // Extract chainId from request or default.
128
+ const chainId = await (async () => {
129
+ if (request.chainId) return request.chainId
130
+ if (parameters.testnet) return defaults.testnetChainId
131
+ return (await getClient({})).chain?.id
132
+ })()
133
+
134
+ // Validate chainId.
135
+ const client = await (async () => {
136
+ try {
137
+ return await getClient({ chainId })
138
+ } catch {
139
+ throw new Error(`No client configured with chainId ${chainId}.`)
140
+ }
141
+ })()
142
+ if (client.chain?.id !== chainId)
143
+ throw new Error(`Client not configured with chainId ${chainId}.`)
144
+
145
+ const resolvedEscrow =
146
+ request.escrowContract ??
147
+ parameters.escrowContract ??
148
+ defaults.escrowContract[chainId as keyof typeof defaults.escrowContract]
149
+
150
+ // Extract feePayer.
151
+ const resolvedFeePayer = (() => {
152
+ const account = typeof request.feePayer === 'object' ? request.feePayer : feePayer
153
+ const requested = request.feePayer !== false && (account ?? feePayer)
154
+ if (credential) return account
155
+ if (requested) return true
156
+ return undefined
157
+ })()
158
+
159
+ return { ...request, chainId, escrowContract: resolvedEscrow, feePayer: resolvedFeePayer }
160
+ },
161
+
162
+ async verify({ credential }) {
163
+ const { challenge, payload } = credential as Credential.Credential<StreamCredentialPayload>
164
+
165
+ const methodDetails = challenge.request.methodDetails as StreamMethodDetails
166
+ const client = await getClient({ chainId: methodDetails.chainId })
167
+
168
+ const resolvedFeePayer = methodDetails.feePayer === true ? feePayer : undefined
169
+ const minVoucherDelta = parseUnits(parameters.minVoucherDelta ?? '0', decimals)
170
+ const effectiveMinVoucherDelta = methodDetails.minVoucherDelta
171
+ ? BigInt(methodDetails.minVoucherDelta)
172
+ : minVoucherDelta
173
+
174
+ let streamReceipt: StreamReceipt
175
+
176
+ switch (payload.action) {
177
+ case 'open':
178
+ streamReceipt = await handleOpen(
179
+ store,
180
+ client,
181
+ challenge,
182
+ payload,
183
+ methodDetails,
184
+ resolvedFeePayer,
185
+ )
186
+ break
187
+
188
+ case 'topUp':
189
+ streamReceipt = await handleTopUp(
190
+ store,
191
+ client,
192
+ challenge,
193
+ payload,
194
+ methodDetails,
195
+ resolvedFeePayer,
196
+ )
197
+ break
198
+
199
+ case 'voucher':
200
+ streamReceipt = await handleVoucher(
201
+ store,
202
+ client,
203
+ effectiveMinVoucherDelta,
204
+ challenge,
205
+ payload,
206
+ methodDetails,
207
+ )
208
+ break
209
+
210
+ case 'close':
211
+ streamReceipt = await handleClose(
212
+ store,
213
+ client,
214
+ challenge,
215
+ payload,
216
+ methodDetails,
217
+ account,
218
+ )
219
+ break
220
+
221
+ default:
222
+ throw new BadRequestError({
223
+ reason: `unknown action: ${(payload as { action: string }).action}`,
224
+ })
225
+ }
226
+
227
+ return streamReceipt
228
+ },
229
+
230
+ // This hook acts as a gate: when it returns a Response, `withReceipt()`
231
+ // in Mppx.ts short-circuits and returns that response directly without
232
+ // invoking the user's route handler. When it returns undefined, the
233
+ // user's handler runs normally and serves content.
234
+ //
235
+ // Management actions (open, topUp, close) are always gated — they
236
+ // return 204 regardless of request method.
237
+ //
238
+ // Voucher POSTs are gated only when they have no request body, which
239
+ // signals a mid-stream voucher update (the client is just topping up
240
+ // the channel balance). Voucher POSTs WITH a body are content requests
241
+ // (e.g., an API call to a POST endpoint) and fall through to the
242
+ // user's handler. GET requests with vouchers always fall through so
243
+ // auto-mode clients (whose fetch wrapper bundles open+voucher into a
244
+ // single GET retry) receive content as expected.
245
+ respond({ credential, input }) {
246
+ const { payload } = credential as Credential.Credential<StreamCredentialPayload>
247
+
248
+ if (payload.action === 'close') return new Response(null, { status: 204 })
249
+
250
+ const isManagement = payload.action === 'open' || payload.action === 'topUp'
251
+ if (isManagement && input.method === 'POST') return new Response(null, { status: 204 })
252
+
253
+ const isVoucher = payload.action === 'voucher'
254
+ if (!isVoucher) return undefined
255
+
256
+ // Only gate voucher POSTs with no body (mid-stream balance updates).
257
+ // POSTs with a body are content requests that should reach the handler.
258
+ if (input.method !== 'POST') return undefined
259
+ const contentLength = input.headers.get('content-length')
260
+ if (contentLength !== null && contentLength !== '0') return undefined
261
+ if (input.headers.has('transfer-encoding')) return undefined
262
+ return new Response(null, { status: 204 })
263
+ },
264
+ })
265
+ }
266
+
267
+ export declare namespace session {
268
+ type Defaults = LooseOmit<
269
+ MethodIntent.RequestDefaults<typeof Intents.session>,
270
+ 'feePayer' | 'recipient'
271
+ >
272
+
273
+ type Parameters = {
274
+ /** Minimum voucher delta to accept (numeric string, default: "0"). */
275
+ minVoucherDelta?: string | undefined
276
+ /** Store backend for channel state. */
277
+ store?: Store.Store | undefined
278
+ /**
279
+ * Enable SSE streaming.
280
+ *
281
+ * Pass `true` to enable with defaults, or an options object
282
+ * to configure the stream (e.g. `{ poll: true }` for
283
+ * Cloudflare Workers compatibility).
284
+ */
285
+ stream?: boolean | Transport.sse.Options | undefined
286
+ /** Testnet mode. */
287
+ testnet?: boolean | undefined
288
+ } & Account.resolve.Parameters &
289
+ Client.getResolver.Parameters &
290
+ Defaults
291
+
292
+ type DeriveDefaults<parameters extends Parameters> = types.DeriveDefaults<
293
+ parameters,
294
+ Defaults
295
+ > & {
296
+ decimals: number
297
+ escrowContract: Address
298
+ }
299
+ }
300
+
301
+ /**
302
+ * One-shot settle: reads highest voucher from store and submits on-chain.
303
+ */
304
+ export async function settle(
305
+ store: ChannelStore.ChannelStore,
306
+ client: viem_Client,
307
+ channelId: Hex,
308
+ escrowContract?: Address | undefined,
309
+ ): Promise<Hex> {
310
+ const channel = await store.getChannel(channelId)
311
+ if (!channel) throw new ChannelNotFoundError({ reason: 'channel not found' })
312
+ if (!channel.highestVoucher) throw new VerificationFailedError({ reason: 'no voucher to settle' })
313
+
314
+ const chainId = client.chain?.id
315
+ const resolvedEscrow =
316
+ escrowContract ?? defaults.escrowContract[chainId as keyof typeof defaults.escrowContract]
317
+ if (!resolvedEscrow) throw new Error(`No escrow contract for chainId ${chainId}.`)
318
+
319
+ const settledAmount = channel.highestVoucher.cumulativeAmount
320
+ const txHash = await settleOnChain(client, resolvedEscrow, channel.highestVoucher)
321
+
322
+ await store.updateChannel(channelId, (current) => {
323
+ if (!current) return null
324
+ const nextSettled =
325
+ settledAmount > current.settledOnChain ? settledAmount : current.settledOnChain
326
+ return { ...current, settledOnChain: nextSettled }
327
+ })
328
+
329
+ return txHash
330
+ }
331
+
332
+ /**
333
+ * Charge against a channel's balance.
334
+ *
335
+ * Exported so consumers can deduct from a channel outside the `stream()`
336
+ * handler (e.g., custom middleware, the SSE `serve()` loop, or direct tests).
337
+ *
338
+ * Delegates to the shared `deductFromChannel` atomic helper and translates
339
+ * failure modes into typed errors (`InsufficientBalanceError`, `ChannelClosedError`).
340
+ */
341
+ export async function charge(
342
+ store: ChannelStore.ChannelStore,
343
+ channelId: Hex,
344
+ amount: bigint,
345
+ ): Promise<ChannelStore.State> {
346
+ let result: Awaited<ReturnType<typeof ChannelStore.deductFromChannel>>
347
+ try {
348
+ result = await ChannelStore.deductFromChannel(store, channelId, amount)
349
+ } catch {
350
+ throw new ChannelClosedError({ reason: 'channel not found' })
351
+ }
352
+ if (!result.ok) {
353
+ const available = result.channel.highestVoucherAmount - result.channel.spent
354
+ throw new InsufficientBalanceError({
355
+ reason: `requested ${amount}, available ${available}`,
356
+ })
357
+ }
358
+ return result.channel
359
+ }
360
+
361
+ /**
362
+ * Validate on-chain channel state.
363
+ */
364
+ function validateOnChainChannel(
365
+ onChain: OnChainChannel,
366
+ recipient: Address,
367
+ currency: Address,
368
+ amount?: bigint,
369
+ ): void {
370
+ if (onChain.deposit === 0n) {
371
+ throw new ChannelNotFoundError({ reason: 'channel not funded on-chain' })
372
+ }
373
+ if (onChain.finalized) {
374
+ throw new ChannelClosedError({ reason: 'channel is finalized on-chain' })
375
+ }
376
+ if (onChain.closeRequestedAt !== 0n) {
377
+ throw new ChannelClosedError({ reason: 'channel has a pending close request' })
378
+ }
379
+ if (onChain.payee.toLowerCase() !== recipient.toLowerCase()) {
380
+ throw new VerificationFailedError({
381
+ reason: 'on-chain payee does not match server destination',
382
+ })
383
+ }
384
+ if (onChain.token.toLowerCase() !== currency.toLowerCase()) {
385
+ throw new VerificationFailedError({ reason: 'on-chain token does not match server token' })
386
+ }
387
+ if (amount !== undefined && onChain.deposit - onChain.settled < amount) {
388
+ throw new InsufficientBalanceError({
389
+ reason: 'channel available balance insufficient for requested amount',
390
+ })
391
+ }
392
+ }
393
+
394
+ /**
395
+ * Shared logic for verifying an incremental voucher and updating channel state.
396
+ * Used by both handleVoucher and (indirectly) handleOpen.
397
+ */
398
+ async function verifyAndAcceptVoucher(parameters: {
399
+ store: ChannelStore.ChannelStore
400
+ minVoucherDelta: bigint
401
+ challenge: Challenge.Challenge
402
+ channel: ChannelStore.State
403
+ channelId: Hex
404
+ voucher: SignedVoucher
405
+ onChain: OnChainChannel
406
+ methodDetails: StreamMethodDetails
407
+ }): Promise<StreamReceipt> {
408
+ const { store, minVoucherDelta, challenge, channel, channelId, voucher, onChain, methodDetails } =
409
+ parameters
410
+
411
+ if (onChain.finalized) {
412
+ throw new ChannelClosedError({ reason: 'channel is finalized on-chain' })
413
+ }
414
+ if (onChain.closeRequestedAt !== 0n) {
415
+ throw new ChannelClosedError({ reason: 'channel has a pending close request' })
416
+ }
417
+
418
+ if (voucher.cumulativeAmount < onChain.settled) {
419
+ throw new VerificationFailedError({
420
+ reason: 'voucher cumulativeAmount is below on-chain settled amount',
421
+ })
422
+ }
423
+
424
+ if (voucher.cumulativeAmount > onChain.deposit) {
425
+ throw new AmountExceedsDepositError({ reason: 'voucher amount exceeds on-chain deposit' })
426
+ }
427
+
428
+ if (voucher.cumulativeAmount <= channel.highestVoucherAmount) {
429
+ return createStreamReceipt({
430
+ challengeId: challenge.id,
431
+ channelId,
432
+ acceptedCumulative: channel.highestVoucherAmount,
433
+ spent: channel.spent,
434
+ units: channel.units,
435
+ })
436
+ }
437
+
438
+ const delta = voucher.cumulativeAmount - channel.highestVoucherAmount
439
+ if (delta < minVoucherDelta) {
440
+ throw new DeltaTooSmallError({
441
+ reason: `voucher delta ${delta} below minimum ${minVoucherDelta}`,
442
+ })
443
+ }
444
+
445
+ const isValid = await verifyVoucher(
446
+ methodDetails.escrowContract,
447
+ methodDetails.chainId,
448
+ voucher,
449
+ channel.authorizedSigner,
450
+ )
451
+
452
+ if (!isValid) {
453
+ throw new InvalidSignatureError({ reason: 'invalid voucher signature' })
454
+ }
455
+
456
+ const updated = await store.updateChannel(channelId, (current) => {
457
+ if (!current) throw new ChannelNotFoundError({ reason: 'channel not found' })
458
+ if (voucher.cumulativeAmount > current.highestVoucherAmount) {
459
+ return {
460
+ ...current,
461
+ deposit: onChain.deposit,
462
+ highestVoucherAmount: voucher.cumulativeAmount,
463
+ highestVoucher: voucher,
464
+ }
465
+ }
466
+ return current
467
+ })
468
+ if (!updated) throw new ChannelNotFoundError({ reason: 'channel not found' })
469
+
470
+ return createStreamReceipt({
471
+ challengeId: challenge.id,
472
+ channelId,
473
+ acceptedCumulative: updated.highestVoucherAmount,
474
+ spent: updated.spent,
475
+ units: updated.units,
476
+ })
477
+ }
478
+
479
+ /**
480
+ * Handle 'open' action - broadcast open transaction, verify voucher, and create channel.
481
+ */
482
+ async function handleOpen(
483
+ store: ChannelStore.ChannelStore,
484
+ client: viem_Client,
485
+ challenge: Challenge.Challenge,
486
+ payload: StreamCredentialPayload & { action: 'open' },
487
+ methodDetails: StreamMethodDetails,
488
+ feePayer: viem_Account | undefined,
489
+ ): Promise<StreamReceipt> {
490
+ const voucher = parseVoucherFromPayload(
491
+ payload.channelId,
492
+ payload.cumulativeAmount,
493
+ payload.signature,
494
+ )
495
+
496
+ const recipient = challenge.request.recipient as Address
497
+ const currency = challenge.request.currency as Address
498
+ const amount = challenge.request.amount ? BigInt(challenge.request.amount as string) : undefined
499
+
500
+ const { onChain, txHash } = await broadcastOpenTransaction({
501
+ client,
502
+ serializedTransaction: payload.transaction,
503
+ escrowContract: methodDetails.escrowContract,
504
+ channelId: payload.channelId,
505
+ recipient,
506
+ currency,
507
+ feePayer,
508
+ })
509
+
510
+ validateOnChainChannel(onChain, recipient, currency, amount)
511
+
512
+ const authorizedSigner =
513
+ onChain.authorizedSigner === '0x0000000000000000000000000000000000000000'
514
+ ? onChain.payer
515
+ : onChain.authorizedSigner
516
+
517
+ if (voucher.cumulativeAmount > onChain.deposit) {
518
+ throw new AmountExceedsDepositError({ reason: 'voucher amount exceeds on-chain deposit' })
519
+ }
520
+
521
+ if (voucher.cumulativeAmount < onChain.settled) {
522
+ throw new VerificationFailedError({
523
+ reason: 'voucher cumulativeAmount is below on-chain settled amount',
524
+ })
525
+ }
526
+
527
+ const isValid = await verifyVoucher(
528
+ methodDetails.escrowContract,
529
+ methodDetails.chainId,
530
+ voucher,
531
+ authorizedSigner,
532
+ )
533
+
534
+ if (!isValid) {
535
+ throw new InvalidSignatureError({ reason: 'invalid voucher signature' })
536
+ }
537
+
538
+ const updated = await store.updateChannel(payload.channelId, (existing) => {
539
+ if (existing) {
540
+ if (voucher.cumulativeAmount < existing.settledOnChain) {
541
+ throw new VerificationFailedError({
542
+ reason: 'voucher amount is below settled on-chain amount',
543
+ })
544
+ }
545
+
546
+ if (voucher.cumulativeAmount > existing.highestVoucherAmount) {
547
+ return {
548
+ ...existing,
549
+ deposit: onChain.deposit,
550
+ highestVoucherAmount: voucher.cumulativeAmount,
551
+ highestVoucher: voucher,
552
+ authorizedSigner,
553
+ }
554
+ }
555
+ return {
556
+ ...existing,
557
+ deposit: onChain.deposit,
558
+ authorizedSigner,
559
+ }
560
+ }
561
+ return {
562
+ channelId: payload.channelId,
563
+ chainId: methodDetails.chainId,
564
+ escrowContract: methodDetails.escrowContract,
565
+ payer: onChain.payer,
566
+ payee: onChain.payee,
567
+ token: onChain.token,
568
+ authorizedSigner,
569
+ deposit: onChain.deposit,
570
+ settledOnChain: 0n,
571
+ highestVoucherAmount: voucher.cumulativeAmount,
572
+ highestVoucher: voucher,
573
+ spent: 0n,
574
+ units: 0,
575
+ finalized: false,
576
+ createdAt: new Date().toISOString(),
577
+ }
578
+ })
579
+
580
+ if (!updated) throw new VerificationFailedError({ reason: 'failed to create channel' })
581
+
582
+ return createStreamReceipt({
583
+ challengeId: challenge.id,
584
+ channelId: payload.channelId,
585
+ acceptedCumulative: updated.highestVoucherAmount,
586
+ spent: updated.spent,
587
+ units: updated.units,
588
+ txHash,
589
+ })
590
+ }
591
+
592
+ /**
593
+ * Handle 'topUp' action - broadcast topUp transaction and update channel deposit.
594
+ *
595
+ * Per spec Section 8.3.2, topUp payloads contain only the transaction and
596
+ * additionalDeposit — no voucher. The client must send a separate 'voucher'
597
+ * action to authorize spending the new funds.
598
+ */
599
+ async function handleTopUp(
600
+ store: ChannelStore.ChannelStore,
601
+ client: viem_Client,
602
+ challenge: Challenge.Challenge,
603
+ payload: StreamCredentialPayload & { action: 'topUp' },
604
+ methodDetails: StreamMethodDetails,
605
+ feePayer: viem_Account | undefined,
606
+ ): Promise<StreamReceipt> {
607
+ const channel = await store.getChannel(payload.channelId)
608
+ if (!channel) {
609
+ throw new ChannelNotFoundError({ reason: 'channel not found' })
610
+ }
611
+
612
+ const declaredDeposit = BigInt(payload.additionalDeposit)
613
+
614
+ const { newDeposit: onChainDeposit } = await broadcastTopUpTransaction({
615
+ client,
616
+ serializedTransaction: payload.transaction,
617
+ escrowContract: methodDetails.escrowContract,
618
+ channelId: payload.channelId,
619
+ declaredDeposit,
620
+ previousDeposit: channel.deposit,
621
+ feePayer,
622
+ })
623
+
624
+ const updated = await store.updateChannel(payload.channelId, (current) => {
625
+ if (!current) throw new ChannelNotFoundError({ reason: 'channel not found' })
626
+ return { ...current, deposit: onChainDeposit }
627
+ })
628
+
629
+ return createStreamReceipt({
630
+ challengeId: challenge.id,
631
+ channelId: payload.channelId,
632
+ acceptedCumulative: updated?.highestVoucherAmount ?? channel.highestVoucherAmount,
633
+ spent: updated?.spent ?? channel.spent,
634
+ units: updated?.units ?? channel.units,
635
+ })
636
+ }
637
+
638
+ /**
639
+ * Handle 'voucher' action - verify and accept a new voucher.
640
+ */
641
+ async function handleVoucher(
642
+ store: ChannelStore.ChannelStore,
643
+ _client: viem_Client,
644
+ minVoucherDelta: bigint,
645
+ challenge: Challenge.Challenge,
646
+ payload: StreamCredentialPayload & { action: 'voucher' },
647
+ methodDetails: StreamMethodDetails,
648
+ ): Promise<StreamReceipt> {
649
+ const channel = await store.getChannel(payload.channelId)
650
+ if (!channel) {
651
+ throw new ChannelNotFoundError({ reason: 'channel not found' })
652
+ }
653
+ if (channel.finalized) {
654
+ throw new ChannelClosedError({ reason: 'channel is finalized' })
655
+ }
656
+
657
+ const voucher = parseVoucherFromPayload(
658
+ payload.channelId,
659
+ payload.cumulativeAmount,
660
+ payload.signature,
661
+ )
662
+
663
+ // Use locally-stored channel state as a trusted cache instead of
664
+ // reading on-chain for every voucher. The on-chain state is verified
665
+ // during `open` and `topUp` actions — subsequent vouchers within the
666
+ // same session can safely use the cached deposit/signer values.
667
+ // This avoids an RPC round-trip per voucher, which is critical for
668
+ // high-frequency SSE streaming where vouchers arrive per-token.
669
+ const cachedOnChain: OnChainChannel = {
670
+ payer: channel.payer,
671
+ payee: channel.payee,
672
+ token: channel.token,
673
+ deposit: channel.deposit,
674
+ settled: channel.settledOnChain,
675
+ finalized: channel.finalized,
676
+ authorizedSigner: channel.authorizedSigner,
677
+ closeRequestedAt: 0n,
678
+ }
679
+
680
+ return verifyAndAcceptVoucher({
681
+ store,
682
+ minVoucherDelta,
683
+ challenge,
684
+ channel,
685
+ channelId: payload.channelId,
686
+ voucher,
687
+ onChain: cachedOnChain,
688
+ methodDetails,
689
+ })
690
+ }
691
+
692
+ /**
693
+ * Handle 'close' action - verify final voucher and close channel.
694
+ */
695
+ async function handleClose(
696
+ store: ChannelStore.ChannelStore,
697
+ client: viem_Client,
698
+ challenge: Challenge.Challenge,
699
+ payload: StreamCredentialPayload & { action: 'close' },
700
+ methodDetails: StreamMethodDetails,
701
+ account?: viem_Account,
702
+ ): Promise<StreamReceipt> {
703
+ const channel = await store.getChannel(payload.channelId)
704
+ if (!channel) {
705
+ throw new ChannelNotFoundError({ reason: 'channel not found' })
706
+ }
707
+ if (channel.finalized) {
708
+ throw new ChannelClosedError({ reason: 'channel is already finalized' })
709
+ }
710
+
711
+ const voucher = parseVoucherFromPayload(
712
+ payload.channelId,
713
+ payload.cumulativeAmount,
714
+ payload.signature,
715
+ )
716
+
717
+ if (voucher.cumulativeAmount < channel.highestVoucherAmount) {
718
+ throw new VerificationFailedError({
719
+ reason: 'close voucher amount must be >= highest accepted voucher',
720
+ })
721
+ }
722
+
723
+ const onChain = await getOnChainChannel(client, methodDetails.escrowContract, payload.channelId)
724
+
725
+ if (onChain.finalized) {
726
+ throw new ChannelClosedError({ reason: 'channel is finalized on-chain' })
727
+ }
728
+
729
+ if (voucher.cumulativeAmount < onChain.settled) {
730
+ throw new VerificationFailedError({
731
+ reason: 'close voucher cumulativeAmount is below on-chain settled amount',
732
+ })
733
+ }
734
+
735
+ if (voucher.cumulativeAmount > onChain.deposit) {
736
+ throw new AmountExceedsDepositError({
737
+ reason: 'close voucher amount exceeds on-chain deposit',
738
+ })
739
+ }
740
+
741
+ const isValid = await verifyVoucher(
742
+ methodDetails.escrowContract,
743
+ methodDetails.chainId,
744
+ voucher,
745
+ channel.authorizedSigner,
746
+ )
747
+
748
+ if (!isValid) {
749
+ throw new InvalidSignatureError({ reason: 'invalid voucher signature' })
750
+ }
751
+
752
+ const txHash = await closeOnChain(client, methodDetails.escrowContract, voucher, account)
753
+
754
+ const updated = await store.updateChannel(payload.channelId, (current) => {
755
+ if (!current) return null
756
+ return {
757
+ ...current,
758
+ deposit: onChain.deposit,
759
+ highestVoucherAmount: voucher.cumulativeAmount,
760
+ highestVoucher: voucher,
761
+ finalized: true,
762
+ }
763
+ })
764
+
765
+ return createStreamReceipt({
766
+ challengeId: challenge.id,
767
+ channelId: payload.channelId,
768
+ acceptedCumulative: voucher.cumulativeAmount,
769
+ spent: updated?.spent ?? channel.spent,
770
+ units: updated?.units ?? channel.units,
771
+ ...(txHash !== undefined && { txHash }),
772
+ })
773
+ }