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
@@ -0,0 +1,240 @@
1
+ import { type Context, Hono } from 'hono'
2
+ import * as z from 'zod/mini'
3
+
4
+ import * as Db from '../../db/Db.js'
5
+ import * as Organizations from '../../db/tables/organizations.js'
6
+ import * as ZoneDelegationRequests from '../../db/tables/zoneDelegationRequests.js'
7
+ import * as ZoneDelegations from '../../db/tables/zoneDelegations.js'
8
+ import * as Cursor from '../../internal/Cursor.js'
9
+ import * as OpenApi from '../../internal/OpenApi.js'
10
+ import * as Response from '../../internal/Response.js'
11
+ import * as ZoneDelegationApproval from '../../internal/ZoneDelegationApproval.js'
12
+ import type * as App from '../App.js'
13
+ import * as Delegations from './zone-delegations.js'
14
+
15
+ /** Request schemas for the Zone delegation approval workflow. */
16
+ namespace schema {
17
+ export const Query = z.object({
18
+ cursor: z
19
+ .optional(
20
+ z.string().check(
21
+ z.maxLength(512),
22
+ z.refine((value) => Boolean(Cursor.decode(value, ['timestamp', 'id']))),
23
+ ),
24
+ )
25
+ .check(
26
+ z.describe('Next-page cursor from a prior response.'),
27
+ z.meta({ examples: [Cursor.encode(['2026-01-01T00:00:00.000Z', 'zdr_example'])] }),
28
+ ),
29
+ })
30
+ export const Params = z.object({
31
+ id: z
32
+ .string()
33
+ .check(z.regex(/^zdr_[A-Za-z0-9_-]+$/), z.describe('Zone delegation request identifier.')),
34
+ })
35
+ }
36
+
37
+ /** Mounts authenticated request, review, approval, application, and cancellation operations. */
38
+ export function zoneDelegationRequests() {
39
+ return new Hono<App.Environment>()
40
+ .use('*', async (c, next) => {
41
+ const config = c.get('delegationApproval')
42
+ if (!config?.groupId || !config.lookup)
43
+ return Response.error(c, {
44
+ code: 'zone_delegation_approval_unconfigured',
45
+ message: 'The internal identity approver group is not configured.',
46
+ status: 503,
47
+ })
48
+ try {
49
+ const member = await config.identify(c.req.raw, c)
50
+ if (
51
+ !ZoneDelegationApproval.active(member) ||
52
+ member.email.toLowerCase() !== c.get('identity')?.email.toLowerCase()
53
+ )
54
+ return Response.error(c, {
55
+ code: 'unauthorized',
56
+ message: 'A verified internal identity session is required.',
57
+ status: 401,
58
+ })
59
+ c.set('approver', member)
60
+ await next()
61
+ if (c.error) throw c.error
62
+ return
63
+ } catch (cause) {
64
+ if (cause instanceof ZoneDelegationApproval.RequiredError) {
65
+ c.res = Response.error(c, {
66
+ code: 'zone_delegation_approval_required',
67
+ message: cause.message,
68
+ status: 403,
69
+ })
70
+ return c.res
71
+ }
72
+ c.get('metrics')?.count('zone_delegation_approval_failure_count', 1)
73
+ // Downstream errors may already have finalized Hono's response.
74
+ c.res = Response.upstream(c, cause)
75
+ return c.res
76
+ }
77
+ })
78
+ .post(
79
+ '/',
80
+ OpenApi.validate('json', Delegations.schema.Body, {
81
+ code: 'body_invalid',
82
+ // Two member lists of 1,000 addresses.
83
+ maxBytes: 256 * 1_024,
84
+ message: 'Invalid delegation request.',
85
+ }),
86
+ OpenApi.describeRoute({
87
+ description: 'File a new delegation for two-approver review. Widening an active delegation is revoking it and filing a new one.', // prettier-ignore
88
+ operationId: 'createZoneDelegationRequest',
89
+ responses: {
90
+ 201: { description: 'The pending Zone delegation request.' },
91
+ 404: { description: 'Organization not found.' },
92
+ },
93
+ summary: 'Request Zone delegation',
94
+ tags: ['Zone Delegation Requests'],
95
+ }),
96
+ async (c) => {
97
+ const config = c.get('delegationApproval')!
98
+ const input = c.req.valid('json')
99
+ const db = Db.get(c.get('db'))
100
+ if (!(await Organizations.get(db, input.orgId))) return notFound(c)
101
+ const request = await ZoneDelegationRequests.create(db, {
102
+ groupId: config.groupId,
103
+ input,
104
+ requester: c.get('approver'),
105
+ })
106
+ return c.json(request, 201)
107
+ },
108
+ )
109
+ .get(
110
+ '/',
111
+ OpenApi.validate('query', schema.Query, {
112
+ code: 'query_invalid',
113
+ message: 'Invalid request cursor.',
114
+ }),
115
+ OpenApi.describeRoute({
116
+ operationId: 'listZoneDelegationRequests',
117
+ responses: { 200: { description: 'A page of Zone delegation requests, newest first.' } },
118
+ summary: 'List delegation requests',
119
+ tags: ['Zone Delegation Requests'],
120
+ }),
121
+ async (c) =>
122
+ c.json(await ZoneDelegationRequests.list(Db.get(c.get('db')), c.req.valid('query')), 200),
123
+ )
124
+ .get(
125
+ '/:id',
126
+ OpenApi.validate('param', schema.Params, {
127
+ code: 'param_invalid',
128
+ message: 'Invalid request id.',
129
+ }),
130
+ OpenApi.describeRoute({
131
+ operationId: 'getZoneDelegationRequest',
132
+ responses: { 200: { description: 'The Zone delegation request with its approvals.' } },
133
+ summary: 'Get delegation request',
134
+ tags: ['Zone Delegation Requests'],
135
+ }),
136
+ async (c) => {
137
+ const request = await ZoneDelegationRequests.get(Db.get(c.get('db')), c.req.valid('param').id) // prettier-ignore
138
+ if (!request)
139
+ return Response.error(c, {
140
+ code: 'not_found',
141
+ message: 'Request not found.',
142
+ status: 404,
143
+ })
144
+ return c.json(request, 200)
145
+ },
146
+ )
147
+ .post(
148
+ '/:id/approve',
149
+ OpenApi.validate('param', schema.Params, {
150
+ code: 'param_invalid',
151
+ message: 'Invalid request id.',
152
+ }),
153
+ OpenApi.describeRoute({
154
+ operationId: 'approveZoneDelegationRequest',
155
+ responses: { 200: { description: 'The request with the recorded approval.' } },
156
+ summary: 'Approve delegation request',
157
+ tags: ['Zone Delegation Requests'],
158
+ }),
159
+ async (c) => {
160
+ const request = await ZoneDelegationRequests.approve(Db.get(c.get('db')), {
161
+ groupId: c.get('delegationApproval')!.groupId,
162
+ id: c.req.valid('param').id,
163
+ lookup: (ids) => c.get('delegationApproval')!.lookup(c.req.raw, ids),
164
+ member: c.get('approver'),
165
+ })
166
+ return c.json(request, 200)
167
+ },
168
+ )
169
+ .post(
170
+ '/:id/apply',
171
+ OpenApi.validate('param', schema.Params, {
172
+ code: 'param_invalid',
173
+ message: 'Invalid request id.',
174
+ }),
175
+ OpenApi.describeRoute({
176
+ operationId: 'applyZoneDelegationRequest',
177
+ responses: OpenApi.responses({
178
+ errors: { 409: 'An active delegation already covers this Zone.' },
179
+ success: { description: 'The created delegation.', schema: Delegations.schema.Response },
180
+ }),
181
+ summary: 'Apply delegation request',
182
+ tags: ['Zone Delegation Requests'],
183
+ }),
184
+ async (c) => {
185
+ const db = Db.get(c.get('db'))
186
+ const request = await ZoneDelegationRequests.claim(db, {
187
+ groupId: c.get('delegationApproval')!.groupId,
188
+ id: c.req.valid('param').id,
189
+ lookup: (ids) => c.get('delegationApproval')!.lookup(c.req.raw, ids),
190
+ member: c.get('approver'),
191
+ })
192
+ try {
193
+ const data = await ZoneDelegations.create(db, request.input)
194
+ await ZoneDelegationRequests.finish(db, request.id, { delegationId: data.id, status: 'applied' }) // prettier-ignore
195
+ return c.json(Response.validated(Delegations.schema.Response, { data }), 200)
196
+ } catch (cause) {
197
+ // A consumed claim is never replayed: the requester files a new request.
198
+ await ZoneDelegationRequests.finish(db, request.id, { status: 'failed' })
199
+ if (cause instanceof ZoneDelegations.ConflictError)
200
+ return Response.error(c, {
201
+ code: 'zone_delegation_exists',
202
+ message: 'An active delegation already covers this organization, environment, and Zone.', // prettier-ignore
203
+ status: 409,
204
+ })
205
+ throw cause
206
+ }
207
+ },
208
+ )
209
+ .delete(
210
+ '/:id',
211
+ OpenApi.validate('param', schema.Params, {
212
+ code: 'param_invalid',
213
+ message: 'Invalid request id.',
214
+ }),
215
+ OpenApi.describeRoute({
216
+ operationId: 'cancelZoneDelegationRequest',
217
+ responses: { 200: { description: 'The cancelled Zone delegation request.' } },
218
+ summary: 'Cancel delegation request',
219
+ tags: ['Zone Delegation Requests'],
220
+ }),
221
+ async (c) => {
222
+ const request = await ZoneDelegationRequests.cancel(Db.get(c.get('db')), c.req.valid('param').id) // prettier-ignore
223
+ if (!request)
224
+ return Response.error(c, {
225
+ code: 'not_found',
226
+ message: 'Pending request not found.',
227
+ status: 404,
228
+ })
229
+ return c.json(request, 200)
230
+ },
231
+ )
232
+ }
233
+
234
+ function notFound(c: Context<App.Environment>) {
235
+ return Response.error(c, {
236
+ code: 'not_found',
237
+ message: 'Organization not found.',
238
+ status: 404,
239
+ })
240
+ }
@@ -1,9 +1,11 @@
1
1
  import * as TestAdmin from '../../../test/Admin.js'
2
2
  import * as TestApp from '../../../test/App.js'
3
3
  import * as ApiKey from '../../ApiKey.js'
4
+ import * as ApiKeys from '../../ApiKeys.js'
4
5
  import * as AuditLogs from '../../db/tables/adminAuditLogs.js'
5
6
  import * as Organizations from '../../db/tables/organizations.js'
6
7
  import * as Projects from '../../db/tables/projects.js'
8
+ import * as ZoneDelegationsTable from '../../db/tables/zoneDelegations.js'
7
9
  import * as ZoneDelegations from './zone-delegations.js'
8
10
 
9
11
  const chainId = 421_700_001
@@ -16,31 +18,29 @@ describe('zoneDelegations', () => {
16
18
  expect((await client['zone-delegations'].$get({ query: {} })).status).toBe(401)
17
19
  })
18
20
 
19
- test('creates, lists, updates, and revokes a delegation with audit entries', async () => {
21
+ test('lists, narrows, and revokes a delegation with audit entries; widening needs approval', async () => {
20
22
  const { client, db } = TestAdmin.setup()
21
23
  const organization = await Organizations.create(db, { name: 'Delegated' })
22
- const project = await Projects.create(db, { name: 'Checkout', orgId: organization.id })
23
24
  const body = {
24
25
  accounts: [`0x${'AB'.repeat(20)}` as const],
25
26
  accountsAll: false,
26
27
  chainId,
27
28
  environment: 'production' as const,
28
29
  orgId: organization.id,
29
- projects: [project.id],
30
30
  tokensAll: true,
31
31
  }
32
32
 
33
+ // Creation only lands through an approved request.
33
34
  const created = await client['zone-delegations'].$post({ json: body })
34
- expect(created.status).toBe(200)
35
- const { data } = await TestApp.json(created, ZoneDelegations.schema.Response)
35
+ expect(created.status).toBe(403)
36
+ expect(await created.json()).toMatchObject({ error: { code: 'zone_delegation_approval_required' } }) // prettier-ignore
37
+ const data = await ZoneDelegationsTable.create(db, body)
38
+ await ZoneDelegationsTable.create(db, { ...body, chainId: chainId + 1 })
36
39
  expect(stable(data)).toMatchInlineSnapshot(`
37
40
  {
38
41
  "accountsAll": false,
39
42
  "chainId": 421700001,
40
43
  "environment": "production",
41
- "projects": [
42
- "prj",
43
- ],
44
44
  "tokensAll": true,
45
45
  }
46
46
  `)
@@ -51,10 +51,6 @@ describe('zoneDelegations', () => {
51
51
  const tokens = await client['zone-delegations'][':id'].tokens.$get({ param: { id: data.id }, query: {} }) // prettier-ignore
52
52
  expect((await TestApp.json(tokens, ZoneDelegations.schema.MembersResponse)).all).toBe(true)
53
53
 
54
- // A second active delegation for the same Zone conflicts; another Zone is free.
55
- expect((await client['zone-delegations'].$post({ json: body })).status).toBe(409)
56
- expect((await client['zone-delegations'].$post({ json: { ...body, chainId: chainId + 1 } })).status).toBe(200) // prettier-ignore
57
-
58
54
  const listed = await client['zone-delegations'].$get({
59
55
  query: { environment: 'production', orgId: organization.id },
60
56
  })
@@ -63,12 +59,14 @@ describe('zoneDelegations', () => {
63
59
  const filtered = await client['zone-delegations'].$get({ query: { chainId: String(chainId) } })
64
60
  expect((await TestApp.json(filtered, ZoneDelegations.schema.ListResponse)).data).toHaveLength(1)
65
61
 
62
+ // Widening edits are refused (revoke and request anew); narrowing ones apply.
63
+ expect((await client['zone-delegations'][':id'].$patch({ json: { accountsAll: true }, param: { id: data.id } })).status).toBe(403) // prettier-ignore
66
64
  const patched = await client['zone-delegations'][':id'].$patch({
67
- json: { accountsAll: true, projects: 'all' },
65
+ json: { tokensAll: false },
68
66
  param: { id: data.id },
69
67
  })
70
68
  expect(patched.status).toBe(200)
71
- expect((await TestApp.json(patched, ZoneDelegations.schema.Response)).data).toMatchObject({ accountsAll: true, id: data.id, projects: 'all' }) // prettier-ignore
69
+ expect((await TestApp.json(patched, ZoneDelegations.schema.Response)).data).toMatchObject({ id: data.id, tokensAll: false }) // prettier-ignore
72
70
  expect((await client['zone-delegations'][':id'].$patch({ json: {}, param: { id: data.id } })).status).toBe(400) // prettier-ignore
73
71
 
74
72
  const revoked = await client['zone-delegations'][':id'].$delete({ param: { id: data.id } })
@@ -79,11 +77,6 @@ describe('zoneDelegations', () => {
79
77
  const fetched = await client['zone-delegations'][':id'].$get({ param: { id: data.id } })
80
78
  expect((await TestApp.json(fetched, ZoneDelegations.schema.Response)).data.revokedAt).toBeDefined() // prettier-ignore
81
79
 
82
- // The freed slot accepts a new delegation under a new id.
83
- const next = await client['zone-delegations'].$post({ json: { ...body, accounts: undefined, accountsAll: true } }) // prettier-ignore
84
- expect(next.status).toBe(200)
85
- expect((await TestApp.json(next, ZoneDelegations.schema.Response)).data.id).not.toBe(data.id)
86
-
87
80
  // Both the exact and the wildcard protected path match the collection, so dedupe by request.
88
81
  const audit = await AuditLogs.list(db, { path: '/zone-delegations' })
89
82
  const seen = new Set<string>()
@@ -93,44 +86,41 @@ describe('zoneDelegations', () => {
93
86
  .map(({ method, status }) => `${method} ${status}`),
94
87
  ).toMatchInlineSnapshot(`
95
88
  [
96
- "POST 200",
97
89
  "GET 200",
98
90
  "PATCH 409",
99
91
  "DELETE 404",
100
92
  "DELETE 200",
101
93
  "PATCH 400",
102
94
  "PATCH 200",
95
+ "PATCH 403",
103
96
  "GET 200",
104
97
  "GET 200",
105
- "POST 200",
106
- "POST 409",
107
98
  "GET 200",
108
99
  "GET 200",
109
- "POST 200",
100
+ "POST 403",
110
101
  ]
111
102
  `)
112
103
  })
113
104
 
114
- test('adds, lists, and removes account and token members', async () => {
105
+ test('adds, lists, and removes members under an all flag; listed members only grow through approval', async () => {
115
106
  const { client, db } = TestAdmin.setup()
116
107
  const organization = await Organizations.create(db, { name: 'Members' })
117
- const created = await client['zone-delegations'].$post({
118
- json: { accountsAll: false, chainId, environment: 'production', orgId: organization.id, projects: 'all', tokensAll: false }, // prettier-ignore
119
- })
120
- const { data } = await TestApp.json(created, ZoneDelegations.schema.Response)
108
+ const data = await ZoneDelegationsTable.create(db, { accountsAll: true, chainId, environment: 'production', orgId: organization.id, tokensAll: false, tokens: [other] }) // prettier-ignore
121
109
  const param = { id: data.id }
110
+ // Members under `all` are inert, so adding them widens nothing.
122
111
  const added = await client['zone-delegations'][':id'].accounts.$post({ json: { addresses: [`0x${'AB'.repeat(20)}`, other] }, param }) // prettier-ignore
123
112
  expect(added.status).toBe(200)
124
113
  const addedBody = await TestApp.json(added, ZoneDelegations.schema.MembersResponse)
125
- expect([addedBody.all, addedBody.data.map(({ address }) => address).sort()]).toEqual([false, [account, other]]) // prettier-ignore
114
+ expect([addedBody.all, addedBody.data.map(({ address }) => address).sort()]).toEqual([true, [account, other]]) // prettier-ignore
126
115
  const removed = await client['zone-delegations'][':id'].accounts.$delete({ json: { addresses: [other] }, param }) // prettier-ignore
127
116
  expect(await TestApp.json(removed, ZoneDelegations.schema.RemovedResponse)).toEqual({ data: [other] }) // prettier-ignore
128
117
  const accounts = await client['zone-delegations'][':id'].accounts.$get({ param, query: {} })
129
118
  expect((await TestApp.json(accounts, ZoneDelegations.schema.MembersResponse)).data.map(({ address }) => address)).toEqual([account]) // prettier-ignore
130
119
 
131
- const tokens = await client['zone-delegations'][':id'].tokens.$post({ json: { addresses: [other] }, param }) // prettier-ignore
132
- expect(tokens.status).toBe(200)
133
- expect((await TestApp.json(await client['zone-delegations'][':id'].tokens.$get({ param, query: {} }), ZoneDelegations.schema.MembersResponse)).data.map(({ address }) => address)).toEqual([other]) // prettier-ignore
120
+ // A listed token set grows only through a new approved delegation; removal stays open.
121
+ expect((await client['zone-delegations'][':id'].tokens.$post({ json: { addresses: [account] }, param })).status).toBe(403) // prettier-ignore
122
+ expect(await TestApp.json(await client['zone-delegations'][':id'].tokens.$delete({ json: { addresses: [other] }, param }), ZoneDelegations.schema.RemovedResponse)).toEqual({ data: [other] }) // prettier-ignore
123
+ expect((await TestApp.json(await client['zone-delegations'][':id'].tokens.$get({ param, query: {} }), ZoneDelegations.schema.MembersResponse)).data).toEqual([]) // prettier-ignore
134
124
 
135
125
  expect((await client['zone-delegations'][':id'].accounts.$post({ json: { addresses: [] }, param })).status).toBe(400) // prettier-ignore
136
126
  expect((await client['zone-delegations'][':id'].accounts.$post({ json: { addresses: ['0xnope' as never] }, param })).status).toBe(400) // prettier-ignore
@@ -175,11 +165,7 @@ describe('zoneDelegations', () => {
175
165
  const plan = await TestApp.json(planned, ZoneDelegations.schema.BackfillPlan)
176
166
  expect({
177
167
  ...plan,
178
- delegations: plan.delegations.map((d) => ({
179
- ...d,
180
- orgId: 'org',
181
- projects: d.projects === 'all' ? d.projects : d.projects.map((id) => id.split('_')[0]),
182
- })),
168
+ delegations: plan.delegations.map((d) => ({ ...d, orgId: 'org' })),
183
169
  wildcard: plan.wildcard.map((k) => ({ ...k, orgId: 'org' })),
184
170
  }).toMatchInlineSnapshot(`
185
171
  {
@@ -189,7 +175,6 @@ describe('zoneDelegations', () => {
189
175
  "chainId": 421700004,
190
176
  "environment": "production",
191
177
  "orgId": "org",
192
- "projects": "all",
193
178
  "tokensAll": true,
194
179
  },
195
180
  {
@@ -197,7 +182,6 @@ describe('zoneDelegations', () => {
197
182
  "chainId": 421700001,
198
183
  "environment": "production",
199
184
  "orgId": "org",
200
- "projects": "all",
201
185
  "tokensAll": true,
202
186
  },
203
187
  ],
@@ -233,38 +217,214 @@ describe('zoneDelegations', () => {
233
217
  expect(rows.map(({ chainId, orgId }) => ({ chainId, owner: orgId.split('_')[0] })).sort((a, b) => a.chainId - b.chainId)).toEqual([{ chainId, owner: 'org' }, { chainId: chainId + 3, owner: 'key' }]) // prettier-ignore
234
218
  })
235
219
 
236
- test('refuses unknown organizations, foreign projects, and malformed addresses', async () => {
220
+ test('plans and applies the per-Zone read scope rewrite', async () => {
221
+ // Three hosted Zones; anything else a scope names is unconfigured.
222
+ const { client, db, store } = TestAdmin.setup({
223
+ zones: [0, 1, 2].map((offset) => TestApp.zone({ chainId: chainId + offset, rpcUrl: 'https://zone.rpc.test' })), // prettier-ignore
224
+ })
225
+ const organization = await Organizations.create(db, { name: 'Rewritten' })
226
+ const whole = { accountsAll: true, environment: 'production', orgId: organization.id, tokensAll: true } as const // prettier-ignore
227
+ await ZoneDelegationsTable.create(db, { ...whole, chainId })
228
+ const record = (id: string, token: string, extra: Record<string, unknown>) =>
229
+ store.put(
230
+ ApiKey.keyFor(token),
231
+ JSON.stringify({ createdAt: '2026-01-01T00:00:00.000Z', environment: 'production', id, orgId: organization.id, tokenLast4: token.slice(-4), ...extra }), // prettier-ignore
232
+ )
233
+ await record('key_reader', 'secret_reader', { scopes: ['data:read', `zone:${chainId}:read`, `zone:${chainId + 1}:read`] }) // prettier-ignore
234
+ // Hand-added tags never earn a delegation; sibling project keys share one Zone slot.
235
+ const project = await Projects.create(db, { name: 'Checkout', orgId: organization.id })
236
+ await record('key_tagged', 'secret_tagged', { projectId: project.id, scopes: [`zone:${chainId + 1}:read`], zoneTags: [chainId + 2] }) // prettier-ignore
237
+ await record('key_wild', 'secret_wild', { name: 'Integration', scopes: ['*'] })
238
+ await record('key_orphan', 'secret_orphan', { orgId: 'key_orphan', scopes: [`zone:${chainId}:read`] }) // prettier-ignore
239
+ await record('key_plain', 'secret_plain', { scopes: ['data:read'] })
240
+ await record('key_mixed', 'secret_mixed', { scopes: [`zone:${chainId}:read`, `zone:${chainId + 1}:write`] }) // prettier-ignore
241
+ // Already in both formats: its missing delegation is still ensured, the record is left alone.
242
+ await record('key_done', 'secret_done', { scopes: [`zone:${chainId + 2}:read`, 'zone:read'], zoneTags: [chainId + 2] }) // prettier-ignore
243
+ // A scope naming a Zone nothing serves earns no tag, and its delegation goes with it.
244
+ await record('key_stale', 'secret_stale', { scopes: [`zone:${chainId}:read`, `zone:${chainId + 9}:read`] }) // prettier-ignore
245
+ const stale = await ZoneDelegationsTable.create(db, { ...whole, chainId: chainId + 9 })
246
+ await record('key_tagless', 'secret_tagless', { scopes: ['data:read', 'zone:read'] })
247
+
248
+ const planned = await client['zone-delegations']['rewrite-keys'].$get()
249
+ expect(planned.status).toBe(200)
250
+ const plan = await TestApp.json(planned, ZoneDelegations.schema.RewritePlan)
251
+ const anonymize = (key: { orgId: string; projectId?: string | undefined }) => ({ ...key, orgId: key.orgId.split('_')[0], ...(key.projectId ? { projectId: 'prj' } : {}) }) // prettier-ignore
252
+ expect({
253
+ ...plan,
254
+ changes: plan.changes.map(anonymize),
255
+ mixed: plan.mixed.map(anonymize),
256
+ orgless: plan.orgless.map(anonymize),
257
+ tagless: plan.tagless.map(anonymize),
258
+ wildcard: plan.wildcard.map(anonymize),
259
+ }).toMatchInlineSnapshot(`
260
+ {
261
+ "changes": [
262
+ {
263
+ "complete": true,
264
+ "environment": "production",
265
+ "id": "key_done",
266
+ "orgId": "org",
267
+ "revoked": [],
268
+ "scopes": [
269
+ "zone:421700003:read",
270
+ "zone:read",
271
+ ],
272
+ "undelegated": [
273
+ 421700003,
274
+ ],
275
+ "zoneTags": [
276
+ 421700003,
277
+ ],
278
+ },
279
+ {
280
+ "complete": false,
281
+ "environment": "production",
282
+ "id": "key_reader",
283
+ "orgId": "org",
284
+ "revoked": [],
285
+ "scopes": [
286
+ "data:read",
287
+ "zone:421700001:read",
288
+ "zone:421700002:read",
289
+ "zone:read",
290
+ ],
291
+ "undelegated": [
292
+ 421700002,
293
+ ],
294
+ "zoneTags": [
295
+ 421700001,
296
+ 421700002,
297
+ ],
298
+ },
299
+ {
300
+ "complete": false,
301
+ "environment": "production",
302
+ "id": "key_tagged",
303
+ "orgId": "org",
304
+ "projectId": "prj",
305
+ "revoked": [],
306
+ "scopes": [
307
+ "zone:421700002:read",
308
+ "zone:read",
309
+ ],
310
+ "undelegated": [
311
+ 421700002,
312
+ ],
313
+ "zoneTags": [
314
+ 421700002,
315
+ 421700003,
316
+ ],
317
+ },
318
+ {
319
+ "complete": false,
320
+ "environment": "production",
321
+ "id": "key_stale",
322
+ "orgId": "org",
323
+ "revoked": [],
324
+ "scopes": [
325
+ "zone:421700001:read",
326
+ "zone:421700010:read",
327
+ "zone:read",
328
+ ],
329
+ "undelegated": [],
330
+ "zoneTags": [
331
+ 421700001,
332
+ ],
333
+ },
334
+ ],
335
+ "expiring": [],
336
+ "invalid": [],
337
+ "keys": 9,
338
+ "mixed": [
339
+ {
340
+ "environment": "production",
341
+ "id": "key_mixed",
342
+ "orgId": "org",
343
+ },
344
+ ],
345
+ "narrow": [],
346
+ "orgless": [
347
+ {
348
+ "environment": "production",
349
+ "id": "key_orphan",
350
+ "orgId": "key",
351
+ },
352
+ ],
353
+ "revoked": [],
354
+ "tagless": [
355
+ {
356
+ "environment": "production",
357
+ "id": "key_tagless",
358
+ "orgId": "org",
359
+ },
360
+ ],
361
+ "unconfigured": [
362
+ 421700010,
363
+ ],
364
+ "unreadableRecords": 0,
365
+ "wildcard": [
366
+ {
367
+ "environment": "production",
368
+ "id": "key_wild",
369
+ "name": "Integration",
370
+ "orgId": "org",
371
+ },
372
+ ],
373
+ }
374
+ `)
375
+ expect((await ApiKeys.resolve(store, 'secret_reader'))?.scopes).toEqual(['data:read', `zone:${chainId}:read`, `zone:${chainId + 1}:read`]) // prettier-ignore
376
+
377
+ // A delegation listing members would narrow a whole-Zone reader, so apply refuses before writing.
378
+ const listed = await ZoneDelegationsTable.create(db, { ...whole, accountsAll: false, chainId: chainId + 1 }) // prettier-ignore
379
+ const refused = await client['zone-delegations']['rewrite-keys'].$post()
380
+ expect(refused.status).toBe(400)
381
+ expect(await refused.json()).toMatchObject({ error: { code: 'zone_delegations_narrow' } })
382
+ expect((await ApiKeys.resolve(store, 'secret_reader'))?.zoneTags).toBeUndefined()
383
+ expect((await TestApp.json(await client['zone-delegations']['rewrite-keys'].$get(), ZoneDelegations.schema.RewritePlan)).narrow.map(({ id }) => id)).toEqual([listed.id]) // prettier-ignore
384
+ // Revoking it is an admin decision the rewrite must not undo: keys reading that Zone are reported and left alone.
385
+ await ZoneDelegationsTable.revoke(db, listed.id)
386
+
387
+ const applied = await client['zone-delegations']['rewrite-keys'].$post()
388
+ expect(applied.status).toBe(200)
389
+ const result = await TestApp.json(applied, ZoneDelegations.schema.RewriteApplied)
390
+ // The complete key's missing slot is ensured without writing its record; the two keys reading the revoked slot are reported and left unwritten.
391
+ expect([result.createdDelegations, result.revokedDelegations, result.rewritten]).toEqual([1, 1, 1]) // prettier-ignore
392
+ expect(result.changes.map(({ id, revoked, undelegated }) => [id, undelegated, revoked] as const).sort(([a], [b]) => a.localeCompare(b))).toEqual([['key_done', [], []], ['key_reader', [chainId + 1], [chainId + 1]], ['key_stale', [], []], ['key_tagged', [chainId + 1], [chainId + 1]]]) // prettier-ignore
393
+ expect(result.revoked.map(({ id }) => id)).toEqual([listed.id])
394
+ expect(await ApiKeys.resolve(store, 'secret_done')).toMatchObject({ scopes: [`zone:${chainId + 2}:read`, 'zone:read'], zoneTags: [chainId + 2] }) // prettier-ignore
395
+ expect((await ZoneDelegationsTable.get(db, stale.id))?.revokedAt).toBeDefined()
396
+ expect(await ApiKeys.resolve(store, 'secret_stale')).toMatchObject({ scopes: [`zone:${chainId}:read`, `zone:${chainId + 9}:read`, 'zone:read'], zoneTags: [chainId] }) // prettier-ignore
397
+ expect((await ApiKeys.resolve(store, 'secret_tagless'))?.scopes).toEqual(['data:read', 'zone:read']) // prettier-ignore
398
+ expect(await ApiKeys.resolve(store, 'secret_tagged')).toMatchObject({ scopes: [`zone:${chainId + 1}:read`], zoneTags: [chainId + 2] }) // prettier-ignore
399
+ expect(result.orgless.map(({ id }) => id)).toEqual(['key_orphan'])
400
+ expect((await ApiKeys.resolve(store, 'secret_orphan'))?.scopes).toEqual([`zone:${chainId}:read`]) // prettier-ignore
401
+ expect((await ApiKeys.resolve(store, 'secret_mixed'))?.zoneTags).toBeUndefined()
402
+ expect((await ApiKeys.resolve(store, 'secret_reader'))?.zoneTags).toBeUndefined()
403
+ const again = await TestApp.json(await client['zone-delegations']['rewrite-keys'].$post(), ZoneDelegations.schema.RewriteApplied) // prettier-ignore
404
+ // The rerun keeps showing the two blocked keys until an admin delegates the Zone again.
405
+ expect([again.rewritten, again.createdDelegations, again.revokedDelegations, again.changes.filter(({ complete }) => !complete).map(({ id }) => id).sort(), again.revoked.length]).toEqual([0, 0, 0, ['key_reader', 'key_tagged'], 1]) // prettier-ignore
406
+ })
407
+
408
+ test('refuses unknown organizations and malformed addresses', async () => {
237
409
  const { client, db } = TestAdmin.setup()
238
410
  const organization = await Organizations.create(db, { name: 'Owner' })
239
- const otherOrganization = await Organizations.create(db, { name: 'Other' })
240
- const foreign = await Projects.create(db, { name: 'Foreign', orgId: otherOrganization.id })
241
411
  const body = {
242
412
  accountsAll: true,
243
413
  chainId,
244
414
  environment: 'sandbox',
245
415
  orgId: organization.id,
246
- projects: 'all',
247
416
  tokensAll: true,
248
417
  } as const
249
418
  expect((await client['zone-delegations'].$post({ json: { ...body, orgId: 'org_missing' } })).status).toBe(404) // prettier-ignore
250
- expect((await client['zone-delegations'].$post({ json: { ...body, projects: [foreign.id] } })).status).toBe(404) // prettier-ignore
251
419
  expect((await client['zone-delegations'].$post({ json: { ...body, accounts: ['0xnope'] as never } })).status).toBe(400) // prettier-ignore
252
420
  expect((await client['zone-delegations'][':id'].$get({ param: { id: 'zd_missing' } })).status).toBe(404) // prettier-ignore
253
- const created = await client['zone-delegations'].$post({ json: body })
254
- const { data } = await TestApp.json(created, ZoneDelegations.schema.Response)
255
- expect((await client['zone-delegations'][':id'].$patch({ json: { projects: [foreign.id] }, param: { id: data.id } })).status).toBe(404) // prettier-ignore
421
+ // The retired `projects` field is no longer part of the contract.
422
+ expect((await client['zone-delegations'].$post({ json: { ...body, projects: 'all' } as never })).status).toBe(400) // prettier-ignore
256
423
  })
257
424
  })
258
425
 
259
- /** Drops generated ids and timestamps; project ids keep their prefix only. */
260
- function stable(record: {
261
- createdAt: string
262
- id: string
263
- updatedAt: string
264
- orgId: string
265
- projects: 'all' | readonly string[]
266
- }) {
267
- // prettier-ignore
268
- const { createdAt: _c, id: _i, orgId: _o, projects, updatedAt: _u, ...rest } = record
269
- return { ...rest, projects: projects === 'all' ? projects : projects.map((id) => id.split('_')[0]) } // prettier-ignore
426
+ /** Drops generated ids and timestamps. */
427
+ function stable(record: { createdAt: string; id: string; updatedAt: string; orgId: string }) {
428
+ const { createdAt: _c, id: _i, orgId: _o, updatedAt: _u, ...rest } = record
429
+ return rest
270
430
  }