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
package/src/ApiKeys.ts CHANGED
@@ -22,10 +22,10 @@ import * as ApiKeyRevocations from './db/tables/apiKeyRevocations.js'
22
22
  import * as Organizations from './db/tables/organizations.js'
23
23
  import * as RoutesPolicies from './db/tables/routesPolicies.js'
24
24
  import * as RoutesSubsidies from './db/tables/routesSubsidies.js'
25
+ import * as ZoneDelegations from './db/tables/zoneDelegations.js'
25
26
  import * as Store from './internal/Store.js'
26
27
  import * as Scope from './Scope.js'
27
- import * as ZoneKeyApproval from './internal/ZoneKeyApproval.js'
28
- import * as ZoneKeyRequests from './db/tables/zoneKeyRequests.js'
28
+ import * as Schema from './internal/Schema.js'
29
29
 
30
30
  const billingSnapshotSchema = z.object({
31
31
  active: z.boolean().check(z.describe('Whether billing is active.')),
@@ -44,7 +44,7 @@ export const minimumExpiryLeadMs = 2 * 60_000
44
44
  /** Zod schemas owned by the API-keys module. */
45
45
  export namespace schema {
46
46
  /** Key expiry requested through an API, far enough ahead that the stored record outlives the request. */
47
- export const ExpiresAt = z.iso.datetime({ offset: true }).check(
47
+ export const ExpiresAt = Schema.dateTime({ offset: true }).check(
48
48
  z.refine((value) => Date.parse(value) >= Date.now() + minimumExpiryLeadMs, {
49
49
  error: 'Expiry must be at least two minutes in the future.',
50
50
  }),
@@ -62,7 +62,7 @@ export namespace schema {
62
62
  .optional(z.string())
63
63
  .check(z.describe('Identity creating the key (e.g. admin email), recorded for audit.')),
64
64
  expiresAt: z
65
- .optional(z.iso.datetime({ offset: true }))
65
+ .optional(Schema.dateTime({ offset: true }))
66
66
  .check(z.describe('ISO 8601 expiry timestamp. Omit for a non-expiring key.')),
67
67
  // TODO: make required once orgs exist.
68
68
  orgId: z.optional(z.string()).check(z.describe('Owning organization id.')),
@@ -175,14 +175,12 @@ export async function mintBounded(
175
175
  input: MintInput,
176
176
  options: mintBounded.Options = {},
177
177
  ): Promise<MintResult> {
178
- if (ZoneKeyApproval.required(input.scopes)) throw new ZoneKeyApproval.RequiredError()
179
178
  return mintAdmitted(db, state, input, options)
180
179
  }
181
180
 
182
181
  /**
183
182
  * Mints a replacement for an existing key on behalf of its owner. The locked source
184
- * must still hold every requested scope, so rotation never widens access and needs no
185
- * Zone read approval; admin rotation keeps using {@link mintBounded} and its gate.
183
+ * must still hold every requested scope, so rotation never widens access.
186
184
  */
187
185
  export async function rotateBounded(
188
186
  db: Db.Db,
@@ -190,7 +188,7 @@ export async function rotateBounded(
190
188
  input: MintInput,
191
189
  options: rotateBounded.Options,
192
190
  ): Promise<MintResult> {
193
- return mintAdmitted(db, state, input, { ...options, sourceScopes: input.scopes })
191
+ return mintAdmitted(db, state, input, { ...options, sourceScopes: withoutRetired(input.scopes) })
194
192
  }
195
193
 
196
194
  export declare namespace rotateBounded {
@@ -198,45 +196,23 @@ export declare namespace rotateBounded {
198
196
  type Options = mint.Options & Required<Pick<mint.Options, 'policySourceApiKeyId'>>
199
197
  }
200
198
 
201
- /** Issues the exact approved settings once using the requester session and unexpired approvals. */
202
- export async function mintApproved(
203
- db: Db.Db,
204
- state: Store.State,
205
- options: mintApproved.Options,
206
- ): Promise<MintResult> {
207
- const request = await ZoneKeyRequests.claim(db, options)
208
- try {
209
- const result = await mintAdmitted(db, state, request.input, {}, request.keyId)
210
- await ZoneKeyRequests.finish(db, request.id, 'issued')
211
- return result
212
- } catch (cause) {
213
- // An ambiguous write never permits another issuance; fence the reserved id before cleanup.
214
- await db.transaction(async (tx) => {
215
- await ApiKeyAdmissions.lock(tx, request.keyId)
216
- await ApiKeyRevocations.markPending(tx, request.keyId, request.input.orgId)
217
- })
218
- await ZoneKeyRequests.finish(db, request.id, 'failed')
219
- await revokeBounded(db, state, request.keyId).catch(() => undefined)
220
- throw cause
221
- }
222
- }
223
-
224
- export declare namespace mintApproved {
225
- /** Authenticated issuance context. */
226
- type Options = ZoneKeyRequests.claim.Options
227
- }
228
-
229
199
  async function mintAdmitted(
230
200
  db: Db.Db,
231
201
  state: Store.State,
232
- input: MintInput,
202
+ input_raw: MintInput,
233
203
  options: mintBounded.Options = {},
234
204
  id = ApiKey.generateId(),
235
205
  ): Promise<MintResult> {
236
206
  if (options.sourceScopes && !options.policySourceApiKeyId)
237
207
  throw new RoutesPolicies.InvalidScopeError()
208
+ // A copy of an existing key (rotation) drops the retired per-Zone scopes the source still holds.
209
+ const input = options.policySourceApiKeyId
210
+ ? { ...input_raw, scopes: withoutRetired(input_raw.scopes) }
211
+ : input_raw
238
212
  if (input.orgId === undefined) {
239
213
  if (options.policySourceApiKeyId) throw new RoutesPolicies.InvalidScopeError()
214
+ // No organization means no delegation, so nothing could bound the key's broadcasts.
215
+ if (boundByDelegations(input.scopes)) throw new UndelegatedZoneTagsError()
240
216
  return mint(state, input, options)
241
217
  }
242
218
  const orgId = input.orgId
@@ -269,14 +245,26 @@ async function mintAdmitted(
269
245
  ...rest
270
246
  } = validated
271
247
  const expiry = earliestExpiry(expiresAt, source.expiresAt)
248
+ // A broadcasting copy keeps only the tags still delegated, so a revoked delegation narrows the
249
+ // replacement instead of blocking the rotation; the gate refuses undelegated Zones anyway.
250
+ const zoneTags = boundByDelegations(rest.scopes)
251
+ ? await delegatedTags(tx, {
252
+ environment: rest.environment,
253
+ orgId,
254
+ zoneTags: source.zoneTags,
255
+ })
256
+ : source.zoneTags
272
257
  return {
273
258
  ...rest,
274
259
  allowedIps: source.allowedIps,
275
260
  ...(expiry === undefined ? {} : { expiresAt: expiry }),
276
261
  ...(source.rateLimits === undefined ? {} : { rateLimits: source.rateLimits }),
277
- ...(source.zoneTags === undefined ? {} : { zoneTags: source.zoneTags }),
262
+ ...(zoneTags === undefined ? {} : { zoneTags }),
278
263
  }
279
264
  })()
265
+ // Requested tags are what bound a broadcasting key, so they are checked as written; a copy's were narrowed above.
266
+ if (!options.policySourceApiKeyId && boundByDelegations(data.scopes))
267
+ await ensureDelegatedTags(tx, { environment: data.environment, orgId, zoneTags: data.zoneTags }) // prettier-ignore
280
268
  // Policy copies share deletion's subsidy-before-owner lock order.
281
269
  if (options.policySourceApiKeyId) await RoutesSubsidies.lockOrganization(tx, orgId)
282
270
  const owner = { orgId, projectId: data.projectId }
@@ -712,6 +700,14 @@ export async function update(
712
700
  if (!recordKey) return null
713
701
  const record = await read(state, recordKey, Date.now(), options.scopeCatalog ?? Scope.catalog)
714
702
  if (!record) return null
703
+ // Scopes the record already holds republish as they are; only new grants must be issuable.
704
+ if (
705
+ input.scopes &&
706
+ !issuableScopes(input.scopes.filter((scope) => !record.scopes.includes(scope)), options.scopeCatalog ?? Scope.catalog) // prettier-ignore
707
+ )
708
+ throw new InvalidInputError(
709
+ 'per-Zone scopes are retired; grant zone:read or zone:write with zoneTags',
710
+ )
715
711
  const owner = {
716
712
  orgId: input.orgId ?? record.orgId,
717
713
  projectId: input.projectId ?? record.projectId,
@@ -792,15 +788,21 @@ export async function updateBounded(
792
788
  )
793
789
  return { type: 'missing' } as const
794
790
  const pendingScopes = ledger.admitted?.pendingScopes
795
- // A reviewed credential accepts only an earlier expiry, or its first organization while it
796
- // still owns itself, without a new reviewed issuance.
797
- const protectedKey =
798
- ZoneKeyApproval.required(record.scopes) || ZoneKeyApproval.required(pendingScopes ?? [])
791
+ // Scopes still awaiting publication count, so a tag edit in that window is bounded too.
792
+ const scopes = input.scopes ?? pendingScopes ?? record.scopes
793
+ // A key that broadcasts through `zone:write` keeps every tag inside its organization's delegations.
799
794
  if (
800
- ZoneKeyApproval.required(input.scopes ?? []) ||
801
- (protectedKey && !shortens(record, input) && !adopts(record, input))
795
+ boundByDelegations(scopes) &&
796
+ (attributing || input.scopes !== undefined || input.zoneTags !== undefined)
802
797
  )
803
- throw new ZoneKeyApproval.RequiredError()
798
+ await ensureDelegatedTags(tx, {
799
+ environment: record.environment,
800
+ orgId: input.orgId ?? record.orgId,
801
+ zoneTags:
802
+ input.zoneTags === undefined
803
+ ? record.zoneTags
804
+ : (Input.parseNullable(ApiKey.schema.ZoneTags, input.zoneTags) ?? undefined),
805
+ })
804
806
  const expiresAt = normalizeExpiresAt(
805
807
  input.expiresAt === undefined ? record.expiresAt : (input.expiresAt ?? undefined),
806
808
  )
@@ -848,10 +850,11 @@ export async function updateBounded(
848
850
  if (reassigning) {
849
851
  // Fail closed across the KV/SQL boundary. The key remains scope-less if
850
852
  // clearing, reassignment, or the eventual SQL commit fails.
853
+ // Retired read scopes are inert once the rewrite ran, so a move to a new organization drops them.
851
854
  await ApiKeyAdmissions.setPendingScopes(
852
855
  tx,
853
856
  id,
854
- input.scopes ?? pendingScopes ?? record.scopes,
857
+ withoutRetired(input.scopes ?? pendingScopes ?? record.scopes),
855
858
  )
856
859
  const disabled = await update(state, id, { scopes: [] }, options)
857
860
  if (!disabled) return { type: 'missing' } as const
@@ -902,22 +905,6 @@ export declare namespace updateBounded {
902
905
  }
903
906
  }
904
907
 
905
- /** Whether an edit only gives a key minted before organizations existed its organization and changes nothing else. */
906
- function adopts(record: Record, input: update.Input) {
907
- if (record.orgId !== record.id) return false
908
- const { orgId, projectId: _projectId, ...rest } = input
909
- if (Object.values(rest).some((value) => value !== undefined)) return false
910
- return orgId !== undefined
911
- }
912
-
913
- /** Whether an edit only moves a key's expiry earlier and changes nothing else. */
914
- function shortens(record: Record, input: update.Input) {
915
- const { expiresAt, ...rest } = input
916
- if (Object.values(rest).some((value) => value !== undefined)) return false
917
- if (expiresAt == null) return false
918
- return record.expiresAt === undefined || Date.parse(expiresAt) <= Date.parse(record.expiresAt)
919
- }
920
-
921
908
  /** Returns the earliest of the given ISO timestamps, ignoring absent ones. */
922
909
  export function earliestExpiry(...values: readonly (string | undefined)[]) {
923
910
  return values
@@ -1115,6 +1102,176 @@ export declare namespace backfill {
1115
1102
  }
1116
1103
  }
1117
1104
 
1105
+ /**
1106
+ * Plans the additive rewrite of stored per-Zone read scopes: `zone:read` and
1107
+ * the chain ids as tags are added beside them, merged with existing tags, so
1108
+ * the running Worker and the next both admit the record. Wildcard keys are
1109
+ * listed and left alone; nothing is written. Admission today ignores tags and
1110
+ * already treats a per-Zone read scope as `zone:read`, so the rewrite changes no decision.
1111
+ */
1112
+ export async function planZoneReadRewrite(
1113
+ state: Store.State,
1114
+ options: planZoneReadRewrite.Options = {},
1115
+ ): Promise<planZoneReadRewrite.Result> {
1116
+ const scopeCatalog = options.scopeCatalog ?? Scope.catalog
1117
+ const { keys } = await state.list({ prefix: ApiKey.recordPrefix })
1118
+ const now = Date.now()
1119
+ const changes: planZoneReadRewrite.Change[] = []
1120
+ const expiring: Record[] = []
1121
+ const invalid: Record[] = []
1122
+ const mixed: Record[] = []
1123
+ const tagless: Record[] = []
1124
+ const wildcard: Record[] = []
1125
+ let unreadableRecords = 0
1126
+ let live = 0
1127
+ for (const { name } of keys) {
1128
+ const record = await read(state, name, now, scopeCatalog)
1129
+ if (!record) {
1130
+ unreadableRecords += 1
1131
+ continue
1132
+ }
1133
+ // Revocation and owner-deletion fences live under their own keys; a fenced record is never rewritten.
1134
+ if (await isFenced(state, record)) continue
1135
+ live += 1
1136
+ if (record.scopes.includes(Scope.wildcard)) {
1137
+ wildcard.push(record)
1138
+ continue
1139
+ }
1140
+ // One record the schema refuses (say, past the tag limit) is reported, never a reason to abort the plan.
1141
+ const change = (() => {
1142
+ try {
1143
+ return rewriteZoneRead(record, options.zones)
1144
+ } catch {
1145
+ return 'invalid' as const
1146
+ }
1147
+ })()
1148
+ if (change === 'invalid') invalid.push(record)
1149
+ else if (change === 'mixed') mixed.push(record)
1150
+ // KV refuses TTLs under a minute, and such a record expires on its own before anyone uses it.
1151
+ else if (change && !change.complete && expiresSoon(record, now)) expiring.push(record)
1152
+ else if (change) changes.push({ ...change, record, recordKey: name })
1153
+ else if (['zone:read', 'zone:write'].some((scope) => record.scopes.includes(scope)) && !record.zoneTags?.length) tagless.push(record) // prettier-ignore
1154
+ }
1155
+ return { changes, expiring, invalid, keys: live, mixed, tagless, unreadableRecords, wildcard }
1156
+ }
1157
+
1158
+ /**
1159
+ * The record with `zone:read` and tags added beside its per-Zone read scopes,
1160
+ * so Workers of either release admit it; undefined when it holds no such scope.
1161
+ * A record already carrying both is `complete`: still planned, so its
1162
+ * delegations are checked, but never written. Zones outside `zones` earn no tag
1163
+ * and lose any they had, since nothing serves them. `mixed` when its write Zones
1164
+ * differ from its read Zones: one tag list cannot express both, so the key is
1165
+ * re-minted by hand instead.
1166
+ */
1167
+ function rewriteZoneRead(record: Record, zones: ReadonlySet<number> | undefined) {
1168
+ const named = [...new Set(record.scopes.flatMap((scope) => Scope.zoneReadChainId(scope) ?? []))].sort((a, b) => a - b) // prettier-ignore
1169
+ const written = [...new Set(record.scopes.flatMap((scope) => Scope.zoneWriteChainId(scope) ?? []))].sort((a, b) => a - b) // prettier-ignore
1170
+ if (named.length === 0 && written.length === 0) return undefined
1171
+ // Zones nothing serves earn no tag, so they are dropped from tags and scopes alike before the
1172
+ // read and write sets are compared. Tags serve both verbs: what the key reads (tags plus read
1173
+ // Zones) must equal what it writes (write Zones, plus its tags when it already holds the generic
1174
+ // write scope), or it is `mixed`; otherwise a read Zone or a leftover tag would become a broadcast grant.
1175
+ const served = (chainIds: readonly number[]) => chainIds.filter((chainId) => zones?.has(chainId) ?? true) // prettier-ignore
1176
+ const kept = served(record.zoneTags ?? [])
1177
+ const chainIds = served(named)
1178
+ const writeChainIds = served(written)
1179
+ const read = new Set([...kept, ...chainIds])
1180
+ const writes = new Set(record.scopes.includes('zone:write') ? [...kept, ...writeChainIds] : writeChainIds) // prettier-ignore
1181
+ const bounded = read.size > 0 || record.scopes.includes('zone:read')
1182
+ if (writes.size && bounded && (writes.size !== read.size || [...writes].some((chainId) => !read.has(chainId)))) return 'mixed' as const // prettier-ignore
1183
+ const all = [...new Set([...named, ...written])]
1184
+ const unconfigured = all.filter((chainId) => !(zones?.has(chainId) ?? true)).sort((a, b) => a - b) // prettier-ignore
1185
+ const zoneTags = [...new Set([...kept, ...chainIds, ...writeChainIds])].sort((a, b) => a - b)
1186
+ // A generic scope is added only when a served Zone for that verb survives into the tags.
1187
+ const generic = [
1188
+ ...(chainIds.length && !record.scopes.includes('zone:read') ? ['zone:read'] : []),
1189
+ ...(writeChainIds.length && !record.scopes.includes('zone:write') ? ['zone:write'] : []),
1190
+ ]
1191
+ const complete =
1192
+ generic.length === 0 &&
1193
+ zoneTags.length === (record.zoneTags?.length ?? 0) &&
1194
+ zoneTags.every((chainId, index) => record.zoneTags?.[index] === chainId)
1195
+ const rewritten = complete ? record : schema.Record.parse({ ...record, scopes: [...record.scopes, ...generic], zoneTags }) // prettier-ignore
1196
+ return { chainIds, complete, rewritten, unconfigured, writeChainIds }
1197
+ }
1198
+
1199
+ export declare namespace planZoneReadRewrite {
1200
+ /** Options for {@link planZoneReadRewrite}. */
1201
+ type Options = {
1202
+ /** Scope catalog accepted while reading keys. */
1203
+ scopeCatalog?: Scope.Catalog | undefined
1204
+ /** Zone chain ids the deployment serves; per-Zone scopes naming any other Zone earn no tag. */
1205
+ zones?: ReadonlySet<number> | undefined
1206
+ }
1207
+
1208
+ /** One stored record and the record replacing it. */
1209
+ type Change = {
1210
+ /** Served Zones the per-Zone read scopes name, alias-resolved; the only Zones the rewrite may delegate. */
1211
+ chainIds: readonly number[]
1212
+ /** Whether the record already holds `zone:read` and every tag; only its delegations are checked, nothing is written. */
1213
+ complete: boolean
1214
+ /** The record as stored. */
1215
+ record: Record
1216
+ /** Storage key of the record. */
1217
+ recordKey: string
1218
+ /** The record with the generic Zone scopes and merged tags added beside its per-Zone scopes. */
1219
+ rewritten: Record
1220
+ /** Zones the per-Zone scopes name that the deployment does not serve; they earn no tag. */
1221
+ unconfigured: readonly number[]
1222
+ /** Served Zones the per-Zone write scopes name, alias-resolved; they need a delegation to bound the tag, never member coverage. */
1223
+ writeChainIds: readonly number[]
1224
+ }
1225
+
1226
+ /** Result of {@link planZoneReadRewrite}. */
1227
+ type Result = {
1228
+ /** Records holding per-Zone read scopes, with their replacements. */
1229
+ changes: readonly Change[]
1230
+ /** Records expiring within a minute; KV refuses their TTL and they lapse on their own, so they are left unchanged. */
1231
+ expiring: readonly Record[]
1232
+ /** Records whose rewritten form the schema refuses; left unchanged for a manual fix. */
1233
+ invalid: readonly Record[]
1234
+ /** Live, unfenced records read. */
1235
+ keys: number
1236
+ /** Records whose per-Zone write Zones differ from their read Zones; one tag list cannot express both, so they are re-minted by hand. */
1237
+ mixed: readonly Record[]
1238
+ /** Records holding `zone:read` with no tags and no per-Zone scope; they read no Zone and are left alone. */
1239
+ tagless: readonly Record[]
1240
+ /** Records the store could not read (expired, or scopes outside the catalog). */
1241
+ unreadableRecords: number
1242
+ /** Live records holding `*`, listed for a decision and left unchanged. */
1243
+ wildcard: readonly Record[]
1244
+ }
1245
+ }
1246
+
1247
+ /**
1248
+ * Rewrites the records a {@link planZoneReadRewrite} plan names, keeping each
1249
+ * record's TTL. Each record is re-read and transformed at write time so an edit
1250
+ * made since the plan survives; a record already in both formats is skipped.
1251
+ */
1252
+ export async function applyZoneReadRewrite(
1253
+ state: Store.State,
1254
+ changes: readonly planZoneReadRewrite.Change[],
1255
+ options: planZoneReadRewrite.Options = {},
1256
+ ): Promise<number> {
1257
+ let written = 0
1258
+ for (const { recordKey } of changes) {
1259
+ const now = Date.now()
1260
+ const current = await read(state, recordKey, now, options.scopeCatalog ?? Scope.catalog)
1261
+ const change = current && rewriteZoneRead(current, options.zones)
1262
+ if (!change || change === 'mixed' || change.complete || expiresSoon(current, now)) continue
1263
+ await state.put(recordKey, JSON.stringify(change.rewritten), { ttl: ttlFor(change.rewritten.expiresAt, now) }) // prettier-ignore
1264
+ written += 1
1265
+ }
1266
+ return written
1267
+ }
1268
+
1269
+ /** Whether a record expires within the minute Cloudflare KV needs for a TTL. */
1270
+ function expiresSoon(record: Record, now: number): boolean {
1271
+ const ttl = ttlFor(record.expiresAt, now)
1272
+ return ttl !== undefined && ttl < 60_000
1273
+ }
1274
+
1118
1275
  /**
1119
1276
  * Reads and validates a stored record by storage key. Invalid, corrupt, or
1120
1277
  * expired records (`expiresAt <= now`) resolve to `null`.
@@ -1228,8 +1385,12 @@ function validateMintInput(
1228
1385
  ): z.output<typeof schema.MintInput> {
1229
1386
  const valid = schema.MintInput.safeParse(input)
1230
1387
  if (!valid.success) throw new InvalidInputError(z.prettifyError(valid.error))
1231
- if (!validScopes(valid.data.scopes, options.scopeCatalog ?? Scope.catalog))
1232
- throw new InvalidInputError('unknown scope')
1388
+ const scopeCatalog = options.scopeCatalog ?? Scope.catalog
1389
+ if (!validScopes(valid.data.scopes, scopeCatalog)) throw new InvalidInputError('unknown scope')
1390
+ if (!issuableScopes(valid.data.scopes, scopeCatalog))
1391
+ throw new InvalidInputError(
1392
+ 'per-Zone scopes are retired; grant zone:read or zone:write with zoneTags',
1393
+ )
1233
1394
  return {
1234
1395
  ...valid.data,
1235
1396
  ...(valid.data.expiresAt === undefined
@@ -1289,6 +1450,66 @@ function validScopes(scopes: readonly string[], scopeCatalog: Scope.Catalog): bo
1289
1450
  return scopes.every((scope) => scope === Scope.wildcard || Scope.includes(scopeCatalog, scope))
1290
1451
  }
1291
1452
 
1453
+ function issuableScopes(scopes: readonly string[], scopeCatalog: Scope.Catalog): boolean {
1454
+ return scopes.every((scope) => scope === Scope.wildcard || Scope.issuable(scopeCatalog, scope))
1455
+ }
1456
+
1457
+ /** Drops retired per-Zone scopes a copied or republished key still holds; the rewrite has tagged it, so they grant nothing new. */
1458
+ function withoutRetired(scopes: readonly Scope.Id[]): readonly Scope.Id[] {
1459
+ return scopes.filter((scope) => Scope.parseZone(scope) === undefined)
1460
+ }
1461
+
1462
+ /** Whether scopes broadcast through the generic Zone write scope, whose tags delegations bound. */
1463
+ function boundByDelegations(scopes: readonly string[]): boolean {
1464
+ return scopes.includes('zone:write')
1465
+ }
1466
+
1467
+ /**
1468
+ * Refuses tags naming Zones the organization is not delegated in the key's
1469
+ * environment. Broadcasts never check accounts or tokens, so the delegation's
1470
+ * existence is what bounds a `zone:write` key; self-serve read keys are held
1471
+ * to the same bound by the management routes.
1472
+ */
1473
+ export async function ensureDelegatedTags(db: Db.Db, options: ensureDelegatedTags.Options) {
1474
+ const { environment = 'production', orgId, zoneTags } = options
1475
+ if (!zoneTags?.length) throw new UndelegatedZoneTagsError()
1476
+ const delegated = await delegatedZones(db, { environment, orgId })
1477
+ const undelegated = zoneTags.filter((chainId) => !delegated.has(chainId))
1478
+ if (undelegated.length) throw new UndelegatedZoneTagsError(undelegated)
1479
+ }
1480
+
1481
+ /** The tags among `zoneTags` whose Zone the organization is still delegated; undefined when none remain. */
1482
+ async function delegatedTags(
1483
+ db: Db.Db,
1484
+ options: ensureDelegatedTags.Options,
1485
+ ): Promise<readonly number[] | undefined> {
1486
+ const { environment = 'production', orgId, zoneTags } = options
1487
+ if (!zoneTags?.length) return undefined
1488
+ const delegated = await delegatedZones(db, { environment, orgId })
1489
+ const kept = zoneTags.filter((chainId) => delegated.has(chainId))
1490
+ return kept.length ? kept : undefined
1491
+ }
1492
+
1493
+ /** Chain ids of the organization's active delegations in one environment. */
1494
+ async function delegatedZones(
1495
+ db: Db.Db,
1496
+ owner: ZoneDelegations.Owner,
1497
+ ): Promise<ReadonlySet<number>> {
1498
+ return new Set((await ZoneDelegations.listActive(db, owner)).map(({ chainId }) => chainId))
1499
+ }
1500
+
1501
+ export declare namespace ensureDelegatedTags {
1502
+ /** Key owner and tags to check. */
1503
+ type Options = {
1504
+ /** Key environment; delegations are per environment. */
1505
+ environment?: ApiKey.ApiKey['environment'] | undefined
1506
+ /** Owning organization id. */
1507
+ orgId: string
1508
+ /** Tags the key would carry. */
1509
+ zoneTags?: readonly number[] | undefined
1510
+ }
1511
+ }
1512
+
1292
1513
  /** Storage-key prefix for the `id → recordKey` index. */
1293
1514
  const idPrefix = 'apikey_id:'
1294
1515
 
@@ -1429,6 +1650,18 @@ export class InvalidInputError extends Error {
1429
1650
  }
1430
1651
  }
1431
1652
 
1653
+ /** Thrown when a Zone key would carry tags outside its organization's active delegations. */
1654
+ export class UndelegatedZoneTagsError extends InvalidInputError {
1655
+ override name = 'ApiKeys.UndelegatedZoneTagsError'
1656
+ constructor(chainIds: readonly number[] = []) {
1657
+ super(
1658
+ chainIds.length
1659
+ ? `\`zoneTags\` name Zones the organization is not delegated: ${chainIds.join(', ')}`
1660
+ : 'Zone scopes require `zoneTags` naming Zones the organization is delegated',
1661
+ )
1662
+ }
1663
+ }
1664
+
1432
1665
  /** Thrown when an API key owner is being deleted or is already deleted. */
1433
1666
  export class OwnerDeletedError extends Error {
1434
1667
  override name = 'ApiKeys.OwnerDeletedError'
package/src/App.ts CHANGED
@@ -362,14 +362,13 @@ export function create<const path extends string = '/'>(options: create.Options<
362
362
  }),
363
363
  }),
364
364
  )
365
- // The shadow Zone delegation decision settles once the handler returns, or
366
- // once the deadline below gives up on it, so the log entry carries it either
367
- // way; it never changes the response.
365
+ // Every Zone decision is metered once the handler returns, after later
366
+ // refusals (quota, sandbox, method allowlist) applied to the recorded grant.
368
367
  base.use('*', async (c, next) => {
369
368
  try {
370
369
  await next()
371
370
  } finally {
372
- await ZoneAccess.settle(c)
371
+ ZoneAccess.settle(c)
373
372
  }
374
373
  })
375
374
 
@@ -495,7 +494,7 @@ export function create<const path extends string = '/'>(options: create.Options<
495
494
  // Reads and writes split by HTTP method; a write scope never reads Zone data.
496
495
  // Recorded before workload identity runs so a refusal keeps its Zone attribution.
497
496
  const method = c.req.method.toUpperCase()
498
- const access = ZoneAccess.authorize(c, {
497
+ const access = await ZoneAccess.authorize(c, {
499
498
  chainId,
500
499
  verb: method === 'GET' || method === 'HEAD' ? 'read' : 'write',
501
500
  })