tapimo 0.12.0 → 0.14.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 (398) hide show
  1. package/dist/App.d.ts +11333 -9563
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +39 -3
  4. package/dist/App.js.map +1 -1
  5. package/dist/Scope.d.ts +2 -0
  6. package/dist/Scope.d.ts.map +1 -1
  7. package/dist/Scope.js +17 -0
  8. package/dist/Scope.js.map +1 -1
  9. package/dist/Webhooks.js +2 -2
  10. package/dist/Webhooks.js.map +1 -1
  11. package/dist/admin/App.d.ts +1064 -924
  12. package/dist/admin/App.d.ts.map +1 -1
  13. package/dist/admin/apps/api-keys.d.ts +80 -0
  14. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  15. package/dist/admin/apps/api-keys.js +54 -0
  16. package/dist/admin/apps/api-keys.js.map +1 -1
  17. package/dist/admin/apps/earn-vaults.d.ts +63 -15
  18. package/dist/admin/apps/earn-vaults.d.ts.map +1 -1
  19. package/dist/admin/apps/earn-vaults.js +2 -1
  20. package/dist/admin/apps/earn-vaults.js.map +1 -1
  21. package/dist/admin/apps/organizations.d.ts +92 -3
  22. package/dist/admin/apps/organizations.d.ts.map +1 -1
  23. package/dist/admin/apps/organizations.js +69 -7
  24. package/dist/admin/apps/organizations.js.map +1 -1
  25. package/dist/admin/apps/verified-tokens.d.ts +7 -7
  26. package/dist/admin/ui.generated.d.ts +1 -1
  27. package/dist/admin/ui.generated.d.ts.map +1 -1
  28. package/dist/admin/ui.generated.js +151 -151
  29. package/dist/admin/ui.generated.js.map +1 -1
  30. package/dist/apps/Zones.d.ts +28 -28
  31. package/dist/apps/Zones.js +7 -7
  32. package/dist/apps/data/App.d.ts +2909 -2523
  33. package/dist/apps/data/App.d.ts.map +1 -1
  34. package/dist/apps/data/App.js +26 -21
  35. package/dist/apps/data/App.js.map +1 -1
  36. package/dist/apps/data/ZoneSelection.d.ts +8 -8
  37. package/dist/apps/data/ZoneSelection.d.ts.map +1 -1
  38. package/dist/apps/data/ZoneSelection.js +1 -1
  39. package/dist/apps/data/ZoneSelection.js.map +1 -1
  40. package/dist/apps/data/routes/activities.d.ts +588 -584
  41. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  42. package/dist/apps/data/routes/activities.js +1561 -1239
  43. package/dist/apps/data/routes/activities.js.map +1 -1
  44. package/dist/apps/data/routes/addresses.d.ts +22 -22
  45. package/dist/apps/data/routes/balances.d.ts +26 -26
  46. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  47. package/dist/apps/data/routes/balances.js +43 -16
  48. package/dist/apps/data/routes/balances.js.map +1 -1
  49. package/dist/apps/data/routes/blocks.d.ts +44 -44
  50. package/dist/apps/data/routes/blocks.d.ts.map +1 -1
  51. package/dist/apps/data/routes/blocks.js +21 -11
  52. package/dist/apps/data/routes/blocks.js.map +1 -1
  53. package/dist/apps/data/routes/coingecko.d.ts +3 -3
  54. package/dist/apps/data/routes/earn.d.ts +201 -25
  55. package/dist/apps/data/routes/earn.d.ts.map +1 -1
  56. package/dist/apps/data/routes/earn.js +434 -75
  57. package/dist/apps/data/routes/earn.js.map +1 -1
  58. package/dist/apps/data/routes/exchanges.d.ts +160 -145
  59. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  60. package/dist/apps/data/routes/exchanges.js +180 -114
  61. package/dist/apps/data/routes/exchanges.js.map +1 -1
  62. package/dist/apps/data/routes/fee-amm.d.ts +2 -2
  63. package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
  64. package/dist/apps/data/routes/fee-amm.js +18 -19
  65. package/dist/apps/data/routes/fee-amm.js.map +1 -1
  66. package/dist/apps/data/routes/receipts.d.ts +155 -55
  67. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  68. package/dist/apps/data/routes/receipts.js +28 -10
  69. package/dist/apps/data/routes/receipts.js.map +1 -1
  70. package/dist/apps/data/routes/tokenlist.d.ts +13 -13
  71. package/dist/apps/data/routes/tokens.d.ts +5 -5
  72. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  73. package/dist/apps/data/routes/tokens.js +51 -20
  74. package/dist/apps/data/routes/tokens.js.map +1 -1
  75. package/dist/apps/data/routes/transactions.d.ts +7 -2
  76. package/dist/apps/data/routes/transactions.d.ts.map +1 -1
  77. package/dist/apps/data/routes/transactions.js +37 -17
  78. package/dist/apps/data/routes/transactions.js.map +1 -1
  79. package/dist/apps/data/routes/transfers.d.ts +13 -13
  80. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  81. package/dist/apps/data/routes/transfers.js +18 -12
  82. package/dist/apps/data/routes/transfers.js.map +1 -1
  83. package/dist/apps/data/routes/valuation.d.ts +31 -31
  84. package/dist/apps/data/routes/valuation.d.ts.map +1 -1
  85. package/dist/apps/data/routes/valuation.js +12 -7
  86. package/dist/apps/data/routes/valuation.js.map +1 -1
  87. package/dist/apps/data/routes/verified-tokens.d.ts +8 -8
  88. package/dist/apps/data/routes/webhooks.d.ts +816 -113
  89. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  90. package/dist/apps/data/routes/webhooks.js +47 -5
  91. package/dist/apps/data/routes/webhooks.js.map +1 -1
  92. package/dist/apps/data/routes/zones.d.ts +14 -14
  93. package/dist/apps/data/routes/zones.js +1 -1
  94. package/dist/apps/funding/App.d.ts +466 -44
  95. package/dist/apps/funding/App.d.ts.map +1 -1
  96. package/dist/apps/funding/App.js +1 -1
  97. package/dist/apps/funding/App.js.map +1 -1
  98. package/dist/apps/funding/routes/deposit-addresses.d.ts +17 -17
  99. package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
  100. package/dist/apps/funding/routes/deposit-addresses.js +17 -12
  101. package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
  102. package/dist/apps/funding/routes/transfers.d.ts +650 -57
  103. package/dist/apps/funding/routes/transfers.d.ts.map +1 -1
  104. package/dist/apps/funding/routes/transfers.js +238 -24
  105. package/dist/apps/funding/routes/transfers.js.map +1 -1
  106. package/dist/apps/management/App.d.ts +5120 -4152
  107. package/dist/apps/management/App.d.ts.map +1 -1
  108. package/dist/apps/management/Billing.d.ts +1 -1
  109. package/dist/apps/management/Billing.d.ts.map +1 -1
  110. package/dist/apps/management/Billing.js +17 -1
  111. package/dist/apps/management/Billing.js.map +1 -1
  112. package/dist/apps/management/routes/api-keys.d.ts +632 -81
  113. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  114. package/dist/apps/management/routes/api-keys.js +236 -4
  115. package/dist/apps/management/routes/api-keys.js.map +1 -1
  116. package/dist/apps/management/routes/billing.d.ts +76 -76
  117. package/dist/apps/management/routes/faucet.d.ts +6 -6
  118. package/dist/apps/management/routes/invitations.d.ts +24 -24
  119. package/dist/apps/management/routes/invite-links.d.ts +4 -4
  120. package/dist/apps/management/routes/members.d.ts +16 -16
  121. package/dist/apps/management/routes/orgs.d.ts +20 -20
  122. package/dist/apps/management/routes/projects.d.ts +4 -4
  123. package/dist/apps/management/routes/usage.d.ts +8 -8
  124. package/dist/apps/management/routes/verified-token-requests.d.ts +17 -17
  125. package/dist/apps/management/routes/webhooks.d.ts +675 -25
  126. package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
  127. package/dist/apps/management/routes/webhooks.js +21 -21
  128. package/dist/apps/management/routes/webhooks.js.map +1 -1
  129. package/dist/apps/relay/App.d.ts.map +1 -1
  130. package/dist/apps/relay/App.js +115 -23
  131. package/dist/apps/relay/App.js.map +1 -1
  132. package/dist/apps/relay/Sponsorships.d.ts.map +1 -1
  133. package/dist/apps/relay/Sponsorships.js +27 -1
  134. package/dist/apps/relay/Sponsorships.js.map +1 -1
  135. package/dist/db/Db.d.ts.map +1 -1
  136. package/dist/db/Db.js +34 -1
  137. package/dist/db/Db.js.map +1 -1
  138. package/dist/db/Schema.d.ts +11 -5
  139. package/dist/db/Schema.d.ts.map +1 -1
  140. package/dist/db/Schema.js +21 -3
  141. package/dist/db/Schema.js.map +1 -1
  142. package/dist/db/tables/fundingCatalog.d.ts +4 -2
  143. package/dist/db/tables/fundingCatalog.d.ts.map +1 -1
  144. package/dist/db/tables/fundingCatalog.js +5 -1
  145. package/dist/db/tables/fundingCatalog.js.map +1 -1
  146. package/dist/db/tables/fundingTransferTransactions.d.ts +38 -0
  147. package/dist/db/tables/fundingTransferTransactions.d.ts.map +1 -1
  148. package/dist/db/tables/fundingTransferTransactions.js +44 -3
  149. package/dist/db/tables/fundingTransferTransactions.js.map +1 -1
  150. package/dist/db/tables/fundingTransfers.d.ts +17 -0
  151. package/dist/db/tables/fundingTransfers.d.ts.map +1 -1
  152. package/dist/db/tables/fundingTransfers.js +21 -0
  153. package/dist/db/tables/fundingTransfers.js.map +1 -1
  154. package/dist/db/tables/organizations.d.ts +24 -0
  155. package/dist/db/tables/organizations.d.ts.map +1 -1
  156. package/dist/db/tables/organizations.js +27 -0
  157. package/dist/db/tables/organizations.js.map +1 -1
  158. package/dist/db/tables/projects.d.ts +25 -1
  159. package/dist/db/tables/projects.d.ts.map +1 -1
  160. package/dist/db/tables/projects.js +26 -0
  161. package/dist/db/tables/projects.js.map +1 -1
  162. package/dist/db/tables/sponsoredTransactions.d.ts +44 -0
  163. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  164. package/dist/db/tables/sponsoredTransactions.js +146 -13
  165. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  166. package/dist/db/tables/webhookSubscriptions.d.ts +10 -6
  167. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  168. package/dist/db/tables/webhookSubscriptions.js +9 -6
  169. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  170. package/dist/handlers/internal/utils.d.ts.map +1 -1
  171. package/dist/handlers/internal/utils.js +9 -0
  172. package/dist/handlers/internal/utils.js.map +1 -1
  173. package/dist/internal/Auth.d.ts +3 -1
  174. package/dist/internal/Auth.d.ts.map +1 -1
  175. package/dist/internal/Auth.js +6 -3
  176. package/dist/internal/Auth.js.map +1 -1
  177. package/dist/internal/EarnVaults.d.ts +3 -3
  178. package/dist/internal/EarnVaults.js +5 -5
  179. package/dist/internal/EarnVaults.js.map +1 -1
  180. package/dist/internal/Fetch.d.ts +23 -0
  181. package/dist/internal/Fetch.d.ts.map +1 -0
  182. package/dist/internal/Fetch.js +26 -0
  183. package/dist/internal/Fetch.js.map +1 -0
  184. package/dist/internal/Log.d.ts +4 -2
  185. package/dist/internal/Log.d.ts.map +1 -1
  186. package/dist/internal/Log.js +16 -3
  187. package/dist/internal/Log.js.map +1 -1
  188. package/dist/internal/OpenApi.d.ts +5 -3
  189. package/dist/internal/OpenApi.d.ts.map +1 -1
  190. package/dist/internal/OpenApi.js +13 -3
  191. package/dist/internal/OpenApi.js.map +1 -1
  192. package/dist/internal/Response.d.ts +1 -1
  193. package/dist/internal/Response.d.ts.map +1 -1
  194. package/dist/internal/Schema.d.ts +6 -3
  195. package/dist/internal/Schema.d.ts.map +1 -1
  196. package/dist/internal/Schema.js +8 -2
  197. package/dist/internal/Schema.js.map +1 -1
  198. package/dist/internal/Store.d.ts +16 -3
  199. package/dist/internal/Store.d.ts.map +1 -1
  200. package/dist/internal/Store.js +82 -16
  201. package/dist/internal/Store.js.map +1 -1
  202. package/dist/internal/Tidx.d.ts.map +1 -1
  203. package/dist/internal/Tidx.js +7 -2
  204. package/dist/internal/Tidx.js.map +1 -1
  205. package/dist/internal/Timeout.d.ts +36 -0
  206. package/dist/internal/Timeout.d.ts.map +1 -0
  207. package/dist/internal/Timeout.js +37 -0
  208. package/dist/internal/Timeout.js.map +1 -0
  209. package/dist/internal/Timing.d.ts +9 -0
  210. package/dist/internal/Timing.d.ts.map +1 -1
  211. package/dist/internal/Timing.js +7 -0
  212. package/dist/internal/Timing.js.map +1 -1
  213. package/dist/internal/Viem.d.ts +16 -1
  214. package/dist/internal/Viem.d.ts.map +1 -1
  215. package/dist/internal/Viem.js +12 -2
  216. package/dist/internal/Viem.js.map +1 -1
  217. package/dist/internal/WebhookDestination.d.ts.map +1 -1
  218. package/dist/internal/WebhookDestination.js +3 -0
  219. package/dist/internal/WebhookDestination.js.map +1 -1
  220. package/dist/internal/funding/Action.d.ts +9 -0
  221. package/dist/internal/funding/Action.d.ts.map +1 -1
  222. package/dist/internal/funding/Action.js +16 -8
  223. package/dist/internal/funding/Action.js.map +1 -1
  224. package/dist/internal/funding/Catalog.d.ts +9 -2
  225. package/dist/internal/funding/Catalog.d.ts.map +1 -1
  226. package/dist/internal/funding/Catalog.js +19 -8
  227. package/dist/internal/funding/Catalog.js.map +1 -1
  228. package/dist/internal/funding/DepositWebhook.d.ts.map +1 -1
  229. package/dist/internal/funding/DepositWebhook.js +2 -1
  230. package/dist/internal/funding/DepositWebhook.js.map +1 -1
  231. package/dist/internal/funding/Evidence.d.ts.map +1 -1
  232. package/dist/internal/funding/Evidence.js +2 -10
  233. package/dist/internal/funding/Evidence.js.map +1 -1
  234. package/dist/internal/funding/Provider.d.ts +108 -13
  235. package/dist/internal/funding/Provider.d.ts.map +1 -1
  236. package/dist/internal/funding/Provider.js +34 -8
  237. package/dist/internal/funding/Provider.js.map +1 -1
  238. package/dist/internal/funding/SourceObservation.d.ts.map +1 -1
  239. package/dist/internal/funding/SourceObservation.js +5 -7
  240. package/dist/internal/funding/SourceObservation.js.map +1 -1
  241. package/dist/internal/funding/Transfer.d.ts +114 -18
  242. package/dist/internal/funding/Transfer.d.ts.map +1 -1
  243. package/dist/internal/funding/Transfer.js +185 -55
  244. package/dist/internal/funding/Transfer.js.map +1 -1
  245. package/dist/internal/funding/TransferReconciliation.d.ts +52 -0
  246. package/dist/internal/funding/TransferReconciliation.d.ts.map +1 -0
  247. package/dist/internal/funding/TransferReconciliation.js +192 -0
  248. package/dist/internal/funding/TransferReconciliation.js.map +1 -0
  249. package/dist/internal/funding/TransferWebhook.d.ts +21 -0
  250. package/dist/internal/funding/TransferWebhook.d.ts.map +1 -0
  251. package/dist/internal/funding/TransferWebhook.js +43 -0
  252. package/dist/internal/funding/TransferWebhook.js.map +1 -0
  253. package/dist/internal/funding/index.d.ts +1 -0
  254. package/dist/internal/funding/index.d.ts.map +1 -1
  255. package/dist/internal/funding/index.js +1 -0
  256. package/dist/internal/funding/index.js.map +1 -1
  257. package/dist/internal/funding/providers/relay.d.ts.map +1 -1
  258. package/dist/internal/funding/providers/relay.js +3 -12
  259. package/dist/internal/funding/providers/relay.js.map +1 -1
  260. package/dist/internal/funding/providers/stargate.d.ts +127 -9
  261. package/dist/internal/funding/providers/stargate.d.ts.map +1 -1
  262. package/dist/internal/funding/providers/stargate.js +548 -112
  263. package/dist/internal/funding/providers/stargate.js.map +1 -1
  264. package/dist/internal/index.d.ts +1 -0
  265. package/dist/internal/index.d.ts.map +1 -1
  266. package/dist/internal/index.js +1 -0
  267. package/dist/internal/index.js.map +1 -1
  268. package/dist/internal/webhookDestinations/slack.d.ts.map +1 -1
  269. package/dist/internal/webhookDestinations/slack.js +16 -0
  270. package/dist/internal/webhookDestinations/slack.js.map +1 -1
  271. package/package.json +2 -2
  272. package/src/App.test.ts +100 -0
  273. package/src/App.ts +43 -3
  274. package/src/Client.test-d.ts +129 -2
  275. package/src/Scope.test.ts +20 -0
  276. package/src/Scope.ts +16 -0
  277. package/src/Webhooks.ts +2 -2
  278. package/src/admin/apps/api-keys.test.ts +75 -0
  279. package/src/admin/apps/api-keys.ts +64 -0
  280. package/src/admin/apps/earn-vaults.test.ts +1 -1
  281. package/src/admin/apps/earn-vaults.ts +2 -1
  282. package/src/admin/apps/organizations.test.ts +53 -1
  283. package/src/admin/apps/organizations.ts +90 -5
  284. package/src/admin/ui/src/lib/api-keys.ts +19 -0
  285. package/src/admin/ui/src/lib/organizations.test.ts +112 -1
  286. package/src/admin/ui/src/lib/organizations.ts +55 -0
  287. package/src/admin/ui/src/routes/api-keys.tsx +89 -2
  288. package/src/admin/ui/src/routes/organizations_.$orgId.tsx +116 -0
  289. package/src/admin/ui.generated.ts +189 -151
  290. package/src/apps/Zones.test.ts +7 -7
  291. package/src/apps/Zones.ts +7 -7
  292. package/src/apps/composition.test.ts +2 -1
  293. package/src/apps/data/App.test.ts +41 -0
  294. package/src/apps/data/App.ts +30 -21
  295. package/src/apps/data/ZoneSelection.test.ts +2 -2
  296. package/src/apps/data/ZoneSelection.ts +8 -8
  297. package/src/apps/data/routes/activities.test.ts +1526 -12
  298. package/src/apps/data/routes/activities.ts +1774 -1356
  299. package/src/apps/data/routes/balances.ts +133 -97
  300. package/src/apps/data/routes/blocks.test.ts +533 -0
  301. package/src/apps/data/routes/blocks.ts +35 -16
  302. package/src/apps/data/routes/earn.test.ts +373 -16
  303. package/src/apps/data/routes/earn.ts +548 -79
  304. package/src/apps/data/routes/earn.zone-access.test.ts +6 -6
  305. package/src/apps/data/routes/exchanges.test.ts +740 -15
  306. package/src/apps/data/routes/exchanges.ts +954 -753
  307. package/src/apps/data/routes/fee-amm.ts +17 -18
  308. package/src/apps/data/routes/receipts.ts +47 -16
  309. package/src/apps/data/routes/tokens.test.ts +578 -0
  310. package/src/apps/data/routes/tokens.ts +283 -225
  311. package/src/apps/data/routes/transactions.ts +68 -29
  312. package/src/apps/data/routes/transfers.test.ts +421 -0
  313. package/src/apps/data/routes/transfers.ts +45 -27
  314. package/src/apps/data/routes/valuation.ts +94 -78
  315. package/src/apps/data/routes/webhooks.test.ts +21 -5
  316. package/src/apps/data/routes/webhooks.ts +63 -8
  317. package/src/apps/data/routes/zones.test.ts +2 -2
  318. package/src/apps/data/routes/zones.ts +1 -1
  319. package/src/apps/funding/App.ts +12 -2
  320. package/src/apps/funding/routes/deposit-addresses.test.ts +29 -7
  321. package/src/apps/funding/routes/deposit-addresses.ts +19 -14
  322. package/src/apps/funding/routes/quotes.test.ts +5 -9
  323. package/src/apps/funding/routes/transfers.test.ts +901 -23
  324. package/src/apps/funding/routes/transfers.ts +347 -64
  325. package/src/apps/management/Billing.ts +18 -5
  326. package/src/apps/management/routes/api-keys.test.ts +220 -0
  327. package/src/apps/management/routes/api-keys.ts +280 -3
  328. package/src/apps/management/routes/webhooks.test.ts +3 -3
  329. package/src/apps/management/routes/webhooks.ts +24 -25
  330. package/src/apps/relay/App.test.ts +212 -1
  331. package/src/apps/relay/App.ts +158 -29
  332. package/src/apps/relay/Sponsorships.ts +24 -1
  333. package/src/db/Db.ts +48 -1
  334. package/src/db/Schema.ts +22 -4
  335. package/src/db/tables/earnVaults.test.ts +9 -9
  336. package/src/db/tables/fundingCatalog.test.ts +16 -2
  337. package/src/db/tables/fundingCatalog.ts +9 -3
  338. package/src/db/tables/fundingTransferTransactions.test.ts +43 -0
  339. package/src/db/tables/fundingTransferTransactions.ts +79 -3
  340. package/src/db/tables/fundingTransfers.test.ts +26 -0
  341. package/src/db/tables/fundingTransfers.ts +36 -0
  342. package/src/db/tables/organizations.test.ts +41 -0
  343. package/src/db/tables/organizations.ts +49 -0
  344. package/src/db/tables/projects.test.ts +55 -0
  345. package/src/db/tables/projects.ts +46 -1
  346. package/src/db/tables/sponsoredTransactions.test.ts +338 -0
  347. package/src/db/tables/sponsoredTransactions.ts +197 -13
  348. package/src/db/tables/webhookSubscriptions.ts +18 -11
  349. package/src/handlers/internal/utils.test.ts +53 -0
  350. package/src/handlers/internal/utils.ts +15 -0
  351. package/src/internal/Auth.ts +12 -4
  352. package/src/internal/EarnVaults.capabilities.test.ts +2 -2
  353. package/src/internal/EarnVaults.test.ts +1 -1
  354. package/src/internal/EarnVaults.ts +5 -5
  355. package/src/internal/Fetch.test.ts +46 -0
  356. package/src/internal/Fetch.ts +56 -0
  357. package/src/internal/Log.test-d.ts +2 -0
  358. package/src/internal/Log.test.ts +21 -0
  359. package/src/internal/Log.ts +22 -5
  360. package/src/internal/OpenApi.ts +14 -4
  361. package/src/internal/Response.ts +1 -1
  362. package/src/internal/Schema.test.ts +2 -0
  363. package/src/internal/Schema.ts +12 -2
  364. package/src/internal/Store.test.ts +178 -0
  365. package/src/internal/Store.ts +104 -18
  366. package/src/internal/Tidx.test.ts +2 -1
  367. package/src/internal/Tidx.ts +7 -2
  368. package/src/internal/Timeout.test.ts +35 -0
  369. package/src/internal/Timeout.ts +73 -0
  370. package/src/internal/Timing.ts +16 -0
  371. package/src/internal/Viem.ts +39 -3
  372. package/src/internal/WebhookDestination.ts +3 -0
  373. package/src/internal/Webhooks.test.ts +45 -0
  374. package/src/internal/funding/Action.test.ts +41 -1
  375. package/src/internal/funding/Action.ts +69 -41
  376. package/src/internal/funding/Catalog.test.ts +34 -0
  377. package/src/internal/funding/Catalog.ts +28 -11
  378. package/src/internal/funding/DepositWebhook.ts +2 -1
  379. package/src/internal/funding/Evidence.ts +2 -13
  380. package/src/internal/funding/Provider.test.ts +73 -0
  381. package/src/internal/funding/Provider.ts +125 -28
  382. package/src/internal/funding/SourceObservation.ts +5 -9
  383. package/src/internal/funding/Transfer.test.ts +155 -0
  384. package/src/internal/funding/Transfer.ts +387 -147
  385. package/src/internal/funding/TransferReconciliation.test.ts +147 -0
  386. package/src/internal/funding/TransferReconciliation.ts +264 -0
  387. package/src/internal/funding/TransferWebhook.test.ts +128 -0
  388. package/src/internal/funding/TransferWebhook.ts +67 -0
  389. package/src/internal/funding/index.test.ts +1 -0
  390. package/src/internal/funding/index.ts +1 -0
  391. package/src/internal/funding/providers/index.test.ts +4 -167
  392. package/src/internal/funding/providers/relay.test.ts +2 -2
  393. package/src/internal/funding/providers/relay.ts +3 -15
  394. package/src/internal/funding/providers/stargate.test.ts +328 -0
  395. package/src/internal/funding/providers/stargate.ts +807 -135
  396. package/src/internal/index.test.ts +1 -0
  397. package/src/internal/index.ts +1 -0
  398. package/src/internal/webhookDestinations/slack.ts +15 -0
@@ -88,6 +88,7 @@ test('publishes generator-ready OpenAPI contracts', async () => {
88
88
  const collection = spec.paths['/v1/orgs/{orgId}/projects/{projectId}/api-keys']
89
89
  const detail = spec.paths['/v1/orgs/{orgId}/projects/{projectId}/api-keys/{keyId}']
90
90
  const organization = spec.paths['/v1/orgs/{orgId}/api-keys']
91
+ const rotation = spec.paths['/v1/orgs/{orgId}/projects/{projectId}/api-keys/{keyId}/rotate']
91
92
 
92
93
  expect({
93
94
  components: [
@@ -96,6 +97,7 @@ test('publishes generator-ready OpenAPI contracts', async () => {
96
97
  'CreateApiKeyRequest',
97
98
  'CreateApiKeyResponse',
98
99
  'RevokeApiKeyResponse',
100
+ 'RotateApiKeyResponse',
99
101
  'UpdateApiKeyRequest',
100
102
  ].filter((name) => spec.components.schemas[name]),
101
103
  create: {
@@ -135,6 +137,15 @@ test('publishes generator-ready OpenAPI contracts', async () => {
135
137
  operationId: detail.delete.operationId,
136
138
  response: detail.delete.responses[200].content['application/json'].schema,
137
139
  },
140
+ rotate: {
141
+ errors: {
142
+ 400: rotation.post.responses[400].content['application/json'].schema,
143
+ 403: rotation.post.responses[403].content['application/json'].schema,
144
+ 404: rotation.post.responses[404].content['application/json'].schema,
145
+ },
146
+ operationId: rotation.post.operationId,
147
+ response: rotation.post.responses[200].content['application/json'].schema,
148
+ },
138
149
  timestamps: {
139
150
  createdAt: spec.components.schemas.ApiKey.properties.createdAt.format,
140
151
  expiresAt: spec.components.schemas.ApiKey.properties.expiresAt.format,
@@ -158,6 +169,7 @@ test('publishes generator-ready OpenAPI contracts', async () => {
158
169
  "CreateApiKeyRequest",
159
170
  "CreateApiKeyResponse",
160
171
  "RevokeApiKeyResponse",
172
+ "RotateApiKeyResponse",
161
173
  "UpdateApiKeyRequest",
162
174
  ],
163
175
  "create": {
@@ -231,6 +243,23 @@ test('publishes generator-ready OpenAPI contracts', async () => {
231
243
  "$ref": "#/components/schemas/RevokeApiKeyResponse",
232
244
  },
233
245
  },
246
+ "rotate": {
247
+ "errors": {
248
+ "400": {
249
+ "$ref": "#/components/schemas/ApiKeyMalformedOrParamInvalidError",
250
+ },
251
+ "403": {
252
+ "$ref": "#/components/schemas/ApiKeyForbiddenOrApiKeyIpForbiddenOrForbiddenOrScopeNotIssuableError",
253
+ },
254
+ "404": {
255
+ "$ref": "#/components/schemas/ApiKeyNotFoundOrApiKeysNotEnabledOrOrganizationNotFoundOrProjectNotFoundError",
256
+ },
257
+ },
258
+ "operationId": "rotateApiKey",
259
+ "response": {
260
+ "$ref": "#/components/schemas/RotateApiKeyResponse",
261
+ },
262
+ },
234
263
  "timestamps": {
235
264
  "createdAt": "date-time",
236
265
  "expiresAt": "date-time",
@@ -287,6 +316,54 @@ describe('keys', () => {
287
316
 
288
317
  const write = await app.request(base, json('POST', owner, { scopes: ['management:write'] }))
289
318
  expect(write.status).toBe(200)
319
+
320
+ const created = await TestApp.json(write, Keys.schema.createApiKey.Response)
321
+ const rotateDenied = await app.request(`${base}/${created.id}/rotate`, {
322
+ headers: { cookie: admin },
323
+ method: 'POST',
324
+ })
325
+ expect(rotateDenied.status).toBe(403)
326
+ expect(await rotateDenied.json()).toMatchObject({ error: { code: 'scope_not_issuable' } })
327
+
328
+ const rotateAllowed = await app.request(`${base}/${created.id}/rotate`, {
329
+ headers: { cookie: owner },
330
+ method: 'POST',
331
+ })
332
+ expect(rotateAllowed.status).toBe(200)
333
+ })
334
+
335
+ test('requires an owner to rotate wildcard keys and allows the super admin', async () => {
336
+ const db = TestApp.database()
337
+ const store = TestApp.kvStore({ keys: [] })
338
+ const app = createApp({ db, kv: { store } })
339
+ const owner = await session(app)
340
+ const { org, project } = await fixture(app, owner)
341
+ const admin = await session(app)
342
+ const user = await TestApp.json(
343
+ await app.request('/v1/me', { headers: { cookie: admin } }),
344
+ Me.schema.User,
345
+ )
346
+ await Memberships.create(db, { orgId: org.id, role: 'admin', userId: user.id })
347
+ const base = `/v1/orgs/${org.id}/projects/${project.id}/api-keys`
348
+ const { record: source } = await ApiKeys.mint(store, {
349
+ environment: 'production',
350
+ orgId: org.id,
351
+ projectId: project.id,
352
+ scopes: [Scope.wildcard],
353
+ })
354
+
355
+ const denied = await app.request(`${base}/${source.id}/rotate`, {
356
+ headers: { cookie: admin },
357
+ method: 'POST',
358
+ })
359
+ expect(denied.status).toBe(403)
360
+ expect(await denied.json()).toMatchObject({ error: { code: 'scope_not_issuable' } })
361
+
362
+ const allowed = await app.request(`${base}/${source.id}/rotate`, {
363
+ headers: { 'tempo-api-key': secret },
364
+ method: 'POST',
365
+ })
366
+ expect(allowed.status).toBe(200)
290
367
  })
291
368
 
292
369
  test('zone scopes are never self-serve', async () => {
@@ -304,6 +381,149 @@ describe('keys', () => {
304
381
  }
305
382
  })
306
383
 
384
+ test('rotates a super-admin-issued zone key without delegating scope issuance', async () => {
385
+ const store = TestApp.kvStore({ keys: [] })
386
+ const app = createApp({ kv: { store } })
387
+ const owner = await session(app)
388
+ const { org, project } = await fixture(app, owner)
389
+ const base = `/v1/orgs/${org.id}/projects/${project.id}/api-keys`
390
+ const source = await TestApp.json(
391
+ await app.request(
392
+ base,
393
+ superAdminJson('POST', {
394
+ allowedIps: ['203.0.113.0/24'],
395
+ environment: 'production',
396
+ name: 'Privy',
397
+ scopes: ['zone:421700001:read', 'zone:421700001:write'],
398
+ }),
399
+ ),
400
+ Keys.schema.createApiKey.Response,
401
+ )
402
+
403
+ const response = await app.request(`${base}/${source.id}/rotate`, {
404
+ headers: { cookie: owner },
405
+ method: 'POST',
406
+ })
407
+ expect(response.status).toBe(200)
408
+ const replacement = await TestApp.json(response, Keys.schema.rotateApiKey.Response)
409
+ expect(replacement).toMatchObject({
410
+ allowedIps: ['203.0.113.0/24'],
411
+ environment: 'production',
412
+ name: 'Privy',
413
+ orgId: org.id,
414
+ projectId: project.id,
415
+ scopes: ['zone:421700001:read', 'zone:421700001:write'],
416
+ })
417
+ expect(replacement.createdBy).not.toBe('super_admin')
418
+ expect(replacement.id).not.toBe(source.id)
419
+ expect(replacement.token).not.toBe(source.token)
420
+ expect(await ApiKeys.resolve(store, source.token)).not.toBeNull()
421
+ expect(await ApiKeys.resolve(store, replacement.token)).not.toBeNull()
422
+ })
423
+
424
+ test('rotates an organization-level super-admin-issued zone key', async () => {
425
+ const store = TestApp.kvStore({ keys: [] })
426
+ const app = createApp({ kv: { store } })
427
+ const owner = await session(app)
428
+ const { org, project } = await fixture(app, owner)
429
+ const { record: projectSource } = await ApiKeys.mint(store, {
430
+ orgId: org.id,
431
+ projectId: project.id,
432
+ scopes: ['data:read'],
433
+ })
434
+ const projectResponse = await app.request(
435
+ `/v1/orgs/${org.id}/api-keys/${projectSource.id}/rotate`,
436
+ { headers: { cookie: owner }, method: 'POST' },
437
+ )
438
+ expect(projectResponse.status).toBe(404)
439
+ const projectRevoke = await app.request(`/v1/orgs/${org.id}/api-keys/${projectSource.id}`, {
440
+ headers: { cookie: owner },
441
+ method: 'DELETE',
442
+ })
443
+ expect(projectRevoke.status).toBe(404)
444
+
445
+ const { record: source } = await ApiKeys.mint(store, {
446
+ createdBy: 'super_admin',
447
+ environment: 'production',
448
+ name: 'Privy',
449
+ orgId: org.id,
450
+ scopes: ['zone:421700001:read', 'zone:421700001:write'],
451
+ })
452
+
453
+ const response = await app.request(`/v1/orgs/${org.id}/api-keys/${source.id}/rotate`, {
454
+ headers: { cookie: owner },
455
+ method: 'POST',
456
+ })
457
+ expect(response.status).toBe(200)
458
+ const replacement = await TestApp.json(response, Keys.schema.rotateApiKey.Response)
459
+ expect(replacement).toMatchObject({
460
+ environment: 'production',
461
+ name: 'Privy',
462
+ orgId: org.id,
463
+ scopes: ['zone:421700001:read', 'zone:421700001:write'],
464
+ })
465
+ expect(replacement.projectId).toBeUndefined()
466
+ expect(replacement.createdBy).not.toBe('super_admin')
467
+ expect(replacement.id).not.toBe(source.id)
468
+
469
+ const revoked = await app.request(`/v1/orgs/${org.id}/api-keys/${replacement.id}`, {
470
+ headers: { cookie: owner },
471
+ method: 'DELETE',
472
+ })
473
+ expect(revoked.status).toBe(200)
474
+ expect(await TestApp.json(revoked, Keys.schema.revokeApiKey.Response)).toEqual({
475
+ id: replacement.id,
476
+ })
477
+ expect(await ApiKeys.resolve(store, replacement.token)).toBeNull()
478
+ })
479
+
480
+ test('refreshes sandbox billing state during rotation', async () => {
481
+ const db = TestApp.database()
482
+ const store = TestApp.kvStore({ keys: [] })
483
+ const app = createApp({ db, kv: { store } })
484
+ const owner = await session(app)
485
+ const { org, project } = await fixture(app, owner)
486
+ const { record: source } = await ApiKeys.mint(store, {
487
+ billingActive: true,
488
+ environment: 'sandbox',
489
+ orgId: org.id,
490
+ projectId: project.id,
491
+ scopes: ['data:read'],
492
+ })
493
+
494
+ const response = await app.request(
495
+ `/v1/orgs/${org.id}/projects/${project.id}/api-keys/${source.id}/rotate`,
496
+ { headers: { cookie: owner }, method: 'POST' },
497
+ )
498
+ expect(response.status).toBe(200)
499
+ const replacement = await TestApp.json(response, Keys.schema.rotateApiKey.Response)
500
+ expect((await ApiKeys.get(store, replacement.id))?.billingActive).toBe(false)
501
+ })
502
+
503
+ test('does not rotate a key from another project', async () => {
504
+ const app = createApp()
505
+ const owner = await session(app)
506
+ const { org, project } = await fixture(app, owner)
507
+ const other = await TestApp.json(
508
+ await app.request(`/v1/orgs/${org.id}/projects`, json('POST', owner, { name: 'Other' })),
509
+ Projects.schema.Project,
510
+ )
511
+ const source = await TestApp.json(
512
+ await app.request(
513
+ `/v1/orgs/${org.id}/projects/${project.id}/api-keys`,
514
+ superAdminJson('POST', { scopes: ['zone:421700001:read'] }),
515
+ ),
516
+ Keys.schema.createApiKey.Response,
517
+ )
518
+
519
+ const response = await app.request(
520
+ `/v1/orgs/${org.id}/projects/${other.id}/api-keys/${source.id}/rotate`,
521
+ { headers: { cookie: owner }, method: 'POST' },
522
+ )
523
+ expect(response.status).toBe(404)
524
+ expect(await response.json()).toMatchObject({ error: { code: 'api_key_not_found' } })
525
+ })
526
+
307
527
  test('restricts project management keys without delegating issuance', async () => {
308
528
  const app = createApp()
309
529
  const cookie = await session(app)
@@ -12,6 +12,7 @@ import * as Billing from '../Billing.js'
12
12
  import * as OpenApi from '../../../internal/OpenApi.js'
13
13
  import * as Response from '../../../internal/Response.js'
14
14
  import * as Schema from '../../../internal/Schema.js'
15
+ import * as Store from '../../../internal/Store.js'
15
16
  import * as Scope from '../../../Scope.js'
16
17
  import * as ManagementError from '../Error.js'
17
18
 
@@ -147,6 +148,24 @@ export namespace schema {
147
148
  })
148
149
  .check(z.describe('Path parameters for one API key.'))
149
150
 
151
+ /** Path parameters addressing one organization-level API key. */
152
+ export const OrgKeyParams = z
153
+ .object({
154
+ keyId: z
155
+ .string()
156
+ .check(
157
+ z.describe('The key id (`key_…`).'),
158
+ z.meta({ examples: ['key_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
159
+ ),
160
+ orgId: z
161
+ .string()
162
+ .check(
163
+ z.describe('The organization id (`org_…`).'),
164
+ z.meta({ examples: ['org_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
165
+ ),
166
+ })
167
+ .check(z.describe('Path parameters for one organization-level API key.'))
168
+
150
169
  /** Request body minting a key. The wildcard scope is not issuable here. */
151
170
  export const CreateApiKeyRequest = OpenApi.component(
152
171
  z
@@ -251,6 +270,25 @@ export namespace schema {
251
270
  )
252
271
  }
253
272
 
273
+ /** Schemas for the rotateApiKey operation. */
274
+ export namespace rotateApiKey {
275
+ /** The replacement key plus its one-time token. */
276
+ export const Response = OpenApi.component(
277
+ Schema.describe(
278
+ z.extend(Key, {
279
+ token: z
280
+ .string()
281
+ .check(
282
+ z.describe('The plaintext token — shown once, unrecoverable afterward.'),
283
+ z.meta({ examples: ['tempo_sandbox:sk:…'] }),
284
+ ),
285
+ }),
286
+ 'The replacement key, including its one-time token.',
287
+ ),
288
+ 'RotateApiKeyResponse',
289
+ )
290
+ }
291
+
254
292
  /** Schemas for the updateApiKey operation. */
255
293
  export namespace updateApiKey {
256
294
  /** Request body replacing or clearing a key's IP allowlist. */
@@ -518,6 +556,124 @@ export function apiKeys(options: apiKeys.Options = {}) {
518
556
  }
519
557
  },
520
558
  )
559
+ .post(
560
+ '/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/api-keys/:keyId{key_[A-Za-z0-9_-]+}/rotate',
561
+ Auth.policy({ session: true }),
562
+ Auth.ensureOrg({ role: 'admin' }),
563
+ OpenApi.validate('param', schema.OrgKeyParams, {
564
+ code: 'param_invalid',
565
+ message: 'Check the path parameters and try again.',
566
+ }),
567
+ OpenApi.describeRoute({
568
+ description:
569
+ 'Create a replacement organization-level API key with the same access as an existing key. The existing key remains active until revoked.',
570
+ hide: hidden,
571
+ operationId: 'rotateOrgApiKey',
572
+ responses: OpenApi.responses({
573
+ errors: {
574
+ 400: { codes: ['param_invalid'], description: 'Malformed API key or invalid path.' },
575
+ 403: {
576
+ codes: ['scope_not_issuable'],
577
+ description: 'The source key contains scopes the caller cannot rotate.',
578
+ },
579
+ 404: {
580
+ codes: ['api_key_not_found', 'api_keys_not_enabled', 'organization_not_found'],
581
+ description: 'No accessible API key or API key surface was found.',
582
+ },
583
+ },
584
+ success: {
585
+ description: 'The replacement key, including its one-time token.',
586
+ schema: schema.rotateApiKey.Response,
587
+ },
588
+ }),
589
+ summary: 'Rotate organization API key',
590
+ tags: ['API Keys'],
591
+ }),
592
+ async (c) => {
593
+ if (Auth.narrowAccess) return Auth.superAdminAccessError(c)
594
+ if (Auth.narrowOrgRole) return Auth.ensureOrgRoleError(c)
595
+ if (OpenApi.narrowValidation)
596
+ return OpenApi.validationError(c, {
597
+ code: 'param_invalid',
598
+ message: 'Check the path parameters and try again.',
599
+ })
600
+
601
+ const kv = c.get('kv')
602
+ if (!kv) return notEnabled(c)
603
+ const createdBy = mintedBy(c)
604
+ if (!createdBy) return ManagementError.sessionRequired(c)
605
+ const { keyId } = c.req.valid('param')
606
+ try {
607
+ const source = await ApiKeys.get(kv.store, keyId, {
608
+ scopeCatalog: c.get('scopeCatalog'),
609
+ })
610
+ if (!source || source.orgId !== Auth.org(c).id || source.projectId !== undefined)
611
+ return keyNotFound(c)
612
+ if (!canRotate(c, source.scopes))
613
+ return Response.error(c, {
614
+ code: 'scope_not_issuable',
615
+ message: 'The source key contains scopes the caller cannot rotate.',
616
+ status: 403,
617
+ })
618
+ return rotate(c, kv.store, source, createdBy)
619
+ } catch (cause) {
620
+ return Response.upstream(c, cause)
621
+ }
622
+ },
623
+ )
624
+ .delete(
625
+ '/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/api-keys/:keyId{key_[A-Za-z0-9_-]+}',
626
+ Auth.policy({ apiKey: { scopes: ['management:write'] }, session: true }),
627
+ Auth.ensureOrg({ role: 'admin' }),
628
+ OpenApi.validate('param', schema.OrgKeyParams, {
629
+ code: 'param_invalid',
630
+ message: 'Check the path parameters and try again.',
631
+ }),
632
+ OpenApi.describeRoute({
633
+ description:
634
+ 'Revoke an organization-level API key. Requests presenting its token stop resolving.',
635
+ hide: hidden,
636
+ operationId: 'revokeOrgApiKey',
637
+ responses: OpenApi.responses({
638
+ errors: {
639
+ 400: { codes: ['param_invalid'], description: 'Malformed API key or invalid path.' },
640
+ 404: {
641
+ codes: ['api_key_not_found', 'api_keys_not_enabled', 'organization_not_found'],
642
+ description: 'No accessible API key or API key surface was found.',
643
+ },
644
+ },
645
+ success: {
646
+ description: 'Confirmation that the key was revoked.',
647
+ schema: schema.revokeApiKey.Response,
648
+ },
649
+ }),
650
+ summary: 'Revoke organization API key',
651
+ tags: ['API Keys'],
652
+ }),
653
+ async (c) => {
654
+ if (Auth.narrowAccess) return Auth.accessError(c)
655
+ if (Auth.narrowOrgRole) return Auth.ensureOrgRoleError(c)
656
+ if (OpenApi.narrowValidation)
657
+ return OpenApi.validationError(c, {
658
+ code: 'param_invalid',
659
+ message: 'Check the path parameters and try again.',
660
+ })
661
+
662
+ const kv = c.get('kv')
663
+ if (!kv) return notEnabled(c)
664
+ const { keyId } = c.req.valid('param')
665
+ try {
666
+ const record = await ApiKeys.get(kv.store, keyId, {
667
+ scopeCatalog: c.get('scopeCatalog'),
668
+ })
669
+ if (!record || record.orgId !== Auth.org(c).id || record.projectId !== undefined)
670
+ return keyNotFound(c)
671
+ return revoke(c, kv.store, keyId)
672
+ } catch (cause) {
673
+ return Response.upstream(c, cause)
674
+ }
675
+ },
676
+ )
521
677
  .patch(
522
678
  '/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/projects/:projectId{prj_[A-Za-z0-9_-]+}/api-keys/:keyId{key_[A-Za-z0-9_-]+}',
523
679
  Auth.policy({ apiKey: { scopes: ['management:write'] }, session: true }),
@@ -594,6 +750,78 @@ export function apiKeys(options: apiKeys.Options = {}) {
594
750
  }
595
751
  },
596
752
  )
753
+ .post(
754
+ '/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/projects/:projectId{prj_[A-Za-z0-9_-]+}/api-keys/:keyId{key_[A-Za-z0-9_-]+}/rotate',
755
+ Auth.policy({ session: true }),
756
+ Auth.ensureOrg({ role: 'admin' }),
757
+ Auth.ensureProject(),
758
+ OpenApi.validate('param', schema.KeyParams, {
759
+ code: 'param_invalid',
760
+ message: 'Check the path parameters and try again.',
761
+ }),
762
+ OpenApi.describeRoute({
763
+ description:
764
+ 'Create a replacement API key with the same access as an existing key. The existing key remains active until revoked.',
765
+ hide: hidden,
766
+ operationId: 'rotateApiKey',
767
+ responses: OpenApi.responses({
768
+ errors: {
769
+ 400: { codes: ['param_invalid'], description: 'Malformed API key or invalid path.' },
770
+ 403: {
771
+ codes: ['scope_not_issuable'],
772
+ description: 'The source key contains scopes the caller cannot rotate.',
773
+ },
774
+ 404: {
775
+ codes: [
776
+ 'api_key_not_found',
777
+ 'api_keys_not_enabled',
778
+ 'organization_not_found',
779
+ 'project_not_found',
780
+ ],
781
+ description: 'No accessible API key or API key surface was found.',
782
+ },
783
+ },
784
+ success: {
785
+ description: 'The replacement key, including its one-time token.',
786
+ schema: schema.rotateApiKey.Response,
787
+ },
788
+ }),
789
+ summary: 'Rotate API key',
790
+ tags: ['API Keys'],
791
+ }),
792
+ async (c) => {
793
+ if (Auth.narrowAccess) return Auth.superAdminAccessError(c)
794
+ if (Auth.narrowScope) return Auth.ensureProjectError(c)
795
+ if (Auth.narrowOrgRole) return Auth.ensureOrgRoleError(c)
796
+ if (OpenApi.narrowValidation)
797
+ return OpenApi.validationError(c, {
798
+ code: 'param_invalid',
799
+ message: 'Check the path parameters and try again.',
800
+ })
801
+
802
+ const kv = c.get('kv')
803
+ if (!kv) return notEnabled(c)
804
+ const createdBy = mintedBy(c)
805
+ if (!createdBy) return ManagementError.sessionRequired(c)
806
+ const { keyId } = c.req.valid('param')
807
+ try {
808
+ const source = await ApiKeys.get(kv.store, keyId, {
809
+ scopeCatalog: c.get('scopeCatalog'),
810
+ })
811
+ if (!source || source.orgId !== Auth.org(c).id || source.projectId !== Auth.project(c).id)
812
+ return keyNotFound(c)
813
+ if (!canRotate(c, source.scopes))
814
+ return Response.error(c, {
815
+ code: 'scope_not_issuable',
816
+ message: 'The source key contains scopes the caller cannot rotate.',
817
+ status: 403,
818
+ })
819
+ return rotate(c, kv.store, source, createdBy)
820
+ } catch (cause) {
821
+ return Response.upstream(c, cause)
822
+ }
823
+ },
824
+ )
597
825
  .delete(
598
826
  '/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/projects/:projectId{prj_[A-Za-z0-9_-]+}/api-keys/:keyId{key_[A-Za-z0-9_-]+}',
599
827
  Auth.policy({ apiKey: { scopes: ['management:write'] }, session: true }),
@@ -649,9 +877,7 @@ export function apiKeys(options: apiKeys.Options = {}) {
649
877
  })
650
878
  if (!record || record.orgId !== Auth.org(c).id || record.projectId !== Auth.project(c).id)
651
879
  return keyNotFound(c)
652
- const revoked = await ApiKeys.revoke(kv.store, keyId)
653
- if (!revoked) return keyNotFound(c)
654
- return c.json(Response.validated(schema.revokeApiKey.Response, { id: keyId }), 200)
880
+ return revoke(c, kv.store, keyId)
655
881
  } catch (cause) {
656
882
  return Response.upstream(c, cause)
657
883
  }
@@ -722,6 +948,57 @@ function canIssue(c: Context<App.Environment>, scopes: readonly Scope.Id[]) {
722
948
  return false
723
949
  }
724
950
 
951
+ /** Checks caller-specific restrictions that still apply when copying existing grants. */
952
+ function canRotate(c: Context<App.Environment>, scopes: readonly Scope.Id[]) {
953
+ const principal = Auth.getPrincipal(c)
954
+ if (principal?.type === 'super_admin') return true
955
+ if (principal?.type !== 'session') return false
956
+ return (
957
+ (!scopes.includes('management:write') && !scopes.includes(Scope.wildcard)) ||
958
+ Auth.membership(c)?.role === 'owner'
959
+ )
960
+ }
961
+
962
+ /** Mints a replacement that preserves the source key's access and attribution. */
963
+ async function rotate(
964
+ c: Context<App.Environment>,
965
+ store: Store.State,
966
+ source: ApiKeys.Record,
967
+ createdBy: string,
968
+ ) {
969
+ const billingActive =
970
+ source.environment === 'sandbox'
971
+ ? await Billing.active(Db.get(c.get('db')), source.orgId, 'sandbox')
972
+ : undefined
973
+ const { record, token } = await ApiKeys.mint(
974
+ store,
975
+ {
976
+ allowedIps: source.allowedIps,
977
+ createdBy,
978
+ environment: source.environment,
979
+ orgId: source.orgId,
980
+ ...(source.projectId === undefined ? {} : { projectId: source.projectId }),
981
+ scopes: source.scopes,
982
+ ...(billingActive === undefined ? {} : { billingActive }),
983
+ ...(source.expiresAt === undefined ? {} : { expiresAt: source.expiresAt }),
984
+ ...(source.name === undefined ? {} : { name: source.name }),
985
+ ...(source.rateLimits === undefined ? {} : { rateLimits: source.rateLimits }),
986
+ },
987
+ { scopeCatalog: c.get('scopeCatalog') },
988
+ )
989
+ return c.json(
990
+ Response.validated(schema.rotateApiKey.Response, { ...serializeKey(record), token }),
991
+ 200,
992
+ )
993
+ }
994
+
995
+ /** Revokes an API key after its route has verified ownership. */
996
+ async function revoke(c: Context<App.Environment>, store: Store.State, keyId: string) {
997
+ const revoked = await ApiKeys.revoke(store, keyId)
998
+ if (!revoked) return keyNotFound(c)
999
+ return c.json(Response.validated(schema.revokeApiKey.Response, { id: keyId }), 200)
1000
+ }
1001
+
725
1002
  function keyNotFound(c: Context<App.Environment>) {
726
1003
  return Response.error(c, {
727
1004
  code: 'api_key_not_found',
@@ -11,7 +11,7 @@ import * as Webhooks from './webhooks.js'
11
11
 
12
12
  const origin = 'http://localhost'
13
13
  const webhook = {
14
- applicationEventTypes: ['funding:deposit.updated'],
14
+ applicationEventTypes: ['funding:deposit.updated', 'funding:transfer.updated'],
15
15
  supportedChainIds: [4217, 42431],
16
16
  } satisfies App.Webhook
17
17
 
@@ -284,7 +284,7 @@ describe('webhooks', () => {
284
284
  `)
285
285
  })
286
286
 
287
- test('creates funding deposit subscriptions without reading the RPC head', async () => {
287
+ test('creates funding transfer subscriptions without reading the RPC head', async () => {
288
288
  const db = TestApp.database()
289
289
  const app = createApp({ db })
290
290
  const cookie = await session(app)
@@ -295,7 +295,7 @@ describe('webhooks', () => {
295
295
  chainId: 4217,
296
296
  destination: { type: 'url', url: 'https://example.com/funding' },
297
297
  environment: 'production',
298
- eventType: 'funding:deposit.updated',
298
+ eventType: 'funding:transfer.updated',
299
299
  filters: { status: 'completed' },
300
300
  }),
301
301
  )