tapimo 0.5.3 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (376) hide show
  1. package/dist/App.d.ts +15830 -7220
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +9 -1
  4. package/dist/App.js.map +1 -1
  5. package/dist/Scope.d.ts +4 -0
  6. package/dist/Scope.d.ts.map +1 -1
  7. package/dist/Scope.js +5 -0
  8. package/dist/Scope.js.map +1 -1
  9. package/dist/admin/App.d.ts +1011 -412
  10. package/dist/admin/App.d.ts.map +1 -1
  11. package/dist/admin/App.js +4 -0
  12. package/dist/admin/App.js.map +1 -1
  13. package/dist/admin/apps/api-keys.d.ts +2 -1
  14. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  15. package/dist/admin/apps/early-access.d.ts +148 -0
  16. package/dist/admin/apps/early-access.d.ts.map +1 -0
  17. package/dist/admin/apps/early-access.js +133 -0
  18. package/dist/admin/apps/early-access.js.map +1 -0
  19. package/dist/admin/apps/organizations.d.ts +124 -1
  20. package/dist/admin/apps/organizations.d.ts.map +1 -1
  21. package/dist/admin/apps/organizations.js +97 -6
  22. package/dist/admin/apps/organizations.js.map +1 -1
  23. package/dist/admin/apps/verified-tokens.d.ts +526 -1
  24. package/dist/admin/apps/verified-tokens.d.ts.map +1 -1
  25. package/dist/admin/apps/verified-tokens.js +214 -1
  26. package/dist/admin/apps/verified-tokens.js.map +1 -1
  27. package/dist/admin/ui.generated.d.ts +1 -1
  28. package/dist/admin/ui.generated.d.ts.map +1 -1
  29. package/dist/admin/ui.generated.js +95 -75
  30. package/dist/admin/ui.generated.js.map +1 -1
  31. package/dist/analytics/Analytics.d.ts.map +1 -1
  32. package/dist/analytics/Analytics.js +20 -0
  33. package/dist/analytics/Analytics.js.map +1 -1
  34. package/dist/analytics/tables/requestEvents.d.ts +18 -0
  35. package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
  36. package/dist/analytics/tables/requestEvents.js +36 -7
  37. package/dist/analytics/tables/requestEvents.js.map +1 -1
  38. package/dist/apps/data/App.d.ts +6260 -4924
  39. package/dist/apps/data/App.d.ts.map +1 -1
  40. package/dist/apps/data/App.js +11 -6
  41. package/dist/apps/data/App.js.map +1 -1
  42. package/dist/apps/data/FundingQuoteProviders.d.ts +34 -1
  43. package/dist/apps/data/FundingQuoteProviders.d.ts.map +1 -1
  44. package/dist/apps/data/FundingQuoteProviders.js +194 -16
  45. package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
  46. package/dist/apps/data/FxOracle.d.ts +71 -0
  47. package/dist/apps/data/FxOracle.d.ts.map +1 -0
  48. package/dist/apps/data/FxOracle.js +97 -0
  49. package/dist/apps/data/FxOracle.js.map +1 -0
  50. package/dist/apps/data/index.d.ts +1 -0
  51. package/dist/apps/data/index.d.ts.map +1 -1
  52. package/dist/apps/data/index.js +1 -0
  53. package/dist/apps/data/index.js.map +1 -1
  54. package/dist/apps/data/routes/activities.d.ts +3599 -1382
  55. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  56. package/dist/apps/data/routes/activities.js +250 -88
  57. package/dist/apps/data/routes/activities.js.map +1 -1
  58. package/dist/apps/data/routes/addresses.d.ts +872 -251
  59. package/dist/apps/data/routes/addresses.d.ts.map +1 -1
  60. package/dist/apps/data/routes/addresses.js +11 -9
  61. package/dist/apps/data/routes/addresses.js.map +1 -1
  62. package/dist/apps/data/routes/balances.d.ts +77 -26
  63. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  64. package/dist/apps/data/routes/balances.js +88 -45
  65. package/dist/apps/data/routes/balances.js.map +1 -1
  66. package/dist/apps/data/routes/blocks.d.ts +39 -39
  67. package/dist/apps/data/routes/coingecko.d.ts +54 -54
  68. package/dist/apps/data/routes/exchanges.d.ts +856 -105
  69. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  70. package/dist/apps/data/routes/exchanges.js +518 -76
  71. package/dist/apps/data/routes/exchanges.js.map +1 -1
  72. package/dist/apps/data/routes/fee-amm.d.ts +81 -67
  73. package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
  74. package/dist/apps/data/routes/fee-amm.js +76 -91
  75. package/dist/apps/data/routes/fee-amm.js.map +1 -1
  76. package/dist/apps/data/routes/funding.d.ts +80 -36
  77. package/dist/apps/data/routes/funding.d.ts.map +1 -1
  78. package/dist/apps/data/routes/funding.js +65 -31
  79. package/dist/apps/data/routes/funding.js.map +1 -1
  80. package/dist/apps/data/routes/receipts.d.ts +392 -315
  81. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  82. package/dist/apps/data/routes/receipts.js +152 -77
  83. package/dist/apps/data/routes/receipts.js.map +1 -1
  84. package/dist/apps/data/routes/tokens.d.ts +167 -174
  85. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  86. package/dist/apps/data/routes/tokens.js +15 -13
  87. package/dist/apps/data/routes/tokens.js.map +1 -1
  88. package/dist/apps/data/routes/transactions.d.ts +277 -536
  89. package/dist/apps/data/routes/transactions.d.ts.map +1 -1
  90. package/dist/apps/data/routes/transactions.js +96 -77
  91. package/dist/apps/data/routes/transactions.js.map +1 -1
  92. package/dist/apps/data/routes/transfers.d.ts +157 -81
  93. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  94. package/dist/apps/data/routes/transfers.js +272 -130
  95. package/dist/apps/data/routes/transfers.js.map +1 -1
  96. package/dist/apps/data/routes/valuation.d.ts +307 -0
  97. package/dist/apps/data/routes/valuation.d.ts.map +1 -0
  98. package/dist/apps/data/routes/valuation.js +292 -0
  99. package/dist/apps/data/routes/valuation.js.map +1 -0
  100. package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
  101. package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
  102. package/dist/apps/data/routes/verified-tokens.js +7 -5
  103. package/dist/apps/data/routes/verified-tokens.js.map +1 -1
  104. package/dist/apps/data/routes/webhooks.d.ts +103 -123
  105. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  106. package/dist/apps/data/routes/webhooks.js +124 -25
  107. package/dist/apps/data/routes/webhooks.js.map +1 -1
  108. package/dist/apps/index.d.ts +2 -1
  109. package/dist/apps/index.d.ts.map +1 -1
  110. package/dist/apps/index.js +2 -1
  111. package/dist/apps/index.js.map +1 -1
  112. package/dist/apps/management/App.d.ts +8554 -1692
  113. package/dist/apps/management/App.d.ts.map +1 -1
  114. package/dist/apps/management/App.js +12 -1
  115. package/dist/apps/management/App.js.map +1 -1
  116. package/dist/apps/management/routes/api-keys.d.ts +7 -0
  117. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  118. package/dist/apps/management/routes/api-keys.js +17 -3
  119. package/dist/apps/management/routes/api-keys.js.map +1 -1
  120. package/dist/apps/management/routes/billing.d.ts +157 -162
  121. package/dist/apps/management/routes/billing.d.ts.map +1 -1
  122. package/dist/apps/management/routes/billing.js +31 -23
  123. package/dist/apps/management/routes/billing.js.map +1 -1
  124. package/dist/apps/management/routes/faucet.d.ts +232 -0
  125. package/dist/apps/management/routes/faucet.d.ts.map +1 -0
  126. package/dist/apps/management/routes/faucet.js +154 -0
  127. package/dist/apps/management/routes/faucet.js.map +1 -0
  128. package/dist/apps/management/routes/invitations.d.ts +60 -60
  129. package/dist/apps/management/routes/invite-links.d.ts +49 -20
  130. package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
  131. package/dist/apps/management/routes/invite-links.js +26 -1
  132. package/dist/apps/management/routes/invite-links.js.map +1 -1
  133. package/dist/apps/management/routes/me.d.ts +6 -6
  134. package/dist/apps/management/routes/members.d.ts +6 -6
  135. package/dist/apps/management/routes/orgs.d.ts +42 -42
  136. package/dist/apps/management/routes/orgs.d.ts.map +1 -1
  137. package/dist/apps/management/routes/orgs.js +5 -0
  138. package/dist/apps/management/routes/orgs.js.map +1 -1
  139. package/dist/apps/management/routes/projects.d.ts +44 -44
  140. package/dist/apps/management/routes/usage.d.ts +3 -0
  141. package/dist/apps/management/routes/usage.d.ts.map +1 -1
  142. package/dist/apps/management/routes/usage.js +4 -0
  143. package/dist/apps/management/routes/usage.js.map +1 -1
  144. package/dist/apps/management/routes/verified-token-requests.d.ts +697 -0
  145. package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -0
  146. package/dist/apps/management/routes/verified-token-requests.js +271 -0
  147. package/dist/apps/management/routes/verified-token-requests.js.map +1 -0
  148. package/dist/apps/management/routes/webhooks.d.ts +6977 -0
  149. package/dist/apps/management/routes/webhooks.d.ts.map +1 -0
  150. package/dist/apps/management/routes/webhooks.js +573 -0
  151. package/dist/apps/management/routes/webhooks.js.map +1 -0
  152. package/dist/apps/mpp/App.d.ts +363 -0
  153. package/dist/apps/mpp/App.d.ts.map +1 -0
  154. package/dist/apps/mpp/App.js +72 -0
  155. package/dist/apps/mpp/App.js.map +1 -0
  156. package/dist/apps/mpp/Mpp.d.ts +178 -0
  157. package/dist/apps/mpp/Mpp.d.ts.map +1 -0
  158. package/dist/apps/mpp/Mpp.js +183 -0
  159. package/dist/apps/mpp/Mpp.js.map +1 -0
  160. package/dist/apps/mpp/index.d.ts +3 -0
  161. package/dist/apps/mpp/index.d.ts.map +1 -0
  162. package/dist/apps/mpp/index.js +3 -0
  163. package/dist/apps/mpp/index.js.map +1 -0
  164. package/dist/apps/relay/App.d.ts.map +1 -1
  165. package/dist/apps/relay/App.js +56 -9
  166. package/dist/apps/relay/App.js.map +1 -1
  167. package/dist/db/Db.d.ts +6 -0
  168. package/dist/db/Db.d.ts.map +1 -1
  169. package/dist/db/Db.js +110 -0
  170. package/dist/db/Db.js.map +1 -1
  171. package/dist/db/Schema.d.ts +53 -0
  172. package/dist/db/Schema.d.ts.map +1 -1
  173. package/dist/db/Schema.js +56 -0
  174. package/dist/db/Schema.js.map +1 -1
  175. package/dist/db/tables/earlyAccess.d.ts +50 -0
  176. package/dist/db/tables/earlyAccess.d.ts.map +1 -0
  177. package/dist/db/tables/earlyAccess.js +63 -0
  178. package/dist/db/tables/earlyAccess.js.map +1 -0
  179. package/dist/db/tables/enabledBillingSources.d.ts +73 -0
  180. package/dist/db/tables/enabledBillingSources.d.ts.map +1 -0
  181. package/dist/db/tables/enabledBillingSources.js +69 -0
  182. package/dist/db/tables/enabledBillingSources.js.map +1 -0
  183. package/dist/db/tables/invitations.d.ts.map +1 -1
  184. package/dist/db/tables/invitations.js +3 -0
  185. package/dist/db/tables/invitations.js.map +1 -1
  186. package/dist/db/tables/inviteLinks.d.ts +10 -2
  187. package/dist/db/tables/inviteLinks.d.ts.map +1 -1
  188. package/dist/db/tables/inviteLinks.js +10 -1
  189. package/dist/db/tables/inviteLinks.js.map +1 -1
  190. package/dist/db/tables/organizations.d.ts +5 -2
  191. package/dist/db/tables/organizations.d.ts.map +1 -1
  192. package/dist/db/tables/organizations.js +13 -2
  193. package/dist/db/tables/organizations.js.map +1 -1
  194. package/dist/db/tables/sponsoredTransactions.d.ts +2 -0
  195. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  196. package/dist/db/tables/sponsoredTransactions.js +2 -0
  197. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  198. package/dist/db/tables/verifiedTokenRequests.d.ts +94 -0
  199. package/dist/db/tables/verifiedTokenRequests.d.ts.map +1 -0
  200. package/dist/db/tables/verifiedTokenRequests.js +120 -0
  201. package/dist/db/tables/verifiedTokenRequests.js.map +1 -0
  202. package/dist/db/tables/webhookSubscriptions.d.ts +14 -0
  203. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  204. package/dist/db/tables/webhookSubscriptions.js +16 -0
  205. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  206. package/dist/handlers/internal/utils.d.ts +1 -16
  207. package/dist/handlers/internal/utils.d.ts.map +1 -1
  208. package/dist/handlers/internal/utils.js +17 -6
  209. package/dist/handlers/internal/utils.js.map +1 -1
  210. package/dist/handlers/relay.d.ts.map +1 -1
  211. package/dist/handlers/relay.js +37 -5
  212. package/dist/handlers/relay.js.map +1 -1
  213. package/dist/internal/Auth.d.ts +11 -2
  214. package/dist/internal/Auth.d.ts.map +1 -1
  215. package/dist/internal/Auth.js +14 -47
  216. package/dist/internal/Auth.js.map +1 -1
  217. package/dist/internal/FundingQuotes.d.ts +147 -47
  218. package/dist/internal/FundingQuotes.d.ts.map +1 -1
  219. package/dist/internal/FundingQuotes.js +119 -33
  220. package/dist/internal/FundingQuotes.js.map +1 -1
  221. package/dist/internal/Log.d.ts +37 -5
  222. package/dist/internal/Log.d.ts.map +1 -1
  223. package/dist/internal/Log.js +52 -3
  224. package/dist/internal/Log.js.map +1 -1
  225. package/dist/internal/MetricSink.d.ts.map +1 -1
  226. package/dist/internal/MetricSink.js +24 -0
  227. package/dist/internal/MetricSink.js.map +1 -1
  228. package/dist/internal/OpenApi.d.ts +2 -2
  229. package/dist/internal/OpenApi.d.ts.map +1 -1
  230. package/dist/internal/OpenApi.js +3 -0
  231. package/dist/internal/OpenApi.js.map +1 -1
  232. package/dist/internal/Response.d.ts +2 -2
  233. package/dist/internal/Response.d.ts.map +1 -1
  234. package/dist/internal/Response.js +12 -4
  235. package/dist/internal/Response.js.map +1 -1
  236. package/dist/internal/Schema.d.ts +14 -5
  237. package/dist/internal/Schema.d.ts.map +1 -1
  238. package/dist/internal/Schema.js +23 -5
  239. package/dist/internal/Schema.js.map +1 -1
  240. package/dist/internal/Value.d.ts +18 -0
  241. package/dist/internal/Value.d.ts.map +1 -1
  242. package/dist/internal/Value.js +11 -1
  243. package/dist/internal/Value.js.map +1 -1
  244. package/dist/internal/VerifiedTokens.d.ts.map +1 -1
  245. package/dist/internal/VerifiedTokens.js +10 -1
  246. package/dist/internal/VerifiedTokens.js.map +1 -1
  247. package/dist/internal/Webhooks.d.ts +24 -3
  248. package/dist/internal/Webhooks.d.ts.map +1 -1
  249. package/dist/internal/Webhooks.js +34 -8
  250. package/dist/internal/Webhooks.js.map +1 -1
  251. package/package.json +7 -2
  252. package/src/App.test.ts +9 -4
  253. package/src/App.ts +23 -3
  254. package/src/Client.test-d.ts +733 -20
  255. package/src/Scope.ts +5 -0
  256. package/src/admin/App.test.ts +4 -0
  257. package/src/admin/App.ts +4 -0
  258. package/src/admin/apps/early-access.test.ts +125 -0
  259. package/src/admin/apps/early-access.ts +176 -0
  260. package/src/admin/apps/organizations.test.ts +86 -2
  261. package/src/admin/apps/organizations.ts +155 -31
  262. package/src/admin/apps/scopes.test.ts +5 -0
  263. package/src/admin/apps/verified-tokens.test.ts +201 -0
  264. package/src/admin/apps/verified-tokens.ts +532 -286
  265. package/src/admin/ui/plugin.ts +2 -1
  266. package/src/admin/ui/src/lib/early-access.ts +63 -0
  267. package/src/admin/ui/src/lib/organizations.test.ts +81 -1
  268. package/src/admin/ui/src/lib/organizations.ts +92 -0
  269. package/src/admin/ui/src/lib/verified-tokens.ts +79 -0
  270. package/src/admin/ui/src/routeTree.gen.ts +33 -2
  271. package/src/admin/ui/src/routes/__root.tsx +2 -1
  272. package/src/admin/ui/src/routes/api-keys.tsx +1 -1
  273. package/src/admin/ui/src/routes/early-access.tsx +162 -0
  274. package/src/admin/ui/src/routes/organizations_.$orgId.tsx +103 -0
  275. package/src/admin/ui/src/routes/verified-tokens.tsx +230 -19
  276. package/src/admin/ui.generated.ts +95 -94
  277. package/src/analytics/Analytics.test.ts +83 -0
  278. package/src/analytics/Analytics.ts +20 -0
  279. package/src/analytics/tables/requestEvents.ts +60 -7
  280. package/src/apps/composition.test.ts +11 -2
  281. package/src/apps/data/App.test.ts +1 -1
  282. package/src/apps/data/App.ts +22 -14
  283. package/src/apps/data/FundingQuoteProviders.test.ts +297 -4
  284. package/src/apps/data/FundingQuoteProviders.ts +255 -29
  285. package/src/apps/data/FxOracle.test.ts +118 -0
  286. package/src/apps/data/FxOracle.ts +153 -0
  287. package/src/apps/data/index.ts +1 -0
  288. package/src/apps/data/routes/activities.test.ts +177 -3
  289. package/src/apps/data/routes/activities.ts +441 -132
  290. package/src/apps/data/routes/addresses.ts +19 -9
  291. package/src/apps/data/routes/balances.test.ts +148 -11
  292. package/src/apps/data/routes/balances.ts +140 -51
  293. package/src/apps/data/routes/exchanges.test.ts +544 -9
  294. package/src/apps/data/routes/exchanges.ts +690 -103
  295. package/src/apps/data/routes/fee-amm.test.ts +47 -23
  296. package/src/apps/data/routes/fee-amm.ts +98 -119
  297. package/src/apps/data/routes/funding.test.ts +113 -8
  298. package/src/apps/data/routes/funding.ts +77 -51
  299. package/src/apps/data/routes/receipts.test.ts +129 -23
  300. package/src/apps/data/routes/receipts.ts +205 -100
  301. package/src/apps/data/routes/tokens.test.ts +43 -0
  302. package/src/apps/data/routes/tokens.ts +28 -13
  303. package/src/apps/data/routes/transactions.test.ts +72 -19
  304. package/src/apps/data/routes/transactions.ts +162 -92
  305. package/src/apps/data/routes/transfers.test.ts +341 -31
  306. package/src/apps/data/routes/transfers.ts +358 -158
  307. package/src/apps/data/routes/valuation.test.ts +437 -0
  308. package/src/apps/data/routes/valuation.ts +423 -0
  309. package/src/apps/data/routes/verified-tokens.ts +14 -2
  310. package/src/apps/data/routes/webhooks.test.ts +56 -8
  311. package/src/apps/data/routes/webhooks.ts +144 -27
  312. package/src/apps/index.ts +2 -1
  313. package/src/apps/management/App.ts +13 -0
  314. package/src/apps/management/routes/api-keys.test.ts +14 -0
  315. package/src/apps/management/routes/api-keys.ts +27 -3
  316. package/src/apps/management/routes/billing.test.ts +152 -20
  317. package/src/apps/management/routes/billing.ts +34 -18
  318. package/src/apps/management/routes/faucet.test.ts +87 -0
  319. package/src/apps/management/routes/faucet.ts +201 -0
  320. package/src/apps/management/routes/invite-links.test.ts +117 -0
  321. package/src/apps/management/routes/invite-links.ts +43 -1
  322. package/src/apps/management/routes/me.test.ts +9 -99
  323. package/src/apps/management/routes/orgs.test.ts +45 -2
  324. package/src/apps/management/routes/orgs.ts +6 -0
  325. package/src/apps/management/routes/scopes.test.ts +1 -0
  326. package/src/apps/management/routes/usage.test.ts +6 -0
  327. package/src/apps/management/routes/usage.ts +7 -0
  328. package/src/apps/management/routes/verified-token-requests.test.ts +377 -0
  329. package/src/apps/management/routes/verified-token-requests.ts +397 -0
  330. package/src/apps/management/routes/webhooks.test.ts +426 -0
  331. package/src/apps/management/routes/webhooks.ts +672 -0
  332. package/src/apps/mpp/App.ts +86 -0
  333. package/src/apps/mpp/Mpp.test.ts +193 -0
  334. package/src/apps/mpp/Mpp.ts +353 -0
  335. package/src/apps/mpp/index.ts +2 -0
  336. package/src/apps/mpp/internal/Adapter.ts +11 -0
  337. package/src/apps/mpp/internal/Error.ts +9 -0
  338. package/src/apps/mpp/internal/Idempotency.ts +7 -0
  339. package/src/apps/mpp/internal/Screening.ts +10 -0
  340. package/src/apps/relay/App.test.ts +88 -6
  341. package/src/apps/relay/App.ts +61 -9
  342. package/src/db/Db.test.ts +19 -0
  343. package/src/db/Db.ts +125 -0
  344. package/src/db/Schema.ts +68 -0
  345. package/src/db/tables/earlyAccess.ts +86 -0
  346. package/src/db/tables/enabledBillingSources.test.ts +303 -0
  347. package/src/db/tables/enabledBillingSources.ts +118 -0
  348. package/src/db/tables/invitations.ts +3 -0
  349. package/src/db/tables/inviteLinks.ts +22 -3
  350. package/src/db/tables/organizations.test.ts +103 -0
  351. package/src/db/tables/organizations.ts +18 -2
  352. package/src/db/tables/sponsoredTransactions.ts +4 -0
  353. package/src/db/tables/verifiedTokenRequests.test.ts +200 -0
  354. package/src/db/tables/verifiedTokenRequests.ts +182 -0
  355. package/src/db/tables/webhookSubscriptions.ts +31 -0
  356. package/src/handlers/internal/utils.ts +22 -6
  357. package/src/handlers/relay.errors.test.ts +153 -0
  358. package/src/handlers/relay.ts +42 -7
  359. package/src/internal/Auth.test.ts +3 -3
  360. package/src/internal/Auth.ts +15 -47
  361. package/src/internal/FundingQuotes.test.ts +161 -6
  362. package/src/internal/FundingQuotes.ts +141 -39
  363. package/src/internal/Log.test-d.ts +21 -0
  364. package/src/internal/Log.test.ts +141 -0
  365. package/src/internal/Log.ts +93 -6
  366. package/src/internal/MetricSink.test.ts +215 -0
  367. package/src/internal/MetricSink.ts +24 -0
  368. package/src/internal/OpenApi.ts +3 -0
  369. package/src/internal/Response.test.ts +28 -0
  370. package/src/internal/Response.ts +12 -4
  371. package/src/internal/Schema.ts +50 -5
  372. package/src/internal/Value.test.ts +20 -0
  373. package/src/internal/Value.ts +24 -1
  374. package/src/internal/VerifiedTokens.ts +9 -1
  375. package/src/internal/Webhooks.test.ts +140 -0
  376. package/src/internal/Webhooks.ts +53 -9
@@ -2,8 +2,10 @@ import { nanoid } from 'nanoid'
2
2
  import Stripe from 'stripe'
3
3
  import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
4
4
 
5
+ import * as ApiKeys from '../../../ApiKeys.js'
5
6
  import * as TestApp from '../../../../test/App.js'
6
7
  import * as TestStripe from '../../../../test/Stripe.js'
8
+ import * as EnabledBillingSources from '../../../db/tables/enabledBillingSources.js'
7
9
  import * as Memberships from '../../../db/tables/memberships.js'
8
10
  import * as SponsoredTransactions from '../../../db/tables/sponsoredTransactions.js'
9
11
  import * as StripeCustomers from '../../../db/tables/stripeCustomers.js'
@@ -59,13 +61,18 @@ function billingWithSandbox(): App.Billing {
59
61
  }
60
62
 
61
63
  function createApp(
62
- options: { billing?: App.Billing | undefined; db?: TestApp.create.Options['db'] } = {},
64
+ options: {
65
+ billing?: App.Billing | undefined
66
+ db?: TestApp.create.Options['db']
67
+ kv?: { store: ReturnType<typeof TestApp.kvStore> }
68
+ } = {},
63
69
  ) {
64
70
  // prettier-ignore
65
71
  return TestApp.create({
66
72
  auth: { superAdmin: { secret } },
67
73
  billing: options.billing ?? billing(),
68
74
  db: options.db ?? TestApp.database(),
75
+ ...(options.kv ? { kv: options.kv } : {}),
69
76
  session: { wallet: { origin } },
70
77
  })
71
78
  }
@@ -98,6 +105,20 @@ async function seeded(app: ReturnType<typeof createApp>) {
98
105
  return { cookie, orgId: id }
99
106
  }
100
107
 
108
+ /** Seeds an owned organization with Stripe setup enabled. */
109
+ async function seededWithStripe(
110
+ app: ReturnType<typeof createApp>,
111
+ db: ReturnType<typeof TestApp.database>,
112
+ ) {
113
+ const result = await seeded(app)
114
+ await EnabledBillingSources.add(db, {
115
+ createdBy: 'test',
116
+ orgId: result.orgId,
117
+ source: 'stripe',
118
+ })
119
+ return result
120
+ }
121
+
101
122
  describe('GET /orgs/:orgId/billing', () => {
102
123
  test('behavior: synthesizes none for orgs without a billing source', async () => {
103
124
  const app = createApp()
@@ -106,6 +127,7 @@ describe('GET /orgs/:orgId/billing', () => {
106
127
  expect(response.status).toBe(200)
107
128
  expect(await response.json()).toMatchInlineSnapshot(`
108
129
  {
130
+ "enabledSources": [],
109
131
  "spend": {
110
132
  "amount": "0",
111
133
  "currency": "usd",
@@ -129,6 +151,28 @@ describe('GET /orgs/:orgId/billing', () => {
129
151
  expect(body.updatedAt).toBeDefined()
130
152
  })
131
153
 
154
+ test('behavior: lists organization-wide sources in canonical order', async () => {
155
+ const db = TestApp.database()
156
+ const app = createApp({ billing: billingWithSandbox(), db })
157
+ const { cookie, orgId } = await seeded(app)
158
+ await EnabledBillingSources.add(db, { createdBy: 'test', orgId, source: 'tempo' })
159
+ await EnabledBillingSources.add(db, { createdBy: 'test', orgId, source: 'stripe' })
160
+
161
+ const production = await app.request(`/v1/orgs/${orgId}/billing`, { headers: { cookie } })
162
+ const sandbox = await app.request(`/v1/orgs/${orgId}/billing?environment=sandbox`, {
163
+ headers: { cookie },
164
+ })
165
+
166
+ expect(await production.json()).toMatchObject({
167
+ enabledSources: ['stripe', 'tempo'],
168
+ status: 'none',
169
+ })
170
+ expect(await sandbox.json()).toMatchObject({
171
+ enabledSources: ['stripe', 'tempo'],
172
+ status: 'none',
173
+ })
174
+ })
175
+
132
176
  test('behavior: the super admin reads any org', async () => {
133
177
  const db = TestApp.database()
134
178
  const app = createApp({ db })
@@ -155,7 +199,7 @@ describe('GET /orgs/:orgId/billing', () => {
155
199
  expect(response.status).toBe(401)
156
200
  })
157
201
 
158
- test('behavior: 501 when billing is unconfigured', async () => {
202
+ test('behavior: reads database-backed state when Stripe is unconfigured', async () => {
159
203
  const app = TestApp.create({
160
204
  auth: { superAdmin: { secret } },
161
205
  db: TestApp.database(),
@@ -163,9 +207,18 @@ describe('GET /orgs/:orgId/billing', () => {
163
207
  })
164
208
  const { cookie, orgId } = await seeded(app)
165
209
  const response = await app.request(`/v1/orgs/${orgId}/billing`, { headers: { cookie } })
166
- expect(response.status).toBe(501)
167
- const body = (await response.json()) as { error: { code: string } }
168
- expect(body.error.code).toMatchInlineSnapshot(`"billing_unconfigured"`)
210
+ expect(response.status).toBe(200)
211
+ expect(await response.json()).toMatchInlineSnapshot(`
212
+ {
213
+ "enabledSources": [],
214
+ "spend": {
215
+ "amount": "0",
216
+ "currency": "usd",
217
+ "period": "month",
218
+ },
219
+ "status": "none",
220
+ }
221
+ `)
169
222
  })
170
223
  })
171
224
 
@@ -192,6 +245,7 @@ describe('PATCH /orgs/:orgId/billing', () => {
192
245
  expect(response.status).toBe(200)
193
246
  expect(await response.json()).toMatchInlineSnapshot(`
194
247
  {
248
+ "enabledSources": [],
195
249
  "spend": {
196
250
  "amount": "0",
197
251
  "currency": "usd",
@@ -284,7 +338,7 @@ describe('PATCH /orgs/:orgId/billing', () => {
284
338
  expect(response.status).toBe(404)
285
339
  })
286
340
 
287
- test('behavior: 501 when billing is unconfigured', async () => {
341
+ test('behavior: updates database-backed state when Stripe is unconfigured', async () => {
288
342
  const app = TestApp.create({
289
343
  auth: { superAdmin: { secret } },
290
344
  db: TestApp.database(),
@@ -293,9 +347,12 @@ describe('PATCH /orgs/:orgId/billing', () => {
293
347
  const { cookie, orgId } = await seeded(app)
294
348
  const response = await app.request(
295
349
  `/v1/orgs/${orgId}/billing`,
296
- patch(cookie, { spendLimit: null }),
350
+ patch(cookie, { spendLimit: { amount: '250' } }),
297
351
  )
298
- expect(response.status).toBe(501)
352
+ expect(response.status).toBe(200)
353
+ expect(
354
+ ((await response.json()) as { spendLimit?: { amount: string } }).spendLimit?.amount,
355
+ ).toBe('250')
299
356
  })
300
357
  })
301
358
 
@@ -345,8 +402,9 @@ describe('POST /orgs/:orgId/billing/stripe/checkout', () => {
345
402
  })
346
403
 
347
404
  test('behavior: an unreachable Stripe maps to the upstream error envelope', async () => {
348
- const app = createApp()
349
- const { cookie, orgId } = await seeded(app)
405
+ const db = TestApp.database()
406
+ const app = createApp({ db })
407
+ const { cookie, orgId } = await seededWithStripe(app, db)
350
408
  // First Stripe call the route makes is the customer create; the connection
351
409
  // is refused for real (no transport fake).
352
410
  const response = await app.request(`/v1/orgs/${orgId}/billing/stripe/checkout`, post(cookie))
@@ -356,6 +414,66 @@ describe('POST /orgs/:orgId/billing/stripe/checkout', () => {
356
414
  })
357
415
  })
358
416
 
417
+ describe('billing source access', () => {
418
+ test('behavior: checkout requires Stripe to be enabled', async () => {
419
+ const app = TestApp.create({
420
+ auth: { superAdmin: { secret } },
421
+ db: TestApp.database(),
422
+ session: { wallet: { origin } },
423
+ })
424
+ const { cookie, orgId } = await seeded(app)
425
+ const response = await app.request(`/v1/orgs/${orgId}/billing/stripe/checkout`, post(cookie))
426
+ expect(response.status).toBe(403)
427
+ const { requestId, ...body } = (await response.json()) as { requestId?: string }
428
+ expect(requestId).toBeDefined()
429
+ expect(body).toMatchInlineSnapshot(`
430
+ {
431
+ "error": {
432
+ "code": "billing_source_disabled",
433
+ "message": "Stripe billing is not enabled for this organization",
434
+ },
435
+ }
436
+ `)
437
+ })
438
+
439
+ test('behavior: the source gate replaces temporary billing early access', async () => {
440
+ const db = TestApp.database()
441
+ const app = createApp({ db })
442
+ const { cookie, orgId } = await seededWithStripe(app, db)
443
+
444
+ // The unreachable Stripe upstream proves source access, not user early access, is the gate.
445
+ const response = await app.request(`/v1/orgs/${orgId}/billing/stripe/checkout`, post(cookie))
446
+ expect(response.status).toBe(502)
447
+ expect(((await response.json()) as { error: { code: string } }).error.code).toBe('upstream_error') // prettier-ignore
448
+ })
449
+
450
+ test('behavior: an enabled management key reaches Stripe', async () => {
451
+ const db = TestApp.database()
452
+ const store = TestApp.kvStore({ keys: [] })
453
+ const app = createApp({ db, kv: { store } })
454
+ const { orgId } = await seededWithStripe(app, db)
455
+ const { token } = await ApiKeys.mint(store, { orgId, scopes: ['management:write'] })
456
+
457
+ const response = await app.request(`/v1/orgs/${orgId}/billing/stripe/checkout`, {
458
+ headers: { 'tempo-api-key': token },
459
+ method: 'POST',
460
+ })
461
+ expect(response.status).toBe(502)
462
+ })
463
+
464
+ test('behavior: an enabled super admin reaches Stripe', async () => {
465
+ const db = TestApp.database()
466
+ const app = createApp({ db })
467
+ const { orgId } = await seededWithStripe(app, db)
468
+
469
+ const response = await app.request(`/v1/orgs/${orgId}/billing/stripe/checkout`, {
470
+ ...as(secret),
471
+ method: 'POST',
472
+ })
473
+ expect(response.status).toBe(502)
474
+ })
475
+ })
476
+
359
477
  describe('POST /orgs/:orgId/billing/stripe/manage', () => {
360
478
  test('behavior: 404 before any billing exists', async () => {
361
479
  const app = createApp()
@@ -440,6 +558,7 @@ describe('DELETE /orgs/:orgId', () => {
440
558
  const db = TestApp.database()
441
559
  const app = createApp({ db })
442
560
  const { cookie, orgId } = await seeded(app)
561
+ await EnabledBillingSources.add(db, { createdBy: 'test', orgId, source: 'stripe' })
443
562
  await StripeCustomers.create(db, { orgId, stripeCustomerId: 'cus_cascade' })
444
563
 
445
564
  const response = await app.request(`/v1/orgs/${orgId}`, {
@@ -447,19 +566,30 @@ describe('DELETE /orgs/:orgId', () => {
447
566
  method: 'DELETE',
448
567
  })
449
568
  expect(response.status).toBe(200)
569
+ expect(await EnabledBillingSources.listByOrg(db, orgId)).toEqual([])
450
570
  expect(await StripeCustomers.get(db, orgId)).toBeUndefined()
451
571
  })
452
572
  })
453
573
 
454
574
  describe('environment scoping', () => {
455
- test('behavior: sandbox is 501 when no sandbox source is configured', async () => {
575
+ test('behavior: sandbox billing state remains readable without a Stripe source', async () => {
456
576
  const app = createApp()
457
577
  const { cookie, orgId } = await seeded(app)
458
578
  const response = await app.request(`/v1/orgs/${orgId}/billing?environment=sandbox`, {
459
579
  headers: { cookie },
460
580
  })
461
- expect(response.status).toBe(501)
462
- expect(((await response.json()) as { error: { code: string } }).error.code).toBe('billing_unconfigured') // prettier-ignore
581
+ expect(response.status).toBe(200)
582
+ expect(await response.json()).toMatchInlineSnapshot(`
583
+ {
584
+ "enabledSources": [],
585
+ "spend": {
586
+ "amount": "0",
587
+ "currency": "usd",
588
+ "period": "month",
589
+ },
590
+ "status": "none",
591
+ }
592
+ `)
463
593
  })
464
594
 
465
595
  test('behavior: rejects an unknown environment', async () => {
@@ -645,7 +775,7 @@ describe.runIf(TestStripe.secretKey)('stripe sandbox (direct)', () => {
645
775
  test('checkout provisions distinct customers per environment', { timeout: 30_000 }, async () => {
646
776
  const db = TestApp.database()
647
777
  const app = createApp({ billing: sandboxBillingBoth(), db })
648
- const { cookie, orgId } = await seeded(app)
778
+ const { cookie, orgId } = await seededWithStripe(app, db)
649
779
 
650
780
  const prod = await app.request(`/v1/orgs/${orgId}/billing/stripe/checkout`, post(cookie))
651
781
  const sbx = await app.request(`/v1/orgs/${orgId}/billing/stripe/checkout?environment=sandbox`, post(cookie)) // prettier-ignore
@@ -672,7 +802,7 @@ describe.runIf(TestStripe.secretKey)('stripe sandbox (direct)', () => {
672
802
  test('checkout provisions a real customer and setup session', { timeout: 30_000 }, async () => {
673
803
  const db = TestApp.database()
674
804
  const app = createApp({ billing: sandboxBilling(), db })
675
- const { cookie, orgId } = await seeded(app)
805
+ const { cookie, orgId } = await seededWithStripe(app, db)
676
806
 
677
807
  const response = await app.request(`/v1/orgs/${orgId}/billing/stripe/checkout`, post(cookie))
678
808
  // Track for teardown before asserting: a failed assertion must not leak
@@ -720,7 +850,7 @@ describe.runIf(TestStripe.secretKey)('stripe sandbox (direct)', () => {
720
850
  // prettier-ignore
721
851
  const db = TestApp.database()
722
852
  const app = createApp({ billing: sandboxBilling(), db })
723
- const { cookie, orgId } = await seeded(app)
853
+ const { cookie, orgId } = await seededWithStripe(app, db)
724
854
  const checkout = await app.request(`/v1/orgs/${orgId}/billing/stripe/checkout`, post(cookie))
725
855
  const customerId = (await StripeCustomers.get(db, orgId))?.stripeCustomerId
726
856
  if (customerId) TestStripe.track(customerId)
@@ -733,8 +863,9 @@ describe.runIf(TestStripe.secretKey)('stripe sandbox (direct)', () => {
733
863
  invoice_settings: { default_payment_method: attached.paymentMethod },
734
864
  })
735
865
  await StripeCustomers.setStatus(db, orgId, 'active')
866
+ await EnabledBillingSources.remove(db, { orgId, source: 'stripe' })
736
867
 
737
- // The list reads live Stripe state: card details plus the default marker.
868
+ // Disabling setup does not block management of the existing Stripe source.
738
869
  const list = await app.request(`/v1/orgs/${orgId}/billing/payment-methods`, {
739
870
  headers: { cookie },
740
871
  })
@@ -789,7 +920,7 @@ describe.runIf(TestStripe.secretKey)('stripe sandbox (direct)', () => {
789
920
  // prettier-ignore
790
921
  const db = TestApp.database()
791
922
  const app = createApp({ billing: sandboxBilling(), db })
792
- const { cookie, orgId } = await seeded(app)
923
+ const { cookie, orgId } = await seededWithStripe(app, db)
793
924
  const checkout = await app.request(`/v1/orgs/${orgId}/billing/stripe/checkout`, post(cookie))
794
925
  const customerId = (await StripeCustomers.get(db, orgId))?.stripeCustomerId
795
926
  if (customerId) TestStripe.track(customerId)
@@ -819,11 +950,12 @@ describe.runIf(TestStripe.secretKey)('stripe sandbox (direct)', () => {
819
950
 
820
951
  const db = TestApp.database()
821
952
  const app = createApp({ billing: sandboxBilling(), db })
822
- const { cookie, orgId } = await seeded(app)
953
+ const { cookie, orgId } = await seededWithStripe(app, db)
823
954
  const checkout = await app.request(`/v1/orgs/${orgId}/billing/stripe/checkout`, post(cookie))
824
955
  const customerId = (await StripeCustomers.get(db, orgId))?.stripeCustomerId
825
956
  if (customerId) TestStripe.track(customerId)
826
957
  expect(checkout.status).toBe(200)
958
+ await EnabledBillingSources.remove(db, { orgId, source: 'stripe' })
827
959
 
828
960
  const response = await app.request(`/v1/orgs/${orgId}/billing/stripe/manage`, post(cookie))
829
961
  expect(response.status).toBe(200)
@@ -852,7 +984,7 @@ describe.runIf(TestStripe.secretKey)('stripe sandbox (live delivery)', () => {
852
984
  })
853
985
 
854
986
  test('attach → active, detach → none, delete → canceled', { timeout: 300_000 }, async () => {
855
- const { cookie, orgId } = await seeded(app)
987
+ const { cookie, orgId } = await seededWithStripe(app, db)
856
988
  const checkout = await app.request(`/v1/orgs/${orgId}/billing/stripe/checkout`, post(cookie))
857
989
  const customerId = (await StripeCustomers.get(db, orgId))?.stripeCustomerId
858
990
  if (customerId) TestStripe.track(customerId)
@@ -6,6 +6,7 @@ import * as ApiKeys from '../../../ApiKeys.js'
6
6
  import * as Auth from '../../../internal/Auth.js'
7
7
  import * as BillingSettings from '../../../db/tables/billingSettings.js'
8
8
  import * as Db from '../../../db/Db.js'
9
+ import * as EnabledBillingSources from '../../../db/tables/enabledBillingSources.js'
9
10
  import * as OpenApi from '../../../internal/OpenApi.js'
10
11
  import * as Response from '../../../internal/Response.js'
11
12
  import * as Schema from '../../../internal/Schema.js'
@@ -113,6 +114,12 @@ export namespace schema {
113
114
  /** An organization's billing state. */
114
115
  export const Billing = Schema.describe(
115
116
  z.object({
117
+ enabledSources: z
118
+ .array(z.enum(EnabledBillingSources.sources))
119
+ .check(
120
+ z.describe('Billing sources this organization may set up.'),
121
+ z.meta({ examples: [['stripe']] }),
122
+ ),
116
123
  spend: z
117
124
  .optional(
118
125
  z.object({
@@ -234,7 +241,7 @@ export namespace schema {
234
241
  }
235
242
 
236
243
  /**
237
- * Mounts billing routes. Reads require membership or scoped API key access; organizations without Stripe state return `none`.
244
+ * Mounts billing routes. Reads require membership or scoped API key access; organizations without provider state return `none`.
238
245
  */
239
246
  export function billing() {
240
247
  return new Hono<Environment>()
@@ -255,7 +262,6 @@ export function billing() {
255
262
  responses: OpenApi.responses({
256
263
  errors: {
257
264
  404: 'No accessible organization was found for this id.',
258
- 501: 'Billing is not configured on this deployment.',
259
265
  },
260
266
  success: { description: "The organization's billing state.", schema: schema.Billing },
261
267
  }),
@@ -276,10 +282,7 @@ export function billing() {
276
282
  message: 'Check the query parameters and try again.',
277
283
  })
278
284
 
279
- const billing = c.get('billing')
280
- if (!billing) return billingUnconfigured(c)
281
285
  const environment = c.req.valid('query').environment
282
- if (!stripeFor(billing, environment)) return billingUnconfigured(c)
283
286
 
284
287
  try {
285
288
  return c.json(
@@ -313,7 +316,6 @@ export function billing() {
313
316
  errors: {
314
317
  403: 'Requires the owner role.',
315
318
  404: 'No accessible organization was found for this id.',
316
- 501: 'Billing is not configured on this deployment.',
317
319
  },
318
320
  success: {
319
321
  description: "The organization's updated billing state.",
@@ -342,10 +344,7 @@ export function billing() {
342
344
  message: 'Check the request body and try again.',
343
345
  })
344
346
 
345
- const billing = c.get('billing')
346
- if (!billing) return billingUnconfigured(c)
347
347
  const environment = c.req.valid('query').environment
348
- if (!stripeFor(billing, environment)) return billingUnconfigured(c)
349
348
 
350
349
  const body = c.req.valid('json')
351
350
  const org = Auth.org(c)
@@ -394,7 +393,10 @@ export function billing() {
394
393
  operationId: 'createStripeCheckout',
395
394
  responses: OpenApi.responses({
396
395
  errors: {
397
- 403: 'Requires the owner role.',
396
+ 403: {
397
+ codes: ['api_key_forbidden', 'billing_source_disabled', 'forbidden'],
398
+ description: 'Requires the owner role and Stripe billing enabled.',
399
+ },
398
400
  404: 'No accessible organization was found for this id.',
399
401
  501: 'Billing is not configured on this deployment.',
400
402
  },
@@ -409,6 +411,7 @@ export function billing() {
409
411
  async (c) => {
410
412
  if (Auth.narrowAccess) return Auth.accessError(c)
411
413
  if (Auth.narrowScope) return Auth.ensureOrgError(c)
414
+ if (Auth.narrowOrgRole) return Auth.ensureOrgRoleError(c)
412
415
  if (OpenApi.narrowValidation)
413
416
  return OpenApi.validationError(c, {
414
417
  code: 'param_invalid',
@@ -420,16 +423,19 @@ export function billing() {
420
423
  message: 'Check the query parameters and try again.',
421
424
  })
422
425
 
423
- const billing = c.get('billing')
424
- if (!billing) return billingUnconfigured(c)
425
- const environment = c.req.valid('query').environment
426
- const source = stripeFor(billing, environment)
427
- if (!source) return billingUnconfigured(c)
428
-
429
- const { client } = source
430
426
  const org = Auth.org(c)
431
427
  const db = Db.get(c.get('db'))
432
428
  try {
429
+ if (!(await EnabledBillingSources.isEnabled(db, { orgId: org.id, source: 'stripe' })))
430
+ return billingSourceDisabled(c)
431
+
432
+ const billing = c.get('billing')
433
+ if (!billing) return billingUnconfigured(c)
434
+ const environment = c.req.valid('query').environment
435
+ const source = stripeFor(billing, environment)
436
+ if (!source) return billingUnconfigured(c)
437
+
438
+ const { client } = source
433
439
  const existing = await StripeCustomers.get(db, org.id, environment)
434
440
  const customerId = existing
435
441
  ? existing.stripeCustomerId
@@ -880,7 +886,8 @@ async function billingBody(
880
886
  environment: ResourceEnvironment,
881
887
  ) {
882
888
  const db = Db.get(c.get('db'))
883
- const [record, settings] = await Promise.all([
889
+ const [enabledSources, record, settings] = await Promise.all([
890
+ EnabledBillingSources.listByOrg(db, orgId),
884
891
  StripeCustomers.get(db, orgId, environment),
885
892
  BillingSettings.get(db, orgId, environment),
886
893
  ])
@@ -895,6 +902,7 @@ async function billingBody(
895
902
  since: core_Billing.periodStart(period),
896
903
  })
897
904
  return {
905
+ enabledSources: enabledSources.map((record) => record.source),
898
906
  spend: { amount: core_Billing.fromBaseUnits(spend), currency, period },
899
907
  ...(settings?.spendLimit != null
900
908
  ? { spendLimit: { amount: settings.spendLimit, currency, period } }
@@ -954,6 +962,14 @@ function billingNotFound(c: Context<Environment>) {
954
962
  })
955
963
  }
956
964
 
965
+ function billingSourceDisabled(c: Context<Environment>) {
966
+ return Response.error(c, {
967
+ code: 'billing_source_disabled',
968
+ message: 'Stripe billing is not enabled for this organization',
969
+ status: 403,
970
+ })
971
+ }
972
+
957
973
  function billingUnconfigured(c: Context<Environment>) {
958
974
  return Response.error(c, {
959
975
  code: 'billing_unconfigured',
@@ -0,0 +1,87 @@
1
+ import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
2
+
3
+ import * as TestApp from '../../../../test/App.js'
4
+ import * as Faucet from './faucet.js'
5
+ import * as Orgs from './orgs.js'
6
+
7
+ const origin = 'http://localhost'
8
+
9
+ function createApp() {
10
+ return TestApp.create({
11
+ rateLimit: { now: () => new Date('2026-01-01T00:00:00.000Z') },
12
+ session: { wallet: { origin } },
13
+ })
14
+ }
15
+
16
+ async function session(app: TestApp.signIn.App) {
17
+ const account = privateKeyToAccount(generatePrivateKey())
18
+ const { cookie } = await TestApp.signIn(app, account)
19
+ return cookie!
20
+ }
21
+
22
+ async function organization(app: ReturnType<typeof createApp>, cookie: string) {
23
+ const response = await app.request('/v1/orgs', {
24
+ body: JSON.stringify({ name: 'Acme' }),
25
+ headers: { 'content-type': 'application/json', cookie },
26
+ method: 'POST',
27
+ })
28
+ return TestApp.json(response, Orgs.schema.Organization)
29
+ }
30
+
31
+ describe('faucet', () => {
32
+ test('funds a member account and rate limits repeated funding', async () => {
33
+ const app = createApp()
34
+ const cookie = await session(app)
35
+ const org = await organization(app, cookie)
36
+ const recipient = privateKeyToAccount(generatePrivateKey()).address
37
+
38
+ const funded = await app.request(`/v1/orgs/${org.id}/faucet`, {
39
+ body: JSON.stringify({ address: recipient }),
40
+ headers: { 'content-type': 'application/json', cookie },
41
+ method: 'POST',
42
+ })
43
+ expect(funded.status).toBe(200)
44
+ const result = await TestApp.json(funded, Faucet.schema.FundResponse)
45
+ expect(result.tokens.length).toBeGreaterThan(0)
46
+ expect(
47
+ result.tokens.every(
48
+ (entry) =>
49
+ entry.token.symbol.length > 0 &&
50
+ BigInt(entry.amount.baseUnits) > 0n &&
51
+ entry.amount.currency === entry.token.currency &&
52
+ entry.amount.decimals === entry.token.decimals &&
53
+ /^\d+(\.\d+)?$/.test(entry.amount.formatted),
54
+ ),
55
+ ).toBe(true)
56
+ expect(result.transactionHashes).toEqual(result.tokens.map((entry) => entry.transactionHash))
57
+
58
+ const repeated = await app.request(`/v1/orgs/${org.id}/faucet`, {
59
+ body: JSON.stringify({ address: recipient }),
60
+ headers: { 'content-type': 'application/json', cookie },
61
+ method: 'POST',
62
+ })
63
+ expect(repeated.status).toBe(429)
64
+ expect(repeated.headers.get('ratelimit-scope')).toBe('faucet-address')
65
+ })
66
+
67
+ test('requires membership and a valid address', async () => {
68
+ const app = createApp()
69
+ const owner = await session(app)
70
+ const org = await organization(app, owner)
71
+ const stranger = await session(app)
72
+
73
+ const inaccessible = await app.request(`/v1/orgs/${org.id}/faucet`, {
74
+ body: JSON.stringify({ address: privateKeyToAccount(generatePrivateKey()).address }),
75
+ headers: { 'content-type': 'application/json', cookie: stranger },
76
+ method: 'POST',
77
+ })
78
+ expect(inaccessible.status).toBe(404)
79
+
80
+ const invalid = await app.request(`/v1/orgs/${org.id}/faucet`, {
81
+ body: JSON.stringify({ address: 'not-an-address' }),
82
+ headers: { 'content-type': 'application/json', cookie: owner },
83
+ method: 'POST',
84
+ })
85
+ expect(invalid.status).toBe(400)
86
+ })
87
+ })