tapimo 0.19.0 → 0.20.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 (494) hide show
  1. package/README.md +13 -5
  2. package/dist/ApiKey.d.ts +2 -2
  3. package/dist/ApiKey.d.ts.map +1 -1
  4. package/dist/ApiKey.js +2 -2
  5. package/dist/ApiKey.js.map +1 -1
  6. package/dist/ApiKeys.d.ts +85 -11
  7. package/dist/ApiKeys.d.ts.map +1 -1
  8. package/dist/ApiKeys.js +218 -56
  9. package/dist/ApiKeys.js.map +1 -1
  10. package/dist/App.d.ts +12543 -13912
  11. package/dist/App.d.ts.map +1 -1
  12. package/dist/App.js +4 -5
  13. package/dist/App.js.map +1 -1
  14. package/dist/Scope.d.ts +12 -20
  15. package/dist/Scope.d.ts.map +1 -1
  16. package/dist/Scope.js +20 -36
  17. package/dist/Scope.js.map +1 -1
  18. package/dist/admin/App.d.ts +871 -828
  19. package/dist/admin/App.d.ts.map +1 -1
  20. package/dist/admin/App.js +14 -26
  21. package/dist/admin/App.js.map +1 -1
  22. package/dist/admin/apps/api-keys.d.ts +93 -186
  23. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  24. package/dist/admin/apps/api-keys.js +3 -5
  25. package/dist/admin/apps/api-keys.js.map +1 -1
  26. package/dist/admin/apps/audit-logs.d.ts +18 -18
  27. package/dist/admin/apps/audit-logs.js +1 -1
  28. package/dist/admin/apps/audit-logs.js.map +1 -1
  29. package/dist/admin/apps/earn-vaults.d.ts +27 -27
  30. package/dist/admin/apps/earn-vaults.d.ts.map +1 -1
  31. package/dist/admin/apps/earn-vaults.js +11 -5
  32. package/dist/admin/apps/earn-vaults.js.map +1 -1
  33. package/dist/admin/apps/organizations.d.ts +17 -17
  34. package/dist/admin/apps/prop-amms.d.ts +4 -4
  35. package/dist/admin/apps/routes-support.js +1 -38
  36. package/dist/admin/apps/routes-support.js.map +1 -1
  37. package/dist/admin/apps/routes.d.ts +153 -153
  38. package/dist/admin/apps/sentinel.d.ts +4 -4
  39. package/dist/admin/apps/verified-tokens.d.ts +24 -24
  40. package/dist/admin/apps/zone-delegation-requests.d.ts +332 -0
  41. package/dist/admin/apps/zone-delegation-requests.d.ts.map +1 -0
  42. package/dist/admin/apps/zone-delegation-requests.js +200 -0
  43. package/dist/admin/apps/zone-delegation-requests.js.map +1 -0
  44. package/dist/admin/apps/zone-delegations.d.ts +512 -52
  45. package/dist/admin/apps/zone-delegations.d.ts.map +1 -1
  46. package/dist/admin/apps/zone-delegations.js +134 -44
  47. package/dist/admin/apps/zone-delegations.js.map +1 -1
  48. package/dist/admin/index.d.ts +1 -1
  49. package/dist/admin/index.d.ts.map +1 -1
  50. package/dist/admin/index.js +1 -1
  51. package/dist/admin/index.js.map +1 -1
  52. package/dist/admin/ui.generated.d.ts +1 -1
  53. package/dist/admin/ui.generated.d.ts.map +1 -1
  54. package/dist/admin/ui.generated.js +341 -397
  55. package/dist/admin/ui.generated.js.map +1 -1
  56. package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
  57. package/dist/analytics/tables/requestEvents.js +4 -2
  58. package/dist/analytics/tables/requestEvents.js.map +1 -1
  59. package/dist/apps/data/App.d.ts +11932 -13214
  60. package/dist/apps/data/App.d.ts.map +1 -1
  61. package/dist/apps/data/App.js +32 -13
  62. package/dist/apps/data/App.js.map +1 -1
  63. package/dist/apps/data/ZoneSelection.d.ts +1 -1
  64. package/dist/apps/data/ZoneSelection.d.ts.map +1 -1
  65. package/dist/apps/data/ZoneSelection.js +2 -2
  66. package/dist/apps/data/ZoneSelection.js.map +1 -1
  67. package/dist/apps/data/routes/activities.d.ts +60 -27
  68. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  69. package/dist/apps/data/routes/activities.js +91 -16
  70. package/dist/apps/data/routes/activities.js.map +1 -1
  71. package/dist/apps/data/routes/addresses.d.ts +893 -875
  72. package/dist/apps/data/routes/addresses.d.ts.map +1 -1
  73. package/dist/apps/data/routes/balances.d.ts +36 -36
  74. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  75. package/dist/apps/data/routes/balances.js +3 -1
  76. package/dist/apps/data/routes/balances.js.map +1 -1
  77. package/dist/apps/data/routes/blocks.d.ts +40 -40
  78. package/dist/apps/data/routes/blocks.d.ts.map +1 -1
  79. package/dist/apps/data/routes/blocks.js +3 -7
  80. package/dist/apps/data/routes/blocks.js.map +1 -1
  81. package/dist/apps/data/routes/coingecko.d.ts +236 -236
  82. package/dist/apps/data/routes/earn-transactions.d.ts +14 -14
  83. package/dist/apps/data/routes/earn.d.ts +261 -1445
  84. package/dist/apps/data/routes/earn.d.ts.map +1 -1
  85. package/dist/apps/data/routes/earn.js +64 -490
  86. package/dist/apps/data/routes/earn.js.map +1 -1
  87. package/dist/apps/data/routes/exchanges.d.ts +144 -144
  88. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  89. package/dist/apps/data/routes/exchanges.js +5 -15
  90. package/dist/apps/data/routes/exchanges.js.map +1 -1
  91. package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
  92. package/dist/apps/data/routes/fee-amm.js +3 -9
  93. package/dist/apps/data/routes/fee-amm.js.map +1 -1
  94. package/dist/apps/data/routes/indexer.d.ts +13 -13
  95. package/dist/apps/data/routes/receipts.d.ts +53 -53
  96. package/dist/apps/data/routes/receipts.js +1 -1
  97. package/dist/apps/data/routes/receipts.js.map +1 -1
  98. package/dist/apps/data/routes/rpc.d.ts +52 -0
  99. package/dist/apps/data/routes/rpc.d.ts.map +1 -1
  100. package/dist/apps/data/routes/rpc.js +19 -18
  101. package/dist/apps/data/routes/rpc.js.map +1 -1
  102. package/dist/apps/data/routes/tokenlist.d.ts +14 -14
  103. package/dist/apps/data/routes/tokenlist.d.ts.map +1 -1
  104. package/dist/apps/data/routes/tokenlist.js +1 -3
  105. package/dist/apps/data/routes/tokenlist.js.map +1 -1
  106. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  107. package/dist/apps/data/routes/tokens.js +7 -5
  108. package/dist/apps/data/routes/tokens.js.map +1 -1
  109. package/dist/apps/data/routes/transactions.d.ts +281 -281
  110. package/dist/apps/data/routes/transactions.js +3 -3
  111. package/dist/apps/data/routes/transactions.js.map +1 -1
  112. package/dist/apps/data/routes/transfers.d.ts +12 -12
  113. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  114. package/dist/apps/data/routes/transfers.js +1 -3
  115. package/dist/apps/data/routes/transfers.js.map +1 -1
  116. package/dist/apps/data/routes/valuation.d.ts +30 -30
  117. package/dist/apps/data/routes/valuation.d.ts.map +1 -1
  118. package/dist/apps/data/routes/valuation.js +1 -3
  119. package/dist/apps/data/routes/valuation.js.map +1 -1
  120. package/dist/apps/data/routes/verified-tokens.d.ts +86 -86
  121. package/dist/apps/data/routes/webhooks.d.ts +102 -102
  122. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  123. package/dist/apps/data/routes/webhooks.js +9 -23
  124. package/dist/apps/data/routes/webhooks.js.map +1 -1
  125. package/dist/apps/data/routes/zone-deposits.d.ts +16 -16
  126. package/dist/apps/data/routes/zone-withdrawals.d.ts +31 -31
  127. package/dist/apps/data/routes/zones.d.ts +28 -28
  128. package/dist/apps/management/App.d.ts +18 -102
  129. package/dist/apps/management/App.d.ts.map +1 -1
  130. package/dist/apps/management/App.js +1 -1
  131. package/dist/apps/management/App.js.map +1 -1
  132. package/dist/apps/management/routes/api-keys.d.ts +253 -339
  133. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  134. package/dist/apps/management/routes/api-keys.js +25 -11
  135. package/dist/apps/management/routes/api-keys.js.map +1 -1
  136. package/dist/apps/management/routes/billing.d.ts +92 -92
  137. package/dist/apps/management/routes/billing.d.ts.map +1 -1
  138. package/dist/apps/management/routes/billing.js +5 -13
  139. package/dist/apps/management/routes/billing.js.map +1 -1
  140. package/dist/apps/management/routes/cli.d.ts.map +1 -1
  141. package/dist/apps/management/routes/cli.js +3 -9
  142. package/dist/apps/management/routes/cli.js.map +1 -1
  143. package/dist/apps/management/routes/faucet.d.ts +16 -16
  144. package/dist/apps/management/routes/fee-payer-usage.d.ts +38 -38
  145. package/dist/apps/management/routes/fee-payer-usage.d.ts.map +1 -1
  146. package/dist/apps/management/routes/fee-payer-usage.js +2 -4
  147. package/dist/apps/management/routes/fee-payer-usage.js.map +1 -1
  148. package/dist/apps/management/routes/invitations.d.ts +52 -52
  149. package/dist/apps/management/routes/invitations.d.ts.map +1 -1
  150. package/dist/apps/management/routes/invitations.js +4 -12
  151. package/dist/apps/management/routes/invitations.js.map +1 -1
  152. package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
  153. package/dist/apps/management/routes/invite-links.js +6 -14
  154. package/dist/apps/management/routes/invite-links.js.map +1 -1
  155. package/dist/apps/management/routes/me.d.ts +6 -6
  156. package/dist/apps/management/routes/me.d.ts.map +1 -1
  157. package/dist/apps/management/routes/me.js +3 -7
  158. package/dist/apps/management/routes/me.js.map +1 -1
  159. package/dist/apps/management/routes/members.d.ts +84 -84
  160. package/dist/apps/management/routes/members.d.ts.map +1 -1
  161. package/dist/apps/management/routes/members.js +2 -4
  162. package/dist/apps/management/routes/members.js.map +1 -1
  163. package/dist/apps/management/routes/orgs.d.ts +44 -44
  164. package/dist/apps/management/routes/orgs.d.ts.map +1 -1
  165. package/dist/apps/management/routes/orgs.js +2 -6
  166. package/dist/apps/management/routes/orgs.js.map +1 -1
  167. package/dist/apps/management/routes/projects.d.ts.map +1 -1
  168. package/dist/apps/management/routes/projects.js +2 -6
  169. package/dist/apps/management/routes/projects.js.map +1 -1
  170. package/dist/apps/management/routes/route-usage.d.ts +34 -34
  171. package/dist/apps/management/routes/route-usage.d.ts.map +1 -1
  172. package/dist/apps/management/routes/route-usage.js +2 -4
  173. package/dist/apps/management/routes/route-usage.js.map +1 -1
  174. package/dist/apps/management/routes/routes-access-requests.d.ts +120 -91
  175. package/dist/apps/management/routes/routes-access-requests.d.ts.map +1 -1
  176. package/dist/apps/management/routes/routes-access-requests.js +49 -9
  177. package/dist/apps/management/routes/routes-access-requests.js.map +1 -1
  178. package/dist/apps/management/routes/usage.d.ts.map +1 -1
  179. package/dist/apps/management/routes/usage.js +8 -16
  180. package/dist/apps/management/routes/usage.js.map +1 -1
  181. package/dist/apps/management/routes/verified-token-requests.d.ts +39 -39
  182. package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -1
  183. package/dist/apps/management/routes/verified-token-requests.js +3 -7
  184. package/dist/apps/management/routes/verified-token-requests.js.map +1 -1
  185. package/dist/apps/management/routes/webhooks.d.ts +2089 -2089
  186. package/dist/apps/management/routes/zone-delegations.d.ts +14 -17
  187. package/dist/apps/management/routes/zone-delegations.d.ts.map +1 -1
  188. package/dist/apps/management/routes/zone-delegations.js +1 -6
  189. package/dist/apps/management/routes/zone-delegations.js.map +1 -1
  190. package/dist/apps/mpp/App.d.ts +44 -44
  191. package/dist/apps/mpp/App.js +3 -3
  192. package/dist/apps/mpp/App.js.map +1 -1
  193. package/dist/apps/mpp/internal/Adapter.js +1 -1
  194. package/dist/apps/mpp/internal/Adapter.js.map +1 -1
  195. package/dist/apps/mpp/internal/Error.d.ts.map +1 -1
  196. package/dist/apps/mpp/internal/Error.js +1 -0
  197. package/dist/apps/mpp/internal/Error.js.map +1 -1
  198. package/dist/apps/mpp/internal/Policy.d.ts +5 -1
  199. package/dist/apps/mpp/internal/Policy.d.ts.map +1 -1
  200. package/dist/apps/mpp/internal/Policy.js +35 -13
  201. package/dist/apps/mpp/internal/Policy.js.map +1 -1
  202. package/dist/apps/relay/App.d.ts +2 -2
  203. package/dist/apps/relay/App.d.ts.map +1 -1
  204. package/dist/apps/relay/App.js +25 -16
  205. package/dist/apps/relay/App.js.map +1 -1
  206. package/dist/apps/routes/App.d.ts +311 -311
  207. package/dist/apps/routes/routes/deposit-addresses.d.ts +1099 -1099
  208. package/dist/apps/routes/routes/list.d.ts +74 -74
  209. package/dist/apps/routes/routes/transfers.d.ts +94 -94
  210. package/dist/cli/ApiKeys.d.ts +19 -26
  211. package/dist/cli/ApiKeys.d.ts.map +1 -1
  212. package/dist/db/Db.d.ts +3 -3
  213. package/dist/db/Db.d.ts.map +1 -1
  214. package/dist/db/Db.js +27 -0
  215. package/dist/db/Db.js.map +1 -1
  216. package/dist/db/Schema.d.ts +28 -62
  217. package/dist/db/Schema.d.ts.map +1 -1
  218. package/dist/db/Schema.js +18 -18
  219. package/dist/db/Schema.js.map +1 -1
  220. package/dist/db/tables/rewardRuns.d.ts +21 -0
  221. package/dist/db/tables/rewardRuns.d.ts.map +1 -1
  222. package/dist/db/tables/rewardRuns.js +19 -0
  223. package/dist/db/tables/rewardRuns.js.map +1 -1
  224. package/dist/db/tables/routesAccessRequests.d.ts +13 -0
  225. package/dist/db/tables/routesAccessRequests.d.ts.map +1 -1
  226. package/dist/db/tables/routesAccessRequests.js +12 -0
  227. package/dist/db/tables/routesAccessRequests.js.map +1 -1
  228. package/dist/db/tables/zoneDelegationRequests.d.ts +165 -0
  229. package/dist/db/tables/zoneDelegationRequests.d.ts.map +1 -0
  230. package/dist/db/tables/{zoneKeyRequests.js → zoneDelegationRequests.js} +43 -42
  231. package/dist/db/tables/zoneDelegationRequests.js.map +1 -0
  232. package/dist/db/tables/zoneDelegations.d.ts +11 -5
  233. package/dist/db/tables/zoneDelegations.d.ts.map +1 -1
  234. package/dist/db/tables/zoneDelegations.js +21 -3
  235. package/dist/db/tables/zoneDelegations.js.map +1 -1
  236. package/dist/handlers/internal/accounts.d.ts +7 -0
  237. package/dist/handlers/internal/accounts.d.ts.map +1 -0
  238. package/dist/handlers/internal/{multisig.js → accounts.js} +10 -10
  239. package/dist/handlers/internal/{multisig.js.map → accounts.js.map} +1 -1
  240. package/dist/handlers/internal/sponsorship.js +17 -17
  241. package/dist/handlers/internal/sponsorship.js.map +1 -1
  242. package/dist/handlers/relay.d.ts +6 -6
  243. package/dist/handlers/relay.d.ts.map +1 -1
  244. package/dist/handlers/relay.js +15 -15
  245. package/dist/handlers/relay.js.map +1 -1
  246. package/dist/internal/Auth.d.ts.map +1 -1
  247. package/dist/internal/Auth.js +4 -8
  248. package/dist/internal/Auth.js.map +1 -1
  249. package/dist/internal/EarnRates.d.ts.map +1 -1
  250. package/dist/internal/EarnRates.js +1 -3
  251. package/dist/internal/EarnRates.js.map +1 -1
  252. package/dist/internal/EarnVaults.js +2 -2
  253. package/dist/internal/Log.d.ts +2 -4
  254. package/dist/internal/Log.d.ts.map +1 -1
  255. package/dist/internal/Log.js +0 -3
  256. package/dist/internal/Log.js.map +1 -1
  257. package/dist/internal/Response.d.ts +1 -1
  258. package/dist/internal/Response.d.ts.map +1 -1
  259. package/dist/internal/Response.js +0 -3
  260. package/dist/internal/Response.js.map +1 -1
  261. package/dist/internal/Schema.d.ts +11 -0
  262. package/dist/internal/Schema.d.ts.map +1 -1
  263. package/dist/internal/Schema.js +24 -1
  264. package/dist/internal/Schema.js.map +1 -1
  265. package/dist/internal/ZoneAccess.d.ts +70 -35
  266. package/dist/internal/ZoneAccess.d.ts.map +1 -1
  267. package/dist/internal/ZoneAccess.js +312 -131
  268. package/dist/internal/ZoneAccess.js.map +1 -1
  269. package/dist/internal/ZoneDelegation.d.ts +7 -19
  270. package/dist/internal/ZoneDelegation.d.ts.map +1 -1
  271. package/dist/internal/ZoneDelegation.js +8 -29
  272. package/dist/internal/ZoneDelegation.js.map +1 -1
  273. package/dist/internal/{ZoneKeyApproval.d.ts → ZoneDelegationApproval.d.ts} +3 -5
  274. package/dist/internal/ZoneDelegationApproval.d.ts.map +1 -0
  275. package/dist/internal/ZoneDelegationApproval.js +16 -0
  276. package/dist/internal/ZoneDelegationApproval.js.map +1 -0
  277. package/dist/internal/ZoneKeyRewrite.d.ts +98 -0
  278. package/dist/internal/ZoneKeyRewrite.d.ts.map +1 -0
  279. package/dist/internal/ZoneKeyRewrite.js +151 -0
  280. package/dist/internal/ZoneKeyRewrite.js.map +1 -0
  281. package/dist/internal/rewards/Campaigns.d.ts +2 -0
  282. package/dist/internal/rewards/Campaigns.d.ts.map +1 -1
  283. package/dist/internal/rewards/Campaigns.js.map +1 -1
  284. package/dist/internal/rewards/Distributions.d.ts.map +1 -1
  285. package/dist/internal/rewards/Distributions.js +2 -6
  286. package/dist/internal/rewards/Distributions.js.map +1 -1
  287. package/dist/internal/rewards/Monitor.d.ts +4 -1
  288. package/dist/internal/rewards/Monitor.d.ts.map +1 -1
  289. package/dist/internal/rewards/Monitor.js +19 -2
  290. package/dist/internal/rewards/Monitor.js.map +1 -1
  291. package/dist/internal/rewards/Runner.d.ts.map +1 -1
  292. package/dist/internal/rewards/Runner.js +9 -1
  293. package/dist/internal/rewards/Runner.js.map +1 -1
  294. package/dist/internal/routes/Action.d.ts.map +1 -1
  295. package/dist/internal/routes/Action.js +2 -3
  296. package/dist/internal/routes/Action.js.map +1 -1
  297. package/dist/internal/routes/Deposit.d.ts.map +1 -1
  298. package/dist/internal/routes/Deposit.js +2 -6
  299. package/dist/internal/routes/Deposit.js.map +1 -1
  300. package/dist/internal/routes/DepositAddress.d.ts.map +1 -1
  301. package/dist/internal/routes/DepositAddress.js +4 -10
  302. package/dist/internal/routes/DepositAddress.js.map +1 -1
  303. package/dist/internal/routes/Policy.d.ts.map +1 -1
  304. package/dist/internal/routes/Policy.js +2 -1
  305. package/dist/internal/routes/Policy.js.map +1 -1
  306. package/dist/internal/routes/Provider.js +8 -8
  307. package/dist/internal/routes/Provider.js.map +1 -1
  308. package/dist/internal/routes/Reconciliation.d.ts.map +1 -1
  309. package/dist/internal/routes/Reconciliation.js +3 -2
  310. package/dist/internal/routes/Reconciliation.js.map +1 -1
  311. package/dist/internal/routes/Transfer.d.ts.map +1 -1
  312. package/dist/internal/routes/Transfer.js +4 -12
  313. package/dist/internal/routes/Transfer.js.map +1 -1
  314. package/dist/internal/routes/UniversalDepositAddress.d.ts.map +1 -1
  315. package/dist/internal/routes/UniversalDepositAddress.js +3 -6
  316. package/dist/internal/routes/UniversalDepositAddress.js.map +1 -1
  317. package/dist/internal/routes/providers/SquidOrder.d.ts.map +1 -1
  318. package/dist/internal/routes/providers/SquidOrder.js +3 -2
  319. package/dist/internal/routes/providers/SquidOrder.js.map +1 -1
  320. package/dist/internal/routes/providers/relay.d.ts.map +1 -1
  321. package/dist/internal/routes/providers/relay.js +4 -3
  322. package/dist/internal/routes/providers/relay.js.map +1 -1
  323. package/package.json +20 -5
  324. package/src/ApiKey.ts +2 -2
  325. package/src/ApiKeys.test.ts +284 -51
  326. package/src/ApiKeys.ts +297 -64
  327. package/src/App.ts +4 -5
  328. package/src/Client.test-d.ts +33 -126
  329. package/src/Scope.test.ts +26 -45
  330. package/src/Scope.ts +24 -37
  331. package/src/admin/App.test.ts +51 -4
  332. package/src/admin/App.ts +25 -33
  333. package/src/admin/Mcp.test.ts +2 -1
  334. package/src/admin/apps/api-keys.test.ts +90 -20
  335. package/src/admin/apps/api-keys.ts +5 -8
  336. package/src/admin/apps/audit-logs.ts +1 -1
  337. package/src/admin/apps/earn-vaults.test.ts +19 -19
  338. package/src/admin/apps/earn-vaults.ts +11 -5
  339. package/src/admin/apps/routes-support.test.ts +254 -3
  340. package/src/admin/apps/routes-support.ts +1 -44
  341. package/src/admin/apps/scopes.test.ts +3 -3
  342. package/src/admin/apps/zone-delegation-requests.test.ts +246 -0
  343. package/src/admin/apps/zone-delegation-requests.ts +240 -0
  344. package/src/admin/apps/zone-delegations.test.ts +218 -58
  345. package/src/admin/apps/zone-delegations.ts +146 -46
  346. package/src/admin/index.ts +1 -1
  347. package/src/admin/ui/src/components/ApiKeyForm.tsx +4 -3
  348. package/src/admin/ui/src/lib/config.ts +3 -3
  349. package/src/admin/ui/src/lib/query-client.ts +1 -1
  350. package/src/admin/ui/src/lib/review-queues.ts +10 -10
  351. package/src/admin/ui/src/lib/routes-setup.test.ts +1 -2
  352. package/src/admin/ui/src/lib/routes-setup.ts +3 -5
  353. package/src/admin/ui/src/lib/scopes.test.ts +3 -6
  354. package/src/admin/ui/src/lib/scopes.ts +2 -9
  355. package/src/admin/ui/src/lib/{zone-key-requests.ts → zone-delegation-requests.ts} +27 -31
  356. package/src/admin/ui/src/routeTree.gen.ts +17 -17
  357. package/src/admin/ui/src/routes/__root.tsx +1 -1
  358. package/src/admin/ui/src/routes/api-keys.tsx +2 -15
  359. package/src/admin/ui/src/routes/audit-logs.tsx +2 -2
  360. package/src/admin/ui/src/routes/index.tsx +11 -11
  361. package/src/admin/ui/src/routes/organizations.tsx +3 -1
  362. package/src/admin/ui/src/routes/organizations_.$orgId.tsx +3 -1
  363. package/src/admin/ui/src/routes/organizations_.$orgId_.setup.tsx +1 -3
  364. package/src/admin/ui/src/routes/{zone-key-requests.tsx → zone-delegation-requests.tsx} +228 -172
  365. package/src/admin/ui/vite.config.ts +14 -1
  366. package/src/admin/ui.generated.ts +412 -583
  367. package/src/analytics/tables/requestEvents.test.ts +13 -3
  368. package/src/analytics/tables/requestEvents.ts +4 -3
  369. package/src/apps/data/App.test.ts +72 -35
  370. package/src/apps/data/App.ts +29 -13
  371. package/src/apps/data/ZoneSelection.test.ts +53 -70
  372. package/src/apps/data/ZoneSelection.ts +2 -2
  373. package/src/apps/data/routes/activities.test.ts +140 -6
  374. package/src/apps/data/routes/activities.ts +124 -18
  375. package/src/apps/data/routes/balances.ts +5 -1
  376. package/src/apps/data/routes/blocks.ts +9 -13
  377. package/src/apps/data/routes/earn-transactions.local.test.ts +6 -1
  378. package/src/apps/data/routes/earn-transactions.test.ts +7 -3
  379. package/src/apps/data/routes/earn.bench.ts +0 -14
  380. package/src/apps/data/routes/earn.discovery.test.ts +4 -39
  381. package/src/apps/data/routes/earn.local.test.ts +27 -34
  382. package/src/apps/data/routes/earn.test.ts +172 -490
  383. package/src/apps/data/routes/earn.ts +103 -652
  384. package/src/apps/data/routes/exchanges.ts +20 -30
  385. package/src/apps/data/routes/fee-amm.ts +12 -18
  386. package/src/apps/data/routes/indexer.test.ts +14 -12
  387. package/src/apps/data/routes/receipts.ts +1 -1
  388. package/src/apps/data/routes/rpc.test.ts +23 -18
  389. package/src/apps/data/routes/rpc.ts +21 -21
  390. package/src/apps/data/routes/rpc.zone.test.ts +60 -6
  391. package/src/apps/data/routes/tokenlist.ts +4 -6
  392. package/src/apps/data/routes/tokens.test.ts +20 -0
  393. package/src/apps/data/routes/tokens.ts +10 -5
  394. package/src/apps/data/routes/transactions.ts +3 -3
  395. package/src/apps/data/routes/transfers.ts +4 -6
  396. package/src/apps/data/routes/valuation.test.ts +2 -4
  397. package/src/apps/data/routes/valuation.ts +4 -6
  398. package/src/apps/data/routes/webhooks.test.ts +1 -5
  399. package/src/apps/data/routes/webhooks.ts +30 -44
  400. package/src/apps/data/routes/zones.test.ts +35 -19
  401. package/src/apps/management/App.ts +3 -1
  402. package/src/apps/management/routes/api-keys.test.ts +112 -22
  403. package/src/apps/management/routes/api-keys.ts +30 -15
  404. package/src/apps/management/routes/billing.ts +17 -25
  405. package/src/apps/management/routes/cli.ts +12 -18
  406. package/src/apps/management/routes/fee-payer-usage.ts +5 -4
  407. package/src/apps/management/routes/invitations.ts +16 -21
  408. package/src/apps/management/routes/invite-links.ts +18 -26
  409. package/src/apps/management/routes/me.ts +9 -13
  410. package/src/apps/management/routes/members.ts +5 -7
  411. package/src/apps/management/routes/orgs.ts +8 -12
  412. package/src/apps/management/routes/projects.ts +8 -12
  413. package/src/apps/management/routes/route-usage.ts +5 -4
  414. package/src/apps/management/routes/routes-access-requests.test.ts +64 -2
  415. package/src/apps/management/routes/routes-access-requests.ts +99 -15
  416. package/src/apps/management/routes/scopes.test.ts +3 -3
  417. package/src/apps/management/routes/usage.ts +20 -28
  418. package/src/apps/management/routes/verified-token-requests.ts +9 -13
  419. package/src/apps/management/routes/zone-delegations.test.ts +3 -5
  420. package/src/apps/management/routes/zone-delegations.ts +4 -9
  421. package/src/apps/mpp/App.ts +3 -3
  422. package/src/apps/mpp/Mpp.zone.test.ts +49 -18
  423. package/src/apps/mpp/internal/Adapter.ts +1 -1
  424. package/src/apps/mpp/internal/Error.test.ts +3 -0
  425. package/src/apps/mpp/internal/Error.ts +1 -0
  426. package/src/apps/mpp/internal/Policy.test.ts +9 -8
  427. package/src/apps/mpp/internal/Policy.ts +37 -14
  428. package/src/apps/relay/App.sandbox.test.ts +15 -14
  429. package/src/apps/relay/App.test.ts +120 -61
  430. package/src/apps/relay/App.ts +28 -18
  431. package/src/apps/relay/App.zone-billing.test.ts +9 -4
  432. package/src/apps/routes/App.test.ts +21 -22
  433. package/src/db/Db.ts +34 -3
  434. package/src/db/Schema.ts +26 -26
  435. package/src/db/tables/rewardRuns.ts +39 -0
  436. package/src/db/tables/routesAccessRequests.test.ts +65 -0
  437. package/src/db/tables/routesAccessRequests.ts +25 -0
  438. package/src/db/tables/{zoneKeyRequests.test.ts → zoneDelegationRequests.test.ts} +36 -77
  439. package/src/db/tables/{zoneKeyRequests.ts → zoneDelegationRequests.ts} +69 -52
  440. package/src/db/tables/zoneDelegations.test.ts +5 -6
  441. package/src/db/tables/zoneDelegations.ts +25 -8
  442. package/src/handlers/internal/{multisig.store.test.ts → accounts.store.test.ts} +16 -16
  443. package/src/handlers/internal/{multisig.test.ts → accounts.test.ts} +85 -82
  444. package/src/handlers/internal/{multisig.ts → accounts.ts} +9 -9
  445. package/src/handlers/internal/sponsorship.ts +20 -20
  446. package/src/handlers/relay.test.ts +48 -46
  447. package/src/handlers/relay.ts +26 -26
  448. package/src/internal/Auth.test.ts +6 -2
  449. package/src/internal/Auth.ts +4 -10
  450. package/src/internal/EarnRates.ts +4 -6
  451. package/src/internal/EarnVaults.ts +2 -2
  452. package/src/internal/Log.test-d.ts +1 -0
  453. package/src/internal/Log.test.ts +4 -6
  454. package/src/internal/Log.ts +2 -6
  455. package/src/internal/Response.ts +0 -3
  456. package/src/internal/Schema.test.ts +42 -0
  457. package/src/internal/Schema.ts +36 -1
  458. package/src/internal/ZoneAccess.test.ts +515 -312
  459. package/src/internal/ZoneAccess.ts +410 -158
  460. package/src/internal/ZoneDelegation.test.ts +1 -41
  461. package/src/internal/ZoneDelegation.ts +10 -48
  462. package/src/internal/ZoneDelegationApproval.test.ts +22 -0
  463. package/src/internal/{ZoneKeyApproval.ts → ZoneDelegationApproval.ts} +4 -11
  464. package/src/internal/ZoneKeyRewrite.ts +231 -0
  465. package/src/internal/rewards/Campaigns.ts +2 -0
  466. package/src/internal/rewards/Distributions.ts +8 -12
  467. package/src/internal/rewards/Monitor.test.ts +64 -0
  468. package/src/internal/rewards/Monitor.ts +27 -4
  469. package/src/internal/rewards/Runner.ts +9 -1
  470. package/src/internal/routes/Action.ts +5 -6
  471. package/src/internal/routes/Deposit.ts +8 -12
  472. package/src/internal/routes/DepositAddress.ts +13 -19
  473. package/src/internal/routes/Policy.ts +2 -1
  474. package/src/internal/routes/Provider.ts +8 -8
  475. package/src/internal/routes/Reconciliation.ts +3 -2
  476. package/src/internal/routes/Transfer.ts +16 -24
  477. package/src/internal/routes/UniversalDepositAddress.ts +9 -12
  478. package/src/internal/routes/providers/SquidOrder.ts +3 -2
  479. package/src/internal/routes/providers/relay.ts +4 -3
  480. package/dist/admin/apps/zone-key-requests.d.ts +0 -406
  481. package/dist/admin/apps/zone-key-requests.d.ts.map +0 -1
  482. package/dist/admin/apps/zone-key-requests.js +0 -183
  483. package/dist/admin/apps/zone-key-requests.js.map +0 -1
  484. package/dist/db/tables/zoneKeyRequests.d.ts +0 -289
  485. package/dist/db/tables/zoneKeyRequests.d.ts.map +0 -1
  486. package/dist/db/tables/zoneKeyRequests.js.map +0 -1
  487. package/dist/handlers/internal/multisig.d.ts +0 -7
  488. package/dist/handlers/internal/multisig.d.ts.map +0 -1
  489. package/dist/internal/ZoneKeyApproval.d.ts.map +0 -1
  490. package/dist/internal/ZoneKeyApproval.js +0 -21
  491. package/dist/internal/ZoneKeyApproval.js.map +0 -1
  492. package/src/admin/apps/zone-key-requests.test.ts +0 -272
  493. package/src/admin/apps/zone-key-requests.ts +0 -231
  494. package/src/internal/ZoneKeyApproval.test.ts +0 -46
@@ -1,14 +1,15 @@
1
1
  import { sql } from 'kysely'
2
2
 
3
+ import * as ApiKey from './ApiKey.js'
3
4
  import * as ApiKeys from './ApiKeys.js'
4
5
  import * as ApiKeyAdmissions from './db/tables/apiKeyAdmissions.js'
5
6
  import * as ApiKeyOwnerTombstones from './db/tables/apiKeyOwnerTombstones.js'
6
7
  import * as ApiKeyRevocations from './db/tables/apiKeyRevocations.js'
7
8
  import * as Organizations from './db/tables/organizations.js'
9
+ import * as ZoneDelegations from './db/tables/zoneDelegations.js'
8
10
  import * as Projects from './db/tables/projects.js'
9
11
  import * as RoutesPolicies from './db/tables/routesPolicies.js'
10
12
  import * as Store from './internal/Store.js'
11
- import * as ZoneKeyApproval from './internal/ZoneKeyApproval.js'
12
13
  import * as Scope from './Scope.js'
13
14
  import * as TestApp from '../test/App.js'
14
15
 
@@ -188,14 +189,25 @@ describe('mint', () => {
188
189
  ).rejects.toThrowError(/invalid API key input/)
189
190
  })
190
191
 
191
- test('mints per-zone read and write scopes', async () => {
192
+ test('refuses fresh per-zone write scopes while stored ones still resolve and rotate', async () => {
192
193
  const state = Store.memory()
193
- const { token } = await ApiKeys.mint(state, {
194
- orgId: 'org_a',
195
- scopes: ['zone:421700001:read', 'zone:421700001:write'],
196
- })
197
- const resolved = await ApiKeys.resolve(state, token)
198
- expect(resolved?.scopes).toEqual(['zone:421700001:read', 'zone:421700001:write'])
194
+ await expect(
195
+ ApiKeys.mint(state, { orgId: 'org_a', scopes: ['zone:read', 'zone:421700001:write'] }),
196
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
197
+ `[ApiKeys.InvalidInputError: invalid API key input: per-Zone scopes are retired; grant zone:read or zone:write with zoneTags]`,
198
+ )
199
+ const token = 'secret_legacy_writer'
200
+ await state.put(
201
+ ApiKey.keyFor(token),
202
+ JSON.stringify({ createdAt: '2026-01-01T00:00:00.000Z', environment: 'production', id: 'key_writer', orgId: 'org_a', scopes: ['zone:421700001:write'], tokenLast4: 'iter' }), // prettier-ignore
203
+ )
204
+ await state.put('apikey_id:key_writer', ApiKey.keyFor(token))
205
+ expect((await ApiKeys.resolve(state, token))?.scopes).toEqual(['zone:421700001:write'])
206
+ // A stored scope may be republished unchanged, never granted anew.
207
+ expect((await ApiKeys.update(state, 'key_writer', { scopes: ['zone:421700001:write'] }))?.scopes).toEqual(['zone:421700001:write']) // prettier-ignore
208
+ await expect(
209
+ ApiKeys.update(state, 'key_writer', { scopes: ['zone:421700001:write', 'zone:421700002:write'] }), // prettier-ignore
210
+ ).rejects.toBeInstanceOf(ApiKeys.InvalidInputError)
199
211
  })
200
212
 
201
213
  test('rejects malformed zone scopes', async () => {
@@ -999,6 +1011,27 @@ describe('mintBounded', () => {
999
1011
  })
1000
1012
 
1001
1013
  describe('mint', () => {
1014
+ test('refuses retired per-Zone read scopes on fresh grants while stored ones republish', async () => {
1015
+ const state = Store.memory()
1016
+ await expect(
1017
+ ApiKeys.mint(state, { scopes: ['data:read', 'zone:421700001:read'] }),
1018
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
1019
+ `[ApiKeys.InvalidInputError: invalid API key input: per-Zone scopes are retired; grant zone:read or zone:write with zoneTags]`,
1020
+ )
1021
+ const token = 'secret_legacy_reader'
1022
+ await state.put(
1023
+ ApiKey.keyFor(token),
1024
+ JSON.stringify({ createdAt: '2026-01-01T00:00:00.000Z', environment: 'production', id: 'key_legacy', orgId: 'org_legacy', scopes: ['zone:421700001:read', 'zone:read'], tokenLast4: 'ader', zoneTags: [421700001] }), // prettier-ignore
1025
+ )
1026
+ await ApiKeys.backfill(state)
1027
+ // Scopes the record already holds republish; a new retired scope is refused.
1028
+ expect((await ApiKeys.update(state, 'key_legacy', { name: 'Renamed', scopes: ['zone:421700001:read', 'zone:read'] }))?.scopes).toEqual(['zone:421700001:read', 'zone:read']) // prettier-ignore
1029
+ await expect(ApiKeys.update(state, 'key_legacy', { scopes: ['zone:421700001:read', 'zone:421700002:read', 'zone:read'] })).rejects.toThrow(ApiKeys.InvalidInputError) // prettier-ignore
1030
+ // The plain update republishes the record; the bounded reassignment drops the inert scope (admin api-keys test).
1031
+ expect((await ApiKeys.update(state, 'key_legacy', { orgId: 'org_next' }))?.scopes).toEqual(['zone:421700001:read', 'zone:read']) // prettier-ignore
1032
+ expect(await ApiKeys.resolve(state, token)).toMatchObject({ orgId: 'org_next', zoneTags: [421700001] }) // prettier-ignore
1033
+ })
1034
+
1002
1035
  test('stores deduplicated Zone tags', async () => {
1003
1036
  const state = Store.memory()
1004
1037
  const { record, token } = await ApiKeys.mint(state, {
@@ -1031,17 +1064,37 @@ describe('rotateBounded', () => {
1031
1064
  expect(record).toMatchObject({ allowedIps: ['203.0.113.0/24'], zoneTags: [421700001] })
1032
1065
  })
1033
1066
 
1034
- test('replaces a Zone read key with the same scopes without approval', async () => {
1067
+ test('narrows a broadcasting copy to the tags still delegated instead of refusing the rotation', async () => {
1068
+ const db = TestApp.database()
1069
+ const state = Store.memory()
1070
+ const org = await Organizations.create(db, { name: 'Zone revoke' })
1071
+ await TestApp.delegateZone(db, { chainIds: [421700001, 421700002], orgId: org.id })
1072
+ const { record: source } = await ApiKeys.mintBounded(db, state, {
1073
+ orgId: org.id,
1074
+ scopes: ['zone:write'],
1075
+ zoneTags: [421700001, 421700002],
1076
+ })
1077
+ const [revoked] = (await ZoneDelegations.listActive(db, { environment: 'production', orgId: org.id })).filter((row) => row.chainId === 421700002) // prettier-ignore
1078
+ await ZoneDelegations.revoke(db, revoked!.id)
1079
+ // One revoked Zone drops out of the copy; the other keeps broadcasting.
1080
+ const { record } = await ApiKeys.rotateBounded(db, state, { orgId: org.id, scopes: source.scopes }, { policySourceApiKeyId: source.id }) // prettier-ignore
1081
+ expect(record).toMatchObject({ scopes: ['zone:write'], zoneTags: [421700001] })
1082
+ // With every delegation gone the copy carries no tags, so it broadcasts nowhere until retagged.
1083
+ for (const row of await ZoneDelegations.listActive(db, { environment: 'production', orgId: org.id })) await ZoneDelegations.revoke(db, row.id) // prettier-ignore
1084
+ const { record: bare } = await ApiKeys.rotateBounded(db, state, { orgId: org.id, scopes: record.scopes }, { policySourceApiKeyId: record.id }) // prettier-ignore
1085
+ expect(bare.scopes).toEqual(['zone:write'])
1086
+ expect(bare).not.toHaveProperty('zoneTags')
1087
+ })
1088
+
1089
+ test('replaces a Zone read key with the same scopes', async () => {
1035
1090
  const db = TestApp.database()
1036
1091
  const state = Store.memory()
1037
1092
  const org = await Organizations.create(db, { name: 'Zone rotation' })
1038
1093
  const { record: source } = await ApiKeys.mint(state, {
1039
1094
  orgId: org.id,
1040
- scopes: ['zone:421700001:read', 'data:read'],
1095
+ scopes: ['zone:read', 'data:read'],
1096
+ zoneTags: [421700001],
1041
1097
  })
1042
- await expect(
1043
- ApiKeys.mintBounded(db, state, { orgId: org.id, scopes: source.scopes }),
1044
- ).rejects.toBeInstanceOf(ZoneKeyApproval.RequiredError)
1045
1098
 
1046
1099
  const { record } = await ApiKeys.rotateBounded(
1047
1100
  db,
@@ -1062,13 +1115,14 @@ describe('rotateBounded', () => {
1062
1115
  const org = await Organizations.create(db, { name: 'Zone widening' })
1063
1116
  const { record: source } = await ApiKeys.mint(state, {
1064
1117
  orgId: org.id,
1065
- scopes: ['zone:421700001:read'],
1118
+ scopes: ['zone:read'],
1119
+ zoneTags: [421700001],
1066
1120
  })
1067
1121
  await expect(
1068
1122
  ApiKeys.rotateBounded(
1069
1123
  db,
1070
1124
  state,
1071
- { orgId: org.id, scopes: ['zone:421700001:read', 'zone:421700002:read'] },
1125
+ { orgId: org.id, scopes: ['zone:read', 'data:read'] },
1072
1126
  { policySourceApiKeyId: source.id },
1073
1127
  ),
1074
1128
  ).rejects.toBeInstanceOf(RoutesPolicies.InvalidScopeError)
@@ -1145,62 +1199,41 @@ describe('updateBounded', () => {
1145
1199
  expect(await ApiKeys.get(state, record.id)).not.toHaveProperty('expiresAt')
1146
1200
  })
1147
1201
 
1148
- test('lets a protected Zone key only move its expiry earlier without approval', async () => {
1202
+ test('lets owners retag and re-bound a Zone read key', async () => {
1149
1203
  const db = TestApp.database()
1150
1204
  const state = Store.memory()
1151
- const org = await Organizations.create(db, { name: 'Zone shortening' })
1205
+ const org = await Organizations.create(db, { name: 'Zone retag' })
1152
1206
  const expiresAt = new Date(Date.now() + 86_400_000).toISOString()
1153
1207
  const { record } = await ApiKeys.mint(state, {
1154
1208
  expiresAt,
1155
1209
  orgId: org.id,
1156
- scopes: ['zone:421700001:read'],
1157
- })
1158
- const gated = [
1159
- { expiresAt: null },
1160
- { expiresAt: new Date(Date.now() + 2 * 86_400_000).toISOString() },
1161
- { zoneTags: [421700001] },
1162
- { zoneTags: null },
1163
- { allowedIps: [] },
1164
- ]
1165
- for (const input of gated)
1166
- await expect(ApiKeys.updateBounded(db, state, record.id, input)).rejects.toBeInstanceOf(
1167
- ZoneKeyApproval.RequiredError,
1168
- )
1210
+ scopes: ['zone:read'],
1211
+ zoneTags: [421700001],
1212
+ })
1169
1213
  const sooner = new Date(Date.now() + 3_600_000).toISOString()
1170
1214
  expect(
1171
- (await ApiKeys.updateBounded(db, state, record.id, { expiresAt: sooner }))?.expiresAt,
1172
- ).toBe(sooner)
1215
+ await ApiKeys.updateBounded(db, state, record.id, { expiresAt: sooner, zoneTags: [421700002] }), // prettier-ignore
1216
+ ).toMatchObject({ expiresAt: sooner, zoneTags: [421700002] })
1217
+ expect((await ApiKeys.updateBounded(db, state, record.id, { zoneTags: null }))?.zoneTags).toBeUndefined() // prettier-ignore
1173
1218
  })
1174
1219
 
1175
- test('gives a self-owned protected Zone key its organization once without approval', async () => {
1220
+ test('gives a self-owned key its organization, keeping its token and tags', async () => {
1176
1221
  const db = TestApp.database()
1177
1222
  const state = Store.memory()
1178
1223
  const org = await Organizations.create(db, { name: 'Zone adopter' })
1179
- const other = await Organizations.create(db, { name: 'Zone other' })
1180
1224
  // Legacy keys minted before organizations existed own themselves.
1181
1225
  const { record, token } = await ApiKeys.mint(state, {
1182
1226
  name: 'legacy',
1183
- scopes: ['zone:421700001:read', 'data:read'],
1227
+ scopes: ['zone:read', 'data:read'],
1228
+ zoneTags: [421700001],
1184
1229
  })
1185
1230
  expect(record.orgId).toBe(record.id)
1186
- for (const input of [
1187
- { orgId: org.id, zoneTags: [421700001] },
1188
- { orgId: org.id, name: 'x' },
1189
- ])
1190
- await expect(ApiKeys.updateBounded(db, state, record.id, input)).rejects.toBeInstanceOf(
1191
- ZoneKeyApproval.RequiredError,
1192
- )
1193
1231
 
1194
1232
  const adopted = await ApiKeys.updateBounded(db, state, record.id, { orgId: org.id })
1195
- expect(adopted).toMatchObject({ id: record.id, orgId: org.id, scopes: record.scopes })
1233
+ expect(adopted).toMatchObject({ id: record.id, orgId: org.id, scopes: record.scopes, zoneTags: [421700001] }) // prettier-ignore
1196
1234
  expect(await ApiKeys.resolve(state, token)).toMatchObject({ id: record.id, orgId: org.id })
1197
1235
  expect((await ApiKeys.listByOrg(state, org.id)).map(({ id }) => id)).toEqual([record.id])
1198
1236
  expect(await ApiKeyAdmissions.get(db, record.id)).toMatchObject({ orgId: org.id })
1199
-
1200
- // Once owned, moving it again is a reviewed change like any other edit.
1201
- await expect(
1202
- ApiKeys.updateBounded(db, state, record.id, { orgId: other.id }),
1203
- ).rejects.toBeInstanceOf(ZoneKeyApproval.RequiredError)
1204
1237
  })
1205
1238
 
1206
1239
  test('rejects null for fields that cannot be cleared and sub-minute expiries', async () => {
@@ -2383,13 +2416,13 @@ describe('update', () => {
2383
2416
  })
2384
2417
 
2385
2418
  const updated = await ApiKeys.update(state, record.id, {
2386
- scopes: ['management:read', 'zone:421700001:write'],
2419
+ scopes: ['management:read', 'webhooks:write'],
2387
2420
  })
2388
2421
 
2389
- expect(updated?.scopes).toEqual(['management:read', 'zone:421700001:write'])
2422
+ expect(updated?.scopes).toEqual(['management:read', 'webhooks:write'])
2390
2423
  expect((await ApiKeys.resolve(state, token))?.scopes).toEqual([
2391
2424
  'management:read',
2392
- 'zone:421700001:write',
2425
+ 'webhooks:write',
2393
2426
  ])
2394
2427
  })
2395
2428
 
@@ -2633,3 +2666,203 @@ async function installDeferredAdmissionFailure(
2633
2666
  `)
2634
2667
  .execute(db.kysely)
2635
2668
  }
2669
+
2670
+ describe('planZoneReadRewrite', () => {
2671
+ test('adds zone:read and merged, alias-resolved tags beside per-Zone read scopes; wildcard, mixed, and invalid keys are listed', async () => {
2672
+ const state = Store.memory()
2673
+ const stored = async (id: string, extra: Record<string, unknown>) => {
2674
+ const token = `secret_${id}`
2675
+ await state.put(
2676
+ ApiKey.keyFor(token),
2677
+ JSON.stringify({ createdAt: '2026-01-01T00:00:00.000Z', environment: 'production', id, orgId: 'org_rewrite', tokenLast4: token.slice(-4), ...extra }), // prettier-ignore
2678
+ )
2679
+ return token
2680
+ }
2681
+ const legacy = await stored('key_legacy', { scopes: ['data:read', 'zone:1424310001:read', 'zone:421700001:read'] }) // prettier-ignore
2682
+ // Writes to the same Zone as the reads share its tag.
2683
+ await stored('key_both', { scopes: ['zone:421700001:read', 'zone:421700001:write'] })
2684
+ // Tags already naming a scope's Zone merge without duplicates.
2685
+ await stored('key_tagged', { scopes: ['zone:421700002:read', 'zone:read'], zoneTags: [421700001] }) // prettier-ignore
2686
+ // Already in both formats: planned so its delegation is checked, never written.
2687
+ await stored('key_done', {
2688
+ scopes: ['zone:421700001:read', 'zone:read'],
2689
+ zoneTags: [421700001],
2690
+ })
2691
+ await stored('key_wild', { scopes: ['*', 'zone:421700001:read'] })
2692
+ await stored('key_plain', { scopes: ['data:read'] })
2693
+ // Writes to another Zone than the reads cannot share one tag list.
2694
+ await stored('key_mixed', { scopes: ['zone:421700001:read', 'zone:421700002:write'] })
2695
+ // Fifty tags plus one more Zone exceeds the tag limit, so the record is reported, not rewritten.
2696
+ await stored('key_full', { scopes: ['zone:read', 'zone:421700001:read'], zoneTags: Array.from({ length: 50 }, (_, index) => 421700100 + index) }) // prettier-ignore
2697
+ await stored('key_revoked', { scopes: ['zone:421700003:read'] })
2698
+ await state.put('apikey_revoked:key_revoked', '1')
2699
+ // KV refuses a TTL under a minute; the record lapses on its own.
2700
+ await stored('key_soon', { expiresAt: new Date(Date.now() + 30_000).toISOString(), scopes: ['zone:421700003:read'] }) // prettier-ignore
2701
+ await stored('key_expired', { expiresAt: '2000-01-01T00:00:00.000Z', scopes: ['zone:421700003:read'] }) // prettier-ignore
2702
+
2703
+ const plan = await ApiKeys.planZoneReadRewrite(state)
2704
+ expect({
2705
+ ...plan,
2706
+ changes: plan.changes.map(({ chainIds, complete, record, rewritten }) => ({ chainIds, complete, id: record.id, scopes: rewritten.scopes, zoneTags: rewritten.zoneTags })), // prettier-ignore
2707
+ expiring: plan.expiring.map(({ id }) => id),
2708
+ invalid: plan.invalid.map(({ id }) => id),
2709
+ mixed: plan.mixed.map(({ id }) => id),
2710
+ wildcard: plan.wildcard.map(({ id }) => id),
2711
+ }).toMatchInlineSnapshot(`
2712
+ {
2713
+ "changes": [
2714
+ {
2715
+ "chainIds": [
2716
+ 421700001,
2717
+ ],
2718
+ "complete": false,
2719
+ "id": "key_both",
2720
+ "scopes": [
2721
+ "zone:421700001:read",
2722
+ "zone:421700001:write",
2723
+ "zone:read",
2724
+ "zone:write",
2725
+ ],
2726
+ "zoneTags": [
2727
+ 421700001,
2728
+ ],
2729
+ },
2730
+ {
2731
+ "chainIds": [
2732
+ 421700001,
2733
+ ],
2734
+ "complete": true,
2735
+ "id": "key_done",
2736
+ "scopes": [
2737
+ "zone:421700001:read",
2738
+ "zone:read",
2739
+ ],
2740
+ "zoneTags": [
2741
+ 421700001,
2742
+ ],
2743
+ },
2744
+ {
2745
+ "chainIds": [
2746
+ 421700001,
2747
+ 1424310003,
2748
+ ],
2749
+ "complete": false,
2750
+ "id": "key_legacy",
2751
+ "scopes": [
2752
+ "data:read",
2753
+ "zone:1424310001:read",
2754
+ "zone:421700001:read",
2755
+ "zone:read",
2756
+ ],
2757
+ "zoneTags": [
2758
+ 421700001,
2759
+ 1424310003,
2760
+ ],
2761
+ },
2762
+ {
2763
+ "chainIds": [
2764
+ 421700002,
2765
+ ],
2766
+ "complete": false,
2767
+ "id": "key_tagged",
2768
+ "scopes": [
2769
+ "zone:421700002:read",
2770
+ "zone:read",
2771
+ ],
2772
+ "zoneTags": [
2773
+ 421700001,
2774
+ 421700002,
2775
+ ],
2776
+ },
2777
+ ],
2778
+ "expiring": [
2779
+ "key_soon",
2780
+ ],
2781
+ "invalid": [
2782
+ "key_full",
2783
+ ],
2784
+ "keys": 9,
2785
+ "mixed": [
2786
+ "key_mixed",
2787
+ ],
2788
+ "tagless": [],
2789
+ "unreadableRecords": 1,
2790
+ "wildcard": [
2791
+ "key_wild",
2792
+ ],
2793
+ }
2794
+ `)
2795
+
2796
+ // key_tagged already holds zone:read; only its tag list grows.
2797
+ expect(await ApiKeys.applyZoneReadRewrite(state, plan.changes)).toBe(3)
2798
+ expect(await ApiKeys.resolve(state, legacy)).toMatchObject({
2799
+ scopes: ['data:read', 'zone:1424310001:read', 'zone:421700001:read', 'zone:read'],
2800
+ zoneTags: [421700001, 1424310003],
2801
+ })
2802
+ expect(await ApiKeys.resolve(state, 'secret_key_both')).toMatchObject({ scopes: ['zone:421700001:read', 'zone:421700001:write', 'zone:read', 'zone:write'], zoneTags: [421700001] }) // prettier-ignore
2803
+ expect(await ApiKeys.resolve(state, 'secret_key_tagged')).toMatchObject({ scopes: ['zone:421700002:read', 'zone:read'], zoneTags: [421700001, 421700002] }) // prettier-ignore
2804
+ // A rerun plans only complete records, for their delegations, and writes nothing.
2805
+ const again = await ApiKeys.planZoneReadRewrite(state)
2806
+ expect(again.changes.map(({ complete, record }) => [record.id, complete]).sort()).toEqual([['key_both', true], ['key_done', true], ['key_legacy', true], ['key_tagged', true]]) // prettier-ignore
2807
+ expect(await ApiKeys.applyZoneReadRewrite(state, again.changes)).toBe(0)
2808
+ })
2809
+
2810
+ test('keeps a key that reads nothing yet holds zone:read out of the write mapping', async () => {
2811
+ const state = Store.memory()
2812
+ // Tags would serve both verbs, so a reader with no Zones yet cannot silently gain reads from its write scope.
2813
+ await state.put(
2814
+ ApiKey.keyFor('secret_reader_writer'),
2815
+ JSON.stringify({ createdAt: '2026-01-01T00:00:00.000Z', environment: 'production', id: 'key_reader_writer', orgId: 'org_rewrite', scopes: ['data:read', 'zone:read', 'zone:421700005:write'], tokenLast4: 'iter' }), // prettier-ignore
2816
+ )
2817
+ // A pure writer has no read scope to widen, so its Zone becomes a tag beside `zone:write`.
2818
+ await state.put(
2819
+ ApiKey.keyFor('secret_writer'),
2820
+ JSON.stringify({ createdAt: '2026-01-01T00:00:00.000Z', environment: 'production', id: 'key_writer', orgId: 'org_rewrite', scopes: ['mpp:write', 'zone:421700005:write'], tokenLast4: 'iter' }), // prettier-ignore
2821
+ )
2822
+ // A writer with leftover tags for other Zones would broadcast to them once tagged for `zone:write`.
2823
+ await state.put(
2824
+ ApiKey.keyFor('secret_tagged_writer'),
2825
+ JSON.stringify({ createdAt: '2026-01-01T00:00:00.000Z', environment: 'production', id: 'key_tagged_writer', orgId: 'org_rewrite', scopes: ['mpp:write', 'zone:421700005:write'], tokenLast4: 'iter', zoneTags: [421700007] }), // prettier-ignore
2826
+ )
2827
+ // A key already broadcasting through `zone:write` writes exactly its tags, so a retired read Zone must not join them.
2828
+ await state.put(
2829
+ ApiKey.keyFor('secret_generic_writer'),
2830
+ JSON.stringify({ createdAt: '2026-01-01T00:00:00.000Z', environment: 'production', id: 'key_generic_writer', orgId: 'org_rewrite', scopes: ['zone:write', 'zone:421700005:read'], tokenLast4: 'iter', zoneTags: [421700007] }), // prettier-ignore
2831
+ )
2832
+ const plan = await ApiKeys.planZoneReadRewrite(state)
2833
+ expect(plan.mixed.map(({ id }) => id).sort()).toEqual([
2834
+ 'key_generic_writer',
2835
+ 'key_reader_writer',
2836
+ 'key_tagged_writer',
2837
+ ])
2838
+ expect(plan.changes.map(({ chainIds, record, writeChainIds }) => [record.id, chainIds, writeChainIds])).toEqual([['key_writer', [], [421700005]]]) // prettier-ignore
2839
+ expect(await ApiKeys.applyZoneReadRewrite(state, plan.changes)).toBe(1)
2840
+ expect(await ApiKeys.resolve(state, 'secret_writer')).toMatchObject({ scopes: ['mpp:write', 'zone:421700005:write', 'zone:write'], zoneTags: [421700005] }) // prettier-ignore
2841
+ })
2842
+
2843
+ test('earns no tag for Zones the deployment does not serve, and drops the tags it finds', async () => {
2844
+ const state = Store.memory()
2845
+ await state.put(
2846
+ ApiKey.keyFor('secret_stale'),
2847
+ JSON.stringify({ createdAt: '2026-01-01T00:00:00.000Z', environment: 'production', id: 'key_stale', orgId: 'org_rewrite', scopes: ['zone:421700001:read', 'zone:421700009:read'], tokenLast4: 'tale', zoneTags: [421700009] }), // prettier-ignore
2848
+ )
2849
+ // A stale tag beside matching read and write scopes is dropped before the verbs are compared, so the key is not mixed.
2850
+ // Unserved Zones are dropped from tags and scopes alike before the verbs are compared, so neither key is mixed.
2851
+ await state.put(
2852
+ ApiKey.keyFor('secret_both_unserved'),
2853
+ JSON.stringify({ createdAt: '2026-01-01T00:00:00.000Z', environment: 'production', id: 'key_both_unserved', orgId: 'org_rewrite', scopes: ['zone:421700001:read', 'zone:421700001:write', 'zone:421700009:write'], tokenLast4: 'tale' }), // prettier-ignore
2854
+ )
2855
+ await state.put(
2856
+ ApiKey.keyFor('secret_both_stale'),
2857
+ JSON.stringify({ createdAt: '2026-01-01T00:00:00.000Z', environment: 'production', id: 'key_both_stale', orgId: 'org_rewrite', scopes: ['zone:421700001:read', 'zone:421700001:write'], tokenLast4: 'tale', zoneTags: [421700009] }), // prettier-ignore
2858
+ )
2859
+ const zones = new Set([421700001])
2860
+ const plan = await ApiKeys.planZoneReadRewrite(state, { zones })
2861
+ expect(plan.mixed).toEqual([])
2862
+ expect(plan.changes.map(({ chainIds, record, unconfigured }) => [record.id, chainIds, unconfigured] as const).sort(([a], [b]) => a.localeCompare(b))).toEqual([['key_both_stale', [421700001], []], ['key_both_unserved', [421700001], [421700009]], ['key_stale', [421700001], [421700009]]]) // prettier-ignore
2863
+ expect(await ApiKeys.applyZoneReadRewrite(state, plan.changes, { zones })).toBe(3)
2864
+ expect(await ApiKeys.resolve(state, 'secret_both_unserved')).toMatchObject({ scopes: ['zone:421700001:read', 'zone:421700001:write', 'zone:421700009:write', 'zone:read', 'zone:write'], zoneTags: [421700001] }) // prettier-ignore
2865
+ expect(await ApiKeys.resolve(state, 'secret_stale')).toMatchObject({ scopes: ['zone:421700001:read', 'zone:421700009:read', 'zone:read'], zoneTags: [421700001] }) // prettier-ignore
2866
+ expect(await ApiKeys.resolve(state, 'secret_both_stale')).toMatchObject({ scopes: ['zone:421700001:read', 'zone:421700001:write', 'zone:read', 'zone:write'], zoneTags: [421700001] }) // prettier-ignore
2867
+ })
2868
+ })