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,429 @@
1
+ import { Base64, Bytes, Hash } from 'ox'
2
+ import type { OneOf } from './internal/types.js'
3
+ import type * as MethodIntent from './MethodIntent.js'
4
+ import * as PaymentRequest from './PaymentRequest.js'
5
+ import * as z from './zod.js'
6
+
7
+ /**
8
+ * Schema for a payment challenge.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { Challenge } from 'mppx'
13
+ *
14
+ * const challenge = Challenge.Schema.parse(data)
15
+ * ```
16
+ */
17
+ export const Schema = z.object({
18
+ /** Optional human-readable description of the payment. */
19
+ description: z.optional(z.string()),
20
+ /** Optional digest of the request body (format: "sha-256=base64hash"). */
21
+ digest: z.optional(z.string().check(z.regex(/^sha-256=/, 'Invalid digest format'))),
22
+ /** Optional expiration timestamp (ISO 8601). */
23
+ expires: z.optional(z.datetime()),
24
+ /** Unique challenge identifier (HMAC-bound). */
25
+ id: z.string(),
26
+ /** Intent type (e.g., "charge", "authorize"). */
27
+ intent: z.string(),
28
+ /** Payment method (e.g., "tempo", "stripe"). */
29
+ method: z.string(),
30
+ /** Server realm (e.g., hostname). */
31
+ realm: z.string(),
32
+ /** Method-specific request data. */
33
+ request: z.record(z.string(), z.unknown()),
34
+ })
35
+
36
+ /**
37
+ * A parsed payment challenge from a `WWW-Authenticate` header.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * import { Challenge } from 'mppx'
42
+ *
43
+ * const challenge: Challenge.Challenge = {
44
+ * id: 'abc123',
45
+ * realm: 'api.example.com',
46
+ * method: 'tempo',
47
+ * intent: 'charge',
48
+ * request: { amount: '1000000', currency: '0x...', recipient: '0x...' },
49
+ * }
50
+ * ```
51
+ */
52
+ export type Challenge<
53
+ request = Record<string, unknown>,
54
+ intent extends string = string,
55
+ method extends string = string,
56
+ > = Omit<z.infer<typeof Schema>, 'intent' | 'method' | 'request'> & {
57
+ intent: intent
58
+ method: method
59
+ request: request
60
+ }
61
+
62
+ /**
63
+ * Extracts a union of challenge types from an array of method intents.
64
+ */
65
+ export type FromMethods<methods extends readonly MethodIntent.AnyMethodIntent[]> = {
66
+ [method in keyof methods]: Challenge<
67
+ z.output<methods[method]['schema']['request']>,
68
+ methods[method]['name'],
69
+ methods[method]['method']
70
+ >
71
+ }[number]
72
+
73
+ /**
74
+ * Creates a challenge from the given parameters.
75
+ *
76
+ * If `secretKey` option is provided, the challenge ID is computed as HMAC-SHA256
77
+ * over the challenge parameters (realm|method|intent|request|expires|digest),
78
+ * cryptographically binding the ID to its contents.
79
+ *
80
+ * @param parameters - Challenge parameters.
81
+ * @param options - Optional settings including secretKey for HMAC-bound ID.
82
+ * @returns A challenge.
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * import { Challenge } from 'mppx'
87
+ *
88
+ * // With HMAC-bound ID (recommended for servers)
89
+ * const challenge = Challenge.from(
90
+ * {
91
+ * realm: 'api.example.com',
92
+ * method: 'tempo',
93
+ * intent: 'charge',
94
+ * request: { amount: '1000000', currency: '0x...', recipient: '0x...' },
95
+ * },
96
+ * { secretKey: 'my-secret' },
97
+ * )
98
+ *
99
+ * // With explicit ID
100
+ * const challenge = Challenge.from({
101
+ * id: 'abc123',
102
+ * realm: 'api.example.com',
103
+ * method: 'tempo',
104
+ * intent: 'charge',
105
+ * request: { amount: '1000000', currency: '0x...', recipient: '0x...' },
106
+ * })
107
+ * ```
108
+ */
109
+ export function from<
110
+ const parameters extends from.Parameters,
111
+ const methods extends readonly MethodIntent.AnyMethodIntent[] | undefined = undefined,
112
+ >(parameters: parameters, options?: from.Options<methods>): from.ReturnType<parameters, methods> {
113
+ void options
114
+ const { description, digest, method: methodName, intent, realm, request, secretKey } = parameters
115
+
116
+ const expires = (parameters.expires ?? request.expires) as string
117
+ const id = secretKey
118
+ ? computeId({ ...parameters, expires }, { secretKey })
119
+ : (parameters as { id: string }).id
120
+
121
+ return Schema.parse({
122
+ id,
123
+ realm,
124
+ method: methodName,
125
+ intent,
126
+ request,
127
+ ...(description && { description }),
128
+ ...(digest && { digest }),
129
+ ...(expires && { expires }),
130
+ }) as from.ReturnType<parameters, methods>
131
+ }
132
+
133
+ export declare namespace from {
134
+ type Options<methods extends readonly MethodIntent.AnyMethodIntent[] | undefined = undefined> = {
135
+ methods?: methods
136
+ }
137
+
138
+ type Parameters = OneOf<
139
+ | {
140
+ /** Explicit challenge ID. */
141
+ id: string
142
+ }
143
+ | {
144
+ /** Secret key for HMAC-bound challenge ID. */
145
+ secretKey: string
146
+ }
147
+ > & {
148
+ /** Optional human-readable description of the payment. */
149
+ description?: string | undefined
150
+ /** Optional digest of the request body. */
151
+ digest?: string | undefined
152
+ /** Optional expiration timestamp (ISO 8601). */
153
+ expires?: string | undefined
154
+ /** Intent type (e.g., "charge", "authorize"). */
155
+ intent: string
156
+ /** Payment method (e.g., "tempo", "stripe"). */
157
+ method: string
158
+ /** Server realm (e.g., hostname). */
159
+ realm: string
160
+ /** Method-specific request data. */
161
+ request: PaymentRequest.Request
162
+ }
163
+
164
+ type ReturnType<
165
+ parameters extends Parameters,
166
+ methods extends readonly MethodIntent.AnyMethodIntent[] | undefined = undefined,
167
+ > = methods extends readonly MethodIntent.AnyMethodIntent[]
168
+ ? FromMethods<methods>
169
+ : Challenge<parameters['request']>
170
+ }
171
+
172
+ /**
173
+ * Creates a validated challenge from a method intent.
174
+ *
175
+ * If `secretKey` option is provided, the challenge ID is computed as HMAC-SHA256
176
+ * over the challenge parameters, cryptographically binding the ID to its contents.
177
+ *
178
+ * @param intent - The method intent to validate against.
179
+ * @param parameters - Challenge parameters (realm, request, optional expires/digest, and id if no secretKey).
180
+ * @param options - Optional settings including secretKey for HMAC-bound ID.
181
+ * @returns A validated challenge.
182
+ *
183
+ * @example
184
+ * ```ts
185
+ * import { Challenge } from 'mppx'
186
+ * import { Intents } from 'mppx/tempo'
187
+ *
188
+ * // With HMAC-bound ID (recommended for servers)
189
+ * const challenge = Challenge.fromIntent(
190
+ * Intents.charge,
191
+ * {
192
+ * realm: 'api.example.com',
193
+ * request: {
194
+ * amount: '1000000',
195
+ * currency: '0x20c0000000000000000000000000000000000001',
196
+ * recipient: '0x742d35Cc6634C0532925a3b844Bc9e7595f8fE00',
197
+ * expires: '2025-01-06T12:00:00Z',
198
+ * },
199
+ * },
200
+ * { secretKey: 'my-secret' },
201
+ * )
202
+ * ```
203
+ */
204
+ export function fromIntent<const intent extends MethodIntent.MethodIntent>(
205
+ intent: intent,
206
+ parameters: fromIntent.Parameters<intent>,
207
+ ): fromIntent.ReturnType<intent> {
208
+ const { method, name } = intent
209
+ const { description, digest, expires, id, realm, secretKey } = parameters
210
+
211
+ const request = PaymentRequest.fromIntent(intent, parameters.request)
212
+
213
+ return from({
214
+ ...(id ? { id } : { secretKey }),
215
+ realm,
216
+ method,
217
+ intent: name,
218
+ request,
219
+ description,
220
+ digest,
221
+ expires,
222
+ } as from.Parameters) as fromIntent.ReturnType<intent>
223
+ }
224
+
225
+ export declare namespace fromIntent {
226
+ type Parameters<intent extends MethodIntent.MethodIntent> = OneOf<
227
+ | {
228
+ /** Explicit challenge ID. */
229
+ id: string
230
+ }
231
+ | {
232
+ /** Secret key for HMAC-bound challenge ID. */
233
+ secretKey: string
234
+ }
235
+ > & {
236
+ /** Optional human-readable description of the payment. */
237
+ description?: string | undefined
238
+ /** Optional digest of the request body. */
239
+ digest?: string | undefined
240
+ /** Optional expiration timestamp (ISO 8601). */
241
+ expires?: string | undefined
242
+ /** Server realm (e.g., hostname). */
243
+ realm: string
244
+ /** Method-specific request data. */
245
+ request: z.input<intent['schema']['request']>
246
+ }
247
+
248
+ type ReturnType<intent extends MethodIntent.MethodIntent> = Challenge<
249
+ z.output<intent['schema']['request']>
250
+ >
251
+ }
252
+
253
+ /**
254
+ * Serializes a challenge to the WWW-Authenticate header format.
255
+ *
256
+ * @param challenge - The challenge to serialize.
257
+ * @returns A string suitable for the WWW-Authenticate header value.
258
+ *
259
+ * @example
260
+ * ```ts
261
+ * import { Challenge } from 'mppx'
262
+ *
263
+ * const header = Challenge.serialize(challenge)
264
+ * // => 'Payment id="abc123", realm="api.example.com", method="tempo", intent="charge", request="eyJhbW91bnQiOi..."'
265
+ * ```
266
+ */
267
+ export function serialize(challenge: Challenge): string {
268
+ const parts = [
269
+ `id="${challenge.id}"`,
270
+ `realm="${challenge.realm}"`,
271
+ `method="${challenge.method}"`,
272
+ `intent="${challenge.intent}"`,
273
+ `request="${PaymentRequest.serialize(challenge.request)}"`,
274
+ ]
275
+
276
+ if (challenge.description !== undefined) parts.push(`description="${challenge.description}"`)
277
+ if (challenge.digest !== undefined) parts.push(`digest="${challenge.digest}"`)
278
+ if (challenge.expires !== undefined) parts.push(`expires="${challenge.expires}"`)
279
+
280
+ return `Payment ${parts.join(', ')}`
281
+ }
282
+
283
+ /**
284
+ * Deserializes a WWW-Authenticate header value to a challenge.
285
+ *
286
+ * @example
287
+ * ```ts
288
+ * import { Challenge } from 'mppx'
289
+ *
290
+ * const challenge = Challenge.deserialize(header)
291
+ *
292
+ * // With methods for type narrowing
293
+ * const challenge = Challenge.deserialize(header, { methods })
294
+ * ```
295
+ *
296
+ * @param header - The WWW-Authenticate header value.
297
+ * @param options - Optional settings to narrow the challenge type.
298
+ * @returns The deserialized challenge.
299
+ */
300
+ export function deserialize<
301
+ const methods extends readonly MethodIntent.AnyMethodIntent[] | undefined = undefined,
302
+ >(value: string, options?: from.Options<methods>): from.ReturnType<from.Parameters, methods> {
303
+ const prefixMatch = value.match(/^Payment\s+(.+)$/i)
304
+ if (!prefixMatch?.[1]) throw new Error('Missing Payment scheme.')
305
+
306
+ const params = prefixMatch[1]
307
+ const result: Record<string, string> = {}
308
+
309
+ for (const match of params.matchAll(/(\w+)="([^"]+)"/g)) {
310
+ const key = match[1]
311
+ const value = match[2]
312
+ if (key && value) result[key] = value
313
+ }
314
+
315
+ const { request, ...rest } = result
316
+ if (!request) throw new Error('Missing request parameter.')
317
+
318
+ return from(
319
+ {
320
+ ...rest,
321
+ request: PaymentRequest.deserialize(request),
322
+ } as from.Parameters,
323
+ options,
324
+ )
325
+ }
326
+
327
+ /**
328
+ * Extracts the challenge from a Headers object.
329
+ *
330
+ * @param headers - The HTTP headers.
331
+ * @param options - Optional settings to narrow the challenge type.
332
+ * @returns The deserialized challenge.
333
+ *
334
+ * @example
335
+ * ```ts
336
+ * import { Challenge } from 'mppx'
337
+ *
338
+ * const challenge = Challenge.fromHeaders(response.headers)
339
+ *
340
+ * // With methods for type narrowing
341
+ * const challenge = Challenge.fromHeaders(response.headers, { methods })
342
+ * ```
343
+ */
344
+ export function fromHeaders<
345
+ const methods extends readonly MethodIntent.AnyMethodIntent[] | undefined = undefined,
346
+ >(headers: Headers, options?: from.Options<methods>): from.ReturnType<from.Parameters, methods> {
347
+ const header = headers.get('WWW-Authenticate')
348
+ if (!header) throw new Error('Missing WWW-Authenticate header.')
349
+ return deserialize(header, options)
350
+ }
351
+
352
+ /**
353
+ * Extracts the challenge from a Response's WWW-Authenticate header.
354
+ *
355
+ * @param response - The HTTP response (must be 402 status).
356
+ * @param options - Optional settings to narrow the challenge type.
357
+ * @returns The deserialized challenge.
358
+ *
359
+ * @example
360
+ * ```ts
361
+ * import { Challenge } from 'mppx'
362
+ *
363
+ * const response = await fetch('/resource')
364
+ * if (response.status === 402)
365
+ * const challenge = Challenge.fromResponse(response)
366
+ *
367
+ * // With methods for type narrowing
368
+ * const challenge = Challenge.fromResponse(response, { methods })
369
+ * ```
370
+ */
371
+ export function fromResponse<
372
+ const methods extends readonly MethodIntent.AnyMethodIntent[] | undefined = undefined,
373
+ >(response: Response, options?: from.Options<methods>): from.ReturnType<from.Parameters, methods> {
374
+ if (response.status !== 402) throw new Error('Response status is not 402.')
375
+ return fromHeaders(response.headers, options)
376
+ }
377
+
378
+ /**
379
+ * Verifies that a challenge ID matches the expected HMAC for the given parameters.
380
+ *
381
+ * @param challenge - The challenge to verify.
382
+ * @param options - Options including the secret key.
383
+ * @returns True if the challenge ID is valid, false otherwise.
384
+ *
385
+ * @example
386
+ * ```ts
387
+ * import { Challenge } from 'mppx'
388
+ *
389
+ * const isValid = Challenge.verify(challenge, { secretKey: 'my-secret' })
390
+ * ```
391
+ */
392
+ export function verify(challenge: Challenge, options: verify.Options): boolean {
393
+ const expectedId = computeId(challenge, options)
394
+ return constantTimeEqual(challenge.id, expectedId)
395
+ }
396
+
397
+ export declare namespace verify {
398
+ type Options = {
399
+ /** Secret key for HMAC-bound challenge ID verification. */
400
+ secretKey: string
401
+ }
402
+ }
403
+
404
+ /** @internal Computes HMAC-SHA256 challenge ID from parameters. */
405
+ function computeId(challenge: Omit<Challenge, 'id'>, options: { secretKey: string }): string {
406
+ const input = [
407
+ challenge.realm,
408
+ challenge.method,
409
+ challenge.intent,
410
+ PaymentRequest.serialize(challenge.request),
411
+ challenge.expires ?? '',
412
+ challenge.digest ?? '',
413
+ ]
414
+ .filter(Boolean)
415
+ .join('|')
416
+
417
+ const key = Bytes.fromString(options.secretKey)
418
+ const data = Bytes.fromString(input)
419
+ const mac = Hash.hmac256(key, data, { as: 'Bytes' })
420
+ return Base64.fromBytes(mac, { url: true, pad: false })
421
+ }
422
+
423
+ /** @internal Constant-time string comparison to prevent timing attacks. */
424
+ function constantTimeEqual(a: string, b: string): boolean {
425
+ if (a.length !== b.length) return false
426
+ let result = 0
427
+ for (let i = 0; i < a.length; i++) result |= a.charCodeAt(i) ^ b.charCodeAt(i)
428
+ return result === 0
429
+ }
@@ -0,0 +1,227 @@
1
+ import { Challenge, Credential } from 'mppx'
2
+ import { describe, expect, test } from 'vitest'
3
+
4
+ const challenge = Challenge.from({
5
+ id: 'x7Tg2pLqR9mKvNwY3hBcZa',
6
+ realm: 'api.example.com',
7
+ method: 'tempo',
8
+ intent: 'charge',
9
+ request: { amount: '1000' },
10
+ })
11
+
12
+ describe('from', () => {
13
+ test('behavior: creates credential with parsed request', () => {
14
+ const credential = Credential.from({
15
+ challenge,
16
+ payload: { signature: '0x1234' },
17
+ })
18
+
19
+ expect(credential).toMatchInlineSnapshot(`
20
+ {
21
+ "challenge": {
22
+ "id": "x7Tg2pLqR9mKvNwY3hBcZa",
23
+ "intent": "charge",
24
+ "method": "tempo",
25
+ "realm": "api.example.com",
26
+ "request": {
27
+ "amount": "1000",
28
+ },
29
+ },
30
+ "payload": {
31
+ "signature": "0x1234",
32
+ },
33
+ }
34
+ `)
35
+ })
36
+
37
+ test('behavior: creates credential with source', () => {
38
+ const credential = Credential.from({
39
+ challenge,
40
+ source: 'did:pkh:eip155:1:0x1234567890abcdef',
41
+ payload: { hash: '0xabcd' },
42
+ })
43
+
44
+ expect(credential).toMatchInlineSnapshot(`
45
+ {
46
+ "challenge": {
47
+ "id": "x7Tg2pLqR9mKvNwY3hBcZa",
48
+ "intent": "charge",
49
+ "method": "tempo",
50
+ "realm": "api.example.com",
51
+ "request": {
52
+ "amount": "1000",
53
+ },
54
+ },
55
+ "payload": {
56
+ "hash": "0xabcd",
57
+ },
58
+ "source": "did:pkh:eip155:1:0x1234567890abcdef",
59
+ }
60
+ `)
61
+ })
62
+
63
+ test('behavior: includes optional challenge fields', () => {
64
+ const credential = Credential.from({
65
+ challenge: {
66
+ ...challenge,
67
+ expires: '2025-01-15T12:00:00Z',
68
+ digest: 'sha-256=abc123',
69
+ },
70
+ payload: { signature: '0x1234' },
71
+ })
72
+
73
+ expect(credential.challenge.expires).toBe('2025-01-15T12:00:00Z')
74
+ expect(credential.challenge.digest).toBe('sha-256=abc123')
75
+ })
76
+ })
77
+
78
+ describe('serialize', () => {
79
+ test('behavior: serializes credential to Authorization header format', () => {
80
+ const credential = Credential.from({
81
+ challenge,
82
+ payload: { signature: '0x1234' },
83
+ })
84
+
85
+ const header = Credential.serialize(credential)
86
+
87
+ expect(header).toMatch(/^Payment /)
88
+ const deserialized = Credential.deserialize(header)
89
+ expect(deserialized.challenge.request).toEqual({ amount: '1000' })
90
+ })
91
+ })
92
+
93
+ describe('deserialize', () => {
94
+ test('behavior: deserializes to credential with parsed request', () => {
95
+ const original = Credential.from({
96
+ challenge,
97
+ payload: { signature: '0x1234' },
98
+ })
99
+ const header = Credential.serialize(original)
100
+
101
+ const credential = Credential.deserialize(header)
102
+
103
+ expect(credential).toMatchInlineSnapshot(`
104
+ {
105
+ "challenge": {
106
+ "id": "x7Tg2pLqR9mKvNwY3hBcZa",
107
+ "intent": "charge",
108
+ "method": "tempo",
109
+ "realm": "api.example.com",
110
+ "request": {
111
+ "amount": "1000",
112
+ },
113
+ },
114
+ "payload": {
115
+ "signature": "0x1234",
116
+ },
117
+ }
118
+ `)
119
+ })
120
+
121
+ test('behavior: roundtrip preserves data', () => {
122
+ const original = Credential.from({
123
+ challenge,
124
+ source: 'did:pkh:eip155:1:0x1234567890abcdef',
125
+ payload: { hash: '0xabcd' },
126
+ })
127
+
128
+ const header = Credential.serialize(original)
129
+ const deserialized = Credential.deserialize(header)
130
+
131
+ expect(deserialized.challenge.id).toBe(original.challenge.id)
132
+ expect(deserialized.challenge.request).toEqual(original.challenge.request)
133
+ expect(deserialized.payload).toEqual(original.payload)
134
+ expect(deserialized.source).toBe(original.source)
135
+ })
136
+
137
+ test('error: throws for missing Payment scheme', () => {
138
+ expect(() => Credential.deserialize('Bearer abc123')).toThrow('Missing Payment scheme.')
139
+ })
140
+
141
+ test('error: throws for invalid base64url', () => {
142
+ expect(() => Credential.deserialize('Payment !!invalid!!')).toThrow(
143
+ 'Invalid base64url or JSON.',
144
+ )
145
+ })
146
+
147
+ test('error: throws for invalid JSON', () => {
148
+ const invalidJson = btoa('not valid json')
149
+ expect(() => Credential.deserialize(`Payment ${invalidJson}`)).toThrow(
150
+ 'Invalid base64url or JSON.',
151
+ )
152
+ })
153
+
154
+ test('error: throws for invalid challenge (missing required fields)', () => {
155
+ const invalidCredential = {
156
+ challenge: {
157
+ id: 'abc123',
158
+ // missing realm, method, intent, request
159
+ },
160
+ payload: { signature: '0x1234' },
161
+ }
162
+ const encoded = btoa(JSON.stringify(invalidCredential))
163
+ expect(() => Credential.deserialize(`Payment ${encoded}`)).toThrow()
164
+ })
165
+
166
+ test('error: throws for invalid challenge (invalid digest format)', () => {
167
+ const invalidCredential = {
168
+ challenge: {
169
+ id: 'abc123',
170
+ realm: 'api.example.com',
171
+ method: 'tempo',
172
+ intent: 'charge',
173
+ request: 'eyJhbW91bnQiOiIxMDAwIn0',
174
+ digest: 'invalid-digest-format',
175
+ },
176
+ payload: { signature: '0x1234' },
177
+ }
178
+ const encoded = btoa(JSON.stringify(invalidCredential))
179
+ expect(() => Credential.deserialize(`Payment ${encoded}`)).toThrow()
180
+ })
181
+ })
182
+
183
+ describe('fromRequest', () => {
184
+ test('behavior: extracts credential from Request', () => {
185
+ const original = Credential.from({
186
+ challenge,
187
+ payload: { signature: '0x1234' },
188
+ })
189
+ const request = new Request('https://api.example.com/resource', {
190
+ headers: { Authorization: Credential.serialize(original) },
191
+ })
192
+
193
+ const credential = Credential.fromRequest(request)
194
+
195
+ expect(credential.challenge.id).toBe('x7Tg2pLqR9mKvNwY3hBcZa')
196
+ expect(credential.challenge.request).toEqual({ amount: '1000' })
197
+ expect(credential.payload).toEqual({ signature: '0x1234' })
198
+ })
199
+
200
+ test('behavior: extracts Payment from multiple Authorization schemes', () => {
201
+ const original = Credential.from({
202
+ challenge,
203
+ payload: { signature: '0x1234' },
204
+ })
205
+ const headers = new Headers()
206
+ headers.append('Authorization', 'Bearer some-jwt-token')
207
+ headers.append('Authorization', Credential.serialize(original))
208
+ const request = new Request('https://api.example.com/resource', { headers })
209
+
210
+ const credential = Credential.fromRequest(request)
211
+
212
+ expect(credential.challenge.id).toBe('x7Tg2pLqR9mKvNwY3hBcZa')
213
+ expect(credential.payload).toEqual({ signature: '0x1234' })
214
+ })
215
+
216
+ test('error: throws for missing Authorization header', () => {
217
+ const request = new Request('https://api.example.com/resource')
218
+ expect(() => Credential.fromRequest(request)).toThrow('Missing Authorization header.')
219
+ })
220
+
221
+ test('error: throws when no Payment scheme present', () => {
222
+ const request = new Request('https://api.example.com/resource', {
223
+ headers: { Authorization: 'Bearer some-jwt-token' },
224
+ })
225
+ expect(() => Credential.fromRequest(request)).toThrow('Missing Payment scheme.')
226
+ })
227
+ })