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
@@ -78,6 +78,13 @@ describe('readBillableCounts', () => {
78
78
  request_id: 'request-client-canceled',
79
79
  status: 499,
80
80
  },
81
+ // A Zone request refused because delegations could not be read never bills, nor do its retries.
82
+ {
83
+ ...rows[0]!,
84
+ error_code: 'zone_access_unavailable',
85
+ request_id: 'request-zone-access-unavailable',
86
+ status: 503,
87
+ },
81
88
  // Admitted handlers may already have reached upstreams, so the cancellation bills.
82
89
  {
83
90
  ...rows[0]!,
@@ -103,7 +110,7 @@ describe('readBillableCounts', () => {
103
110
  .toMatchInlineSnapshot(`
104
111
  [
105
112
  {
106
- "count": 16,
113
+ "count": 18,
107
114
  },
108
115
  ]
109
116
  `)
@@ -554,9 +561,12 @@ describe('createQueueSink', () => {
554
561
  test('stores the Zone access decision and trusted client IP', async () => {
555
562
  const chainId = 421_700_001
556
563
  const rows: RequestEvents.Table[] = []
557
- const key = { ...TestApp.key, scopes: ['data:read', `zone:${chainId}:read`] }
564
+ const key = TestApp.zoneReader({ ...TestApp.key, scopes: ['data:read'] }, [chainId])
565
+ const db = TestApp.database()
566
+ await TestApp.delegateZone(db, { chainIds: [chainId] })
558
567
  const app = TestApp.create({
559
568
  auth: { keys: [key] },
569
+ db,
560
570
  metrics: Metrics.cloudflare({
561
571
  analytics: RequestEvents.createQueueSink({
562
572
  async send(row) {
@@ -591,7 +601,7 @@ describe('createQueueSink', () => {
591
601
  "principal_type": "api_key",
592
602
  "zone_accounts": [],
593
603
  "zone_chain_id": 421700001,
594
- "zone_grant": "scope",
604
+ "zone_grant": "delegation",
595
605
  },
596
606
  {
597
607
  "client_ip": null,
@@ -507,7 +507,8 @@ export async function readBillableCounts(
507
507
  'client_canceled',
508
508
  'forbidden',
509
509
  'payment_required',
510
- 'rate_limit_exceeded'
510
+ 'rate_limit_exceeded',
511
+ 'zone_access_unavailable'
511
512
  )
512
513
  AND key_environment = ${ClickHouseValue.literal(options.environment)}
513
514
  ${sandboxGate}
@@ -582,8 +583,8 @@ function event(entry: Log.Entry, context: Metrics.cloudflare.Context): Table {
582
583
  timings: entry.timings ?? {},
583
584
  zone_accounts: entry.zoneAccounts ?? [],
584
585
  zone_chain_id: entry.zoneChainId ?? null,
585
- zone_decision_agreement:
586
- entry.zoneDecisionAgreement === undefined ? null : Number(entry.zoneDecisionAgreement),
586
+ // Retired column: delegations decide admission, so there is no scope decision to compare against.
587
+ zone_decision_agreement: null,
587
588
  zone_delegation_decision: entry.zoneDelegationDecision ?? null,
588
589
  zone_delegation_id: entry.zoneDelegationId ?? null,
589
590
  zone_grant: entry.zoneGrant ?? null,
@@ -211,12 +211,15 @@ describe('zone chain gate', () => {
211
211
  /** A second zone, isolating per-zone grants. */
212
212
  const zoneB = 4_217_000_002
213
213
 
214
- const legacyModeratoReader = {
215
- id: 'key_legacy_moderato',
216
- orgId: 'org_test',
217
- scopes: ['data:read', 'indexer:query', `zone:${legacyModerato}:read`],
218
- token: 'secret_legacy_moderato',
219
- } satisfies TestApp.kvStore.Key
214
+ const legacyModeratoReader = TestApp.zoneReader(
215
+ {
216
+ id: 'key_legacy_moderato',
217
+ orgId: 'org_test',
218
+ scopes: ['data:read', 'indexer:query'],
219
+ token: 'secret_legacy_moderato',
220
+ },
221
+ [moderato],
222
+ )
220
223
 
221
224
  function zoneOptions(
222
225
  chainId: number,
@@ -229,21 +232,22 @@ describe('zone chain gate', () => {
229
232
  })
230
233
  }
231
234
 
232
- /** Zone reader: holds the per-zone scope for `zone` (only). */
233
- const zoneReader = {
234
- id: 'key_zone',
235
- orgId: 'org_test',
236
- scopes: ['data:read', `zone:${zone}:read`],
237
- token: 'secret_zone',
238
- } satisfies TestApp.kvStore.Key
235
+ /** Zone reader: tagged for `zone` (only) under a delegated organization. */
236
+ const zoneReader = TestApp.zoneReader(
237
+ { id: 'key_zone', orgId: 'org_test', scopes: ['data:read'], token: 'secret_zone' },
238
+ [zone],
239
+ )
239
240
 
240
241
  /** Zone reader owned by an organization outside the OIDC rollout. */
241
- const otherOrganizationReader = {
242
- id: 'key_other_organization',
243
- orgId: 'org_other',
244
- scopes: ['data:read', `zone:${zone}:read`],
245
- token: 'secret_other_organization',
246
- } satisfies TestApp.kvStore.Key
242
+ const otherOrganizationReader = TestApp.zoneReader(
243
+ {
244
+ id: 'key_other_organization',
245
+ orgId: 'org_other',
246
+ scopes: ['data:read'],
247
+ token: 'secret_other_organization',
248
+ },
249
+ [zone],
250
+ )
247
251
 
248
252
  /** Zone writer: broadcasts signed transactions to `zone` without reading its data. */
249
253
  const zoneWriter = {
@@ -253,14 +257,40 @@ describe('zone chain gate', () => {
253
257
  token: 'secret_zone_writer',
254
258
  } satisfies TestApp.kvStore.Key
255
259
 
256
- /** Full-access key: the wildcard covers every zone. */
260
+ /** Zone writer through the generic scope, tagged for `zone`. */
261
+ const taggedZoneWriter = {
262
+ id: 'key_tagged_zone_writer',
263
+ orgId: 'org_test',
264
+ scopes: ['data:read', 'zone:write'],
265
+ token: 'secret_tagged_zone_writer',
266
+ zoneTags: [zone],
267
+ } satisfies TestApp.kvStore.Key
268
+
269
+ /** Full-access key: the wildcard reads Zones only through an explicit `zone:read` plus tags. */
257
270
  const admin = {
258
271
  id: 'key_admin',
259
272
  orgId: 'org_test',
260
- scopes: ['*'],
273
+ scopes: ['*', 'zone:read'],
261
274
  token: 'secret_admin',
275
+ zoneTags: [zone, zoneB],
262
276
  } satisfies TestApp.kvStore.Key
263
277
 
278
+ /** Wildcard key with tags but no `zone:read`: the wildcard grants no Zone. */
279
+ const adminUntagged = {
280
+ id: 'key_admin_untagged',
281
+ orgId: 'org_test',
282
+ scopes: ['*'],
283
+ token: 'secret_admin_untagged',
284
+ zoneTags: [zone],
285
+ } satisfies TestApp.kvStore.Key
286
+
287
+ /** Organizations of the readers hold whole-Zone delegations. */
288
+ const db = TestApp.database()
289
+ beforeAll(async () => {
290
+ await TestApp.delegateZone(db, { chainIds: [zone, zoneB, moderato] })
291
+ await TestApp.delegateZone(db, { chainIds: [zone], orgId: 'org_other' })
292
+ })
293
+
264
294
  /** Data reader without any zone grant. */
265
295
  const outsider = {
266
296
  id: 'key_outsider',
@@ -272,7 +302,8 @@ describe('zone chain gate', () => {
272
302
  /** A deployment serving two zones. */
273
303
  function zoneApp() {
274
304
  return TestApp.create({
275
- auth: { keys: [admin, outsider, zoneReader, zoneWriter] },
305
+ auth: { keys: [admin, adminUntagged, outsider, taggedZoneWriter, zoneReader, zoneWriter] },
306
+ db,
276
307
  zones: [zoneOptions(zone), zoneOptions(zoneB)],
277
308
  })
278
309
  }
@@ -366,6 +397,7 @@ describe('zone chain gate', () => {
366
397
  .sign(privateKey)
367
398
  const app = TestApp.create({
368
399
  auth: { keys: [admin, otherOrganizationReader, zoneReader] },
400
+ db,
369
401
  logger: (entry) => void entries.push(entry),
370
402
  vercelOidc,
371
403
  zones: [
@@ -443,7 +475,7 @@ describe('zone chain gate', () => {
443
475
  expect(entries[4]).not.toHaveProperty('vercelOidc')
444
476
  })
445
477
 
446
- test('the legacy Moderato id and read scope route to current upstreams', async () => {
478
+ test('the legacy Moderato id routes to current upstreams', async () => {
447
479
  const fetch = vi
448
480
  .spyOn(globalThis, 'fetch')
449
481
  .mockResolvedValueOnce(Response.json({ id: 1, jsonrpc: '2.0', result: '0x54e89ac3' }))
@@ -451,6 +483,7 @@ describe('zone chain gate', () => {
451
483
  try {
452
484
  const app = TestApp.create({
453
485
  auth: { keys: [legacyModeratoReader] },
486
+ db,
454
487
  rpc: { url: (chainId) => `https://${chainId}.rpc.test` },
455
488
  tidx: { baseUrl: (chainId) => `https://${chainId}.tidx.test` },
456
489
  zones: true,
@@ -476,9 +509,11 @@ describe('zone chain gate', () => {
476
509
  })
477
510
 
478
511
  test('a Zone write scope cannot read Zone data', async () => {
479
- const response = await zoneApp().request(`/v1/tokenlist?chainId=${zone}`, as(zoneWriter))
480
- expect(response.status).toBe(403)
481
- expect(await response.json()).toMatchObject({ error: { code: 'api_key_forbidden' } })
512
+ for (const writer of [zoneWriter, taggedZoneWriter]) {
513
+ const response = await zoneApp().request(`/v1/tokenlist?chainId=${zone}`, as(writer))
514
+ expect(response.status).toBe(403)
515
+ expect(await response.json()).toMatchObject({ error: { code: 'api_key_forbidden' } })
516
+ }
482
517
  })
483
518
 
484
519
  test('zone authorization applies to every data route', async () => {
@@ -497,30 +532,32 @@ describe('zone chain gate', () => {
497
532
  }
498
533
  })
499
534
 
500
- test('the wildcard scope covers every zone', async () => {
535
+ test('a wildcard key reads only the delegated zones its explicit zone:read and tags name', async () => {
501
536
  const app = zoneApp()
502
537
  expect((await app.request(`/v1/tokenlist?chainId=${zone}`, as(admin))).status).toBe(200)
503
538
  expect((await app.request(`/v1/tokenlist?chainId=${zoneB}`, as(admin))).status).toBe(200)
539
+ // The wildcard alone grants no Zone, tags or not.
540
+ expect((await app.request(`/v1/tokenlist?chainId=${zone}`, as(adminUntagged))).status).toBe(403) // prettier-ignore
504
541
  })
505
542
 
506
- test('a key without the zone scope is refused, naming the scope', async () => {
543
+ test('a key without the zone grant is refused, naming the grant', async () => {
507
544
  const app = zoneApp()
508
545
  const response = await app.request(`/v1/transfers?chainId=${zone}`, as(outsider))
509
546
  const body = (await response.json()) as { error: { code: string; message: string } }
510
547
 
511
548
  expect(response.status).toBe(403)
512
549
  expect(body.error.code).toBe('api_key_forbidden')
513
- expect(body.error.message).toContain(`zone:${zone}:read`)
550
+ expect(body.error.message).toContain(`\`zone:read\` and tagged for Zone ${zone}`)
514
551
  expect(body.error.message).not.toContain('X-Authorization-Token')
515
552
  })
516
553
 
517
- test('a zone scope does not grant another zone', async () => {
554
+ test('a zone tag does not grant another zone', async () => {
518
555
  const app = zoneApp()
519
556
  const response = await app.request(`/v1/transfers?chainId=${zoneB}`, as(zoneReader))
520
557
  const body = (await response.json()) as { error: { code: string; message: string } }
521
558
 
522
559
  expect(response.status).toBe(403)
523
- expect(body.error.message).toContain(`zone:${zoneB}:read`)
560
+ expect(body.error.message).toContain(`tagged for Zone ${zoneB}`)
524
561
  })
525
562
 
526
563
  test('a caller-signed Zone token cannot authorize REST data', async () => {
@@ -638,12 +675,12 @@ describe('sandbox chain guard', () => {
638
675
  })
639
676
 
640
677
  test('rejects a sandbox key on a mainnet-backed Zone', async () => {
641
- const key = {
642
- ...sandbox,
643
- scopes: ['data:read', `zone:${mainnetZone}:read`],
644
- } satisfies TestApp.kvStore.Key
678
+ const key = TestApp.zoneReader({ ...sandbox, scopes: ['data:read'] }, [mainnetZone])
679
+ const db = TestApp.database()
680
+ await TestApp.delegateZone(db, { chainIds: [mainnetZone], environment: 'sandbox', orgId: key.orgId }) // prettier-ignore
645
681
  const client = TestApp.client({
646
682
  auth: { keys: [key] },
683
+ db,
647
684
  zones: [
648
685
  TestApp.zone({
649
686
  chainId: mainnetZone,
@@ -190,6 +190,7 @@ export function data(options: data.Options = {}) {
190
190
  message: '`include=zones` requires an API key with readable Zone scopes.',
191
191
  status: 403,
192
192
  })
193
+ await ZoneAccess.ensure(c)
193
194
  const zoneChainIds = ZoneSelection.readableChainIds(c, {
194
195
  parentChainId: selection.chainId,
195
196
  principal,
@@ -197,23 +198,33 @@ export function data(options: data.Options = {}) {
197
198
  tidx: tidx_config,
198
199
  zones,
199
200
  })
200
- if (zoneChainIds.length === 0)
201
+ // Each candidate Zone is decided on its own against the request's
202
+ // addresses; the selection keeps the admitted ones and the recorded
203
+ // decision is an admitted one, before workload identity runs.
204
+ const zoneChainIds_readable: number[] = []
205
+ let unavailable: ZoneAccess.check.Denial | undefined
206
+ for (const zoneChainId of zoneChainIds) {
207
+ const access = await ZoneAccess.authorize(c, { ...pathAddresses(c), chainId: zoneChainId, verb: 'read' }) // prettier-ignore
208
+ if (access.allowed) zoneChainIds_readable.push(zoneChainId)
209
+ else if (access.error.status === 503) unavailable = access.error
210
+ }
211
+ // Answering with only the Zones that could be verified would silently drop the rest.
212
+ if (unavailable) return Response.error(c, unavailable)
213
+ if (zoneChainIds_readable.length === 0)
201
214
  return Response.error(c, {
202
215
  code: 'api_key_forbidden',
203
216
  message: 'The API key has no readable configured Zones.',
204
217
  status: 403,
205
218
  })
206
- // Every selected Zone is readable; record one granted decision before
207
- // workload identity runs so a refusal keeps its Zone attribution.
208
- for (const zoneChainId of zoneChainIds)
209
- ZoneAccess.authorize(c, { ...pathAddresses(c), chainId: zoneChainId, verb: 'read' })
219
+ const last = zoneChainIds_readable.at(-1)
220
+ c.set('zoneAccess', c.get('zoneAccessDecisions')?.find((decision) => decision.chainId === last)) // prettier-ignore
210
221
  const policy = c.get('vercelOidc')
211
222
  if (policy?.applies(principal)) {
212
223
  const results = await Auth.check(c, {
213
224
  allows: (results) => results.every((result) => !VercelOidc.authorizationError(result)),
214
225
  fn: () =>
215
226
  Promise.all(
216
- zoneChainIds.flatMap((chainId) => {
227
+ zoneChainIds_readable.flatMap((chainId) => {
217
228
  const zone = zones.get(chainId)
218
229
  if (zone?.sourceId === undefined || !Viem.isMainnet(zone.sourceId)) return []
219
230
  return [policy.verify(c.req.raw, zone)]
@@ -230,8 +241,8 @@ export function data(options: data.Options = {}) {
230
241
  return Response.error(c, error)
231
242
  }
232
243
  }
233
- c.set('supportedChainIds', new Set([selection.chainId, ...zoneChainIds]))
234
- c.set('zoneChainIds', zoneChainIds)
244
+ c.set('supportedChainIds', new Set([selection.chainId, ...zoneChainIds_readable]))
245
+ c.set('zoneChainIds', zoneChainIds_readable)
235
246
  return next()
236
247
  }
237
248
  c.set('chainId', selection.chainId)
@@ -260,7 +271,7 @@ export function data(options: data.Options = {}) {
260
271
  // Record the attempt first so a workload-identity refusal keeps its Zone
261
272
  // attribution; the raw RPC handler re-records per method afterwards.
262
273
  const zoneToken = Boolean(c.req.header(ZoneRpcAuthentication.headerName)?.trim())
263
- const access = ZoneAccess.authorize(c, {
274
+ const access = await ZoneAccess.authorize(c, {
264
275
  ...pathAddresses(c),
265
276
  chainId,
266
277
  verb: 'read',
@@ -281,15 +292,20 @@ export function data(options: data.Options = {}) {
281
292
  return Response.error(c, error)
282
293
  }
283
294
  }
284
- // Raw RPC decides and records per method in its handler; this gate only
285
- // refuses callers holding no Zone grant at all.
295
+ // Raw RPC decides and records per method with the batch's accounts in its
296
+ // handler; this gate only refuses callers holding no Zone grant at all.
286
297
  if (selection.rpc) {
298
+ const write = ZoneAccess.check(c, { chainId, verb: 'write', zoneToken })
299
+ // An unreadable delegation passes too: the handler answers broadcasts 503 and reads the key cannot perform 403.
287
300
  const granted =
288
- access.allowed || ZoneAccess.check(c, { chainId, verb: 'write', zoneToken }).allowed
301
+ access.allowed ||
302
+ ZoneAccess.eligible(c, { chainId }) ||
303
+ write.allowed ||
304
+ write.error.status === 503
289
305
  if (!granted) {
290
306
  return Response.error(c, {
291
307
  code: 'api_key_forbidden',
292
- message: `Chain ID ${chainId} is a Zone. Use an API key granting \`zone:${chainId}:read\` (or \`zone:${chainId}:write\` for broadcasts), or pass a Zone token via \`X-Authorization-Token\`.`,
308
+ message: `Chain ID ${chainId} is a Zone. Use an API key granting \`zone:read\` and tagged for Zone ${chainId}, issued to an organization delegated that Zone (or \`zone:write\` with a tag for Zone ${chainId} for broadcasts), or pass a Zone token via \`X-Authorization-Token\`.`,
293
309
  status: 403,
294
310
  })
295
311
  }
@@ -53,29 +53,23 @@ describe('transaction activity Zone query', () => {
53
53
  })
54
54
 
55
55
  describe('readableChainIds', () => {
56
- test('intersects the parent chain, read scopes, and configured routable Zones', () => {
57
- const principal = apiKeyPrincipal({
58
- scopes: [
59
- 'data:read',
60
- `zone:${Zones.zoneMainnetInternal.id}:read`,
61
- `zone:${Zones.zoneModeratoInternal.id}:read`,
62
- ],
63
- })
56
+ const both = [Zones.zoneMainnetInternal.id, Zones.zoneModeratoInternal.id]
57
+ const hosted = new Map(Zones.chains.map((zone) => [zone.id, zone]))
64
58
 
65
- expect(
66
- readable({
67
- parentChainId: Viem.chainId.testnet,
68
- principal,
69
- zones: new Map(Zones.chains.map((zone) => [zone.id, zone])),
70
- }),
71
- ).toStrictEqual([Zones.zoneModeratoInternal.id])
72
- expect(
73
- readable({
74
- parentChainId: Viem.chainId.mainnet,
75
- principal,
76
- zones: new Map(Zones.chains.map((zone) => [zone.id, zone])),
77
- }),
78
- ).toStrictEqual([Zones.zoneMainnetInternal.id])
59
+ test('intersects the parent chain, tags, delegations, and configured routable Zones', () => {
60
+ const principal = apiKeyPrincipal({ scopes: ['data:read', 'zone:read'], zoneTags: both })
61
+
62
+ expect(readable({ parentChainId: Viem.chainId.testnet, principal, zones: hosted }, both)).toStrictEqual([Zones.zoneModeratoInternal.id]) // prettier-ignore
63
+ expect(readable({ parentChainId: Viem.chainId.mainnet, principal, zones: hosted }, both)).toStrictEqual([Zones.zoneMainnetInternal.id]) // prettier-ignore
64
+ })
65
+
66
+ test('requires both a tag and a delegation for each Zone', () => {
67
+ const tagged = apiKeyPrincipal({ scopes: ['data:read', 'zone:read'], zoneTags: [Zones.zoneModeratoInternal.id] }) // prettier-ignore
68
+ const untagged = apiKeyPrincipal({ scopes: ['data:read', 'zone:read'] })
69
+
70
+ expect(readable({ parentChainId: Viem.chainId.testnet, principal: tagged, zones: hosted }, both)).toStrictEqual([Zones.zoneModeratoInternal.id]) // prettier-ignore
71
+ expect(readable({ parentChainId: Viem.chainId.testnet, principal: tagged, zones: hosted }, [Zones.zoneMainnetInternal.id])).toStrictEqual([]) // prettier-ignore
72
+ expect(readable({ parentChainId: Viem.chainId.testnet, principal: untagged, zones: hosted }, both)).toStrictEqual([]) // prettier-ignore
79
73
  })
80
74
 
81
75
  test('never treats broadcast-only Zone write scopes as readable', () => {
@@ -88,49 +82,26 @@ describe('readableChainIds', () => {
88
82
  })
89
83
 
90
84
  for (const parentChainId of [Viem.chainId.mainnet, Viem.chainId.testnet])
91
- expect(
92
- readable({
93
- parentChainId,
94
- principal,
95
- zones: new Map(Zones.chains.map((zone) => [zone.id, zone])),
96
- }),
97
- ).toStrictEqual([])
85
+ expect(readable({ parentChainId, principal, zones: hosted }, both)).toStrictEqual([])
98
86
  })
99
87
 
100
- test('lets wildcard keys read every configured routable Zone of the parent chain', () => {
88
+ test('lets wildcard keys read delegated Zones only through an explicit zone:read and tags', () => {
101
89
  expect(
102
- readable({
103
- parentChainId: Viem.chainId.testnet,
104
- principal: apiKeyPrincipal({ scopes: ['*'] }),
105
- zones: new Map(Zones.chains.map((zone) => [zone.id, zone])),
106
- }),
90
+ readable(
91
+ { parentChainId: Viem.chainId.testnet, principal: apiKeyPrincipal({ scopes: ['*', 'zone:read'], zoneTags: both }), zones: hosted }, // prettier-ignore
92
+ both,
93
+ ),
107
94
  ).toStrictEqual([Zones.zoneModeratoInternal.id])
95
+ expect(
96
+ readable({ parentChainId: Viem.chainId.testnet, principal: apiKeyPrincipal({ scopes: ['*'], zoneTags: both }), zones: hosted }, both), // prettier-ignore
97
+ ).toStrictEqual([])
108
98
  })
109
99
 
110
100
  test('filters sandbox keys by each Zone parent chain', () => {
111
- const principal = apiKeyPrincipal({
112
- environment: 'sandbox',
113
- scopes: [
114
- 'data:read',
115
- `zone:${Zones.zoneMainnetInternal.id}:read`,
116
- `zone:${Zones.zoneModeratoInternal.id}:read`,
117
- ],
118
- })
101
+ const principal = apiKeyPrincipal({ environment: 'sandbox', scopes: ['data:read', 'zone:read'], zoneTags: both }) // prettier-ignore
119
102
 
120
- expect(
121
- readable({
122
- parentChainId: Viem.chainId.testnet,
123
- principal,
124
- zones: new Map(Zones.chains.map((zone) => [zone.id, zone])),
125
- }),
126
- ).toStrictEqual([Zones.zoneModeratoInternal.id])
127
- expect(
128
- readable({
129
- parentChainId: Viem.chainId.mainnet,
130
- principal,
131
- zones: new Map(Zones.chains.map((zone) => [zone.id, zone])),
132
- }),
133
- ).toStrictEqual([])
103
+ expect(readable({ parentChainId: Viem.chainId.testnet, principal, zones: hosted }, both)).toStrictEqual([Zones.zoneModeratoInternal.id]) // prettier-ignore
104
+ expect(readable({ parentChainId: Viem.chainId.mainnet, principal, zones: hosted }, both)).toStrictEqual([]) // prettier-ignore
134
105
  })
135
106
 
136
107
  test('requires data read access and both upstreams', () => {
@@ -142,18 +113,16 @@ describe('readableChainIds', () => {
142
113
  const zones = new Map([[zone.id, zone]])
143
114
 
144
115
  expect(
145
- readable({
146
- parentChainId: zone.sourceId,
147
- principal: apiKeyPrincipal({ scopes: [`zone:${zone.id}:read`] }),
148
- zones,
149
- }),
116
+ readable(
117
+ { parentChainId: zone.sourceId, principal: apiKeyPrincipal({ scopes: ['zone:read'], zoneTags: [zone.id] }), zones }, // prettier-ignore
118
+ [zone.id],
119
+ ),
150
120
  ).toStrictEqual([])
151
121
  expect(
152
- readable({
153
- parentChainId: zone.sourceId,
154
- principal: apiKeyPrincipal({ scopes: ['data:read', `zone:${zone.id}:read`] }),
155
- zones,
156
- }),
122
+ readable(
123
+ { parentChainId: zone.sourceId, principal: apiKeyPrincipal({ scopes: ['data:read', 'zone:read'], zoneTags: [zone.id] }), zones }, // prettier-ignore
124
+ [zone.id],
125
+ ),
157
126
  ).toStrictEqual([])
158
127
  })
159
128
  })
@@ -191,12 +160,24 @@ describe('uniqueMatch', () => {
191
160
  })
192
161
  })
193
162
 
194
- /** Runs `readableChainIds` on a context carrying the principal. */
195
- function readable(options: ZoneSelection.readableChainIds.Options) {
163
+ /** Runs `readableChainIds` on a context carrying the principal and its organization's whole-Zone delegations. */
164
+ function readable(options: ZoneSelection.readableChainIds.Options, delegated: readonly number[]) {
196
165
  const c = new Context(new Request('http://tempo-api.test/'))
197
166
  c.set('principal' as never, options.principal as never)
167
+ const records = delegated.map((chainId) => ({
168
+ accountsAll: true,
169
+ chainId,
170
+ createdAt: '2026-01-01T00:00:00.000Z',
171
+ environment: options.principal.environment,
172
+ id: `zd_${chainId}`,
173
+ orgId: options.principal.orgId,
174
+ projects: 'all',
175
+ tokensAll: true,
176
+ updatedAt: '2026-01-01T00:00:00.000Z',
177
+ }))
178
+ c.set('zoneDelegations' as never, { records } as never)
198
179
  const chainIds = ZoneSelection.readableChainIds(c as never, options)
199
- // Filtering decides visibility only; the data gate records the selected Zones.
180
+ // Filtering decides eligibility only; the data gate decides and records the selected Zones.
200
181
  expect(c.get('zoneAccess' as never)).toBeUndefined()
201
182
  return chainIds
202
183
  }
@@ -216,6 +197,7 @@ function apiKeyPrincipal(
216
197
  id: apiKey.id,
217
198
  orgId: apiKey.orgId,
218
199
  type: 'api_key',
200
+ ...(options.zoneTags === undefined ? {} : { zoneTags: options.zoneTags }),
219
201
  }
220
202
  }
221
203
 
@@ -223,5 +205,6 @@ declare namespace apiKeyPrincipal {
223
205
  type Options = {
224
206
  environment?: 'production' | 'sandbox' | undefined
225
207
  scopes: readonly string[]
208
+ zoneTags?: readonly number[] | undefined
226
209
  }
227
210
  }
@@ -7,7 +7,7 @@ import * as Viem from '../../internal/Viem.js'
7
7
  import * as ZoneAccess from '../../internal/ZoneAccess.js'
8
8
  import * as Scope from '../../Scope.js'
9
9
 
10
- /** Returns configured and routable Zones that an API key may read. */
10
+ /** Returns configured and routable Zones an API key is eligible to read, after `ZoneAccess.ensure` settled; the gate decides each with the request addresses. */
11
11
  export function readableChainIds<environment extends ZoneAccess.Environment>(
12
12
  c: Context<environment>,
13
13
  options: readableChainIds.Options,
@@ -18,7 +18,7 @@ export function readableChainIds<environment extends ZoneAccess.Environment>(
18
18
 
19
19
  const chainIds = [...zones.values()].flatMap((zone) => {
20
20
  if (zone.sourceId !== parentChainId) return []
21
- if (!ZoneAccess.check(c, { chainId: zone.id, verb: 'read' }).allowed) return []
21
+ if (!ZoneAccess.eligible(c, { chainId: zone.id })) return []
22
22
  if (
23
23
  principal.environment === 'sandbox' &&
24
24
  (zone.sourceId === undefined || Viem.isMainnet(zone.sourceId))