mppx 0.8.17 → 0.8.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (914) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +15 -0
  3. package/dist/Challenge.js +21 -4
  4. package/dist/bin.js +3 -3
  5. package/dist/cli/THIRD-PARTY-LICENSES.txt +328 -0
  6. package/dist/cli/cli.js +253 -1505
  7. package/dist/cli/plugins/index.js +19 -5
  8. package/dist/cli/plugins/tempo.js +7 -3
  9. package/dist/cli/sessions/Manager.js +7 -4
  10. package/dist/cli/sessions/request.d.ts +2 -0
  11. package/dist/cli/sessions/request.js +18 -0
  12. package/dist/cli/sessions/store.js +28 -14
  13. package/dist/discovery/Discovery.js +2 -1
  14. package/dist/evm/client/Charge.js +8 -0
  15. package/dist/server/core.d.ts +9 -0
  16. package/dist/server/core.js +9 -0
  17. package/dist/stripe/server/Charge.js +2 -4
  18. package/dist/stripe/server/Methods.d.ts +2 -2
  19. package/dist/stripe/server/Methods.js +17 -11
  20. package/dist/stripe/server/internal/html.gen.d.ts +1 -1
  21. package/dist/stripe/server/internal/html.gen.js +1 -1
  22. package/dist/stripe/server/spt.d.ts +8 -0
  23. package/dist/stripe/server/spt.js +8 -0
  24. package/dist/tempo/Methods.d.ts +13 -0
  25. package/dist/tempo/Methods.js +14 -3
  26. package/dist/tempo/client/Charge.d.ts +3 -0
  27. package/dist/tempo/client/Charge.js +31 -9
  28. package/dist/tempo/client/Methods.d.ts +13 -0
  29. package/dist/tempo/internal/defaults.d.ts +11 -0
  30. package/dist/tempo/internal/defaults.js +11 -0
  31. package/dist/tempo/internal/machine-token-charge.d.ts +51 -0
  32. package/dist/tempo/internal/machine-token-charge.js +113 -0
  33. package/dist/tempo/internal/machine-token-session.d.ts +88 -0
  34. package/dist/tempo/internal/machine-token-session.js +173 -0
  35. package/dist/tempo/internal/signature-envelope.d.ts +14 -0
  36. package/dist/tempo/internal/signature-envelope.js +28 -0
  37. package/dist/tempo/legacy/client/ChannelOps.d.ts +2 -2
  38. package/dist/tempo/legacy/client/ChannelOps.js +11 -5
  39. package/dist/tempo/legacy/client/Session.d.ts +13 -1
  40. package/dist/tempo/legacy/client/Session.js +3 -3
  41. package/dist/tempo/legacy/client/SessionManager.d.ts +3 -1
  42. package/dist/tempo/legacy/client/SessionManager.js +2 -0
  43. package/dist/tempo/server/Charge.d.ts +3 -0
  44. package/dist/tempo/server/Charge.js +58 -17
  45. package/dist/tempo/server/Methods.d.ts +21 -4
  46. package/dist/tempo/server/Methods.js +8 -4
  47. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  48. package/dist/tempo/server/internal/html.gen.js +1 -1
  49. package/dist/tempo/session/client/ChannelOps.d.ts +18 -3
  50. package/dist/tempo/session/client/ChannelOps.js +35 -6
  51. package/dist/tempo/session/client/ChannelStore.d.ts +0 -5
  52. package/dist/tempo/session/client/ChannelStore.js +2 -2
  53. package/dist/tempo/session/client/CredentialState.d.ts +17 -2
  54. package/dist/tempo/session/client/CredentialState.js +77 -76
  55. package/dist/tempo/session/client/Runtime.d.ts +4 -2
  56. package/dist/tempo/session/client/Runtime.js +21 -5
  57. package/dist/tempo/session/client/Session.d.ts +15 -3
  58. package/dist/tempo/session/client/Session.js +186 -63
  59. package/dist/tempo/session/client/SessionManager.d.ts +3 -1
  60. package/dist/tempo/session/client/SessionManager.js +35 -11
  61. package/dist/tempo/session/client/Transports.d.ts +2 -0
  62. package/dist/tempo/session/client/Transports.js +12 -2
  63. package/dist/tempo/session/precompile/Chain.d.ts +10 -2
  64. package/dist/tempo/session/precompile/Chain.js +50 -10
  65. package/dist/tempo/session/precompile/Protocol.d.ts +15 -0
  66. package/dist/tempo/session/precompile/Protocol.js +7 -0
  67. package/dist/tempo/session/precompile/Voucher.d.ts +3 -1
  68. package/dist/tempo/session/precompile/Voucher.js +6 -12
  69. package/dist/tempo/session/server/ChannelStore.d.ts +6 -0
  70. package/dist/tempo/session/server/ChannelStore.js +11 -4
  71. package/dist/tempo/session/server/CredentialVerification.d.ts +11 -9
  72. package/dist/tempo/session/server/CredentialVerification.js +185 -109
  73. package/dist/tempo/session/server/MeteredStream.d.ts +1 -1
  74. package/dist/tempo/session/server/MeteredStream.js +5 -4
  75. package/dist/tempo/session/server/RequestState.d.ts +19 -0
  76. package/dist/tempo/session/server/RequestState.js +61 -15
  77. package/dist/tempo/session/server/Session.d.ts +14 -2
  78. package/dist/tempo/session/server/Session.js +37 -6
  79. package/dist/tempo/session/server/Settlement.d.ts +11 -4
  80. package/dist/tempo/session/server/Settlement.js +53 -21
  81. package/dist/x402/client/Exact.d.ts +8 -0
  82. package/dist/x402/client/Exact.js +40 -10
  83. package/dist/x402/server/EvmCharge.d.ts +22 -0
  84. package/dist/x402/server/EvmCharge.js +23 -1
  85. package/package.json +15 -37
  86. package/dist/BodyDigest.d.ts.map +0 -1
  87. package/dist/BodyDigest.js.map +0 -1
  88. package/dist/Challenge.d.ts.map +0 -1
  89. package/dist/Challenge.js.map +0 -1
  90. package/dist/Constants.d.ts.map +0 -1
  91. package/dist/Constants.js.map +0 -1
  92. package/dist/Credential.d.ts.map +0 -1
  93. package/dist/Credential.js.map +0 -1
  94. package/dist/Errors.d.ts.map +0 -1
  95. package/dist/Errors.js.map +0 -1
  96. package/dist/Expires.d.ts.map +0 -1
  97. package/dist/Expires.js.map +0 -1
  98. package/dist/Html.d.ts.map +0 -1
  99. package/dist/Html.js.map +0 -1
  100. package/dist/Mcp.d.ts.map +0 -1
  101. package/dist/Mcp.js.map +0 -1
  102. package/dist/Method.d.ts.map +0 -1
  103. package/dist/Method.js.map +0 -1
  104. package/dist/PaymentRequest.d.ts.map +0 -1
  105. package/dist/PaymentRequest.js.map +0 -1
  106. package/dist/Receipt.d.ts.map +0 -1
  107. package/dist/Receipt.js.map +0 -1
  108. package/dist/Store.d.ts.map +0 -1
  109. package/dist/Store.js.map +0 -1
  110. package/dist/attestation/Client.d.ts.map +0 -1
  111. package/dist/attestation/Client.js.map +0 -1
  112. package/dist/attestation/Constants.d.ts.map +0 -1
  113. package/dist/attestation/Constants.js.map +0 -1
  114. package/dist/attestation/Server.d.ts.map +0 -1
  115. package/dist/attestation/Server.js.map +0 -1
  116. package/dist/attestation/Types.d.ts.map +0 -1
  117. package/dist/attestation/Types.js.map +0 -1
  118. package/dist/attestation/index.d.ts.map +0 -1
  119. package/dist/attestation/index.js.map +0 -1
  120. package/dist/attestation/internal/HttpMessageSignature.d.ts.map +0 -1
  121. package/dist/attestation/internal/HttpMessageSignature.js.map +0 -1
  122. package/dist/attestation/internal/SigningContext.d.ts.map +0 -1
  123. package/dist/attestation/internal/SigningContext.js.map +0 -1
  124. package/dist/bin.d.ts.map +0 -1
  125. package/dist/bin.js.map +0 -1
  126. package/dist/cli/account.d.ts.map +0 -1
  127. package/dist/cli/account.js.map +0 -1
  128. package/dist/cli/cli.d.ts.map +0 -1
  129. package/dist/cli/cli.js.map +0 -1
  130. package/dist/cli/config.d.ts.map +0 -1
  131. package/dist/cli/config.js.map +0 -1
  132. package/dist/cli/internal.d.ts.map +0 -1
  133. package/dist/cli/internal.js.map +0 -1
  134. package/dist/cli/plugins/evm.d.ts.map +0 -1
  135. package/dist/cli/plugins/evm.js.map +0 -1
  136. package/dist/cli/plugins/index.d.ts.map +0 -1
  137. package/dist/cli/plugins/index.js.map +0 -1
  138. package/dist/cli/plugins/plugin.d.ts.map +0 -1
  139. package/dist/cli/plugins/plugin.js.map +0 -1
  140. package/dist/cli/plugins/stripe.d.ts.map +0 -1
  141. package/dist/cli/plugins/stripe.js.map +0 -1
  142. package/dist/cli/plugins/tempo.d.ts.map +0 -1
  143. package/dist/cli/plugins/tempo.js.map +0 -1
  144. package/dist/cli/sessions/Manager.d.ts.map +0 -1
  145. package/dist/cli/sessions/Manager.js.map +0 -1
  146. package/dist/cli/sessions/commands.d.ts.map +0 -1
  147. package/dist/cli/sessions/commands.js.map +0 -1
  148. package/dist/cli/sessions/request.d.ts.map +0 -1
  149. package/dist/cli/sessions/request.js.map +0 -1
  150. package/dist/cli/sessions/store.d.ts.map +0 -1
  151. package/dist/cli/sessions/store.js.map +0 -1
  152. package/dist/cli/utils.d.ts.map +0 -1
  153. package/dist/cli/utils.js.map +0 -1
  154. package/dist/cli/validate/challenge.d.ts.map +0 -1
  155. package/dist/cli/validate/challenge.js.map +0 -1
  156. package/dist/cli/validate/discovery.d.ts.map +0 -1
  157. package/dist/cli/validate/discovery.js.map +0 -1
  158. package/dist/cli/validate/helpers.d.ts.map +0 -1
  159. package/dist/cli/validate/helpers.js.map +0 -1
  160. package/dist/cli/validate/index.d.ts.map +0 -1
  161. package/dist/cli/validate/index.js.map +0 -1
  162. package/dist/cli/validate/messages.d.ts.map +0 -1
  163. package/dist/cli/validate/messages.js.map +0 -1
  164. package/dist/cli/validate/payment.d.ts.map +0 -1
  165. package/dist/cli/validate/payment.js.map +0 -1
  166. package/dist/client/Methods.d.ts.map +0 -1
  167. package/dist/client/Methods.js.map +0 -1
  168. package/dist/client/Mppx.d.ts.map +0 -1
  169. package/dist/client/Mppx.js.map +0 -1
  170. package/dist/client/Transport.d.ts.map +0 -1
  171. package/dist/client/Transport.js.map +0 -1
  172. package/dist/client/index.d.ts.map +0 -1
  173. package/dist/client/index.js.map +0 -1
  174. package/dist/client/internal/Fetch.d.ts.map +0 -1
  175. package/dist/client/internal/Fetch.js.map +0 -1
  176. package/dist/client/internal/MethodChallenge.d.ts.map +0 -1
  177. package/dist/client/internal/MethodChallenge.js.map +0 -1
  178. package/dist/client/internal/MethodResponse.d.ts.map +0 -1
  179. package/dist/client/internal/MethodResponse.js.map +0 -1
  180. package/dist/client/internal/protocols/Mcp.d.ts.map +0 -1
  181. package/dist/client/internal/protocols/Mcp.js.map +0 -1
  182. package/dist/client/internal/protocols/Mpp.d.ts.map +0 -1
  183. package/dist/client/internal/protocols/Mpp.js.map +0 -1
  184. package/dist/client/internal/protocols/Protocol.d.ts.map +0 -1
  185. package/dist/client/internal/protocols/Protocol.js.map +0 -1
  186. package/dist/client/internal/protocols/Shared.d.ts.map +0 -1
  187. package/dist/client/internal/protocols/Shared.js.map +0 -1
  188. package/dist/client/internal/protocols/X402.d.ts.map +0 -1
  189. package/dist/client/internal/protocols/X402.js.map +0 -1
  190. package/dist/client/node.d.ts.map +0 -1
  191. package/dist/client/node.js.map +0 -1
  192. package/dist/discovery/Discovery.d.ts.map +0 -1
  193. package/dist/discovery/Discovery.js.map +0 -1
  194. package/dist/discovery/OpenApi.d.ts.map +0 -1
  195. package/dist/discovery/OpenApi.js.map +0 -1
  196. package/dist/discovery/Validate.d.ts.map +0 -1
  197. package/dist/discovery/Validate.js.map +0 -1
  198. package/dist/discovery/index.d.ts.map +0 -1
  199. package/dist/discovery/index.js.map +0 -1
  200. package/dist/discovery/internal/Metadata.d.ts.map +0 -1
  201. package/dist/discovery/internal/Metadata.js.map +0 -1
  202. package/dist/evm/Assets.d.ts.map +0 -1
  203. package/dist/evm/Assets.js.map +0 -1
  204. package/dist/evm/Chains.d.ts.map +0 -1
  205. package/dist/evm/Chains.js.map +0 -1
  206. package/dist/evm/Methods.d.ts.map +0 -1
  207. package/dist/evm/Methods.js.map +0 -1
  208. package/dist/evm/Types.d.ts.map +0 -1
  209. package/dist/evm/Types.js.map +0 -1
  210. package/dist/evm/client/Charge.d.ts.map +0 -1
  211. package/dist/evm/client/Charge.js.map +0 -1
  212. package/dist/evm/client/Methods.d.ts.map +0 -1
  213. package/dist/evm/client/Methods.js.map +0 -1
  214. package/dist/evm/client/index.d.ts.map +0 -1
  215. package/dist/evm/client/index.js.map +0 -1
  216. package/dist/evm/index.d.ts.map +0 -1
  217. package/dist/evm/index.js.map +0 -1
  218. package/dist/evm/server/Charge.d.ts.map +0 -1
  219. package/dist/evm/server/Charge.js.map +0 -1
  220. package/dist/evm/server/Methods.d.ts.map +0 -1
  221. package/dist/evm/server/Methods.js.map +0 -1
  222. package/dist/evm/server/index.d.ts.map +0 -1
  223. package/dist/evm/server/index.js.map +0 -1
  224. package/dist/index.d.ts.map +0 -1
  225. package/dist/index.js.map +0 -1
  226. package/dist/internal/AcceptPayment.d.ts.map +0 -1
  227. package/dist/internal/AcceptPayment.js.map +0 -1
  228. package/dist/internal/HeaderCodec.d.ts.map +0 -1
  229. package/dist/internal/HeaderCodec.js.map +0 -1
  230. package/dist/internal/constantTimeEqual.d.ts.map +0 -1
  231. package/dist/internal/constantTimeEqual.js.map +0 -1
  232. package/dist/internal/env.d.ts.map +0 -1
  233. package/dist/internal/env.js.map +0 -1
  234. package/dist/internal/types.d.ts.map +0 -1
  235. package/dist/internal/types.js.map +0 -1
  236. package/dist/mcp/client/McpClient.d.ts.map +0 -1
  237. package/dist/mcp/client/McpClient.js.map +0 -1
  238. package/dist/mcp/client/index.d.ts.map +0 -1
  239. package/dist/mcp/client/index.js.map +0 -1
  240. package/dist/mcp/server/Transport.d.ts.map +0 -1
  241. package/dist/mcp/server/Transport.js.map +0 -1
  242. package/dist/mcp/server/index.d.ts.map +0 -1
  243. package/dist/mcp/server/index.js.map +0 -1
  244. package/dist/middlewares/elysia.d.ts.map +0 -1
  245. package/dist/middlewares/elysia.js.map +0 -1
  246. package/dist/middlewares/express.d.ts.map +0 -1
  247. package/dist/middlewares/express.js.map +0 -1
  248. package/dist/middlewares/hono.d.ts.map +0 -1
  249. package/dist/middlewares/hono.js.map +0 -1
  250. package/dist/middlewares/internal/mppx.d.ts.map +0 -1
  251. package/dist/middlewares/internal/mppx.js.map +0 -1
  252. package/dist/middlewares/nextjs.d.ts.map +0 -1
  253. package/dist/middlewares/nextjs.js.map +0 -1
  254. package/dist/proxy/Proxy.d.ts.map +0 -1
  255. package/dist/proxy/Proxy.js.map +0 -1
  256. package/dist/proxy/Service.d.ts.map +0 -1
  257. package/dist/proxy/Service.js.map +0 -1
  258. package/dist/proxy/index.d.ts.map +0 -1
  259. package/dist/proxy/index.js.map +0 -1
  260. package/dist/proxy/internal/Headers.d.ts.map +0 -1
  261. package/dist/proxy/internal/Headers.js.map +0 -1
  262. package/dist/proxy/internal/Route.d.ts.map +0 -1
  263. package/dist/proxy/internal/Route.js.map +0 -1
  264. package/dist/proxy/services/anthropic.d.ts.map +0 -1
  265. package/dist/proxy/services/anthropic.js.map +0 -1
  266. package/dist/proxy/services/openai.d.ts.map +0 -1
  267. package/dist/proxy/services/openai.js.map +0 -1
  268. package/dist/proxy/services/stripe.d.ts.map +0 -1
  269. package/dist/proxy/services/stripe.js.map +0 -1
  270. package/dist/server/Methods.d.ts.map +0 -1
  271. package/dist/server/Methods.js.map +0 -1
  272. package/dist/server/Mppx.d.ts.map +0 -1
  273. package/dist/server/Mppx.js.map +0 -1
  274. package/dist/server/NodeListener.d.ts.map +0 -1
  275. package/dist/server/NodeListener.js.map +0 -1
  276. package/dist/server/Request.d.ts.map +0 -1
  277. package/dist/server/Request.js.map +0 -1
  278. package/dist/server/Response.d.ts.map +0 -1
  279. package/dist/server/Response.js.map +0 -1
  280. package/dist/server/Transport.d.ts.map +0 -1
  281. package/dist/server/Transport.js.map +0 -1
  282. package/dist/server/index.d.ts.map +0 -1
  283. package/dist/server/index.js.map +0 -1
  284. package/dist/server/internal/html/compose.main.gen.d.ts.map +0 -1
  285. package/dist/server/internal/html/compose.main.gen.js.map +0 -1
  286. package/dist/server/internal/html/config.d.ts.map +0 -1
  287. package/dist/server/internal/html/config.js.map +0 -1
  288. package/dist/server/internal/html/constants.d.ts.map +0 -1
  289. package/dist/server/internal/html/constants.js.map +0 -1
  290. package/dist/server/internal/html/serviceWorker.client.d.ts.map +0 -1
  291. package/dist/server/internal/html/serviceWorker.client.js.map +0 -1
  292. package/dist/server/internal/html/serviceWorker.gen.d.ts.map +0 -1
  293. package/dist/server/internal/html/serviceWorker.gen.js.map +0 -1
  294. package/dist/server/internal/scope.d.ts.map +0 -1
  295. package/dist/server/internal/scope.js.map +0 -1
  296. package/dist/stripe/Methods.d.ts.map +0 -1
  297. package/dist/stripe/Methods.js.map +0 -1
  298. package/dist/stripe/client/Charge.d.ts.map +0 -1
  299. package/dist/stripe/client/Charge.js.map +0 -1
  300. package/dist/stripe/client/Methods.d.ts.map +0 -1
  301. package/dist/stripe/client/Methods.js.map +0 -1
  302. package/dist/stripe/client/index.d.ts.map +0 -1
  303. package/dist/stripe/client/index.js.map +0 -1
  304. package/dist/stripe/index.d.ts.map +0 -1
  305. package/dist/stripe/index.js.map +0 -1
  306. package/dist/stripe/internal/constants.d.ts.map +0 -1
  307. package/dist/stripe/internal/constants.js.map +0 -1
  308. package/dist/stripe/internal/types.d.ts.map +0 -1
  309. package/dist/stripe/internal/types.js.map +0 -1
  310. package/dist/stripe/server/Charge.d.ts.map +0 -1
  311. package/dist/stripe/server/Charge.js.map +0 -1
  312. package/dist/stripe/server/Methods.d.ts.map +0 -1
  313. package/dist/stripe/server/Methods.js.map +0 -1
  314. package/dist/stripe/server/index.d.ts.map +0 -1
  315. package/dist/stripe/server/index.js.map +0 -1
  316. package/dist/stripe/server/internal/deposit-address.d.ts.map +0 -1
  317. package/dist/stripe/server/internal/deposit-address.js.map +0 -1
  318. package/dist/stripe/server/internal/html/types.d.ts.map +0 -1
  319. package/dist/stripe/server/internal/html/types.js.map +0 -1
  320. package/dist/stripe/server/internal/html.gen.d.ts.map +0 -1
  321. package/dist/stripe/server/internal/html.gen.js.map +0 -1
  322. package/dist/stripe/server/internal/record-payment.d.ts.map +0 -1
  323. package/dist/stripe/server/internal/record-payment.js.map +0 -1
  324. package/dist/stripe/server/internal/request.d.ts.map +0 -1
  325. package/dist/stripe/server/internal/request.js.map +0 -1
  326. package/dist/tap/Constants.d.ts.map +0 -1
  327. package/dist/tap/Constants.js.map +0 -1
  328. package/dist/tap/Types.d.ts.map +0 -1
  329. package/dist/tap/Types.js.map +0 -1
  330. package/dist/tap/client/index.d.ts.map +0 -1
  331. package/dist/tap/client/index.js.map +0 -1
  332. package/dist/tap/index.d.ts.map +0 -1
  333. package/dist/tap/index.js.map +0 -1
  334. package/dist/tap/server/index.d.ts.map +0 -1
  335. package/dist/tap/server/index.js.map +0 -1
  336. package/dist/tempo/Attribution.d.ts.map +0 -1
  337. package/dist/tempo/Attribution.js.map +0 -1
  338. package/dist/tempo/Methods.d.ts.map +0 -1
  339. package/dist/tempo/Methods.js.map +0 -1
  340. package/dist/tempo/Proof.d.ts.map +0 -1
  341. package/dist/tempo/Proof.js.map +0 -1
  342. package/dist/tempo/client/Charge.d.ts.map +0 -1
  343. package/dist/tempo/client/Charge.js.map +0 -1
  344. package/dist/tempo/client/Methods.d.ts.map +0 -1
  345. package/dist/tempo/client/Methods.js.map +0 -1
  346. package/dist/tempo/client/ResolveAccount.d.ts.map +0 -1
  347. package/dist/tempo/client/ResolveAccount.js.map +0 -1
  348. package/dist/tempo/client/Subscription.d.ts.map +0 -1
  349. package/dist/tempo/client/Subscription.js.map +0 -1
  350. package/dist/tempo/client/index.d.ts.map +0 -1
  351. package/dist/tempo/client/index.js.map +0 -1
  352. package/dist/tempo/index.d.ts.map +0 -1
  353. package/dist/tempo/index.js.map +0 -1
  354. package/dist/tempo/internal/account.d.ts.map +0 -1
  355. package/dist/tempo/internal/account.js.map +0 -1
  356. package/dist/tempo/internal/address.d.ts.map +0 -1
  357. package/dist/tempo/internal/address.js.map +0 -1
  358. package/dist/tempo/internal/auto-swap.d.ts.map +0 -1
  359. package/dist/tempo/internal/auto-swap.js.map +0 -1
  360. package/dist/tempo/internal/charge.d.ts.map +0 -1
  361. package/dist/tempo/internal/charge.js.map +0 -1
  362. package/dist/tempo/internal/defaults.d.ts.map +0 -1
  363. package/dist/tempo/internal/defaults.js.map +0 -1
  364. package/dist/tempo/internal/fee-payer.d.ts.map +0 -1
  365. package/dist/tempo/internal/fee-payer.js.map +0 -1
  366. package/dist/tempo/internal/fee-token.d.ts.map +0 -1
  367. package/dist/tempo/internal/fee-token.js.map +0 -1
  368. package/dist/tempo/internal/proof.d.ts.map +0 -1
  369. package/dist/tempo/internal/proof.js.map +0 -1
  370. package/dist/tempo/internal/selectors.d.ts.map +0 -1
  371. package/dist/tempo/internal/selectors.js.map +0 -1
  372. package/dist/tempo/internal/types.d.ts.map +0 -1
  373. package/dist/tempo/internal/types.js.map +0 -1
  374. package/dist/tempo/legacy/client/ChannelOps.d.ts.map +0 -1
  375. package/dist/tempo/legacy/client/ChannelOps.js.map +0 -1
  376. package/dist/tempo/legacy/client/Session.d.ts.map +0 -1
  377. package/dist/tempo/legacy/client/Session.js.map +0 -1
  378. package/dist/tempo/legacy/client/SessionManager.d.ts.map +0 -1
  379. package/dist/tempo/legacy/client/SessionManager.js.map +0 -1
  380. package/dist/tempo/legacy/client/index.d.ts.map +0 -1
  381. package/dist/tempo/legacy/client/index.js.map +0 -1
  382. package/dist/tempo/legacy/index.d.ts.map +0 -1
  383. package/dist/tempo/legacy/index.js.map +0 -1
  384. package/dist/tempo/legacy/session/Chain.d.ts.map +0 -1
  385. package/dist/tempo/legacy/session/Chain.js.map +0 -1
  386. package/dist/tempo/legacy/session/Channel.d.ts.map +0 -1
  387. package/dist/tempo/legacy/session/Channel.js.map +0 -1
  388. package/dist/tempo/legacy/session/Types.d.ts.map +0 -1
  389. package/dist/tempo/legacy/session/Types.js.map +0 -1
  390. package/dist/tempo/legacy/session/Voucher.d.ts.map +0 -1
  391. package/dist/tempo/legacy/session/Voucher.js.map +0 -1
  392. package/dist/tempo/legacy/session/escrow.abi.d.ts.map +0 -1
  393. package/dist/tempo/legacy/session/escrow.abi.js.map +0 -1
  394. package/dist/tempo/legacy/session/index.d.ts.map +0 -1
  395. package/dist/tempo/legacy/session/index.js.map +0 -1
  396. package/dist/tempo/server/Charge.d.ts.map +0 -1
  397. package/dist/tempo/server/Charge.js.map +0 -1
  398. package/dist/tempo/server/Methods.d.ts.map +0 -1
  399. package/dist/tempo/server/Methods.js.map +0 -1
  400. package/dist/tempo/server/Relay.d.ts.map +0 -1
  401. package/dist/tempo/server/Relay.js.map +0 -1
  402. package/dist/tempo/server/SponsorBudget.d.ts.map +0 -1
  403. package/dist/tempo/server/SponsorBudget.js.map +0 -1
  404. package/dist/tempo/server/Subscription.d.ts.map +0 -1
  405. package/dist/tempo/server/Subscription.js.map +0 -1
  406. package/dist/tempo/server/index.d.ts.map +0 -1
  407. package/dist/tempo/server/index.js.map +0 -1
  408. package/dist/tempo/server/internal/html.gen.d.ts.map +0 -1
  409. package/dist/tempo/server/internal/html.gen.js.map +0 -1
  410. package/dist/tempo/server/internal/request-body.d.ts.map +0 -1
  411. package/dist/tempo/server/internal/request-body.js.map +0 -1
  412. package/dist/tempo/server/internal/transport.d.ts.map +0 -1
  413. package/dist/tempo/server/internal/transport.js.map +0 -1
  414. package/dist/tempo/session/Snapshot.d.ts.map +0 -1
  415. package/dist/tempo/session/Snapshot.js.map +0 -1
  416. package/dist/tempo/session/client/ChannelOps.d.ts.map +0 -1
  417. package/dist/tempo/session/client/ChannelOps.js.map +0 -1
  418. package/dist/tempo/session/client/ChannelStore.d.ts.map +0 -1
  419. package/dist/tempo/session/client/ChannelStore.js.map +0 -1
  420. package/dist/tempo/session/client/CredentialState.d.ts.map +0 -1
  421. package/dist/tempo/session/client/CredentialState.js.map +0 -1
  422. package/dist/tempo/session/client/ReceiptCoordinator.d.ts.map +0 -1
  423. package/dist/tempo/session/client/ReceiptCoordinator.js.map +0 -1
  424. package/dist/tempo/session/client/Runtime.d.ts.map +0 -1
  425. package/dist/tempo/session/client/Runtime.js.map +0 -1
  426. package/dist/tempo/session/client/Session.d.ts.map +0 -1
  427. package/dist/tempo/session/client/Session.js.map +0 -1
  428. package/dist/tempo/session/client/SessionManager.d.ts.map +0 -1
  429. package/dist/tempo/session/client/SessionManager.js.map +0 -1
  430. package/dist/tempo/session/client/Transports.d.ts.map +0 -1
  431. package/dist/tempo/session/client/Transports.js.map +0 -1
  432. package/dist/tempo/session/client/index.d.ts.map +0 -1
  433. package/dist/tempo/session/client/index.js.map +0 -1
  434. package/dist/tempo/session/client/internal/SessionManager.d.ts.map +0 -1
  435. package/dist/tempo/session/client/internal/SessionManager.js.map +0 -1
  436. package/dist/tempo/session/index.d.ts.map +0 -1
  437. package/dist/tempo/session/index.js.map +0 -1
  438. package/dist/tempo/session/precompile/Chain.d.ts.map +0 -1
  439. package/dist/tempo/session/precompile/Chain.js.map +0 -1
  440. package/dist/tempo/session/precompile/Channel.d.ts.map +0 -1
  441. package/dist/tempo/session/precompile/Channel.js.map +0 -1
  442. package/dist/tempo/session/precompile/Protocol.d.ts.map +0 -1
  443. package/dist/tempo/session/precompile/Protocol.js.map +0 -1
  444. package/dist/tempo/session/precompile/Voucher.d.ts.map +0 -1
  445. package/dist/tempo/session/precompile/Voucher.js.map +0 -1
  446. package/dist/tempo/session/precompile/escrow.abi.d.ts.map +0 -1
  447. package/dist/tempo/session/precompile/escrow.abi.js.map +0 -1
  448. package/dist/tempo/session/precompile/index.d.ts.map +0 -1
  449. package/dist/tempo/session/precompile/index.js.map +0 -1
  450. package/dist/tempo/session/server/ChannelOps.d.ts.map +0 -1
  451. package/dist/tempo/session/server/ChannelOps.js.map +0 -1
  452. package/dist/tempo/session/server/ChannelStore.d.ts.map +0 -1
  453. package/dist/tempo/session/server/ChannelStore.js.map +0 -1
  454. package/dist/tempo/session/server/CredentialVerification.d.ts.map +0 -1
  455. package/dist/tempo/session/server/CredentialVerification.js.map +0 -1
  456. package/dist/tempo/session/server/MeteredStream.d.ts.map +0 -1
  457. package/dist/tempo/session/server/MeteredStream.js.map +0 -1
  458. package/dist/tempo/session/server/RequestState.d.ts.map +0 -1
  459. package/dist/tempo/session/server/RequestState.js.map +0 -1
  460. package/dist/tempo/session/server/Session.d.ts.map +0 -1
  461. package/dist/tempo/session/server/Session.js.map +0 -1
  462. package/dist/tempo/session/server/Settlement.d.ts.map +0 -1
  463. package/dist/tempo/session/server/Settlement.js.map +0 -1
  464. package/dist/tempo/session/server/Sse.d.ts.map +0 -1
  465. package/dist/tempo/session/server/Sse.js.map +0 -1
  466. package/dist/tempo/session/server/Transports.d.ts.map +0 -1
  467. package/dist/tempo/session/server/Transports.js.map +0 -1
  468. package/dist/tempo/session/server/Ws.d.ts.map +0 -1
  469. package/dist/tempo/session/server/Ws.js.map +0 -1
  470. package/dist/tempo/session/server/index.d.ts.map +0 -1
  471. package/dist/tempo/session/server/index.js.map +0 -1
  472. package/dist/tempo/subscription/KeyAuthorization.d.ts.map +0 -1
  473. package/dist/tempo/subscription/KeyAuthorization.js.map +0 -1
  474. package/dist/tempo/subscription/Receipt.d.ts.map +0 -1
  475. package/dist/tempo/subscription/Receipt.js.map +0 -1
  476. package/dist/tempo/subscription/Store.d.ts.map +0 -1
  477. package/dist/tempo/subscription/Store.js.map +0 -1
  478. package/dist/tempo/subscription/Types.d.ts.map +0 -1
  479. package/dist/tempo/subscription/Types.js.map +0 -1
  480. package/dist/tempo/subscription/index.d.ts.map +0 -1
  481. package/dist/tempo/subscription/index.js.map +0 -1
  482. package/dist/validation/core.d.ts.map +0 -1
  483. package/dist/validation/core.js.map +0 -1
  484. package/dist/validation/index.d.ts.map +0 -1
  485. package/dist/validation/index.js.map +0 -1
  486. package/dist/viem/Account.d.ts.map +0 -1
  487. package/dist/viem/Account.js.map +0 -1
  488. package/dist/viem/Client.d.ts.map +0 -1
  489. package/dist/viem/Client.js.map +0 -1
  490. package/dist/web-bot-auth/Constants.d.ts.map +0 -1
  491. package/dist/web-bot-auth/Constants.js.map +0 -1
  492. package/dist/web-bot-auth/Types.d.ts.map +0 -1
  493. package/dist/web-bot-auth/Types.js.map +0 -1
  494. package/dist/web-bot-auth/client/index.d.ts.map +0 -1
  495. package/dist/web-bot-auth/client/index.js.map +0 -1
  496. package/dist/web-bot-auth/index.d.ts.map +0 -1
  497. package/dist/web-bot-auth/index.js.map +0 -1
  498. package/dist/web-bot-auth/internal/JwkThumbprint.d.ts.map +0 -1
  499. package/dist/web-bot-auth/internal/JwkThumbprint.js.map +0 -1
  500. package/dist/web-bot-auth/internal/SignatureAgent.d.ts.map +0 -1
  501. package/dist/web-bot-auth/internal/SignatureAgent.js.map +0 -1
  502. package/dist/web-bot-auth/server/index.d.ts.map +0 -1
  503. package/dist/web-bot-auth/server/index.js.map +0 -1
  504. package/dist/x402/Assets.d.ts.map +0 -1
  505. package/dist/x402/Assets.js.map +0 -1
  506. package/dist/x402/Header.d.ts.map +0 -1
  507. package/dist/x402/Header.js.map +0 -1
  508. package/dist/x402/Types.d.ts.map +0 -1
  509. package/dist/x402/Types.js.map +0 -1
  510. package/dist/x402/client/Exact.d.ts.map +0 -1
  511. package/dist/x402/client/Exact.js.map +0 -1
  512. package/dist/x402/index.d.ts.map +0 -1
  513. package/dist/x402/index.js.map +0 -1
  514. package/dist/x402/internal/ChallengeBrand.d.ts.map +0 -1
  515. package/dist/x402/internal/ChallengeBrand.js.map +0 -1
  516. package/dist/x402/internal/RouteBinding.d.ts.map +0 -1
  517. package/dist/x402/internal/RouteBinding.js.map +0 -1
  518. package/dist/x402/server/EvmCharge.d.ts.map +0 -1
  519. package/dist/x402/server/EvmCharge.js.map +0 -1
  520. package/dist/x402/server/Facilitator.d.ts.map +0 -1
  521. package/dist/x402/server/Facilitator.js.map +0 -1
  522. package/dist/zod.d.ts.map +0 -1
  523. package/dist/zod.js.map +0 -1
  524. package/src/BodyDigest.test.ts +0 -43
  525. package/src/BodyDigest.ts +0 -55
  526. package/src/Challenge.fuzz.test.ts +0 -154
  527. package/src/Challenge.test-d.ts +0 -82
  528. package/src/Challenge.test.ts +0 -1060
  529. package/src/Challenge.ts +0 -687
  530. package/src/Constants.ts +0 -58
  531. package/src/Credential.fuzz.test.ts +0 -62
  532. package/src/Credential.test.ts +0 -378
  533. package/src/Credential.ts +0 -217
  534. package/src/Errors.test.ts +0 -520
  535. package/src/Errors.ts +0 -448
  536. package/src/Expires.test.ts +0 -112
  537. package/src/Expires.ts +0 -59
  538. package/src/Html.ts +0 -67
  539. package/src/Mcp.ts +0 -85
  540. package/src/Method.test.ts +0 -177
  541. package/src/Method.ts +0 -631
  542. package/src/PaymentRequest.test.ts +0 -147
  543. package/src/PaymentRequest.ts +0 -108
  544. package/src/Receipt.test.ts +0 -151
  545. package/src/Receipt.ts +0 -146
  546. package/src/Store.test-d.ts +0 -211
  547. package/src/Store.test.ts +0 -407
  548. package/src/Store.ts +0 -397
  549. package/src/attestation/Attestation.integration.test.ts +0 -208
  550. package/src/attestation/Client.test.ts +0 -83
  551. package/src/attestation/Client.ts +0 -34
  552. package/src/attestation/Constants.ts +0 -15
  553. package/src/attestation/Server.test.ts +0 -60
  554. package/src/attestation/Server.ts +0 -12
  555. package/src/attestation/Types.test-d.ts +0 -54
  556. package/src/attestation/Types.ts +0 -77
  557. package/src/attestation/index.ts +0 -5
  558. package/src/attestation/internal/HttpMessageSignature.test.ts +0 -101
  559. package/src/attestation/internal/HttpMessageSignature.ts +0 -429
  560. package/src/attestation/internal/SigningContext.ts +0 -13
  561. package/src/bin.js +0 -2
  562. package/src/bin.ts +0 -4
  563. package/src/cli/account.ts +0 -269
  564. package/src/cli/cli.test.ts +0 -2221
  565. package/src/cli/cli.ts +0 -1650
  566. package/src/cli/config.test.ts +0 -89
  567. package/src/cli/config.ts +0 -50
  568. package/src/cli/internal.ts +0 -114
  569. package/src/cli/mcp.test.ts +0 -262
  570. package/src/cli/plugins/evm.ts +0 -55
  571. package/src/cli/plugins/index.ts +0 -4
  572. package/src/cli/plugins/plugin.ts +0 -84
  573. package/src/cli/plugins/stripe.ts +0 -174
  574. package/src/cli/plugins/tempo.ts +0 -1102
  575. package/src/cli/sessions/Manager.test.ts +0 -249
  576. package/src/cli/sessions/Manager.ts +0 -93
  577. package/src/cli/sessions/commands.ts +0 -444
  578. package/src/cli/sessions/request.test.ts +0 -51
  579. package/src/cli/sessions/request.ts +0 -353
  580. package/src/cli/sessions/store.test.ts +0 -581
  581. package/src/cli/sessions/store.ts +0 -940
  582. package/src/cli/utils.test.ts +0 -156
  583. package/src/cli/utils.ts +0 -394
  584. package/src/cli/validate/challenge.ts +0 -565
  585. package/src/cli/validate/discovery.test.ts +0 -435
  586. package/src/cli/validate/discovery.ts +0 -221
  587. package/src/cli/validate/helpers.ts +0 -166
  588. package/src/cli/validate/index.ts +0 -229
  589. package/src/cli/validate/messages.ts +0 -7
  590. package/src/cli/validate/payment.ts +0 -774
  591. package/src/cli/validate.test.ts +0 -843
  592. package/src/client/Methods.ts +0 -8
  593. package/src/client/Mppx.test-d.ts +0 -287
  594. package/src/client/Mppx.test.ts +0 -1065
  595. package/src/client/Mppx.ts +0 -487
  596. package/src/client/Transport.test.ts +0 -586
  597. package/src/client/Transport.ts +0 -170
  598. package/src/client/index.ts +0 -29
  599. package/src/client/internal/Fetch.browser.test.ts +0 -197
  600. package/src/client/internal/Fetch.test-d.ts +0 -109
  601. package/src/client/internal/Fetch.test.ts +0 -2336
  602. package/src/client/internal/Fetch.ts +0 -1075
  603. package/src/client/internal/MethodChallenge.ts +0 -35
  604. package/src/client/internal/MethodResponse.ts +0 -67
  605. package/src/client/internal/protocols/Mcp.test.ts +0 -220
  606. package/src/client/internal/protocols/Mcp.ts +0 -186
  607. package/src/client/internal/protocols/Mpp.ts +0 -21
  608. package/src/client/internal/protocols/Protocol.ts +0 -10
  609. package/src/client/internal/protocols/Shared.ts +0 -25
  610. package/src/client/internal/protocols/X402.ts +0 -46
  611. package/src/client/node.test.ts +0 -115
  612. package/src/client/node.ts +0 -247
  613. package/src/discovery/Discovery.test.ts +0 -214
  614. package/src/discovery/Discovery.ts +0 -105
  615. package/src/discovery/OpenApi.test.ts +0 -623
  616. package/src/discovery/OpenApi.ts +0 -192
  617. package/src/discovery/Validate.test.ts +0 -305
  618. package/src/discovery/Validate.ts +0 -77
  619. package/src/discovery/index.ts +0 -3
  620. package/src/discovery/internal/Metadata.ts +0 -51
  621. package/src/env.d.ts +0 -12
  622. package/src/evm/Assets.ts +0 -1
  623. package/src/evm/Chains.ts +0 -11
  624. package/src/evm/Methods.ts +0 -44
  625. package/src/evm/PublicInterface.test-d.ts +0 -134
  626. package/src/evm/Types.ts +0 -140
  627. package/src/evm/client/Charge.test.ts +0 -347
  628. package/src/evm/client/Charge.ts +0 -187
  629. package/src/evm/client/Methods.ts +0 -19
  630. package/src/evm/client/index.ts +0 -6
  631. package/src/evm/index.ts +0 -16
  632. package/src/evm/server/Charge.test.ts +0 -339
  633. package/src/evm/server/Charge.ts +0 -295
  634. package/src/evm/server/Methods.ts +0 -22
  635. package/src/evm/server/index.ts +0 -6
  636. package/src/index.ts +0 -14
  637. package/src/internal/AcceptPayment.test.ts +0 -347
  638. package/src/internal/AcceptPayment.ts +0 -366
  639. package/src/internal/HeaderCodec.ts +0 -36
  640. package/src/internal/changeset.test.ts +0 -106
  641. package/src/internal/constantTimeEqual.test.ts +0 -47
  642. package/src/internal/constantTimeEqual.ts +0 -10
  643. package/src/internal/env.test.ts +0 -42
  644. package/src/internal/env.ts +0 -49
  645. package/src/internal/types.test-d.ts +0 -21
  646. package/src/internal/types.ts +0 -479
  647. package/src/mcp/client/McpClient.integration.test.ts +0 -639
  648. package/src/mcp/client/McpClient.test-d.ts +0 -144
  649. package/src/mcp/client/McpClient.test.ts +0 -463
  650. package/src/mcp/client/McpClient.ts +0 -307
  651. package/src/mcp/client/McpClient.unit.test.ts +0 -135
  652. package/src/mcp/client/index.ts +0 -2
  653. package/src/mcp/server/Transport.test.ts +0 -191
  654. package/src/mcp/server/Transport.ts +0 -111
  655. package/src/mcp/server/index.ts +0 -1
  656. package/src/middlewares/elysia.test.ts +0 -285
  657. package/src/middlewares/elysia.ts +0 -116
  658. package/src/middlewares/express.test.ts +0 -361
  659. package/src/middlewares/express.ts +0 -140
  660. package/src/middlewares/hono.test.ts +0 -473
  661. package/src/middlewares/hono.ts +0 -153
  662. package/src/middlewares/internal/mppx.test.ts +0 -177
  663. package/src/middlewares/internal/mppx.ts +0 -67
  664. package/src/middlewares/nextjs.test.ts +0 -359
  665. package/src/middlewares/nextjs.ts +0 -107
  666. package/src/proxy/Proxy.test.ts +0 -1038
  667. package/src/proxy/Proxy.ts +0 -483
  668. package/src/proxy/Proxy.upstreamError.test.ts +0 -364
  669. package/src/proxy/Service.test.ts +0 -252
  670. package/src/proxy/Service.ts +0 -280
  671. package/src/proxy/index.ts +0 -6
  672. package/src/proxy/internal/Headers.test.ts +0 -139
  673. package/src/proxy/internal/Headers.ts +0 -66
  674. package/src/proxy/internal/Route.test.ts +0 -217
  675. package/src/proxy/internal/Route.ts +0 -64
  676. package/src/proxy/services/anthropic.test.ts +0 -132
  677. package/src/proxy/services/anthropic.ts +0 -53
  678. package/src/proxy/services/hooks.test-d.ts +0 -14
  679. package/src/proxy/services/hooks.test.ts +0 -44
  680. package/src/proxy/services/openai.test.ts +0 -157
  681. package/src/proxy/services/openai.ts +0 -60
  682. package/src/proxy/services/stripe.test.ts +0 -175
  683. package/src/proxy/services/stripe.ts +0 -59
  684. package/src/server/Methods.test-d.ts +0 -88
  685. package/src/server/Methods.test.ts +0 -69
  686. package/src/server/Methods.ts +0 -3
  687. package/src/server/Mppx.authorize.test.ts +0 -210
  688. package/src/server/Mppx.test-d.ts +0 -494
  689. package/src/server/Mppx.test.ts +0 -6711
  690. package/src/server/Mppx.ts +0 -2919
  691. package/src/server/NodeListener.test.ts +0 -293
  692. package/src/server/NodeListener.ts +0 -45
  693. package/src/server/Request.test.ts +0 -255
  694. package/src/server/Request.ts +0 -155
  695. package/src/server/Response.test.ts +0 -32
  696. package/src/server/Response.ts +0 -45
  697. package/src/server/Transport.test.ts +0 -598
  698. package/src/server/Transport.ts +0 -320
  699. package/src/server/index.ts +0 -9
  700. package/src/server/internal/html/compose.main.gen.ts +0 -2
  701. package/src/server/internal/html/compose.main.ts +0 -88
  702. package/src/server/internal/html/config.ts +0 -672
  703. package/src/server/internal/html/constants.ts +0 -28
  704. package/src/server/internal/html/serviceWorker.client.ts +0 -28
  705. package/src/server/internal/html/serviceWorker.gen.ts +0 -2
  706. package/src/server/internal/html/serviceWorker.ts +0 -27
  707. package/src/server/internal/html/tsconfig.compose.json +0 -8
  708. package/src/server/internal/html/tsconfig.worker.client.json +0 -8
  709. package/src/server/internal/html/tsconfig.worker.json +0 -8
  710. package/src/server/internal/scope.ts +0 -43
  711. package/src/stripe/Charge.integration.test.ts +0 -329
  712. package/src/stripe/Methods.test.ts +0 -62
  713. package/src/stripe/Methods.ts +0 -44
  714. package/src/stripe/client/Charge.test.ts +0 -205
  715. package/src/stripe/client/Charge.ts +0 -132
  716. package/src/stripe/client/Methods.ts +0 -37
  717. package/src/stripe/client/index.ts +0 -2
  718. package/src/stripe/index.ts +0 -1
  719. package/src/stripe/internal/constants.ts +0 -7
  720. package/src/stripe/internal/types.ts +0 -47
  721. package/src/stripe/server/Charge.test-d.ts +0 -81
  722. package/src/stripe/server/Charge.test.ts +0 -700
  723. package/src/stripe/server/Charge.ts +0 -487
  724. package/src/stripe/server/Methods.test-d.ts +0 -157
  725. package/src/stripe/server/Methods.test.ts +0 -403
  726. package/src/stripe/server/Methods.ts +0 -441
  727. package/src/stripe/server/index.ts +0 -2
  728. package/src/stripe/server/internal/deposit-address.ts +0 -57
  729. package/src/stripe/server/internal/html/main.ts +0 -165
  730. package/src/stripe/server/internal/html/node_modules/.bin/mppx +0 -22
  731. package/src/stripe/server/internal/html/node_modules/.bin/mppx.src +0 -22
  732. package/src/stripe/server/internal/html/package.json +0 -9
  733. package/src/stripe/server/internal/html/stripe-js-pure.d.ts +0 -7
  734. package/src/stripe/server/internal/html/tsconfig.json +0 -8
  735. package/src/stripe/server/internal/html/types.ts +0 -5
  736. package/src/stripe/server/internal/html.gen.ts +0 -2
  737. package/src/stripe/server/internal/record-payment.ts +0 -66
  738. package/src/stripe/server/internal/request.ts +0 -49
  739. package/src/tap/Constants.ts +0 -32
  740. package/src/tap/Tap.test.ts +0 -125
  741. package/src/tap/Types.ts +0 -11
  742. package/src/tap/client/index.ts +0 -54
  743. package/src/tap/index.ts +0 -4
  744. package/src/tap/server/index.ts +0 -48
  745. package/src/tempo/AccessKeyAuthorization.test.ts +0 -141
  746. package/src/tempo/Attribution.test.ts +0 -294
  747. package/src/tempo/Attribution.ts +0 -185
  748. package/src/tempo/Methods.test.ts +0 -425
  749. package/src/tempo/Methods.ts +0 -367
  750. package/src/tempo/Proof.conformance.test.ts +0 -146
  751. package/src/tempo/Proof.test-d.ts +0 -32
  752. package/src/tempo/Proof.test.ts +0 -40
  753. package/src/tempo/Proof.ts +0 -69
  754. package/src/tempo/PublicExports.test-d.ts +0 -130
  755. package/src/tempo/Subscription.integration.test.ts +0 -591
  756. package/src/tempo/client/Charge.test.ts +0 -501
  757. package/src/tempo/client/Charge.ts +0 -271
  758. package/src/tempo/client/Methods.ts +0 -45
  759. package/src/tempo/client/ResolveAccount.ts +0 -46
  760. package/src/tempo/client/Subscription.test.ts +0 -180
  761. package/src/tempo/client/Subscription.ts +0 -156
  762. package/src/tempo/client/index.ts +0 -17
  763. package/src/tempo/index.ts +0 -5
  764. package/src/tempo/internal/account.ts +0 -56
  765. package/src/tempo/internal/address.test.ts +0 -42
  766. package/src/tempo/internal/address.ts +0 -6
  767. package/src/tempo/internal/auto-swap.test.ts +0 -198
  768. package/src/tempo/internal/auto-swap.ts +0 -145
  769. package/src/tempo/internal/charge.test.ts +0 -66
  770. package/src/tempo/internal/charge.ts +0 -43
  771. package/src/tempo/internal/defaults.test.ts +0 -95
  772. package/src/tempo/internal/defaults.ts +0 -53
  773. package/src/tempo/internal/fee-payer.test.ts +0 -1177
  774. package/src/tempo/internal/fee-payer.ts +0 -808
  775. package/src/tempo/internal/fee-token.test.ts +0 -165
  776. package/src/tempo/internal/fee-token.ts +0 -72
  777. package/src/tempo/internal/proof.test.ts +0 -97
  778. package/src/tempo/internal/proof.ts +0 -87
  779. package/src/tempo/internal/selectors.ts +0 -10
  780. package/src/tempo/internal/types.ts +0 -19
  781. package/src/tempo/legacy/AccessKeyAuthorization.test.ts +0 -162
  782. package/src/tempo/legacy/README.md +0 -9
  783. package/src/tempo/legacy/client/ChannelOps.test.ts +0 -379
  784. package/src/tempo/legacy/client/ChannelOps.ts +0 -271
  785. package/src/tempo/legacy/client/Session.test.ts +0 -800
  786. package/src/tempo/legacy/client/Session.ts +0 -397
  787. package/src/tempo/legacy/client/SessionManager.test.ts +0 -468
  788. package/src/tempo/legacy/client/SessionManager.ts +0 -909
  789. package/src/tempo/legacy/client/index.ts +0 -6
  790. package/src/tempo/legacy/index.ts +0 -4
  791. package/src/tempo/legacy/session/Chain.test.ts +0 -1349
  792. package/src/tempo/legacy/session/Chain.ts +0 -830
  793. package/src/tempo/legacy/session/Channel.test.ts +0 -109
  794. package/src/tempo/legacy/session/Channel.ts +0 -51
  795. package/src/tempo/legacy/session/Types.ts +0 -91
  796. package/src/tempo/legacy/session/Voucher.test.ts +0 -410
  797. package/src/tempo/legacy/session/Voucher.ts +0 -183
  798. package/src/tempo/legacy/session/escrow.abi.ts +0 -760
  799. package/src/tempo/legacy/session/index.ts +0 -8
  800. package/src/tempo/server/AtomicStore.test-d.ts +0 -63
  801. package/src/tempo/server/Charge.test.ts +0 -6056
  802. package/src/tempo/server/Charge.ts +0 -1379
  803. package/src/tempo/server/Methods.ts +0 -86
  804. package/src/tempo/server/Relay.test.ts +0 -677
  805. package/src/tempo/server/Relay.ts +0 -297
  806. package/src/tempo/server/SponsorBudget.test.ts +0 -154
  807. package/src/tempo/server/SponsorBudget.ts +0 -219
  808. package/src/tempo/server/Sse.test.ts +0 -321
  809. package/src/tempo/server/Subscription.test.ts +0 -1944
  810. package/src/tempo/server/Subscription.ts +0 -1336
  811. package/src/tempo/server/index.ts +0 -12
  812. package/src/tempo/server/internal/html/main.ts +0 -115
  813. package/src/tempo/server/internal/html/node_modules/.bin/mppx +0 -22
  814. package/src/tempo/server/internal/html/node_modules/.bin/mppx.src +0 -22
  815. package/src/tempo/server/internal/html/package.json +0 -10
  816. package/src/tempo/server/internal/html/tsconfig.json +0 -8
  817. package/src/tempo/server/internal/html.gen.ts +0 -2
  818. package/src/tempo/server/internal/request-body.test.ts +0 -210
  819. package/src/tempo/server/internal/request-body.ts +0 -61
  820. package/src/tempo/server/internal/transport.test.ts +0 -922
  821. package/src/tempo/server/internal/transport.ts +0 -322
  822. package/src/tempo/session/README.md +0 -320
  823. package/src/tempo/session/Snapshot.test.ts +0 -41
  824. package/src/tempo/session/Snapshot.ts +0 -95
  825. package/src/tempo/session/client/ChannelOps.test.ts +0 -218
  826. package/src/tempo/session/client/ChannelOps.ts +0 -348
  827. package/src/tempo/session/client/ChannelStore.ts +0 -111
  828. package/src/tempo/session/client/CredentialState.test.ts +0 -789
  829. package/src/tempo/session/client/CredentialState.ts +0 -942
  830. package/src/tempo/session/client/ReceiptCoordinator.ts +0 -95
  831. package/src/tempo/session/client/Runtime.test.ts +0 -1104
  832. package/src/tempo/session/client/Runtime.ts +0 -1008
  833. package/src/tempo/session/client/Session.test.ts +0 -1355
  834. package/src/tempo/session/client/Session.ts +0 -420
  835. package/src/tempo/session/client/SessionManager.test.ts +0 -1899
  836. package/src/tempo/session/client/SessionManager.ts +0 -996
  837. package/src/tempo/session/client/Transports.test.ts +0 -1141
  838. package/src/tempo/session/client/Transports.ts +0 -1534
  839. package/src/tempo/session/client/index.ts +0 -40
  840. package/src/tempo/session/client/internal/SessionManager.ts +0 -36
  841. package/src/tempo/session/index.ts +0 -7
  842. package/src/tempo/session/precompile/Chain.integration.test.ts +0 -339
  843. package/src/tempo/session/precompile/Chain.test.ts +0 -1641
  844. package/src/tempo/session/precompile/Chain.ts +0 -1269
  845. package/src/tempo/session/precompile/Channel.test.ts +0 -147
  846. package/src/tempo/session/precompile/Channel.ts +0 -108
  847. package/src/tempo/session/precompile/Protocol.test.ts +0 -358
  848. package/src/tempo/session/precompile/Protocol.ts +0 -520
  849. package/src/tempo/session/precompile/Voucher.test.ts +0 -354
  850. package/src/tempo/session/precompile/Voucher.ts +0 -162
  851. package/src/tempo/session/precompile/escrow.abi.ts +0 -226
  852. package/src/tempo/session/precompile/index.ts +0 -33
  853. package/src/tempo/session/server/ChannelOps.test.ts +0 -129
  854. package/src/tempo/session/server/ChannelOps.ts +0 -157
  855. package/src/tempo/session/server/ChannelStore.test.ts +0 -1093
  856. package/src/tempo/session/server/ChannelStore.ts +0 -856
  857. package/src/tempo/session/server/CredentialVerification.test.ts +0 -299
  858. package/src/tempo/session/server/CredentialVerification.ts +0 -1087
  859. package/src/tempo/session/server/MeteredStream.ts +0 -99
  860. package/src/tempo/session/server/RequestState.test.ts +0 -574
  861. package/src/tempo/session/server/RequestState.ts +0 -507
  862. package/src/tempo/session/server/Session.integration.test.ts +0 -449
  863. package/src/tempo/session/server/Session.test.ts +0 -4247
  864. package/src/tempo/session/server/Session.ts +0 -669
  865. package/src/tempo/session/server/Settlement.test.ts +0 -345
  866. package/src/tempo/session/server/Settlement.ts +0 -527
  867. package/src/tempo/session/server/Sse.fuzz.test.ts +0 -138
  868. package/src/tempo/session/server/Sse.test.ts +0 -634
  869. package/src/tempo/session/server/Sse.ts +0 -261
  870. package/src/tempo/session/server/Transports.test.ts +0 -346
  871. package/src/tempo/session/server/Transports.ts +0 -256
  872. package/src/tempo/session/server/Ws.test.ts +0 -765
  873. package/src/tempo/session/server/Ws.ts +0 -391
  874. package/src/tempo/session/server/index.ts +0 -12
  875. package/src/tempo/subscription/KeyAuthorization.test.ts +0 -282
  876. package/src/tempo/subscription/KeyAuthorization.ts +0 -396
  877. package/src/tempo/subscription/Receipt.ts +0 -28
  878. package/src/tempo/subscription/Store.test.ts +0 -609
  879. package/src/tempo/subscription/Store.ts +0 -449
  880. package/src/tempo/subscription/Types.ts +0 -68
  881. package/src/tempo/subscription/index.ts +0 -23
  882. package/src/tsconfig.json +0 -10
  883. package/src/validation/core.ts +0 -386
  884. package/src/validation/index.ts +0 -11
  885. package/src/viem/Account.test.ts +0 -72
  886. package/src/viem/Account.ts +0 -30
  887. package/src/viem/Client.test.ts +0 -366
  888. package/src/viem/Client.ts +0 -93
  889. package/src/web-bot-auth/Constants.ts +0 -24
  890. package/src/web-bot-auth/Types.ts +0 -9
  891. package/src/web-bot-auth/WebBotAuth.test.ts +0 -182
  892. package/src/web-bot-auth/client/index.ts +0 -84
  893. package/src/web-bot-auth/index.ts +0 -6
  894. package/src/web-bot-auth/internal/JwkThumbprint.ts +0 -21
  895. package/src/web-bot-auth/internal/SignatureAgent.ts +0 -18
  896. package/src/web-bot-auth/server/index.ts +0 -113
  897. package/src/x402/Assets.test.ts +0 -253
  898. package/src/x402/Assets.ts +0 -221
  899. package/src/x402/Exact.e2e.test.ts +0 -488
  900. package/src/x402/Exact.localnet.test.ts +0 -475
  901. package/src/x402/Header.test.ts +0 -73
  902. package/src/x402/Header.ts +0 -79
  903. package/src/x402/PublicInterface.test-d.ts +0 -39
  904. package/src/x402/Types.ts +0 -248
  905. package/src/x402/client/Exact.test.ts +0 -309
  906. package/src/x402/client/Exact.ts +0 -186
  907. package/src/x402/index.ts +0 -6
  908. package/src/x402/internal/ChallengeBrand.ts +0 -14
  909. package/src/x402/internal/RouteBinding.ts +0 -18
  910. package/src/x402/server/EvmCharge.ts +0 -410
  911. package/src/x402/server/Facilitator.test.ts +0 -111
  912. package/src/x402/server/Facilitator.ts +0 -56
  913. package/src/zod.test.ts +0 -169
  914. package/src/zod.ts +0 -71
@@ -1,2919 +0,0 @@
1
- import type { IncomingMessage, ServerResponse } from 'node:http'
2
- import { isDeepStrictEqual } from 'node:util'
3
-
4
- import * as AttestationServer from '../attestation/Server.js'
5
- import type * as Attestation from '../attestation/Types.js'
6
- import * as Challenge from '../Challenge.js'
7
- import * as Constants from '../Constants.js'
8
- import * as Credential from '../Credential.js'
9
- import * as Errors from '../Errors.js'
10
- import * as Expires from '../Expires.js'
11
- import * as AcceptPayment from '../internal/AcceptPayment.js'
12
- import * as Env from '../internal/env.js'
13
- import type { DeepReadonly, MaybePromise } from '../internal/types.js'
14
- import * as Method from '../Method.js'
15
- import * as PaymentRequest from '../PaymentRequest.js'
16
- import type * as Receipt from '../Receipt.js'
17
- import * as x402_Header from '../x402/Header.js'
18
- import * as x402_Types from '../x402/Types.js'
19
- import * as z from '../zod.js'
20
- import * as Html from './internal/html/config.js'
21
- import { serviceWorker } from './internal/html/serviceWorker.gen.js'
22
- import * as Scope from './internal/scope.js'
23
- import * as NodeListener from './NodeListener.js'
24
- import * as Request from './Request.js'
25
- import * as Transport from './Transport.js'
26
-
27
- const minimumSecretKeyBytes = 32
28
- const secretKeyGenerationCommand = 'openssl rand -base64 32'
29
-
30
- export type Methods = readonly (Method.AnyServer | readonly Method.AnyServer[])[]
31
-
32
- export type ServerEventMap<
33
- methods extends readonly Method.Method[] = readonly Method.Method[],
34
- transport extends Transport.AnyTransport = Transport.AnyTransport,
35
- > = {
36
- 'challenge.created': ChallengeContext<methods[number], transport>
37
- 'payment.failed': PaymentFailedContext<methods[number], transport>
38
- 'payment.success': PaymentSuccessContext<methods[number], transport>
39
- }
40
-
41
- export type ServerEventName<
42
- methods extends readonly Method.Method[] = readonly Method.Method[],
43
- transport extends Transport.AnyTransport = Transport.AnyTransport,
44
- > = keyof ServerEventMap<methods, transport> | '*'
45
-
46
- export type ServerEventEnvelope<
47
- methods extends readonly Method.Method[] = readonly Method.Method[],
48
- transport extends Transport.AnyTransport = Transport.AnyTransport,
49
- > = {
50
- [name in keyof ServerEventMap<methods, transport>]: Readonly<{
51
- name: name
52
- payload: ServerEventMap<methods, transport>[name]
53
- }>
54
- }[keyof ServerEventMap<methods, transport>]
55
-
56
- export type ServerEventPayload<
57
- methods extends readonly Method.Method[] = readonly Method.Method[],
58
- transport extends Transport.AnyTransport = Transport.AnyTransport,
59
- name extends ServerEventName<methods, transport> = ServerEventName<methods, transport>,
60
- > = name extends '*'
61
- ? ServerEventEnvelope<methods, transport>
62
- : name extends keyof ServerEventMap<methods, transport>
63
- ? ServerEventMap<methods, transport>[name]
64
- : never
65
-
66
- /**
67
- * Server event handler.
68
- *
69
- * Handlers are awaited inline and sequentially on the payment request path.
70
- * Errors are swallowed so observers cannot change payment control flow, but
71
- * slow handlers still delay the response.
72
- */
73
- export type ServerEventHandler<
74
- methods extends readonly Method.Method[] = readonly Method.Method[],
75
- transport extends Transport.AnyTransport = Transport.AnyTransport,
76
- name extends ServerEventName<methods, transport> = ServerEventName<methods, transport>,
77
- > = (context: ServerEventPayload<methods, transport, name>) => MaybePromise<void>
78
-
79
- /** Removes a registered server event handler. */
80
- export type Unsubscribe = () => void
81
-
82
- /** Inert method descriptor exposed to server event handlers. */
83
- export type ServerMethodDescriptor<
84
- method extends Pick<Method.Method, 'intent' | 'name'> = Method.Method,
85
- > = Readonly<{
86
- intent: method['intent']
87
- name: method['name']
88
- }>
89
-
90
- /** A configured payment offer exposed to the server's `selectOffers` hook. */
91
- export type ServerOffer<method extends Method.Method = Method.Method> = method extends unknown
92
- ? ServerOfferFor<method>
93
- : never
94
-
95
- type ServerOfferFor<method extends Method.Method> = Readonly<{
96
- /** Canonical wire key for the configured method. */
97
- key: `${method['name']}/${method['intent']}`
98
- /** Immutable method metadata for this offer. */
99
- method: ServerOfferMethodDescriptor<method>
100
- /** Schema-normalized request configured for this offer. */
101
- request: DeepReadonly<z.output<method['schema']['request']>>
102
- }>
103
-
104
- /** Immutable method metadata exposed to offer-selection hooks. */
105
- export type ServerOfferMethodDescriptor<method extends Method.Method = Method.Method> = Readonly<{
106
- /** Optional public alias used to register this server method. */
107
- alias?: method extends { alias?: infer alias } ? alias : string
108
- intent: method['intent']
109
- name: method['name']
110
- }>
111
-
112
- /**
113
- * Selects a subset of configured offers before payment challenges are issued.
114
- *
115
- * The hook must select at least one offer. Returned offers must come from the input,
116
- * contain no duplicates, and preserve their original order. Offers and their
117
- * normalized requests are deeply immutable. The incoming request is cloned so
118
- * the hook may safely inspect its body when it has not already been consumed.
119
- */
120
- export type SelectOffers<methods extends readonly Method.Method[]> = (
121
- offers: readonly ServerOffer<methods[number]>[],
122
- context: Readonly<{ request: globalThis.Request }>,
123
- ) => MaybePromise<readonly ServerOffer<methods[number]>[]>
124
-
125
- /** Context passed to `onChallengeCreated`. */
126
- export type ChallengeContext<
127
- method extends Method.Method = Method.Method,
128
- transport extends Transport.AnyTransport = Transport.AnyTransport,
129
- > = Readonly<{
130
- capturedRequest?: Method.CapturedRequest | undefined
131
- challenge: Challenge.Challenge
132
- credential?: Credential.Credential | null | undefined
133
- error?: Errors.PaymentError | undefined
134
- input?: Transport.InputOf<transport> | undefined
135
- method: ServerMethodDescriptor<method>
136
- request: z.output<method['schema']['request']>
137
- }>
138
-
139
- /** Context passed to `onPaymentFailed`. */
140
- export type PaymentFailedContext<
141
- method extends Method.Method = Method.Method,
142
- transport extends Transport.AnyTransport = Transport.AnyTransport,
143
- > = Readonly<{
144
- capturedRequest?: Method.CapturedRequest | undefined
145
- challenge: Challenge.Challenge
146
- credential: Credential.Credential | null
147
- error: Errors.PaymentError
148
- input?: Transport.InputOf<transport> | undefined
149
- method: ServerMethodDescriptor<method>
150
- request: z.output<method['schema']['request']>
151
- retryChallenge?: Challenge.Challenge | undefined
152
- submittedChallenge?: Challenge.Challenge | undefined
153
- }>
154
-
155
- /** Context passed to `onPaymentSuccess`. */
156
- export type PaymentSuccessContext<
157
- method extends Method.Method = Method.Method,
158
- transport extends Transport.AnyTransport = Transport.AnyTransport,
159
- > = Readonly<{
160
- capturedRequest?: Method.CapturedRequest | undefined
161
- challenge: Challenge.Challenge<
162
- z.output<method['schema']['request']>,
163
- method['intent'],
164
- method['name']
165
- >
166
- credential?:
167
- | Credential.Credential<
168
- z.output<method['schema']['credential']['payload']>,
169
- Challenge.Challenge<z.output<method['schema']['request']>, method['intent'], method['name']>
170
- >
171
- | undefined
172
- envelope?:
173
- | Method.VerifiedChallengeEnvelope<
174
- z.output<method['schema']['request']>,
175
- z.output<method['schema']['credential']['payload']>,
176
- method['intent'],
177
- method['name']
178
- >
179
- | undefined
180
- input?: Transport.InputOf<transport> | undefined
181
- method: ServerMethodDescriptor<method>
182
- receipt: Receipt.Receipt
183
- request: z.output<method['schema']['request']>
184
- }>
185
-
186
- /** Options for standalone credential verification. */
187
- export type VerifyCredentialOptions = {
188
- capturedRequest?: Method.CapturedRequest | undefined
189
- meta?: Record<string, string> | undefined
190
- realm?: string | undefined
191
- request?: Record<string, unknown> | undefined
192
- /** Optional expected route/resource scope bound via challenge `opaque`. */
193
- scope?: string | undefined
194
- }
195
-
196
- /**
197
- * Payment handler.
198
- */
199
- export type Mppx<
200
- methods extends Methods = Methods,
201
- transport extends Transport.AnyTransport = Transport.Http,
202
- > = {
203
- /** Methods to configure. */
204
- methods: FlattenMethods<methods>
205
- /** Server realm (e.g., hostname). */
206
- realm: string
207
- /** The transport used. */
208
- transport: transport
209
- /** Register a server event handler by canonical event name. */
210
- on<name extends ServerEventName<FlattenMethods<methods>, transport>>(
211
- name: name,
212
- handler: ServerEventHandler<FlattenMethods<methods>, transport, name>,
213
- ): Unsubscribe
214
- /** Register a handler for issued payment challenges. */
215
- onChallengeCreated(
216
- handler: ServerEventHandler<FlattenMethods<methods>, transport, 'challenge.created'>,
217
- ): Unsubscribe
218
- /** Register a handler for failed submitted payment credentials. */
219
- onPaymentFailed(
220
- handler: ServerEventHandler<FlattenMethods<methods>, transport, 'payment.failed'>,
221
- ): Unsubscribe
222
- /** Register a handler for successful verified payments. */
223
- onPaymentSuccess(
224
- handler: ServerEventHandler<FlattenMethods<methods>, transport, 'payment.success'>,
225
- ): Unsubscribe
226
- } & (transport extends Transport.Http
227
- ? {
228
- /**
229
- * Combines multiple method handlers into a single route handler that presents
230
- * all methods to the client via multiple `WWW-Authenticate` headers.
231
- *
232
- * Each entry is a `[method, options]` tuple where `method` is one of the
233
- * server methods passed to `Mppx.create()`, looked up by `name`+`intent`.
234
- *
235
- * Only available on HTTP transports.
236
- * No-credential authorize hooks run in entry order; the first 200 response
237
- * wins, and earlier hooks may have already run side effects.
238
- *
239
- * @example
240
- * ```ts
241
- * import { Mppx, tempo, stripe } from 'mppx/server'
242
- *
243
- * const mppx = Mppx.create({
244
- * methods: [
245
- * tempo.charge({ currency: USDC, recipient: '0x...' }),
246
- * stripe.charge({
247
- * client: stripeClient,
248
- * networkId: 'internal',
249
- * currency: 'usd',
250
- * decimals: 2,
251
- * paymentMethodTypes: ['card'],
252
- * }),
253
- * ],
254
- * secretKey,
255
- * })
256
- *
257
- * app.get('/api/resource', async (req) => {
258
- * const result = await mppx.compose(
259
- * ['tempo/charge', { amount: '100' }],
260
- * ['stripe/charge', { amount: '100' }],
261
- * )(req)
262
- * if (result.status === 402) return result.challenge
263
- * return result.withReceipt(new Response('OK'))
264
- * })
265
- * ```
266
- */
267
- compose(...entries: ComposeEntry<FlattenMethods<methods>>[]): ComposedHandler
268
- }
269
- : {}) &
270
- Handlers<FlattenMethods<methods>, transport> & {
271
- /**
272
- * Generate Challenge objects for registered methods without going through
273
- * the HTTP 402 request lifecycle. Uses the same options, defaults, and
274
- * schema transforms as the corresponding intent handler.
275
- *
276
- * @example
277
- * ```ts
278
- * const challenge = await mppx.challenge.tempo.charge({ amount: '25.92' })
279
- * ```
280
- */
281
- challenge: ChallengeHandlers<FlattenMethods<methods>>
282
-
283
- /**
284
- * Verify a credential string or object end-to-end: deserialize,
285
- * HMAC-check, match to a registered method, validate payload schema,
286
- * check expiry, and call the method's broadcast or legacy verify function.
287
- *
288
- * Method verification can broadcast payments and persist state. For example,
289
- * subscription credentials may activate or renew a subscription. Failed
290
- * standalone verification emits `payment.failed` once a credential challenge
291
- * can be parsed; strings that cannot be deserialized have no challenge
292
- * context to report.
293
- *
294
- * @deprecated Use `broadcastCredential()` instead. This legacy name maps
295
- * to the same mutating operation and does not provide validation-only
296
- * semantics.
297
- *
298
- * @example
299
- * ```ts
300
- * const receipt = await mppx.verifyCredential('eyJjaGFsbGVuZ2...')
301
- * const receipt = await mppx.verifyCredential(credential)
302
- * const receipt = await mppx.verifyCredential(credential, { request: { amount: '1000' } })
303
- * ```
304
- */
305
- verifyCredential(
306
- credential: string | Credential.Credential,
307
- options?: VerifyCredentialOptions | undefined,
308
- ): Promise<Receipt.Receipt>
309
- /**
310
- * Validate a credential without consuming or broadcasting it.
311
- *
312
- * This is an advisory pre-check. Methods that support it must not write
313
- * replay state, sign fee-payer transactions, broadcast, or otherwise
314
- * mutate payment state. Use `broadcastCredential()` when accepting payment.
315
- */
316
- validateCredential(
317
- credential: string | Credential.Credential,
318
- options?: VerifyCredentialOptions | undefined,
319
- ): Promise<Method.Validation>
320
- /**
321
- * Re-validates and broadcasts a credential, returning a receipt.
322
- *
323
- * `verifyCredential()` is retained as a backwards-compatible alias for
324
- * this mutating path.
325
- */
326
- broadcastCredential(
327
- credential: string | Credential.Credential,
328
- options?: VerifyCredentialOptions | undefined,
329
- ): Promise<Receipt.Receipt>
330
- }
331
-
332
- /** Extracts the transport override from a method, if any. */
333
- type TransportOverrideOf<mi> = mi extends { transport?: infer transport }
334
- ? Exclude<transport, undefined> extends Transport.AnyTransport
335
- ? Exclude<transport, undefined>
336
- : never
337
- : never
338
-
339
- /** Resolves the effective transport for a method: override if present, else global default. */
340
- type EffectiveTransportOf<mi, defaultTransport extends Transport.AnyTransport> = [
341
- TransportOverrideOf<mi>,
342
- ] extends [never]
343
- ? defaultTransport
344
- : TransportOverrideOf<mi>
345
-
346
- const reservedMppxKeyValues = [
347
- 'challenge',
348
- 'compose',
349
- 'methods',
350
- 'on',
351
- 'onChallengeCreated',
352
- 'onPaymentFailed',
353
- 'onPaymentSuccess',
354
- 'realm',
355
- 'broadcastCredential',
356
- 'transport',
357
- 'validateCredential',
358
- 'verifyCredential',
359
- ] as const
360
-
361
- /** Public instance keys that payment method names and shorthand intents cannot shadow. */
362
- export type ReservedKey = (typeof reservedMppxKeyValues)[number]
363
-
364
- /** Public instance keys that payment method names and shorthand intents cannot shadow. */
365
- export const reservedMppxKeys: ReadonlySet<ReservedKey> = new Set(reservedMppxKeyValues)
366
-
367
- /** True when exactly one method has the given intent (no name collision). */
368
- type IsUniqueIntent<methods extends readonly Method.AnyServer[], intent extends string> =
369
- Extract<methods[number], { intent: intent }> extends infer M
370
- ? M extends M
371
- ? [Exclude<Extract<methods[number], { intent: intent }>, M>] extends [never]
372
- ? true
373
- : false
374
- : never
375
- : never
376
-
377
- /** Shorthand intent handlers. Single-method intents get direct handlers;
378
- * multi-method intents get an implicit compose wrapper. */
379
- type IntentHandlers<
380
- methods extends readonly Method.AnyServer[],
381
- transport extends Transport.AnyTransport,
382
- > = {
383
- [intent in methods[number]['intent'] as intent extends ReservedKey
384
- ? never
385
- : intent]: IsUniqueIntent<methods, intent> extends true
386
- ? MethodFn<
387
- Extract<methods[number], { intent: intent }>,
388
- EffectiveTransportOf<Extract<methods[number], { intent: intent }>, transport>,
389
- NonNullable<Extract<methods[number], { intent: intent }>['defaults']>
390
- > &
391
- MethodExtensions<Extract<methods[number], { intent: intent }>>
392
- : MultiMethodFn<Extract<methods[number], { intent: intent }>, transport>
393
- }
394
-
395
- /** Handler for an intent with multiple methods - takes the intersection of all methods' options. */
396
- type MultiMethodFn<methods extends Method.AnyServer, transport extends Transport.AnyTransport> = (
397
- options: UnionToIntersection<MultiMethodFnDistribute<methods>>,
398
- ) => (input: Transport.InputOf<transport>) => Promise<MethodFn.Response<transport>>
399
-
400
- type MultiMethodFnDistribute<M> =
401
- M extends Method.Server<infer method, infer defaults, any, any, any>
402
- ? MethodFn.Options<method, NonNullable<defaults>>
403
- : never
404
-
405
- type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (x: infer I) => void
406
- ? I
407
- : never
408
-
409
- /** Nested handlers: `mppx.tempo.charge(...)`, grouped by method name then intent. */
410
- type PublicAlias<method extends Method.AnyServer> = method extends {
411
- alias?: infer alias
412
- }
413
- ? Exclude<alias, undefined> extends infer definedAlias
414
- ? definedAlias extends string
415
- ? string extends definedAlias
416
- ? never
417
- : definedAlias
418
- : never
419
- : never
420
- : never
421
-
422
- type PublicIntent<method extends Method.AnyServer> = [PublicAlias<method>] extends [never]
423
- ? method extends { intent: infer intent extends string }
424
- ? intent
425
- : never
426
- : PublicAlias<method>
427
-
428
- type NestedHandlers<
429
- methods extends readonly Method.AnyServer[],
430
- transport extends Transport.AnyTransport,
431
- > = {
432
- [name in methods[number]['name'] as name extends ReservedKey ? never : name]: {
433
- [mi in Extract<methods[number], { name: name }> as PublicIntent<mi>]: MethodFn<
434
- mi,
435
- EffectiveTransportOf<mi, transport>,
436
- NonNullable<mi['defaults']>
437
- > &
438
- MethodExtensions<mi> & { _method: mi }
439
- }
440
- }
441
-
442
- type Handlers<
443
- methods extends readonly Method.AnyServer[],
444
- transport extends Transport.AnyTransport,
445
- > = {
446
- [mi in methods[number] as `${mi['name']}/${mi['intent']}`]: MethodFn<
447
- mi,
448
- EffectiveTransportOf<mi, transport>,
449
- NonNullable<mi['defaults']>
450
- > &
451
- MethodExtensions<mi>
452
- } & IntentHandlers<methods, transport> &
453
- NestedHandlers<methods, transport> & {
454
- [mi in methods[number] as PublicAlias<mi> extends string
455
- ? `${mi['name']}/${PublicAlias<mi>}`
456
- : never]: MethodFn<mi, EffectiveTransportOf<mi, transport>, NonNullable<mi['defaults']>> &
457
- MethodExtensions<mi>
458
- }
459
-
460
- type MethodExtensions<method extends Method.AnyServer> = method extends {
461
- extensions?: (infer extensions) | undefined
462
- }
463
- ? NonNullable<extensions> extends object
464
- ? NonNullable<extensions>
465
- : {}
466
- : {}
467
-
468
- /** Nested challenge generators: `mppx.challenge.tempo.charge(...)`. */
469
- type ChallengeHandlers<methods extends readonly Method.AnyServer[]> = {
470
- [name in methods[number]['name']]: {
471
- [mi in Extract<methods[number], { name: name }> as PublicIntent<mi>]: ChallengeFn<
472
- mi,
473
- NonNullable<mi['defaults']>
474
- >
475
- }
476
- }
477
-
478
- /** A function that generates a Challenge object from intent options. */
479
- type ChallengeFn<method extends Method.Method, defaults extends Record<string, unknown>> = (
480
- options: MethodFn.Options<method, defaults>,
481
- ) => Promise<Challenge.Challenge>
482
-
483
- /**
484
- * Creates a server-side payment handler from methods.
485
- *
486
- * It is highly recommended to set a `secretKey` to bind challenges to their contents,
487
- * and allow the server to verify that incoming credentials match challenges it issued.
488
- *
489
- * @example
490
- * ```ts
491
- * import { Mppx, tempo } from 'mppx/server'
492
- *
493
- * const payment = Mppx.create({
494
- * methods: [tempo()],
495
- * secretKey: process.env.PAYMENT_SECRET_KEY,
496
- * })
497
- * ```
498
- */
499
- export function create<
500
- const methods extends Methods,
501
- const transport extends Transport.AnyTransport = Transport.Http,
502
- >(config: create.Config<methods, transport>): Mppx<methods, transport> {
503
- const {
504
- attestation,
505
- realm = Env.get('realm'),
506
- selectOffers,
507
- secretKey = Env.get('secretKey'),
508
- transport = Transport.http() as transport,
509
- } = config
510
-
511
- if (!secretKey) {
512
- throw new Error(
513
- 'Missing secret key. Set the MPP_SECRET_KEY environment variable or pass `secretKey` to Mppx.create().',
514
- )
515
- }
516
- assertSecretKey(secretKey)
517
-
518
- const methods = config.methods.flat() as unknown as FlattenMethods<methods>
519
- const serverEvents = createServerEventDispatcher<FlattenMethods<methods>, transport>()
520
- const verifyAttestation = attestation
521
- ? createAttestationGuard(attestation as Attestation.VerifierMap)
522
- : undefined
523
-
524
- const handlers: Record<string, unknown> = {}
525
- const intentCount: Record<string, number> = {}
526
-
527
- for (const mi of methods) {
528
- intentCount[mi.intent] = (intentCount[mi.intent] ?? 0) + 1
529
- if (mi.onPaymentSuccess) {
530
- serverEvents.on('payment.success', (async (ctx: PaymentSuccessContext) => {
531
- if (ctx.method.name === mi.name && ctx.method.intent === mi.intent) {
532
- await mi.onPaymentSuccess({
533
- input: ctx.input,
534
- receipt: ctx.receipt,
535
- request: ctx.request,
536
- })
537
- }
538
- }) as never)
539
- }
540
- }
541
- assertNoReservedMppxKeys(methods as readonly Method.AnyServer[])
542
-
543
- for (const mi of methods) {
544
- const fn = createMethodFn({
545
- ...(verifyAttestation && { attest: verifyAttestation }),
546
- authorize: mi.authorize as never,
547
- defaults: mi.defaults,
548
- method: mi,
549
- realm,
550
- events: serverEvents as never,
551
- preflight: mi.preflight as never,
552
- request: mi.request as never,
553
- respond: mi.respond as never,
554
- broadcast: mi.broadcast as never,
555
- secretKey,
556
- stableBinding: mi.stableBinding as never,
557
- transport: (mi.transport ?? transport) as never,
558
- validate: mi.validate as never,
559
- verify: mi.verify as never,
560
- })
561
- const wireKey = `${mi.name}/${mi.intent}`
562
- const aliasKey = mi.alias ? `${mi.name}/${mi.alias}` : undefined
563
- if (mi.extensions) Object.assign(fn, mi.extensions)
564
- if (!aliasKey || !handlers[wireKey]) handlers[wireKey] = fn
565
- if (aliasKey) handlers[aliasKey] = fn
566
- }
567
-
568
- // Set shorthand intent key: single method gets direct handler,
569
- // multiple methods get an implicit compose wrapper.
570
- const processedIntents = new Set<string>()
571
- for (const mi of methods) {
572
- if (processedIntents.has(mi.intent)) continue
573
- processedIntents.add(mi.intent)
574
-
575
- if (intentCount[mi.intent] === 1) {
576
- handlers[mi.intent] = handlers[`${mi.name}/${mi.intent}`]
577
- } else {
578
- const intentMethods = (methods as readonly Method.AnyServer[]).filter(
579
- (m) => m.intent === mi.intent,
580
- )
581
- handlers[mi.intent] = (options: Record<string, unknown>) => {
582
- const configured = intentMethods.map((m) => {
583
- const key = `${m.name}/${m.intent}`
584
- const handlerFn = handlers[key] as AnyMethodFn
585
- return handlerFn(options)
586
- })
587
- return composeHandlers(
588
- configured as ConfiguredHandler[],
589
- undefined,
590
- selectOffers as SelectOffers<readonly Method.AnyServer[]> | undefined,
591
- )
592
- }
593
- }
594
- }
595
-
596
- // Build nested handlers: mppx.tempo.charge(...)
597
- for (const mi of methods) {
598
- if (!handlers[mi.name]) handlers[mi.name] = {}
599
- const key = mi.alias ? `${mi.name}/${mi.alias}` : `${mi.name}/${mi.intent}`
600
- const fn = handlers[key] as AnyMethodFn & { _method?: Method.AnyServer }
601
- fn._method = mi
602
- ;(handlers[mi.name] as Record<string, unknown>)[mi.alias ?? mi.intent] = fn
603
- }
604
-
605
- // Build challenge generators: mppx.challenge.tempo.charge(...)
606
- const challengeHandlers: Record<string, Record<string, unknown>> = {}
607
- for (const mi of methods) {
608
- if (!challengeHandlers[mi.name]) challengeHandlers[mi.name] = {}
609
- challengeHandlers[mi.name]![mi.alias ?? mi.intent] = createChallengeFn({
610
- defaults: mi.defaults,
611
- method: mi,
612
- realm,
613
- request: mi.request as never,
614
- secretKey,
615
- })
616
- }
617
-
618
- async function prepareStandaloneCredential(
619
- input: string | Credential.Credential,
620
- options: VerifyCredentialOptions | undefined,
621
- parameters: { emitFailures?: boolean | undefined; requireValidate?: boolean | undefined } = {},
622
- ) {
623
- const credential = hydrateCredentialMeta(
624
- typeof input === 'string' ? Credential.deserialize(input) : input,
625
- )
626
-
627
- const emitFailures = parameters.emitFailures === true
628
- const { method: credMethod, intent: credIntent } = credential.challenge
629
- const methodCandidates = (methods as readonly Method.AnyServer[]).filter(
630
- (m) => m.name === credMethod && m.intent === credIntent,
631
- )
632
- const mi = methodCandidates[0]
633
- const eventMethod =
634
- mi ?? ({ intent: credIntent, name: credMethod } satisfies ServerMethodDescriptor)
635
-
636
- const emitStandalonePaymentFailed = async (failure: {
637
- challenge: Challenge.Challenge
638
- credential: Credential.Credential | null
639
- error: Errors.PaymentError
640
- request: Record<string, unknown>
641
- submittedChallenge?: Challenge.Challenge | undefined
642
- }) => {
643
- if (!emitFailures) return
644
- await serverEvents.emit(
645
- 'payment.failed',
646
- createPaymentFailedContext({
647
- capturedRequest: options?.capturedRequest,
648
- challenge: failure.challenge,
649
- credential: failure.credential,
650
- error: failure.error,
651
- method: eventMethod,
652
- request: failure.request,
653
- submittedChallenge: failure.submittedChallenge,
654
- }) as never,
655
- )
656
- }
657
-
658
- const fail = async (
659
- error: Errors.PaymentError,
660
- failure: {
661
- credential?: Credential.Credential | null | undefined
662
- request?: Record<string, unknown> | undefined
663
- submittedChallenge?: Challenge.Challenge | undefined
664
- } = {},
665
- thrown: unknown = error,
666
- ): Promise<never> => {
667
- await emitStandalonePaymentFailed({
668
- challenge: credential.challenge,
669
- credential: failure.credential ?? credential,
670
- error,
671
- request: failure.request ?? (credential.challenge.request as Record<string, unknown>),
672
- submittedChallenge: failure.submittedChallenge ?? credential.challenge,
673
- })
674
- throw thrown
675
- }
676
-
677
- if (!mi) {
678
- await fail(
679
- new Errors.InvalidChallengeError({
680
- id: credential.challenge.id,
681
- reason: `no registered method for ${credMethod}/${credIntent}`,
682
- }),
683
- )
684
- }
685
-
686
- if (parameters.requireValidate && !mi.validate)
687
- await fail(
688
- new Errors.VerificationFailedError({
689
- details: { intent: credIntent, method: credMethod },
690
- reason: `${credMethod}/${credIntent} does not support non-mutating credential validation`,
691
- }),
692
- )
693
-
694
- if (!Challenge.verify(credential.challenge, { secretKey: secretKey! })) {
695
- await fail(
696
- new Errors.InvalidChallengeError({
697
- id: credential.challenge.id,
698
- reason: 'challenge was not issued by this server',
699
- }),
700
- )
701
- }
702
-
703
- try {
704
- Expires.assert(credential.challenge.expires, credential.challenge.id)
705
- } catch (e) {
706
- if (e instanceof Errors.PaymentError) await fail(e)
707
- throw e
708
- }
709
-
710
- let parsedCredential!: Credential.Credential
711
- try {
712
- parsedCredential = withParsedCredentialPayload(
713
- credential,
714
- mi.schema.credential.payload.parse(credential.payload),
715
- )
716
- } catch (e) {
717
- await fail(new Errors.InvalidPayloadError(), {}, e)
718
- }
719
-
720
- const expectedMeta = Scope.merge({ meta: options?.meta, scope: options?.scope })
721
-
722
- if (options?.scope !== undefined && Scope.read(credential.challenge.meta) !== options.scope) {
723
- await fail(
724
- new Errors.InvalidChallengeError({
725
- id: credential.challenge.id,
726
- reason: "credential scope does not match this route's requirements",
727
- }),
728
- { credential: parsedCredential },
729
- )
730
- }
731
-
732
- const shouldValidateRoute =
733
- options?.capturedRequest !== undefined ||
734
- options?.meta !== undefined ||
735
- options?.realm !== undefined ||
736
- options?.request !== undefined
737
- const expectedRealm =
738
- options?.realm ??
739
- realm ??
740
- (options?.capturedRequest === undefined ? credential.challenge.realm : undefined)
741
-
742
- let parsedRequest = credential.challenge.request as Record<string, unknown>
743
- let request: z.input<typeof mi.schema.request>
744
- try {
745
- request = shouldValidateRoute
746
- ? await resolveRouteChallenge({
747
- capturedRequest: options?.capturedRequest,
748
- credential: parsedCredential,
749
- defaults: mi.defaults,
750
- expires: credential.challenge.expires,
751
- meta: expectedMeta,
752
- method: mi,
753
- realm: expectedRealm,
754
- request: mi.request as never,
755
- routeRequest: options?.request ?? {},
756
- secretKey: secretKey!,
757
- }).then((resolved) => {
758
- const mismatch = getChallengeBindingMismatch(
759
- resolved.challenge,
760
- credential.challenge,
761
- mi.stableBinding as never,
762
- )
763
- if (mismatch)
764
- throw new Errors.InvalidChallengeError({
765
- id: credential.challenge.id,
766
- reason: `credential ${mismatch} does not match this route's requirements`,
767
- })
768
-
769
- parsedRequest = resolved.parsedRequest
770
- return resolved.request as z.input<typeof mi.schema.request>
771
- })
772
- : (credential.challenge.request as z.input<typeof mi.schema.request>)
773
- } catch (e) {
774
- if (e instanceof Errors.PaymentError)
775
- await fail(e, {
776
- credential: parsedCredential,
777
- request: credential.challenge.request as Record<string, unknown>,
778
- })
779
- throw e
780
- }
781
-
782
- const envelope = options?.capturedRequest
783
- ? ({
784
- capturedRequest: options.capturedRequest,
785
- challenge: credential.challenge,
786
- credential: parsedCredential,
787
- request: parsedRequest,
788
- } as Method.VerifiedChallengeEnvelope)
789
- : undefined
790
-
791
- return {
792
- credential,
793
- envelope,
794
- eventMethod,
795
- method: mi,
796
- parsedCredential,
797
- parsedRequest,
798
- request,
799
- }
800
- }
801
-
802
- async function validateCredentialFn(
803
- input: string | Credential.Credential,
804
- options?: VerifyCredentialOptions,
805
- ): Promise<Method.Validation> {
806
- const prepared = await prepareStandaloneCredential(input, options, { requireValidate: true })
807
- return prepared.method.validate!({
808
- credential: prepared.parsedCredential,
809
- envelope: prepared.envelope,
810
- request: prepared.request,
811
- } as never)
812
- }
813
-
814
- // broadcastCredential: single-call end-to-end validation and broadcast
815
- async function broadcastCredentialFn(
816
- input: string | Credential.Credential,
817
- options?: VerifyCredentialOptions,
818
- ): Promise<Receipt.Receipt> {
819
- const prepared = await prepareStandaloneCredential(input, options, { emitFailures: true })
820
- const { method: mi, parsedCredential, parsedRequest, request, envelope } = prepared
821
-
822
- const emitStandalonePaymentFailed = async (parameters: {
823
- challenge: Challenge.Challenge
824
- credential: Credential.Credential | null
825
- error: Errors.PaymentError
826
- request: Record<string, unknown>
827
- submittedChallenge?: Challenge.Challenge | undefined
828
- }) => {
829
- await serverEvents.emit(
830
- 'payment.failed',
831
- createPaymentFailedContext({
832
- capturedRequest: options?.capturedRequest,
833
- challenge: parameters.challenge,
834
- credential: parameters.credential,
835
- error: parameters.error,
836
- method: prepared.eventMethod,
837
- request: parameters.request,
838
- submittedChallenge: parameters.submittedChallenge,
839
- }) as never,
840
- )
841
- }
842
-
843
- let receipt: Receipt.Receipt
844
- try {
845
- if (mi.broadcast && mi.validate)
846
- await mi.validate({ credential: parsedCredential, envelope, request } as never)
847
- const broadcast = mi.broadcast ?? mi.verify
848
- receipt = await broadcast({ credential: parsedCredential, envelope, request } as never)
849
- } catch (e) {
850
- const error = e instanceof Errors.PaymentError ? e : new Errors.VerificationFailedError()
851
- await emitStandalonePaymentFailed({
852
- challenge: prepared.credential.challenge,
853
- credential: parsedCredential,
854
- error,
855
- request: parsedRequest,
856
- submittedChallenge: prepared.credential.challenge,
857
- })
858
- throw e
859
- }
860
-
861
- await serverEvents.emit(
862
- 'payment.success',
863
- createPaymentSuccessContext({
864
- capturedRequest: options?.capturedRequest,
865
- challenge: prepared.credential.challenge,
866
- credential: parsedCredential,
867
- envelope,
868
- method: mi,
869
- receipt,
870
- request: parsedRequest,
871
- }) as never,
872
- )
873
-
874
- return receipt
875
- }
876
-
877
- const verifyCredentialFn = broadcastCredentialFn
878
-
879
- function composeFn(
880
- ...entries: readonly [
881
- Method.AnyServer | AnyMethodFnWithMethod | string,
882
- Record<string, unknown>,
883
- ][]
884
- ) {
885
- if (transport.name !== 'http') throw new Error('compose() only supports HTTP transport')
886
- if (entries.length === 0) throw new Error('compose() requires at least one entry')
887
- const configured = entries.map(([methodOrKey, options]) => {
888
- const key =
889
- typeof methodOrKey === 'string'
890
- ? methodOrKey
891
- : typeof methodOrKey === 'function' && '_method' in methodOrKey
892
- ? `${(methodOrKey._method as Method.AnyServer).name}/${(methodOrKey._method as Method.AnyServer).alias ?? (methodOrKey._method as Method.AnyServer).intent}`
893
- : `${(methodOrKey as Method.AnyServer).name}/${(methodOrKey as Method.AnyServer).alias ?? (methodOrKey as Method.AnyServer).intent}`
894
- const handlerFn = handlers[key] as AnyMethodFn | undefined
895
- if (!handlerFn)
896
- throw new Error(`No handler for "${key}". Is this method in your methods array?`)
897
- return handlerFn(options)
898
- })
899
- return composeHandlers(
900
- configured as ConfiguredHandler[],
901
- undefined,
902
- selectOffers as SelectOffers<readonly Method.AnyServer[]> | undefined,
903
- )
904
- }
905
-
906
- function onChallengeCreated(
907
- handler: ServerEventHandler<FlattenMethods<methods>, transport, 'challenge.created'>,
908
- ) {
909
- return serverEvents.on('challenge.created', handler)
910
- }
911
-
912
- function onPaymentFailed(
913
- handler: ServerEventHandler<FlattenMethods<methods>, transport, 'payment.failed'>,
914
- ) {
915
- return serverEvents.on('payment.failed', handler)
916
- }
917
-
918
- function onPaymentSuccess(
919
- handler: ServerEventHandler<FlattenMethods<methods>, transport, 'payment.success'>,
920
- ) {
921
- return serverEvents.on('payment.success', handler)
922
- }
923
-
924
- return {
925
- methods,
926
- challenge: challengeHandlers,
927
- compose: composeFn,
928
- on: serverEvents.on,
929
- onChallengeCreated,
930
- onPaymentFailed,
931
- onPaymentSuccess,
932
- realm: realm as string | undefined,
933
- broadcastCredential: broadcastCredentialFn,
934
- transport,
935
- validateCredential: validateCredentialFn,
936
- verifyCredential: verifyCredentialFn,
937
- ...handlers,
938
- } as never
939
- }
940
-
941
- function assertSecretKey(secretKey: string) {
942
- const byteLength = new TextEncoder().encode(secretKey).byteLength
943
- if (byteLength >= minimumSecretKeyBytes) return
944
-
945
- throw new Error(
946
- `Secret key must be at least ${minimumSecretKeyBytes} bytes. Generate one with \`${secretKeyGenerationCommand}\` and set MPP_SECRET_KEY or pass it to Mppx.create().`,
947
- )
948
- }
949
-
950
- export declare namespace create {
951
- type Config<
952
- methods extends Methods = Methods,
953
- transport extends Transport.AnyTransport = Transport.Http,
954
- > = {
955
- /** Request-attestation verifiers required before handling an HTTP payment request. */
956
- attestation?: transport extends Transport.Http ? Attestation.VerifierMap | undefined : never
957
- /** Array of configured methods. @example [tempo()] */
958
- methods: methods
959
- /** Server realm (e.g., hostname). Resolution order: explicit value > env vars (`MPP_REALM`, `FLY_APP_NAME`, `VERCEL_URL`, etc.) > request URL hostname > `"MPP Payment"`. */
960
- realm?: string | undefined
961
- /** Secret key for HMAC-bound challenge IDs for stateless verification. Must be at least 32 bytes. Auto-detected from `MPP_SECRET_KEY` environment variable. */
962
- secretKey?: string | undefined
963
- /** Selects a subset of composed HTTP offers before challenges are issued. Successfully matched credential dispatch bypasses this hook. */
964
- selectOffers?: transport extends Transport.Http
965
- ? SelectOffers<FlattenMethods<methods>> | undefined
966
- : never
967
- /** Transport to use. @default Transport.http() */
968
- transport?: transport | undefined
969
- }
970
- }
971
-
972
- function createMethodFn<
973
- method extends Method.Method,
974
- transport extends Transport.AnyTransport,
975
- defaults extends Record<string, unknown>,
976
- >(
977
- parameters: createMethodFn.Parameters<method, transport, defaults>,
978
- ): createMethodFn.ReturnType<method, transport, defaults>
979
- // biome-ignore lint/correctness/noUnusedVariables: _
980
- function createMethodFn(parameters: createMethodFn.Parameters): createMethodFn.ReturnType {
981
- const {
982
- attest,
983
- authorize,
984
- defaults,
985
- events,
986
- method,
987
- preflight,
988
- realm,
989
- respond,
990
- secretKey,
991
- broadcast,
992
- stableBinding,
993
- transport,
994
- validate,
995
- verify,
996
- } = parameters
997
-
998
- return (options) => {
999
- const { description, meta, scope, ...rest } = options
1000
- const staticMeta = Scope.merge({ meta, scope })
1001
- const internal = {
1002
- ...method,
1003
- ...defaults,
1004
- ...options,
1005
- ...(staticMeta !== undefined ? { meta: staticMeta } : {}),
1006
- name: method.name,
1007
- intent: method.intent,
1008
- html: method.html,
1009
- _canonicalRequest: PaymentRequest.fromMethod(method, { ...defaults, ...rest }),
1010
- _stableBinding: stableBinding as never,
1011
- } as unknown as ConfiguredHandler['_internal']
1012
- Object.defineProperty(internal, '_method', { value: method })
1013
-
1014
- const handler = async (input: Transport.InputOf): Promise<MethodFn.Response> => {
1015
- if (attest && input instanceof globalThis.Request) {
1016
- const response = await attest(input)
1017
- if (response)
1018
- return {
1019
- status: 200,
1020
- withReceipt() {
1021
- return response
1022
- },
1023
- } as MethodFn.Response
1024
- }
1025
-
1026
- if (method.html && isServiceWorkerRequest(input))
1027
- return {
1028
- status: 402,
1029
- challenge: createServiceWorkerResponse(),
1030
- } as MethodFn.Response<Transport.Http>
1031
-
1032
- const expires =
1033
- 'expires' in options
1034
- ? normalizeExpires(options.expires as z.DatetimeInput | undefined)
1035
- : Expires.minutes(5)
1036
- const capturedRequest = await captureRequest(transport, input)
1037
- const effectiveMeta =
1038
- scope === undefined && input instanceof globalThis.Request
1039
- ? Scope.merge({ meta: staticMeta, scope: Scope.get(input) })
1040
- : staticMeta
1041
-
1042
- // Extract credential once — getCredential may have side effects (e.g. SSE transports).
1043
- let [credential, credentialError] = (() => {
1044
- try {
1045
- const credential = transport.getCredential(input) as Credential.Credential | null
1046
- return [credential ? hydrateCredentialMeta(credential) : null, undefined] as const
1047
- } catch (e) {
1048
- return [null, e as Error] as const
1049
- }
1050
- })()
1051
-
1052
- if (preflight && input instanceof globalThis.Request) {
1053
- const response = await preflight({
1054
- capturedRequest,
1055
- credential,
1056
- expires,
1057
- input,
1058
- options: { ...defaults, ...rest },
1059
- realm: realm ?? new URL(input.url).hostname ?? 'MPP Payment',
1060
- secretKey,
1061
- } as never)
1062
- if (response) {
1063
- if (response.status === 402) return { challenge: response, status: 402 }
1064
- return {
1065
- status: 200,
1066
- withReceipt() {
1067
- return response
1068
- },
1069
- } as MethodFn.Response
1070
- }
1071
- }
1072
-
1073
- const emitChallenge = async (parameters: {
1074
- challenge: Challenge.Challenge
1075
- credential?: Credential.Credential | null | undefined
1076
- error?: Errors.PaymentError | undefined
1077
- html?: Method.Method['html'] | undefined
1078
- request: Record<string, unknown>
1079
- }) => {
1080
- const response = await transport.respondChallenge({
1081
- challenge: parameters.challenge,
1082
- input,
1083
- ...(parameters.error && { error: parameters.error }),
1084
- ...(parameters.html && { html: parameters.html }),
1085
- })
1086
- if (isIssuedChallengeResponse(response))
1087
- await events.emit(
1088
- 'challenge.created',
1089
- createChallengeContext({
1090
- capturedRequest,
1091
- challenge: parameters.challenge,
1092
- credential: parameters.credential,
1093
- error: parameters.error,
1094
- input,
1095
- method,
1096
- request: parameters.request,
1097
- }) as never,
1098
- )
1099
- return response
1100
- }
1101
-
1102
- const emitPaymentFailed = async (parameters: {
1103
- challenge: Challenge.Challenge
1104
- credential: Credential.Credential | null
1105
- error: Errors.PaymentError
1106
- request: Record<string, unknown>
1107
- retryChallenge?: Challenge.Challenge | undefined
1108
- submittedChallenge?: Challenge.Challenge | undefined
1109
- }) => {
1110
- await events.emit(
1111
- 'payment.failed',
1112
- createPaymentFailedContext({
1113
- capturedRequest,
1114
- challenge: parameters.challenge,
1115
- credential: parameters.credential,
1116
- error: parameters.error,
1117
- input,
1118
- method,
1119
- request: parameters.request,
1120
- retryChallenge: parameters.retryChallenge,
1121
- submittedChallenge: parameters.submittedChallenge,
1122
- }) as never,
1123
- )
1124
- }
1125
-
1126
- const routeChallenge = await resolveRouteChallenge({
1127
- capturedRequest,
1128
- credential,
1129
- defaults,
1130
- description,
1131
- expires,
1132
- meta: effectiveMeta,
1133
- method,
1134
- realm,
1135
- request: parameters.request,
1136
- routeRequest: rest,
1137
- secretKey,
1138
- }).catch(async (e) => {
1139
- if (!(e instanceof Errors.PaymentError)) throw e
1140
- const challenge = createFallbackChallenge({
1141
- capturedRequest,
1142
- defaults: defaults ?? {},
1143
- description,
1144
- expires,
1145
- meta: effectiveMeta,
1146
- method,
1147
- realm,
1148
- routeRequest: rest,
1149
- secretKey,
1150
- })
1151
- if (credential)
1152
- await emitPaymentFailed({
1153
- challenge,
1154
- credential,
1155
- error: e,
1156
- request: challenge.request,
1157
- retryChallenge: challenge,
1158
- submittedChallenge: credential.challenge,
1159
- })
1160
- const response = await emitChallenge({
1161
- challenge,
1162
- credential,
1163
- request: challenge.request,
1164
- error: e,
1165
- html: method.html,
1166
- })
1167
- return { response }
1168
- })
1169
- if ('response' in routeChallenge) return { challenge: routeChallenge.response, status: 402 }
1170
- const { challenge, parsedRequest, request } = routeChallenge
1171
- internal._canonicalRequest = parsedRequest
1172
-
1173
- if (credential && transport.bindCredential) {
1174
- try {
1175
- credential = hydrateCredentialMeta(
1176
- (await transport.bindCredential({
1177
- challenge,
1178
- credential,
1179
- input,
1180
- })) as Credential.Credential,
1181
- )
1182
- } catch (e) {
1183
- credential = null
1184
- credentialError = e as Error
1185
- }
1186
- }
1187
-
1188
- // Credential was provided but malformed
1189
- if (credentialError) {
1190
- const reason = getSafeCredentialReason(credentialError)
1191
- const error = new Errors.MalformedCredentialError(reason ? { reason } : {})
1192
- await emitPaymentFailed({
1193
- challenge,
1194
- credential: null,
1195
- error,
1196
- request: parsedRequest,
1197
- retryChallenge: challenge,
1198
- })
1199
- const response = await emitChallenge({
1200
- challenge,
1201
- credential: null,
1202
- request: parsedRequest,
1203
- error,
1204
- html: method.html,
1205
- })
1206
- return { challenge: response, status: 402 }
1207
- }
1208
-
1209
- const success = (
1210
- receiptData: Receipt.Receipt,
1211
- options: {
1212
- challengeId?: string | undefined
1213
- credentialForReceipt?: Credential.Credential | undefined
1214
- envelopeForReceipt?: Method.VerifiedChallengeEnvelope | undefined
1215
- managementResponse?: globalThis.Response | undefined
1216
- } = {},
1217
- ): MethodFn.Response => {
1218
- const {
1219
- challengeId = challenge.id,
1220
- credentialForReceipt = { challenge, payload: {} } as Credential.Credential,
1221
- envelopeForReceipt,
1222
- managementResponse,
1223
- } = options
1224
-
1225
- return {
1226
- status: 200,
1227
- withReceipt<response>(response?: response) {
1228
- if (managementResponse) {
1229
- return transport.respondReceipt({
1230
- challengeId,
1231
- credential: credentialForReceipt,
1232
- ...(envelopeForReceipt ? { envelope: envelopeForReceipt } : {}),
1233
- input,
1234
- receipt: receiptData,
1235
- response: managementResponse as never,
1236
- }) as response
1237
- }
1238
- if (!response) throw new MissingReceiptResponseError()
1239
- return transport.respondReceipt({
1240
- challengeId,
1241
- credential: credentialForReceipt,
1242
- ...(envelopeForReceipt ? { envelope: envelopeForReceipt } : {}),
1243
- input,
1244
- receipt: receiptData,
1245
- response: response as never,
1246
- }) as response
1247
- },
1248
- }
1249
- }
1250
-
1251
- // No credential provided—issue challenge
1252
- if (!credential) {
1253
- if (authorize && input instanceof globalThis.Request) {
1254
- try {
1255
- const authorized = await authorize({
1256
- challenge,
1257
- input,
1258
- request: challenge.request,
1259
- } as never)
1260
- if (authorized) {
1261
- await events.emit(
1262
- 'payment.success',
1263
- createPaymentSuccessContext({
1264
- capturedRequest,
1265
- challenge,
1266
- input,
1267
- method,
1268
- receipt: authorized.receipt,
1269
- request: parsedRequest,
1270
- }) as never,
1271
- )
1272
- return success(authorized.receipt, {
1273
- managementResponse: authorized.response,
1274
- })
1275
- }
1276
- } catch (e) {
1277
- if (!(e instanceof Errors.PaymentError))
1278
- console.error('mppx: internal authorization error', e)
1279
- const error =
1280
- e instanceof Errors.PaymentError ? e : new Errors.VerificationFailedError()
1281
- await emitPaymentFailed({
1282
- challenge,
1283
- credential: null,
1284
- error,
1285
- request: parsedRequest,
1286
- retryChallenge: challenge,
1287
- })
1288
- const response = await emitChallenge({
1289
- challenge,
1290
- request: parsedRequest,
1291
- error,
1292
- html: method.html,
1293
- })
1294
- return { challenge: response, status: 402 }
1295
- }
1296
- }
1297
-
1298
- const error = new Errors.PaymentRequiredError({ description })
1299
- const response = await emitChallenge({
1300
- challenge,
1301
- credential: null,
1302
- request: parsedRequest,
1303
- error,
1304
- html: method.html,
1305
- })
1306
- return { challenge: response, status: 402 }
1307
- }
1308
-
1309
- // ── Tier 1: HMAC provenance check (primary gate) ──────────────────
1310
- //
1311
- // Recompute the HMAC-SHA256 over the credential's echoed challenge
1312
- // parameters (realm|method|intent|request|expires|digest|opaque) and
1313
- // compare to the echoed `id`. This proves the challenge was issued by
1314
- // this server with these exact parameters — including opaque/meta,
1315
- // expires, and the full serialized request blob.
1316
- //
1317
- // This is the authoritative binding per §5.1.2.1.1 of the spec
1318
- // (https://paymentauth.org/draft-httpauth-payment-00.html#section-5.1.2.1.1).
1319
- // No database lookup is needed; the HMAC is stateless verification.
1320
- if (!Challenge.verify(credential.challenge, { secretKey })) {
1321
- const error = new Errors.InvalidChallengeError({
1322
- id: credential.challenge.id,
1323
- reason: 'challenge was not issued by this server',
1324
- })
1325
- await emitPaymentFailed({
1326
- challenge,
1327
- credential,
1328
- error,
1329
- request: parsedRequest,
1330
- retryChallenge: challenge,
1331
- submittedChallenge: credential.challenge,
1332
- })
1333
- const response = await emitChallenge({
1334
- challenge,
1335
- credential,
1336
- request: parsedRequest,
1337
- error,
1338
- html: method.html,
1339
- })
1340
- return { challenge: response, status: 402 }
1341
- }
1342
-
1343
- // ── Tier 2: Pinned field safety net ──────────────────────────────
1344
- //
1345
- // The HMAC check above (Tier 1) is the primary gate — it already
1346
- // covers ALL challenge fields including opaque, digest, and the full
1347
- // serialized request. So why this second check?
1348
- //
1349
- // The `request()` hook can produce credential-dependent output: for
1350
- // example, `feePayer` may differ between the 402 challenge call (no
1351
- // credential) and the credential-bearing call. This means the
1352
- // recomputed challenge here has a different `request` blob — and
1353
- // thus a different HMAC — than the original challenge the client
1354
- // echoes back. The HMAC check above verifies the *echoed* challenge
1355
- // was signed by us, but it cannot verify that the echoed challenge
1356
- // matches *this route's current configuration* when the request
1357
- // hook transforms fields between calls.
1358
- //
1359
- // This check compares the fields that MUST be stable across both
1360
- // calls. That includes the economically significant request fields
1361
- // plus `opaque`, which can carry route-scoping metadata (for example,
1362
- // sibling route identity) that must not be replayable across handlers.
1363
- // `expires` still is not pinned here because its default is generated
1364
- // per invocation, and `digest` is already bound by the echoed HMAC.
1365
- {
1366
- const mismatch = getChallengeBindingMismatch(
1367
- challenge,
1368
- credential.challenge,
1369
- stableBinding as never,
1370
- )
1371
- if (mismatch) {
1372
- const error = new Errors.InvalidChallengeError({
1373
- id: credential.challenge.id,
1374
- reason: `credential ${mismatch} does not match this route's requirements`,
1375
- })
1376
- await emitPaymentFailed({
1377
- challenge,
1378
- credential,
1379
- error,
1380
- request: parsedRequest,
1381
- retryChallenge: challenge,
1382
- submittedChallenge: credential.challenge,
1383
- })
1384
- const response = await emitChallenge({
1385
- challenge,
1386
- credential,
1387
- request: parsedRequest,
1388
- error,
1389
- html: method.html,
1390
- })
1391
- return { challenge: response, status: 402 }
1392
- }
1393
- }
1394
-
1395
- // Reject credentials without expires (fail-closed) or with expired timestamp
1396
- try {
1397
- Expires.assert(credential.challenge.expires, credential.challenge.id)
1398
- } catch (error) {
1399
- await emitPaymentFailed({
1400
- challenge,
1401
- credential,
1402
- error: error as Errors.PaymentError,
1403
- request: parsedRequest,
1404
- retryChallenge: challenge,
1405
- submittedChallenge: credential.challenge,
1406
- })
1407
- const response = await emitChallenge({
1408
- challenge,
1409
- credential,
1410
- request: parsedRequest,
1411
- error: error as Errors.PaymentError,
1412
- })
1413
- return { challenge: response, status: 402 }
1414
- }
1415
- // Validate payload structure against method schema
1416
- let parsedCredential: Credential.Credential
1417
- try {
1418
- parsedCredential = withParsedCredentialPayload(
1419
- credential,
1420
- method.schema.credential.payload.parse(credential.payload),
1421
- )
1422
- } catch {
1423
- const error = new Errors.InvalidPayloadError()
1424
- await emitPaymentFailed({
1425
- challenge,
1426
- credential,
1427
- error,
1428
- request: parsedRequest,
1429
- retryChallenge: challenge,
1430
- submittedChallenge: credential.challenge,
1431
- })
1432
- const response = await emitChallenge({
1433
- challenge,
1434
- credential,
1435
- request: parsedRequest,
1436
- error,
1437
- })
1438
- return { challenge: response, status: 402 }
1439
- }
1440
-
1441
- const envelope: Method.VerifiedChallengeEnvelope = Object.freeze({
1442
- capturedRequest,
1443
- challenge: credential.challenge,
1444
- credential: parsedCredential,
1445
- request: parsedRequest,
1446
- })
1447
-
1448
- // User-provided verification (e.g., check signature, submit tx, verify payment).
1449
- // If verification fails, re-issue the challenge so the client can retry.
1450
- let receiptData: Receipt.Receipt
1451
- try {
1452
- if (broadcast && validate)
1453
- await validate({ credential: parsedCredential, envelope, request } as never)
1454
- const broadcastCredential = broadcast ?? verify
1455
- receiptData = await broadcastCredential({
1456
- credential: parsedCredential,
1457
- envelope,
1458
- request,
1459
- } as never)
1460
- } catch (e) {
1461
- if (!(e instanceof Errors.PaymentError))
1462
- console.error('mppx: internal verification error', e)
1463
- const error = e instanceof Errors.PaymentError ? e : new Errors.VerificationFailedError()
1464
- await emitPaymentFailed({
1465
- challenge,
1466
- credential: parsedCredential,
1467
- error,
1468
- request: parsedRequest,
1469
- retryChallenge: challenge,
1470
- submittedChallenge: credential.challenge,
1471
- })
1472
- const response = await emitChallenge({
1473
- challenge,
1474
- credential: parsedCredential,
1475
- request: parsedRequest,
1476
- error,
1477
- })
1478
- return { challenge: response, status: 402 }
1479
- }
1480
-
1481
- // If the method's `respond` hook returns a Response, it means this
1482
- // request is a management action (e.g. channel open, voucher POST)
1483
- // and the user's route handler should NOT run. `withReceipt()` will
1484
- // return the management response directly. If undefined, `withReceipt()`
1485
- // expects the caller to pass the user handler's response instead.
1486
- const managementResponse = respond
1487
- ? await respond({
1488
- credential: parsedCredential,
1489
- envelope,
1490
- input,
1491
- receipt: receiptData,
1492
- request,
1493
- } as never)
1494
- : undefined
1495
-
1496
- await events.emit(
1497
- 'payment.success',
1498
- createPaymentSuccessContext({
1499
- capturedRequest,
1500
- challenge: credential.challenge,
1501
- credential: parsedCredential,
1502
- envelope,
1503
- input,
1504
- method,
1505
- receipt: receiptData,
1506
- request: parsedRequest,
1507
- }) as never,
1508
- )
1509
-
1510
- return success(receiptData, {
1511
- challengeId: credential.challenge.id,
1512
- credentialForReceipt: parsedCredential,
1513
- envelopeForReceipt: envelope,
1514
- managementResponse,
1515
- })
1516
- }
1517
-
1518
- return Object.assign(handler, { _internal: internal })
1519
- }
1520
- }
1521
-
1522
- /**
1523
- * Creates a challenge generator for a single method+intent.
1524
- * Applies the same defaults and request transform as createMethodFn,
1525
- * but returns a Challenge object directly instead of a request handler.
1526
- */
1527
- function createChallengeFn(parameters: {
1528
- defaults?: Record<string, unknown>
1529
- method: Method.Method
1530
- realm: string | undefined
1531
- request?: Method.RequestFn<Method.Method>
1532
- secretKey: string
1533
- }): (options: Record<string, unknown>) => Promise<Challenge.Challenge> {
1534
- const { defaults, method, realm, secretKey } = parameters
1535
-
1536
- return async (options) => {
1537
- const { description, meta, scope, ...rest } = options as {
1538
- description?: string
1539
- expires?: z.DatetimeInput
1540
- meta?: Record<string, string>
1541
- scope?: string
1542
- [key: string]: unknown
1543
- }
1544
- const effectiveMeta = Scope.merge({ meta, scope })
1545
- const expires =
1546
- 'expires' in options
1547
- ? normalizeExpires(options.expires as z.DatetimeInput | undefined)
1548
- : Expires.minutes(5)
1549
-
1550
- return resolveRouteChallenge({
1551
- defaults,
1552
- description,
1553
- expires,
1554
- meta: effectiveMeta,
1555
- method,
1556
- realm,
1557
- request: parameters.request,
1558
- routeRequest: rest,
1559
- secretKey,
1560
- }).then((resolved) => resolved.challenge)
1561
- }
1562
- }
1563
-
1564
- declare namespace createMethodFn {
1565
- type Parameters<
1566
- method extends Method.Method = Method.Method,
1567
- transport extends Transport.AnyTransport = Transport.Http,
1568
- defaults extends Record<string, unknown> = Record<string, unknown>,
1569
- > = {
1570
- attest?: (request: globalThis.Request) => Promise<globalThis.Response | undefined>
1571
- authorize?: Method.AuthorizeFn<method>
1572
- defaults?: defaults
1573
- method: method
1574
- events: ServerEventDispatcher<readonly [method], transport>
1575
- preflight?: Method.PreflightFn<method>
1576
- realm: string | undefined
1577
- request?: Method.RequestFn<method>
1578
- respond?: Method.RespondFn<method>
1579
- broadcast?: Method.BroadcastFn<method>
1580
- secretKey: string
1581
- stableBinding?: Method.StableBindingFn<method>
1582
- transport: transport
1583
- validate?: Method.ValidateFn<method>
1584
- verify: Method.VerifyFn<method>
1585
- }
1586
-
1587
- type ReturnType<
1588
- method extends Method.Method = Method.Method,
1589
- transport extends Transport.AnyTransport = Transport.Http,
1590
- defaults extends Record<string, unknown> = Record<string, unknown>,
1591
- > = MethodFn<method, transport, defaults>
1592
- }
1593
-
1594
- function createAttestationGuard(verifiers: Attestation.VerifierMap) {
1595
- const entries = Object.entries(verifiers)
1596
- if (entries.length === 0)
1597
- throw new TypeError('Mppx server attestation must configure at least one verifier.')
1598
-
1599
- const cache = new WeakMap<globalThis.Request, Promise<globalThis.Response | undefined>>()
1600
- return (request: globalThis.Request): Promise<globalThis.Response | undefined> => {
1601
- const cached = cache.get(request)
1602
- if (cached) return cached
1603
-
1604
- const verification = AttestationServer.verify(request, verifiers).then((outcomes) => {
1605
- for (const [name, outcome] of Object.entries(outcomes))
1606
- if (outcome.status === 'invalid')
1607
- return new globalThis.Response(
1608
- `Request attestation "${name}" is invalid: ${outcome.reason}`,
1609
- { status: 401 },
1610
- )
1611
-
1612
- for (const [name, outcome] of Object.entries(outcomes)) {
1613
- if (outcome.status === 'verified') continue
1614
- const reason =
1615
- outcome.status === 'unverified'
1616
- ? `Request attestation "${name}" could not be verified: ${outcome.reason}`
1617
- : `Request attestation "${name}" is required.`
1618
- return new globalThis.Response(reason, { status: 403 })
1619
- }
1620
- return undefined
1621
- })
1622
- cache.set(request, verification)
1623
- return verification
1624
- }
1625
- }
1626
-
1627
- type ServerEventDispatcher<
1628
- methods extends readonly Method.Method[],
1629
- transport extends Transport.AnyTransport,
1630
- > = {
1631
- emit<name extends keyof ServerEventMap<methods, transport>>(
1632
- name: name,
1633
- context: ServerEventMap<methods, transport>[name],
1634
- ): Promise<void>
1635
- on<name extends ServerEventName<methods, transport>>(
1636
- name: name,
1637
- handler: ServerEventHandler<methods, transport, name>,
1638
- ): Unsubscribe
1639
- }
1640
-
1641
- function createServerEventDispatcher<
1642
- methods extends readonly Method.Method[],
1643
- transport extends Transport.AnyTransport,
1644
- >(): ServerEventDispatcher<methods, transport> {
1645
- const handlers = {
1646
- '*': new Set<ServerEventHandler<methods, transport, '*'>>(),
1647
- 'challenge.created': new Set<ServerEventHandler<methods, transport, 'challenge.created'>>(),
1648
- 'payment.failed': new Set<ServerEventHandler<methods, transport, 'payment.failed'>>(),
1649
- 'payment.success': new Set<ServerEventHandler<methods, transport, 'payment.success'>>(),
1650
- }
1651
-
1652
- const on: ServerEventDispatcher<methods, transport>['on'] = (name, handler) => {
1653
- switch (name) {
1654
- case '*':
1655
- case 'challenge.created':
1656
- case 'payment.failed':
1657
- case 'payment.success':
1658
- handlers[name].add(handler as never)
1659
- return () => handlers[name].delete(handler as never)
1660
- default:
1661
- throw new Error(`Unknown server event "${String(name)}".`)
1662
- }
1663
- }
1664
-
1665
- return {
1666
- async emit(name, context) {
1667
- await emitServerEventHandlers(handlers[name], context)
1668
- await emitServerEventHandlers(handlers['*'], toServerEventEnvelope(name, context))
1669
- },
1670
- on,
1671
- }
1672
- }
1673
-
1674
- function toServerEventEnvelope<
1675
- methods extends readonly Method.Method[],
1676
- transport extends Transport.AnyTransport,
1677
- name extends keyof ServerEventMap<methods, transport>,
1678
- >(
1679
- name: name,
1680
- payload: ServerEventMap<methods, transport>[name],
1681
- ): ServerEventPayload<methods, transport, '*'> {
1682
- return Object.freeze({ name, payload }) as ServerEventPayload<methods, transport, '*'>
1683
- }
1684
-
1685
- async function emitServerEventHandlers(
1686
- handlers: ReadonlySet<(context: never) => MaybePromise<void>>,
1687
- context: unknown,
1688
- ): Promise<void> {
1689
- for (const handler of handlers) {
1690
- try {
1691
- await handler(context as never)
1692
- } catch {
1693
- // Errors are isolated, but handlers are still awaited inline.
1694
- }
1695
- }
1696
- }
1697
-
1698
- function assertNoReservedMppxKeys(methods: readonly Method.AnyServer[]) {
1699
- for (const method of methods) {
1700
- if (reservedMppxKeys.has(method.name as ReservedKey))
1701
- throw new Error(`Method name "${method.name}" conflicts with a reserved Mppx property.`)
1702
- if (reservedMppxKeys.has(method.intent as ReservedKey))
1703
- throw new Error(`Method intent "${method.intent}" conflicts with a reserved Mppx property.`)
1704
- }
1705
- }
1706
-
1707
- function createChallengeContext(parameters: {
1708
- capturedRequest?: Method.CapturedRequest | undefined
1709
- challenge: Challenge.Challenge
1710
- credential?: Credential.Credential | null | undefined
1711
- error?: Errors.PaymentError | undefined
1712
- input?: unknown
1713
- method: Method.Method | ServerMethodDescriptor
1714
- request: Record<string, unknown>
1715
- }): ChallengeContext {
1716
- return Object.freeze({
1717
- ...(parameters.capturedRequest
1718
- ? { capturedRequest: snapshotCapturedRequest(parameters.capturedRequest) }
1719
- : {}),
1720
- challenge: snapshotValue(parameters.challenge),
1721
- credential:
1722
- parameters.credential === undefined
1723
- ? undefined
1724
- : snapshotNullableValue(parameters.credential),
1725
- error: snapshotError(parameters.error),
1726
- ...snapshotInputProperty(parameters.input),
1727
- method: snapshotMethod(parameters.method),
1728
- request: snapshotValue(parameters.request),
1729
- }) as never
1730
- }
1731
-
1732
- function createPaymentFailedContext(parameters: {
1733
- capturedRequest?: Method.CapturedRequest | undefined
1734
- challenge: Challenge.Challenge
1735
- credential: Credential.Credential | null
1736
- error: Errors.PaymentError
1737
- input?: unknown
1738
- method: Method.Method | ServerMethodDescriptor
1739
- request: Record<string, unknown>
1740
- retryChallenge?: Challenge.Challenge | undefined
1741
- submittedChallenge?: Challenge.Challenge | undefined
1742
- }): PaymentFailedContext {
1743
- return Object.freeze({
1744
- ...(parameters.capturedRequest
1745
- ? { capturedRequest: snapshotCapturedRequest(parameters.capturedRequest) }
1746
- : {}),
1747
- challenge: snapshotValue(parameters.challenge),
1748
- credential: snapshotNullableValue(parameters.credential),
1749
- error: snapshotError(parameters.error),
1750
- ...snapshotInputProperty(parameters.input),
1751
- method: snapshotMethod(parameters.method),
1752
- request: snapshotValue(parameters.request),
1753
- ...(parameters.retryChallenge
1754
- ? { retryChallenge: snapshotValue(parameters.retryChallenge) }
1755
- : {}),
1756
- ...(parameters.submittedChallenge
1757
- ? { submittedChallenge: snapshotValue(parameters.submittedChallenge) }
1758
- : {}),
1759
- }) as never
1760
- }
1761
-
1762
- function createPaymentSuccessContext(parameters: {
1763
- capturedRequest?: Method.CapturedRequest | undefined
1764
- challenge: Challenge.Challenge
1765
- credential?: Credential.Credential | undefined
1766
- envelope?: Method.VerifiedChallengeEnvelope | undefined
1767
- input?: unknown
1768
- method: Method.Method | ServerMethodDescriptor
1769
- receipt: Receipt.Receipt
1770
- request: Record<string, unknown>
1771
- }): PaymentSuccessContext {
1772
- return Object.freeze({
1773
- ...(parameters.capturedRequest
1774
- ? { capturedRequest: snapshotCapturedRequest(parameters.capturedRequest) }
1775
- : {}),
1776
- challenge: snapshotValue(parameters.challenge),
1777
- ...(parameters.credential ? { credential: snapshotValue(parameters.credential) } : {}),
1778
- ...(parameters.envelope ? { envelope: snapshotVerifiedEnvelope(parameters.envelope) } : {}),
1779
- ...snapshotInputProperty(parameters.input),
1780
- method: snapshotMethod(parameters.method),
1781
- receipt: snapshotValue(parameters.receipt),
1782
- request: snapshotValue(parameters.request),
1783
- }) as never
1784
- }
1785
-
1786
- function snapshotMethod<method extends Pick<Method.Method, 'intent' | 'name'>>(
1787
- method: method,
1788
- ): ServerMethodDescriptor<method> {
1789
- return Object.freeze({
1790
- intent: method.intent,
1791
- name: method.name,
1792
- }) as ServerMethodDescriptor<method>
1793
- }
1794
-
1795
- function snapshotError<error extends Errors.PaymentError | undefined>(error: error): error {
1796
- if (!error) return error
1797
- const snapshot = Object.assign(Object.create(Object.getPrototypeOf(error)), error)
1798
- Object.defineProperties(snapshot, {
1799
- message: { value: error.message, enumerable: false },
1800
- name: { value: error.name, enumerable: false },
1801
- })
1802
- return Object.freeze(snapshot) as error
1803
- }
1804
-
1805
- function snapshotVerifiedEnvelope(
1806
- envelope: Method.VerifiedChallengeEnvelope,
1807
- ): Method.VerifiedChallengeEnvelope {
1808
- return Object.freeze({
1809
- capturedRequest: snapshotCapturedRequest(envelope.capturedRequest),
1810
- challenge: snapshotValue(envelope.challenge),
1811
- credential: snapshotValue(envelope.credential),
1812
- request: snapshotValue(envelope.request),
1813
- }) as Method.VerifiedChallengeEnvelope
1814
- }
1815
-
1816
- function snapshotCapturedRequest(capturedRequest: Method.CapturedRequest): Method.CapturedRequest {
1817
- return Object.freeze({
1818
- headers: new Headers(capturedRequest.headers),
1819
- hasBody: capturedRequest.hasBody,
1820
- method: capturedRequest.method,
1821
- url: new URL(capturedRequest.url),
1822
- })
1823
- }
1824
-
1825
- function snapshotNullableValue<value>(value: value | null): value | null {
1826
- if (value === null) return null
1827
- return snapshotValue(value)
1828
- }
1829
-
1830
- function snapshotValue<value>(value: value): value {
1831
- try {
1832
- return freezeSnapshot(structuredClone(value))
1833
- } catch {
1834
- return freezeSnapshot(value)
1835
- }
1836
- }
1837
-
1838
- function snapshotInputProperty(input: unknown): { input: unknown } | {} {
1839
- if (input === undefined) return {}
1840
- const snapshot = snapshotTransportInput(input)
1841
- return snapshot === undefined ? {} : { input: snapshot }
1842
- }
1843
-
1844
- function snapshotTransportInput<input>(input: input): input | undefined {
1845
- if (input instanceof globalThis.Request) {
1846
- try {
1847
- return new globalThis.Request(input.url, {
1848
- headers: new Headers(input.headers),
1849
- method: input.method,
1850
- }) as input
1851
- } catch {
1852
- return undefined
1853
- }
1854
- }
1855
- try {
1856
- return freezeSnapshot(structuredClone(input))
1857
- } catch {
1858
- warnOnce(
1859
- Warnings.transportInputSnapshot,
1860
- 'Could not clone server event input; omitting `context.input`. Use `capturedRequest` for request correlation.',
1861
- )
1862
- return undefined
1863
- }
1864
- }
1865
-
1866
- // Event payloads are cloned before listeners see them; shallow freezing keeps
1867
- // the guard simple while preventing top-level mutation of receipts/challenges.
1868
- function freezeSnapshot<value>(value: value): value {
1869
- if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value
1870
- Object.freeze(value)
1871
- return value
1872
- }
1873
-
1874
- function isServiceWorkerRequest(input: unknown): input is Request {
1875
- return (
1876
- input instanceof globalThis.Request &&
1877
- new URL(input.url).searchParams.has(Html.params.serviceWorker)
1878
- )
1879
- }
1880
-
1881
- function createServiceWorkerResponse() {
1882
- return new Response(serviceWorker, {
1883
- status: 200,
1884
- headers: {
1885
- 'Content-Type': 'application/javascript',
1886
- 'Cache-Control': 'no-store',
1887
- },
1888
- })
1889
- }
1890
-
1891
- function isIssuedChallengeResponse(response: unknown): boolean {
1892
- return !(response instanceof globalThis.Response) || response.status === 402
1893
- }
1894
-
1895
- function getSafeCredentialReason(error: unknown): string | undefined {
1896
- if (error instanceof Credential.InvalidCredentialEncodingError) return error.message
1897
- if (error instanceof Credential.MissingAuthorizationHeaderError) return error.message
1898
- if (error instanceof Credential.MissingPaymentSchemeError) return error.message
1899
- return undefined
1900
- }
1901
-
1902
- const defaultRealm = 'MPP Payment'
1903
- const Warnings = {
1904
- realmFallback: 'realm-fallback',
1905
- transportInputSnapshot: 'transport-input-snapshot',
1906
- } as const
1907
- const missingReceiptResponseErrorName = 'MissingReceiptResponseError'
1908
- const missingReceiptResponseErrorMessage = 'withReceipt() requires a response argument'
1909
-
1910
- /** Error thrown when `withReceipt()` needs a response but none was provided. */
1911
- export class MissingReceiptResponseError extends Error {
1912
- override name = missingReceiptResponseErrorName
1913
-
1914
- constructor() {
1915
- super(missingReceiptResponseErrorMessage)
1916
- }
1917
- }
1918
-
1919
- /** Returns true when an error is the typed `withReceipt()` no-response sentinel. */
1920
- export function isMissingReceiptResponseError(
1921
- error: unknown,
1922
- ): error is MissingReceiptResponseError {
1923
- if (error instanceof MissingReceiptResponseError) return true
1924
- if (!error || typeof error !== 'object') return false
1925
- const value = error as { message?: unknown; name?: unknown }
1926
- return (
1927
- value.name === missingReceiptResponseErrorName &&
1928
- value.message === missingReceiptResponseErrorMessage
1929
- )
1930
- }
1931
-
1932
- function normalizeExpires(expires: z.DatetimeInput | undefined): string | undefined {
1933
- return expires === undefined ? undefined : z.toDatetimeString(expires)
1934
- }
1935
-
1936
- const _warned = new Set<string>()
1937
- function warnOnce(key: string, message: string) {
1938
- if (_warned.has(key)) return
1939
- _warned.add(key)
1940
- console.warn(`[mppx] ${message}`)
1941
- }
1942
-
1943
- /** Extracts hostname from the captured request URL, falling back to a default. */
1944
- function resolveRealmFromCapturedRequest(capturedRequest: Method.CapturedRequest): string {
1945
- try {
1946
- const { protocol, hostname } = capturedRequest.url
1947
- if (/^https?:$/.test(protocol) && hostname) return hostname
1948
- } catch {}
1949
- warnOnce(
1950
- Warnings.realmFallback,
1951
- `Could not auto-detect realm from request. Falling back to "${defaultRealm}". Set \`realm\` in Mppx.create() or the MPP_REALM env var.`,
1952
- )
1953
- return defaultRealm
1954
- }
1955
-
1956
- async function resolveRouteChallenge(parameters: {
1957
- capturedRequest?: Method.CapturedRequest | undefined
1958
- credential?: Credential.Credential | null | undefined
1959
- defaults?: Record<string, unknown> | undefined
1960
- description?: string | undefined
1961
- expires?: string | undefined
1962
- meta?: Record<string, string> | undefined
1963
- method: Method.Method
1964
- realm?: string | undefined
1965
- request?: Method.RequestFn<Method.Method> | undefined
1966
- routeRequest: Record<string, unknown>
1967
- secretKey: string
1968
- }): Promise<{
1969
- challenge: Challenge.Challenge
1970
- parsedRequest: Record<string, unknown>
1971
- request: Record<string, unknown>
1972
- }> {
1973
- // Resolve the route's canonical request exactly as the handler path does:
1974
- const request = await (async () => {
1975
- // start from defaults + route options, then let the method request hook
1976
- const merged = { ...parameters.defaults, ...parameters.routeRequest }
1977
- // normalize or enrich it using the captured request and credential.
1978
- return parameters.request
1979
- ? ((await parameters.request({
1980
- capturedRequest: parameters.capturedRequest,
1981
- credential: parameters.credential,
1982
- request: merged,
1983
- } as never)) as Record<string, unknown>)
1984
- : merged
1985
- })()
1986
-
1987
- const effectiveRealm =
1988
- parameters.realm ??
1989
- (parameters.capturedRequest
1990
- ? resolveRealmFromCapturedRequest(parameters.capturedRequest)
1991
- : defaultRealm)
1992
-
1993
- const challenge = Challenge.fromMethod(parameters.method, {
1994
- description: parameters.description,
1995
- expires: parameters.expires,
1996
- meta: parameters.meta,
1997
- realm: effectiveRealm,
1998
- request: request as never,
1999
- secretKey: parameters.secretKey,
2000
- })
2001
-
2002
- return {
2003
- challenge,
2004
- parsedRequest: challenge.request as Record<string, unknown>,
2005
- request,
2006
- }
2007
- }
2008
-
2009
- function createFallbackChallenge(parameters: {
2010
- capturedRequest?: Method.CapturedRequest | undefined
2011
- defaults: Record<string, unknown>
2012
- description?: string | undefined
2013
- expires?: string | undefined
2014
- meta?: Record<string, string> | undefined
2015
- method: Method.Method
2016
- realm?: string | undefined
2017
- routeRequest: Record<string, unknown>
2018
- secretKey: string
2019
- }) {
2020
- return Challenge.fromMethod(parameters.method, {
2021
- description: parameters.description,
2022
- expires: parameters.expires,
2023
- meta: parameters.meta,
2024
- realm:
2025
- parameters.realm ??
2026
- (parameters.capturedRequest
2027
- ? resolveRealmFromCapturedRequest(parameters.capturedRequest)
2028
- : defaultRealm),
2029
- request: { ...parameters.defaults, ...parameters.routeRequest } as never,
2030
- secretKey: parameters.secretKey,
2031
- })
2032
- }
2033
-
2034
- /**
2035
- * Captures the transport request into a frozen snapshot at the start of the
2036
- * verification flow. This snapshot is threaded through request() → verify() →
2037
- * respond() → respondReceipt() so every hook sees the same authoritative
2038
- * request state — preventing the raw transport input from being re-read or
2039
- * mutated between verification steps.
2040
- *
2041
- * Note: Object.freeze is shallow — it prevents reassigning top-level properties
2042
- * but does not deep-freeze mutable class instances like Headers or URL. This is
2043
- * an accidental-mutation guard for trusted server events, not a security boundary.
2044
- */
2045
- async function captureRequest(
2046
- transport: Transport.AnyTransport,
2047
- input: unknown,
2048
- ): Promise<Method.CapturedRequest> {
2049
- const capturedRequest = transport.captureRequest
2050
- ? await transport.captureRequest(input)
2051
- : captureRequestFromInput(input)
2052
-
2053
- return Object.freeze(capturedRequest)
2054
- }
2055
-
2056
- function captureRequestFromInput(input: unknown): Method.CapturedRequest {
2057
- const source = input as {
2058
- body?: unknown
2059
- headers?: HeadersInit | undefined
2060
- method?: string | undefined
2061
- url?: string | URL | undefined
2062
- }
2063
-
2064
- return {
2065
- headers: new Headers(source.headers),
2066
- hasBody: source.body === undefined ? undefined : source.body !== null,
2067
- method: source.method ?? 'POST',
2068
- url: Transport.safeUrl(source.url),
2069
- }
2070
- }
2071
-
2072
- const coreBindingFields = ['amount', 'currency', 'recipient'] as const
2073
- const methodBindingFields = ['chainId', 'memo', 'sessionProtocol', 'splits', 'unitType'] as const
2074
- const pinnedRequestBindingFields = [...coreBindingFields, ...methodBindingFields] as const
2075
-
2076
- type CoreBindingField = (typeof coreBindingFields)[number]
2077
- type MethodBindingField = (typeof methodBindingFields)[number]
2078
- type PinnedRequestBindingField = (typeof pinnedRequestBindingFields)[number]
2079
- type PinnedChallengeField = 'method' | 'intent' | 'realm' | 'opaque' | PinnedRequestBindingField
2080
- type StableBinding = Record<string, unknown>
2081
-
2082
- function getChallengeBindingMismatch(
2083
- expectedChallenge: Challenge.Challenge,
2084
- actualChallenge: Challenge.Challenge,
2085
- stableBinding?: Method.StableBindingFn<Method.Method> | undefined,
2086
- ): string | undefined {
2087
- if (!stableBinding) return getPinnedChallengeMismatch(expectedChallenge, actualChallenge)
2088
-
2089
- for (const field of ['method', 'intent', 'realm'] as const) {
2090
- if (actualChallenge[field] !== expectedChallenge[field]) return field
2091
- }
2092
-
2093
- if (!opaqueValuesMatch(expectedChallenge.meta, actualChallenge.meta)) return 'opaque'
2094
-
2095
- return getRequestBindingMismatch(
2096
- getStableBinding(expectedChallenge.request as Record<string, unknown>, stableBinding),
2097
- getStableBinding(actualChallenge.request as Record<string, unknown>, stableBinding),
2098
- )
2099
- }
2100
-
2101
- /**
2102
- * Compares only the fields that MUST be stable across request-hook transforms.
2103
- *
2104
- * This is NOT the primary integrity check — the HMAC binding (Challenge.verify)
2105
- * already covers every challenge field including opaque, digest, and the full
2106
- * serialized request. This function exists as a secondary safety net for the
2107
- * case where the `request()` hook produces credential-dependent output, causing
2108
- * the recomputed challenge to differ from the original in non-economic fields
2109
- * (e.g. `feePayer`). We only need to verify that the economically significant
2110
- * subset hasn't drifted.
2111
- */
2112
- function getPinnedChallengeMismatch(
2113
- expectedChallenge: Challenge.Challenge,
2114
- actualChallenge: Challenge.Challenge,
2115
- ): PinnedChallengeField | undefined {
2116
- for (const field of ['method', 'intent', 'realm'] as const) {
2117
- if (actualChallenge[field] !== expectedChallenge[field]) return field
2118
- }
2119
-
2120
- if (!opaqueValuesMatch(expectedChallenge.meta, actualChallenge.meta)) return 'opaque'
2121
-
2122
- return getPinnedRequestBindingMismatch(
2123
- expectedChallenge.request as Record<string, unknown>,
2124
- actualChallenge.request as Record<string, unknown>,
2125
- )
2126
- }
2127
-
2128
- function getPinnedRequestBindingMismatch(
2129
- expectedRequest: Record<string, unknown>,
2130
- actualRequest: Record<string, unknown>,
2131
- ): PinnedRequestBindingField | undefined {
2132
- const expected = getPinnedRequestBinding(expectedRequest)
2133
- const actual = getPinnedRequestBinding(actualRequest)
2134
-
2135
- return (
2136
- getCoreBindingMismatch(expected.coreBinding, actual.coreBinding) ??
2137
- getMethodBindingMismatch(expected.methodBinding, actual.methodBinding)
2138
- )
2139
- }
2140
-
2141
- function getCoreBindingMismatch(
2142
- expected: CoreBinding,
2143
- actual: CoreBinding,
2144
- ): CoreBindingField | undefined {
2145
- return coreBindingFields.find((field) => !isDeepStrictEqual(expected[field], actual[field]))
2146
- }
2147
-
2148
- function getMethodBindingMismatch(
2149
- expected: MethodBinding,
2150
- actual: MethodBinding,
2151
- ): MethodBindingField | undefined {
2152
- return methodBindingFields.find((field) => !isDeepStrictEqual(expected[field], actual[field]))
2153
- }
2154
-
2155
- function getPinnedRequestBinding(request: Record<string, unknown>): PinnedRequestBinding {
2156
- const methodDetails = (request.methodDetails ?? {}) as Record<string, unknown>
2157
- const amount = normalizeScalar(request.amount ?? methodDetails.amount)
2158
- const chainId = normalizeScalar(request.chainId ?? methodDetails.chainId)
2159
- const currency = normalizeScalar(request.currency ?? methodDetails.currency)
2160
- const memo = normalizeHex(methodDetails.memo)
2161
- const recipient = normalizeScalar(request.recipient ?? methodDetails.recipient)
2162
- const sessionProtocol = normalizeScalar(methodDetails.sessionProtocol)
2163
- const splits = normalizeComparable(methodDetails.splits)
2164
- const unitType = normalizeScalar(request.unitType ?? methodDetails.unitType)
2165
-
2166
- return {
2167
- coreBinding: {
2168
- ...(amount !== undefined ? { amount } : {}),
2169
- ...(currency !== undefined ? { currency } : {}),
2170
- ...(recipient !== undefined ? { recipient } : {}),
2171
- },
2172
- methodBinding: {
2173
- ...(chainId !== undefined ? { chainId } : {}),
2174
- ...(memo !== undefined ? { memo } : {}),
2175
- ...(sessionProtocol !== undefined ? { sessionProtocol } : {}),
2176
- ...(splits !== undefined ? { splits } : {}),
2177
- ...(unitType !== undefined ? { unitType } : {}),
2178
- },
2179
- }
2180
- }
2181
-
2182
- function getRequestBindingMismatch(
2183
- expected: StableBinding,
2184
- actual: StableBinding,
2185
- ): string | undefined {
2186
- const fields = [
2187
- ...Object.keys(expected),
2188
- ...Object.keys(actual).filter((key) => !(key in expected)),
2189
- ]
2190
-
2191
- return fields.find(
2192
- (field) =>
2193
- !isDeepStrictEqual(normalizeComparable(expected[field]), normalizeComparable(actual[field])),
2194
- )
2195
- }
2196
-
2197
- function getStableBinding(
2198
- request: Record<string, unknown>,
2199
- stableBinding: Method.StableBindingFn<Method.Method>,
2200
- ): StableBinding {
2201
- return stableBinding(request as never)
2202
- }
2203
-
2204
- /** Top-level economic fields that should never drift after challenge issuance. */
2205
- type CoreBinding = {
2206
- [field in CoreBindingField]?: string
2207
- }
2208
-
2209
- /** Method-specific fields that are pinned by the fallback binding check. */
2210
- type MethodBinding = {
2211
- [field in MethodBindingField]?: unknown
2212
- }
2213
-
2214
- /** Normalized request subset used when a method does not provide a custom stable binding. */
2215
- type PinnedRequestBinding = {
2216
- coreBinding: CoreBinding
2217
- methodBinding: MethodBinding
2218
- }
2219
-
2220
- function normalizeScalar(value: unknown): string | undefined {
2221
- return value === undefined ? undefined : String(value)
2222
- }
2223
-
2224
- function normalizeHex(value: unknown): string | undefined {
2225
- if (value === undefined) return undefined
2226
-
2227
- const normalized = String(value)
2228
- return normalized.startsWith('0x') ? normalized.toLowerCase() : normalized
2229
- }
2230
-
2231
- function normalizeComparable(value: unknown): unknown {
2232
- if (value === undefined) return undefined
2233
- if (Array.isArray(value)) return value.map(normalizeComparable)
2234
-
2235
- if (value && typeof value === 'object') {
2236
- return Object.fromEntries(
2237
- Object.entries(value as Record<string, unknown>)
2238
- .sort(([left], [right]) => left.localeCompare(right))
2239
- .map(([key, nested]) => [key, normalizeComparable(nested)]),
2240
- )
2241
- }
2242
-
2243
- return typeof value === 'string' ? normalizeHex(value) : value
2244
- }
2245
-
2246
- function opaqueValuesMatch(
2247
- expected: Record<string, string> | undefined,
2248
- actual: Record<string, string> | undefined,
2249
- ): boolean {
2250
- return isDeepStrictEqual(expected, actual)
2251
- }
2252
-
2253
- function hydrateCredentialMeta<payload>(
2254
- credential: Credential.Credential<payload>,
2255
- ): Credential.Credential<payload> {
2256
- const { challenge } = credential
2257
- if (challenge.opaque === undefined) return credential
2258
- const hydratedChallenge = Challenge.Schema.parse({
2259
- ...challenge,
2260
- meta: PaymentRequest.deserialize(challenge.opaque),
2261
- })
2262
- return {
2263
- ...credential,
2264
- challenge: hydratedChallenge,
2265
- }
2266
- }
2267
-
2268
- function withParsedCredentialPayload<payload>(
2269
- credential: Credential.Credential,
2270
- payload: payload,
2271
- ): Credential.Credential<payload> {
2272
- return {
2273
- ...credential,
2274
- payload,
2275
- }
2276
- }
2277
- export type MethodFn<
2278
- method extends Method.Method,
2279
- transport extends Transport.AnyTransport,
2280
- defaults extends Record<string, unknown>,
2281
- > = (
2282
- options: MethodFn.Options<method, defaults>,
2283
- ) => (input: Transport.InputOf<transport>) => Promise<MethodFn.Response<transport>>
2284
- /** @internal */
2285
- export type AnyMethodFn = (options: any) => (input: any) => Promise<any>
2286
- /** A MethodFn tagged with its source Method (set by `create()`). @internal */
2287
- type AnyMethodFnWithMethod = AnyMethodFn & { _method: Method.AnyServer }
2288
-
2289
- /** @internal */
2290
- declare namespace MethodFn {
2291
- export type Options<
2292
- method extends Method.Method,
2293
- defaults extends Record<string, unknown> = Record<string, unknown>,
2294
- > = {
2295
- /** Optional human-readable description of the payment. */
2296
- description?: string | undefined
2297
- /** Optional challenge expiration timestamp (ISO 8601) or Date. */
2298
- expires?: z.DatetimeInput | undefined
2299
- /** Optional server-defined correlation data (serialized as `opaque` in the request). Flat string-to-string map; clients MUST NOT modify. */
2300
- meta?: Record<string, string> | undefined
2301
- /** Optional route/resource scope bound via reserved challenge metadata. */
2302
- scope?: string | undefined
2303
- } & Method.WithDefaults<z.input<method['schema']['request']>, defaults>
2304
-
2305
- export type Response<transport extends Transport.AnyTransport = Transport.Http> =
2306
- | {
2307
- challenge: Transport.ChallengeOutputOf<transport>
2308
- status: 402
2309
- }
2310
- | {
2311
- status: 200
2312
- withReceipt: Transport.WithReceipt<transport>
2313
- }
2314
- }
2315
-
2316
- /** A configured handler — the return value of e.g. `mppx.charge({ ... })`. @internal */
2317
- type ConfiguredHandler = ((input: Request) => Promise<MethodFn.Response<Transport.Http>>) & {
2318
- _internal: {
2319
- _method: Method.AnyServer
2320
- description?: string | undefined
2321
- name: string
2322
- intent: string
2323
- html: Html.Options | undefined
2324
- meta?: Record<string, string> | undefined
2325
- scope?: string | undefined
2326
- _canonicalRequest: Record<string, unknown>
2327
- _stableBinding?: Method.StableBindingFn<Method.Method> | undefined
2328
- }
2329
- }
2330
-
2331
- type ComposedHandler = ((input: Request) => Promise<MethodFn.Response<Transport.Http>>) & {
2332
- _internal?: {
2333
- offers: readonly ConfiguredHandler['_internal'][]
2334
- }
2335
- }
2336
-
2337
- type HandlerDiscoveryMetadata =
2338
- | ConfiguredHandler['_internal']
2339
- | NonNullable<ComposedHandler['_internal']>
2340
-
2341
- const paymentAuthChallengeHeader = Constants.Headers.wwwAuthenticate
2342
-
2343
- const challengeHeaderMerges = [
2344
- {
2345
- name: paymentAuthChallengeHeader,
2346
- values: (context) =>
2347
- context.challengeEntries
2348
- .map((entry) =>
2349
- (entry.result.challenge as Response).headers.get(paymentAuthChallengeHeader),
2350
- )
2351
- .filter((value): value is string => value !== null),
2352
- merge: (values) => values,
2353
- },
2354
- {
2355
- name: x402_Types.paymentRequiredHeader,
2356
- values: (context) =>
2357
- context.negotiatedChallengeResponses
2358
- .map((response) => response.headers.get(x402_Types.paymentRequiredHeader))
2359
- .filter((value): value is string => value !== null),
2360
- merge: mergeX402PaymentRequiredHeaders,
2361
- },
2362
- ] satisfies readonly ChallengeHeaderMerge[]
2363
-
2364
- /** An entry for `compose()`: a method reference, handler function ref, or string key paired with its options. */
2365
- type ComposeEntry<methods extends readonly Method.AnyServer[]> =
2366
- | {
2367
- [i in keyof methods]: readonly [
2368
- methods[i],
2369
- MethodFn.Options<methods[i], NonNullable<methods[i]['defaults']>>,
2370
- ]
2371
- }[number]
2372
- | {
2373
- [i in keyof methods]: readonly [
2374
- `${methods[i]['name']}/${methods[i]['intent']}`,
2375
- MethodFn.Options<methods[i], NonNullable<methods[i]['defaults']>>,
2376
- ]
2377
- }[number]
2378
- | {
2379
- [i in keyof methods]: readonly [
2380
- MethodFn<methods[i], any, any> & { _method: methods[i] },
2381
- MethodFn.Options<methods[i], NonNullable<methods[i]['defaults']>>,
2382
- ]
2383
- }[number]
2384
-
2385
- /**
2386
- * Combines multiple configured payment handlers into a single route handler
2387
- * that presents all methods to the client via multiple `WWW-Authenticate` headers.
2388
- *
2389
- * When no credential is present, all handlers are called and their challenges
2390
- * are merged into a single 402 response. When a credential is present, it is
2391
- * dispatched to the handler matching the credential's `method`+`intent`.
2392
- *
2393
- * @example
2394
- * ```ts
2395
- * import { Mppx, tempo, stripe } from 'mppx/server'
2396
- *
2397
- * const mppx = Mppx.create({
2398
- * methods: [tempo(), stripe()],
2399
- * secretKey: process.env.PAYMENT_SECRET_KEY,
2400
- * })
2401
- *
2402
- * app.get('/api/resource', async (req) => {
2403
- * const result = await Mppx.compose(
2404
- * mppx['tempo/charge']({ amount: '100', currency: USDC, recipient: '0x...' }),
2405
- * mppx['stripe/charge']({ amount: '100', currency: 'usd' }),
2406
- * )(req)
2407
- * if (result.status === 402) return result.challenge
2408
- * return result.withReceipt(new Response('OK'))
2409
- * })
2410
- * ```
2411
- */
2412
- type ComposeHtmlOptions = Html.Config
2413
-
2414
- export function compose(...args: readonly unknown[]): ComposedHandler {
2415
- // Extract optional html options from last argument
2416
- const last = args[args.length - 1]
2417
- const composeOptions: Html.Options | undefined =
2418
- typeof last === 'object' &&
2419
- last !== null &&
2420
- typeof last !== 'function' &&
2421
- !('_internal' in last)
2422
- ? (() => {
2423
- const opts = last as ComposeHtmlOptions
2424
- return {
2425
- config: {},
2426
- content: '',
2427
- formatAmount: () => '',
2428
- text: opts.text,
2429
- theme: opts.theme,
2430
- }
2431
- })()
2432
- : undefined
2433
- const handlers = (composeOptions ? args.slice(0, -1) : args) as readonly ((
2434
- input: Request,
2435
- ) => Promise<MethodFn.Response<Transport.Http>>)[]
2436
-
2437
- return composeHandlers(handlers, composeOptions)
2438
- }
2439
-
2440
- function composeHandlers(
2441
- handlers: readonly ((input: Request) => Promise<MethodFn.Response<Transport.Http>>)[],
2442
- composeOptions?: Html.Options,
2443
- selectOffers?: SelectOffers<readonly Method.AnyServer[]>,
2444
- ): (input: Request) => Promise<MethodFn.Response<Transport.Http>> {
2445
- if (handlers.length === 0) throw new Error('compose() requires at least one handler')
2446
-
2447
- const offerSelector = selectOffers
2448
- const offerEntries = handlers.map((handler) => {
2449
- const internal = (handler as ConfiguredHandler)._internal
2450
- const offer =
2451
- internal && !isComposedHandlerMetadata(internal) ? createServerOffer(internal) : undefined
2452
- const method = internal && !isComposedHandlerMetadata(internal) ? internal._method : undefined
2453
- return { handler, method, offer }
2454
- })
2455
- const hasOfferPolicy =
2456
- offerSelector !== undefined || offerEntries.some(({ method }) => method?.canOffer !== undefined)
2457
-
2458
- const composed: ComposedHandler = async (input: Request) => {
2459
- // Serve service worker for html-enabled compose
2460
- if (new URL(input.url).searchParams.has(Html.params.serviceWorker)) {
2461
- const hasHtml = handlers.some((h) => (h as ConfiguredHandler)._internal?.html)
2462
- if (hasHtml)
2463
- return {
2464
- status: 402,
2465
- challenge: new Response(serviceWorker, {
2466
- status: 200,
2467
- headers: {
2468
- 'Content-Type': 'application/javascript',
2469
- 'Cache-Control': 'no-store',
2470
- },
2471
- }),
2472
- } as MethodFn.Response<Transport.Http>
2473
- }
2474
-
2475
- // Try to extract a Payment credential to decide whether to dispatch or challenge.
2476
- // Only gate on the Payment scheme — other auth schemes (Bearer, Basic, etc.)
2477
- // should fall through to the merged-402 path so all offers are presented.
2478
- const header = input.headers.get(Constants.Headers.authorization)
2479
- const paymentHeader = header ? Credential.extractPaymentScheme(header) : null
2480
-
2481
- if (paymentHeader) {
2482
- // Parse the credential to find method+intent for dispatch.
2483
- let credential: Credential.Credential | undefined
2484
- try {
2485
- credential = hydrateCredentialMeta(Credential.deserialize(paymentHeader))
2486
- } catch {}
2487
-
2488
- if (credential) {
2489
- const { method: credMethod, intent: credIntent } = credential.challenge
2490
- const credReq = credential.challenge.request as Record<string, unknown>
2491
-
2492
- // Filter by name+intent, then narrow by comparing stable request fields
2493
- // from the echoed challenge against each handler's configured offers.
2494
- // Uses the schema-parsed canonical form (not raw options) so that
2495
- // transformed fields (e.g. amount with decimals) match correctly.
2496
- // Also checks inside methodDetails for fields moved there by transforms.
2497
- const candidates = handlers.filter((handler) =>
2498
- getConfiguredOffers(handler).some((internal) => {
2499
- try {
2500
- if (internal.name !== credMethod || internal.intent !== credIntent) return false
2501
- const mismatch = internal._stableBinding
2502
- ? getRequestBindingMismatch(
2503
- getStableBinding(internal._canonicalRequest, internal._stableBinding),
2504
- getStableBinding(credReq, internal._stableBinding),
2505
- )
2506
- : getPinnedRequestBindingMismatch(internal._canonicalRequest, credReq)
2507
- return !mismatch && opaqueValuesMatch(internal.meta, credential.challenge.meta)
2508
- } catch {
2509
- return false
2510
- }
2511
- }),
2512
- )
2513
-
2514
- const match =
2515
- candidates[0] ??
2516
- handlers.find((handler) =>
2517
- getConfiguredOffers(handler).some(
2518
- (internal) => internal.name === credMethod && internal.intent === credIntent,
2519
- ),
2520
- )
2521
- if (match) return match(input)
2522
- }
2523
-
2524
- // Unrecognized or malformed credentials still use the currently eligible
2525
- // offer set before the first handler issues its rejection challenge.
2526
- }
2527
-
2528
- const selectedHandlers = hasOfferPolicy
2529
- ? await selectOfferHandlers({
2530
- entries: offerEntries,
2531
- input,
2532
- selectOffers: offerSelector,
2533
- })
2534
- : handlers
2535
-
2536
- // No credential — evaluate handlers sequentially so authorize()/renewal hooks
2537
- // can safely claim the request without racing each other.
2538
- const results: MethodFn.Response<Transport.Http>[] = []
2539
- for (const handler of selectedHandlers) {
2540
- const result = await handler(input)
2541
- if (result.status === 200) return result
2542
- results.push(result)
2543
- }
2544
-
2545
- const challengeEntries = (() => {
2546
- const entries: {
2547
- handler: ConfiguredHandler
2548
- challenge: Challenge.Challenge
2549
- result: Extract<MethodFn.Response<Transport.Http>, { status: 402 }>
2550
- }[] = []
2551
-
2552
- for (let i = 0; i < selectedHandlers.length; i++) {
2553
- const result = results[i]
2554
- if (result?.status !== 402) continue
2555
-
2556
- const response = result.challenge as Response
2557
- const wwwAuth = response.headers.get(paymentAuthChallengeHeader)
2558
- if (!wwwAuth) continue
2559
-
2560
- entries.push({
2561
- handler: selectedHandlers[i] as ConfiguredHandler,
2562
- challenge: Challenge.deserialize(wwwAuth),
2563
- result,
2564
- })
2565
- }
2566
-
2567
- const acceptPayment = input.headers.get(Constants.Headers.acceptPayment)
2568
- if (!acceptPayment) return entries
2569
-
2570
- try {
2571
- const ranked = AcceptPayment.rank(
2572
- entries.map((entry) => entry.challenge),
2573
- AcceptPayment.parse(acceptPayment),
2574
- )
2575
- if (ranked.length === 0) return entries
2576
-
2577
- const entriesById = new Map(entries.map((entry) => [entry.challenge.id, entry] as const))
2578
- return ranked.map((challenge) => entriesById.get(challenge.id)!)
2579
- } catch {
2580
- return entries
2581
- }
2582
- })()
2583
-
2584
- const challengeResponses = results.flatMap((result) =>
2585
- result.status === 402 ? [result.challenge as Response] : [],
2586
- )
2587
- const unnegotiatedX402Responses =
2588
- input.headers.has(Constants.Headers.acceptPayment) || challengeEntries.length === 0
2589
- ? []
2590
- : challengeResponses.filter(
2591
- (response) =>
2592
- response.headers.has(x402_Types.paymentRequiredHeader) &&
2593
- !response.headers.has(paymentAuthChallengeHeader),
2594
- )
2595
- const negotiatedChallengeResponses =
2596
- challengeEntries.length > 0
2597
- ? [
2598
- ...challengeEntries.map((entry) => entry.result.challenge as Response),
2599
- ...unnegotiatedX402Responses,
2600
- ]
2601
- : challengeResponses
2602
-
2603
- // Merge challenge headers from the negotiated 402 responses.
2604
- const mergedHeaders = new Headers()
2605
- mergedHeaders.set('Cache-Control', 'no-store')
2606
-
2607
- for (const header of challengeHeaderMerges) {
2608
- for (const value of header.merge(
2609
- header.values({
2610
- challengeEntries,
2611
- challengeResponses,
2612
- negotiatedChallengeResponses,
2613
- }),
2614
- )) {
2615
- mergedHeaders.append(header.name, value)
2616
- }
2617
- }
2618
-
2619
- // Collect html-enabled handlers and their challenges
2620
- const htmlEntries = challengeEntries.filter((entry) => entry.handler._internal?.html)
2621
-
2622
- const wantsHtml = input.headers.get('Accept')?.includes('text/html')
2623
- if (wantsHtml && htmlEntries.length > 0) {
2624
- const { theme, text } = Html.resolveOptions(
2625
- // Use compose-level options or first html-enabled method's config for the page shell
2626
- composeOptions ?? htmlEntries[0]?.handler._internal.html ?? ({} as Html.Options),
2627
- )
2628
-
2629
- // Build data map keyed by challenge.id
2630
- const dataMap: Record<string, Html.Data> = {}
2631
- for (let i = 0; i < htmlEntries.length; i++) {
2632
- const entry = htmlEntries[i]!
2633
- dataMap[entry.challenge.id] = {
2634
- label: entry.handler._internal.name,
2635
- rootId: `${Html.ids.root}-${i}`,
2636
- formattedAmount: await entry.handler._internal.html!.formatAmount(
2637
- entry.challenge.request,
2638
- ),
2639
- config: entry.handler._internal.html!.config,
2640
- challenge: entry.challenge as never,
2641
- text,
2642
- theme,
2643
- }
2644
- }
2645
-
2646
- mergedHeaders.set('Content-Type', 'text/html; charset=utf-8')
2647
-
2648
- const firstData = Object.values(dataMap)[0]!
2649
- const body = Html.render({
2650
- entries: htmlEntries.map((entry) => ({
2651
- challenge: entry.challenge,
2652
- content: entry.handler._internal.html!.content,
2653
- })),
2654
- dataMap,
2655
- formattedAmount: firstData.formattedAmount,
2656
- panels: true,
2657
- text,
2658
- theme,
2659
- })
2660
-
2661
- return {
2662
- status: 402,
2663
- challenge: new Response(body, { status: 402, headers: mergedHeaders }),
2664
- }
2665
- }
2666
-
2667
- // Non-HTML fallback: prefer the first Payment-auth body, otherwise use
2668
- // the first transport-specific 402 body.
2669
- let body: string | null = null
2670
- const bodyResponses =
2671
- challengeEntries.length > 0
2672
- ? challengeEntries.map((entry) => entry.result.challenge as Response)
2673
- : challengeResponses
2674
- for (const response of bodyResponses) {
2675
- if (!body) {
2676
- const contentType = response.headers.get('Content-Type')
2677
- if (contentType) mergedHeaders.set('Content-Type', contentType)
2678
- body = await response.text()
2679
- break
2680
- }
2681
- }
2682
-
2683
- return {
2684
- status: 402,
2685
- challenge: new Response(body, { status: 402, headers: mergedHeaders }),
2686
- }
2687
- }
2688
-
2689
- const offers = handlers.flatMap(getConfiguredOffers)
2690
- if (offers.length > 0) composed._internal = { offers }
2691
- return composed
2692
- }
2693
-
2694
- /** Returns the flattened configured offers accepted by a handler. */
2695
- function getConfiguredOffers(
2696
- handler: ConfiguredHandler | ComposedHandler,
2697
- ): readonly ConfiguredHandler['_internal'][] {
2698
- const internal = handler._internal
2699
- if (!internal) return []
2700
- if (isComposedHandlerMetadata(internal)) return internal.offers
2701
- return [internal]
2702
- }
2703
-
2704
- function isComposedHandlerMetadata(
2705
- internal: HandlerDiscoveryMetadata,
2706
- ): internal is NonNullable<ComposedHandler['_internal']> {
2707
- return 'offers' in internal && !('_canonicalRequest' in internal)
2708
- }
2709
-
2710
- function createServerOffer(internal: ConfiguredHandler['_internal']): ServerOffer {
2711
- return Object.freeze({
2712
- key: `${internal.name}/${internal.intent}`,
2713
- method: Object.freeze({
2714
- ...(internal._method.alias ? { alias: internal._method.alias } : {}),
2715
- intent: internal.intent,
2716
- name: internal.name,
2717
- }),
2718
- request: immutableSnapshot(internal._canonicalRequest),
2719
- }) as ServerOffer
2720
- }
2721
-
2722
- /** Applies and validates the server-level offer selection policy. */
2723
- async function selectOfferHandlers(parameters: {
2724
- entries: readonly {
2725
- handler: (input: Request) => Promise<MethodFn.Response<Transport.Http>>
2726
- method: Method.AnyServer | undefined
2727
- offer: ServerOffer | undefined
2728
- }[]
2729
- input: Request
2730
- selectOffers: SelectOffers<readonly Method.AnyServer[]> | undefined
2731
- }) {
2732
- const { entries, input, selectOffers } = parameters
2733
- const eligibleEntries: (typeof entries)[number][] = []
2734
- for (const entry of entries) {
2735
- const canOffer = entry.method?.canOffer
2736
- if (canOffer) {
2737
- const approved = await canOffer({
2738
- input: cloneOfferInput(input),
2739
- request: entry.offer!.request,
2740
- })
2741
- if (typeof approved !== 'boolean') throw new Error('canOffer() must return a boolean')
2742
- if (!approved) continue
2743
- }
2744
- eligibleEntries.push(entry)
2745
- }
2746
-
2747
- if (eligibleEntries.length === 0)
2748
- throw new Error('No payment offers are available for this request')
2749
- if (!selectOffers) return eligibleEntries.map(({ handler }) => handler)
2750
-
2751
- const offers = eligibleEntries.map(({ offer }) => {
2752
- if (!offer) throw new Error('selectOffers() can only select configured payment offers')
2753
- return offer
2754
- })
2755
- const selected = await selectOffers(
2756
- Object.freeze(offers),
2757
- Object.freeze({ request: cloneOfferInput(input) }),
2758
- )
2759
-
2760
- if (!Array.isArray(selected)) throw new Error('selectOffers() must return an array of offers')
2761
- if (selected.length === 0) throw new Error('selectOffers() must select at least one offer')
2762
-
2763
- let previousIndex = -1
2764
- const selectedHandlers = selected.map((offer) => {
2765
- const index = offers.indexOf(offer)
2766
- if (index === -1) throw new Error('selectOffers() must return only offers from its input array')
2767
- if (index <= previousIndex)
2768
- throw new Error('selectOffers() must preserve offer order and must not return duplicates')
2769
- previousIndex = index
2770
- return eligibleEntries[index]!.handler
2771
- })
2772
-
2773
- return selectedHandlers
2774
- }
2775
-
2776
- /** Clones a request for offer hooks, falling back to a bodyless metadata copy. */
2777
- function cloneOfferInput(input: Request): Request {
2778
- try {
2779
- return input.clone()
2780
- } catch {
2781
- return new globalThis.Request(input.url, {
2782
- headers: new Headers(input.headers),
2783
- method: input.method,
2784
- signal: input.signal,
2785
- })
2786
- }
2787
- }
2788
-
2789
- /** Clones and recursively freezes protocol data exposed to offer hooks. */
2790
- function immutableSnapshot<value>(value: value): DeepReadonly<value> {
2791
- return deepFreeze(structuredClone(value))
2792
- }
2793
-
2794
- function deepFreeze<value>(value: value): DeepReadonly<value> {
2795
- if (!value || typeof value !== 'object' || Object.isFrozen(value))
2796
- return value as DeepReadonly<value>
2797
-
2798
- for (const nested of Object.values(value)) deepFreeze(nested)
2799
- return Object.freeze(value) as DeepReadonly<value>
2800
- }
2801
-
2802
- type ChallengeHeaderMerge = {
2803
- name: string
2804
- values(context: {
2805
- challengeEntries: readonly {
2806
- handler: ConfiguredHandler
2807
- challenge: Challenge.Challenge
2808
- result: Extract<MethodFn.Response<Transport.Http>, { status: 402 }>
2809
- }[]
2810
- challengeResponses: readonly Response[]
2811
- negotiatedChallengeResponses: readonly Response[]
2812
- }): readonly string[]
2813
- merge(values: readonly string[]): readonly string[]
2814
- }
2815
-
2816
- function mergeX402PaymentRequiredHeaders(values: readonly string[]): readonly string[] {
2817
- if (values.length === 0) return []
2818
- const [first, ...rest] = values.map((value) => x402_Header.decodePaymentRequired(value))
2819
- if (!first) throw new Error('Expected at least one x402 payment-required header.')
2820
- const incompatible = rest.some(
2821
- (value) =>
2822
- !isDeepStrictEqual(value.resource, first.resource) ||
2823
- !isDeepStrictEqual(value.extensions, first.extensions),
2824
- )
2825
- if (incompatible)
2826
- return [
2827
- x402_Header.encodePaymentRequired({
2828
- ...first,
2829
- error: [first.error, 'Cannot merge x402 payment requirements with different resources.']
2830
- .filter((value): value is string => value !== undefined && value.length > 0)
2831
- .join('; '),
2832
- }),
2833
- ]
2834
- const error = [first.error, ...rest.map((value) => value.error)]
2835
- .filter((value): value is string => value !== undefined && value.length > 0)
2836
- .join('; ')
2837
- return [
2838
- x402_Header.encodePaymentRequired({
2839
- ...first,
2840
- accepts: [first.accepts, ...rest.map((value) => value.accepts)].flat(),
2841
- ...(error ? { error } : {}),
2842
- }),
2843
- ]
2844
- }
2845
-
2846
- /**
2847
- * Wraps a payment handler to create a Node.js HTTP listener.
2848
- *
2849
- * On 402: writes the challenge response and ends the connection.
2850
- * On 200: sets the Payment-Receipt header; caller should write response body.
2851
- *
2852
- * @example
2853
- * ```ts
2854
- * import * as http from 'node:http'
2855
- * import { Mppx } from 'mppx/server'
2856
- *
2857
- * const payment = Mppx.create({ ... })
2858
- *
2859
- * http.createServer(async (req, res) => {
2860
- * const result = await Mppx.toNodeListener(
2861
- * payment.charge({
2862
- * amount: '1', currency: '...', recipient: '0x...',
2863
- * }),
2864
- * )(req, res)
2865
- * if (result.status === 402) return
2866
- * res.end('OK')
2867
- * })
2868
- * ```
2869
- */
2870
- export function toNodeListener(
2871
- handler: (input: globalThis.Request) => Promise<MethodFn.Response<Transport.Http>>,
2872
- ): (req: IncomingMessage, res: ServerResponse) => Promise<MethodFn.Response<Transport.Http>> {
2873
- return async (req, res) => {
2874
- const result = await handler(Request.fromNodeListener(req, res))
2875
-
2876
- if (result.status === 402) {
2877
- await NodeListener.sendResponse(res, result.challenge as globalThis.Response)
2878
- } else {
2879
- const managementResponse = getManagementResponse(result)
2880
- if (managementResponse) {
2881
- await NodeListener.sendResponse(res, managementResponse)
2882
- return { challenge: managementResponse, status: 402 }
2883
- }
2884
-
2885
- const wrapped = result.withReceipt(new globalThis.Response()) as globalThis.Response
2886
- for (const [name, value] of wrapped.headers) res.setHeader(name, value)
2887
- }
2888
-
2889
- return result
2890
- }
2891
- }
2892
-
2893
- function getManagementResponse(
2894
- result: Extract<MethodFn.Response<Transport.Http>, { status: 200 }>,
2895
- ): globalThis.Response | null {
2896
- try {
2897
- return (result.withReceipt as () => globalThis.Response)()
2898
- } catch (error) {
2899
- if (isMissingReceiptResponseError(error)) {
2900
- return null
2901
- }
2902
- throw error
2903
- }
2904
- }
2905
-
2906
- /**
2907
- * Flattens a methods config tuple, preserving positional types.
2908
- * @internal
2909
- */
2910
- type FlattenMethods<methods extends Methods> = methods extends readonly [
2911
- infer head,
2912
- ...infer tail extends Methods,
2913
- ]
2914
- ? head extends readonly Method.AnyServer[]
2915
- ? readonly [...head, ...FlattenMethods<tail>]
2916
- : head extends Method.AnyServer
2917
- ? readonly [head, ...FlattenMethods<tail>]
2918
- : never
2919
- : readonly []