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.
- package/README.md +13 -5
- package/dist/ApiKey.d.ts +2 -2
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKey.js +2 -2
- package/dist/ApiKey.js.map +1 -1
- package/dist/ApiKeys.d.ts +85 -11
- package/dist/ApiKeys.d.ts.map +1 -1
- package/dist/ApiKeys.js +218 -56
- package/dist/ApiKeys.js.map +1 -1
- package/dist/App.d.ts +12543 -13912
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +4 -5
- package/dist/App.js.map +1 -1
- package/dist/Scope.d.ts +12 -20
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +20 -36
- package/dist/Scope.js.map +1 -1
- package/dist/admin/App.d.ts +871 -828
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/App.js +14 -26
- package/dist/admin/App.js.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +93 -186
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +3 -5
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/audit-logs.d.ts +18 -18
- package/dist/admin/apps/audit-logs.js +1 -1
- package/dist/admin/apps/audit-logs.js.map +1 -1
- package/dist/admin/apps/earn-vaults.d.ts +27 -27
- package/dist/admin/apps/earn-vaults.d.ts.map +1 -1
- package/dist/admin/apps/earn-vaults.js +11 -5
- package/dist/admin/apps/earn-vaults.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts +17 -17
- package/dist/admin/apps/prop-amms.d.ts +4 -4
- package/dist/admin/apps/routes-support.js +1 -38
- package/dist/admin/apps/routes-support.js.map +1 -1
- package/dist/admin/apps/routes.d.ts +153 -153
- package/dist/admin/apps/sentinel.d.ts +4 -4
- package/dist/admin/apps/verified-tokens.d.ts +24 -24
- package/dist/admin/apps/zone-delegation-requests.d.ts +332 -0
- package/dist/admin/apps/zone-delegation-requests.d.ts.map +1 -0
- package/dist/admin/apps/zone-delegation-requests.js +200 -0
- package/dist/admin/apps/zone-delegation-requests.js.map +1 -0
- package/dist/admin/apps/zone-delegations.d.ts +512 -52
- package/dist/admin/apps/zone-delegations.d.ts.map +1 -1
- package/dist/admin/apps/zone-delegations.js +134 -44
- package/dist/admin/apps/zone-delegations.js.map +1 -1
- package/dist/admin/index.d.ts +1 -1
- package/dist/admin/index.d.ts.map +1 -1
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.d.ts.map +1 -1
- package/dist/admin/ui.generated.js +341 -397
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
- package/dist/analytics/tables/requestEvents.js +4 -2
- package/dist/analytics/tables/requestEvents.js.map +1 -1
- package/dist/apps/data/App.d.ts +11932 -13214
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +32 -13
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/ZoneSelection.d.ts +1 -1
- package/dist/apps/data/ZoneSelection.d.ts.map +1 -1
- package/dist/apps/data/ZoneSelection.js +2 -2
- package/dist/apps/data/ZoneSelection.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +60 -27
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +91 -16
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +893 -875
- package/dist/apps/data/routes/addresses.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +36 -36
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +3 -1
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/blocks.d.ts +40 -40
- package/dist/apps/data/routes/blocks.d.ts.map +1 -1
- package/dist/apps/data/routes/blocks.js +3 -7
- package/dist/apps/data/routes/blocks.js.map +1 -1
- package/dist/apps/data/routes/coingecko.d.ts +236 -236
- package/dist/apps/data/routes/earn-transactions.d.ts +14 -14
- package/dist/apps/data/routes/earn.d.ts +261 -1445
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +64 -490
- package/dist/apps/data/routes/earn.js.map +1 -1
- package/dist/apps/data/routes/exchanges.d.ts +144 -144
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +5 -15
- package/dist/apps/data/routes/exchanges.js.map +1 -1
- package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
- package/dist/apps/data/routes/fee-amm.js +3 -9
- package/dist/apps/data/routes/fee-amm.js.map +1 -1
- package/dist/apps/data/routes/indexer.d.ts +13 -13
- package/dist/apps/data/routes/receipts.d.ts +53 -53
- package/dist/apps/data/routes/receipts.js +1 -1
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/rpc.d.ts +52 -0
- package/dist/apps/data/routes/rpc.d.ts.map +1 -1
- package/dist/apps/data/routes/rpc.js +19 -18
- package/dist/apps/data/routes/rpc.js.map +1 -1
- package/dist/apps/data/routes/tokenlist.d.ts +14 -14
- package/dist/apps/data/routes/tokenlist.d.ts.map +1 -1
- package/dist/apps/data/routes/tokenlist.js +1 -3
- package/dist/apps/data/routes/tokenlist.js.map +1 -1
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/tokens.js +7 -5
- package/dist/apps/data/routes/tokens.js.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +281 -281
- package/dist/apps/data/routes/transactions.js +3 -3
- package/dist/apps/data/routes/transactions.js.map +1 -1
- package/dist/apps/data/routes/transfers.d.ts +12 -12
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +1 -3
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/valuation.d.ts +30 -30
- package/dist/apps/data/routes/valuation.d.ts.map +1 -1
- package/dist/apps/data/routes/valuation.js +1 -3
- package/dist/apps/data/routes/valuation.js.map +1 -1
- package/dist/apps/data/routes/verified-tokens.d.ts +86 -86
- package/dist/apps/data/routes/webhooks.d.ts +102 -102
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +9 -23
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/data/routes/zone-deposits.d.ts +16 -16
- package/dist/apps/data/routes/zone-withdrawals.d.ts +31 -31
- package/dist/apps/data/routes/zones.d.ts +28 -28
- package/dist/apps/management/App.d.ts +18 -102
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/App.js +1 -1
- package/dist/apps/management/App.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +253 -339
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +25 -11
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +92 -92
- package/dist/apps/management/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js +5 -13
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/cli.d.ts.map +1 -1
- package/dist/apps/management/routes/cli.js +3 -9
- package/dist/apps/management/routes/cli.js.map +1 -1
- package/dist/apps/management/routes/faucet.d.ts +16 -16
- package/dist/apps/management/routes/fee-payer-usage.d.ts +38 -38
- package/dist/apps/management/routes/fee-payer-usage.d.ts.map +1 -1
- package/dist/apps/management/routes/fee-payer-usage.js +2 -4
- package/dist/apps/management/routes/fee-payer-usage.js.map +1 -1
- package/dist/apps/management/routes/invitations.d.ts +52 -52
- package/dist/apps/management/routes/invitations.d.ts.map +1 -1
- package/dist/apps/management/routes/invitations.js +4 -12
- package/dist/apps/management/routes/invitations.js.map +1 -1
- package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
- package/dist/apps/management/routes/invite-links.js +6 -14
- package/dist/apps/management/routes/invite-links.js.map +1 -1
- package/dist/apps/management/routes/me.d.ts +6 -6
- package/dist/apps/management/routes/me.d.ts.map +1 -1
- package/dist/apps/management/routes/me.js +3 -7
- package/dist/apps/management/routes/me.js.map +1 -1
- package/dist/apps/management/routes/members.d.ts +84 -84
- package/dist/apps/management/routes/members.d.ts.map +1 -1
- package/dist/apps/management/routes/members.js +2 -4
- package/dist/apps/management/routes/members.js.map +1 -1
- package/dist/apps/management/routes/orgs.d.ts +44 -44
- package/dist/apps/management/routes/orgs.d.ts.map +1 -1
- package/dist/apps/management/routes/orgs.js +2 -6
- package/dist/apps/management/routes/orgs.js.map +1 -1
- package/dist/apps/management/routes/projects.d.ts.map +1 -1
- package/dist/apps/management/routes/projects.js +2 -6
- package/dist/apps/management/routes/projects.js.map +1 -1
- package/dist/apps/management/routes/route-usage.d.ts +34 -34
- package/dist/apps/management/routes/route-usage.d.ts.map +1 -1
- package/dist/apps/management/routes/route-usage.js +2 -4
- package/dist/apps/management/routes/route-usage.js.map +1 -1
- package/dist/apps/management/routes/routes-access-requests.d.ts +120 -91
- package/dist/apps/management/routes/routes-access-requests.d.ts.map +1 -1
- package/dist/apps/management/routes/routes-access-requests.js +49 -9
- package/dist/apps/management/routes/routes-access-requests.js.map +1 -1
- package/dist/apps/management/routes/usage.d.ts.map +1 -1
- package/dist/apps/management/routes/usage.js +8 -16
- package/dist/apps/management/routes/usage.js.map +1 -1
- package/dist/apps/management/routes/verified-token-requests.d.ts +39 -39
- package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -1
- package/dist/apps/management/routes/verified-token-requests.js +3 -7
- package/dist/apps/management/routes/verified-token-requests.js.map +1 -1
- package/dist/apps/management/routes/webhooks.d.ts +2089 -2089
- package/dist/apps/management/routes/zone-delegations.d.ts +14 -17
- package/dist/apps/management/routes/zone-delegations.d.ts.map +1 -1
- package/dist/apps/management/routes/zone-delegations.js +1 -6
- package/dist/apps/management/routes/zone-delegations.js.map +1 -1
- package/dist/apps/mpp/App.d.ts +44 -44
- package/dist/apps/mpp/App.js +3 -3
- package/dist/apps/mpp/App.js.map +1 -1
- package/dist/apps/mpp/internal/Adapter.js +1 -1
- package/dist/apps/mpp/internal/Adapter.js.map +1 -1
- package/dist/apps/mpp/internal/Error.d.ts.map +1 -1
- package/dist/apps/mpp/internal/Error.js +1 -0
- package/dist/apps/mpp/internal/Error.js.map +1 -1
- package/dist/apps/mpp/internal/Policy.d.ts +5 -1
- package/dist/apps/mpp/internal/Policy.d.ts.map +1 -1
- package/dist/apps/mpp/internal/Policy.js +35 -13
- package/dist/apps/mpp/internal/Policy.js.map +1 -1
- package/dist/apps/relay/App.d.ts +2 -2
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +25 -16
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/apps/routes/App.d.ts +311 -311
- package/dist/apps/routes/routes/deposit-addresses.d.ts +1099 -1099
- package/dist/apps/routes/routes/list.d.ts +74 -74
- package/dist/apps/routes/routes/transfers.d.ts +94 -94
- package/dist/cli/ApiKeys.d.ts +19 -26
- package/dist/cli/ApiKeys.d.ts.map +1 -1
- package/dist/db/Db.d.ts +3 -3
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +27 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +28 -62
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +18 -18
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/rewardRuns.d.ts +21 -0
- package/dist/db/tables/rewardRuns.d.ts.map +1 -1
- package/dist/db/tables/rewardRuns.js +19 -0
- package/dist/db/tables/rewardRuns.js.map +1 -1
- package/dist/db/tables/routesAccessRequests.d.ts +13 -0
- package/dist/db/tables/routesAccessRequests.d.ts.map +1 -1
- package/dist/db/tables/routesAccessRequests.js +12 -0
- package/dist/db/tables/routesAccessRequests.js.map +1 -1
- package/dist/db/tables/zoneDelegationRequests.d.ts +165 -0
- package/dist/db/tables/zoneDelegationRequests.d.ts.map +1 -0
- package/dist/db/tables/{zoneKeyRequests.js → zoneDelegationRequests.js} +43 -42
- package/dist/db/tables/zoneDelegationRequests.js.map +1 -0
- package/dist/db/tables/zoneDelegations.d.ts +11 -5
- package/dist/db/tables/zoneDelegations.d.ts.map +1 -1
- package/dist/db/tables/zoneDelegations.js +21 -3
- package/dist/db/tables/zoneDelegations.js.map +1 -1
- package/dist/handlers/internal/accounts.d.ts +7 -0
- package/dist/handlers/internal/accounts.d.ts.map +1 -0
- package/dist/handlers/internal/{multisig.js → accounts.js} +10 -10
- package/dist/handlers/internal/{multisig.js.map → accounts.js.map} +1 -1
- package/dist/handlers/internal/sponsorship.js +17 -17
- package/dist/handlers/internal/sponsorship.js.map +1 -1
- package/dist/handlers/relay.d.ts +6 -6
- package/dist/handlers/relay.d.ts.map +1 -1
- package/dist/handlers/relay.js +15 -15
- package/dist/handlers/relay.js.map +1 -1
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +4 -8
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/EarnRates.d.ts.map +1 -1
- package/dist/internal/EarnRates.js +1 -3
- package/dist/internal/EarnRates.js.map +1 -1
- package/dist/internal/EarnVaults.js +2 -2
- package/dist/internal/Log.d.ts +2 -4
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +0 -3
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/Response.d.ts +1 -1
- package/dist/internal/Response.d.ts.map +1 -1
- package/dist/internal/Response.js +0 -3
- package/dist/internal/Response.js.map +1 -1
- package/dist/internal/Schema.d.ts +11 -0
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +24 -1
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/ZoneAccess.d.ts +70 -35
- package/dist/internal/ZoneAccess.d.ts.map +1 -1
- package/dist/internal/ZoneAccess.js +312 -131
- package/dist/internal/ZoneAccess.js.map +1 -1
- package/dist/internal/ZoneDelegation.d.ts +7 -19
- package/dist/internal/ZoneDelegation.d.ts.map +1 -1
- package/dist/internal/ZoneDelegation.js +8 -29
- package/dist/internal/ZoneDelegation.js.map +1 -1
- package/dist/internal/{ZoneKeyApproval.d.ts → ZoneDelegationApproval.d.ts} +3 -5
- package/dist/internal/ZoneDelegationApproval.d.ts.map +1 -0
- package/dist/internal/ZoneDelegationApproval.js +16 -0
- package/dist/internal/ZoneDelegationApproval.js.map +1 -0
- package/dist/internal/ZoneKeyRewrite.d.ts +98 -0
- package/dist/internal/ZoneKeyRewrite.d.ts.map +1 -0
- package/dist/internal/ZoneKeyRewrite.js +151 -0
- package/dist/internal/ZoneKeyRewrite.js.map +1 -0
- package/dist/internal/rewards/Campaigns.d.ts +2 -0
- package/dist/internal/rewards/Campaigns.d.ts.map +1 -1
- package/dist/internal/rewards/Campaigns.js.map +1 -1
- package/dist/internal/rewards/Distributions.d.ts.map +1 -1
- package/dist/internal/rewards/Distributions.js +2 -6
- package/dist/internal/rewards/Distributions.js.map +1 -1
- package/dist/internal/rewards/Monitor.d.ts +4 -1
- package/dist/internal/rewards/Monitor.d.ts.map +1 -1
- package/dist/internal/rewards/Monitor.js +19 -2
- package/dist/internal/rewards/Monitor.js.map +1 -1
- package/dist/internal/rewards/Runner.d.ts.map +1 -1
- package/dist/internal/rewards/Runner.js +9 -1
- package/dist/internal/rewards/Runner.js.map +1 -1
- package/dist/internal/routes/Action.d.ts.map +1 -1
- package/dist/internal/routes/Action.js +2 -3
- package/dist/internal/routes/Action.js.map +1 -1
- package/dist/internal/routes/Deposit.d.ts.map +1 -1
- package/dist/internal/routes/Deposit.js +2 -6
- package/dist/internal/routes/Deposit.js.map +1 -1
- package/dist/internal/routes/DepositAddress.d.ts.map +1 -1
- package/dist/internal/routes/DepositAddress.js +4 -10
- package/dist/internal/routes/DepositAddress.js.map +1 -1
- package/dist/internal/routes/Policy.d.ts.map +1 -1
- package/dist/internal/routes/Policy.js +2 -1
- package/dist/internal/routes/Policy.js.map +1 -1
- package/dist/internal/routes/Provider.js +8 -8
- package/dist/internal/routes/Provider.js.map +1 -1
- package/dist/internal/routes/Reconciliation.d.ts.map +1 -1
- package/dist/internal/routes/Reconciliation.js +3 -2
- package/dist/internal/routes/Reconciliation.js.map +1 -1
- package/dist/internal/routes/Transfer.d.ts.map +1 -1
- package/dist/internal/routes/Transfer.js +4 -12
- package/dist/internal/routes/Transfer.js.map +1 -1
- package/dist/internal/routes/UniversalDepositAddress.d.ts.map +1 -1
- package/dist/internal/routes/UniversalDepositAddress.js +3 -6
- package/dist/internal/routes/UniversalDepositAddress.js.map +1 -1
- package/dist/internal/routes/providers/SquidOrder.d.ts.map +1 -1
- package/dist/internal/routes/providers/SquidOrder.js +3 -2
- package/dist/internal/routes/providers/SquidOrder.js.map +1 -1
- package/dist/internal/routes/providers/relay.d.ts.map +1 -1
- package/dist/internal/routes/providers/relay.js +4 -3
- package/dist/internal/routes/providers/relay.js.map +1 -1
- package/package.json +20 -5
- package/src/ApiKey.ts +2 -2
- package/src/ApiKeys.test.ts +284 -51
- package/src/ApiKeys.ts +297 -64
- package/src/App.ts +4 -5
- package/src/Client.test-d.ts +33 -126
- package/src/Scope.test.ts +26 -45
- package/src/Scope.ts +24 -37
- package/src/admin/App.test.ts +51 -4
- package/src/admin/App.ts +25 -33
- package/src/admin/Mcp.test.ts +2 -1
- package/src/admin/apps/api-keys.test.ts +90 -20
- package/src/admin/apps/api-keys.ts +5 -8
- package/src/admin/apps/audit-logs.ts +1 -1
- package/src/admin/apps/earn-vaults.test.ts +19 -19
- package/src/admin/apps/earn-vaults.ts +11 -5
- package/src/admin/apps/routes-support.test.ts +254 -3
- package/src/admin/apps/routes-support.ts +1 -44
- package/src/admin/apps/scopes.test.ts +3 -3
- package/src/admin/apps/zone-delegation-requests.test.ts +246 -0
- package/src/admin/apps/zone-delegation-requests.ts +240 -0
- package/src/admin/apps/zone-delegations.test.ts +218 -58
- package/src/admin/apps/zone-delegations.ts +146 -46
- package/src/admin/index.ts +1 -1
- package/src/admin/ui/src/components/ApiKeyForm.tsx +4 -3
- package/src/admin/ui/src/lib/config.ts +3 -3
- package/src/admin/ui/src/lib/query-client.ts +1 -1
- package/src/admin/ui/src/lib/review-queues.ts +10 -10
- package/src/admin/ui/src/lib/routes-setup.test.ts +1 -2
- package/src/admin/ui/src/lib/routes-setup.ts +3 -5
- package/src/admin/ui/src/lib/scopes.test.ts +3 -6
- package/src/admin/ui/src/lib/scopes.ts +2 -9
- package/src/admin/ui/src/lib/{zone-key-requests.ts → zone-delegation-requests.ts} +27 -31
- package/src/admin/ui/src/routeTree.gen.ts +17 -17
- package/src/admin/ui/src/routes/__root.tsx +1 -1
- package/src/admin/ui/src/routes/api-keys.tsx +2 -15
- package/src/admin/ui/src/routes/audit-logs.tsx +2 -2
- package/src/admin/ui/src/routes/index.tsx +11 -11
- package/src/admin/ui/src/routes/organizations.tsx +3 -1
- package/src/admin/ui/src/routes/organizations_.$orgId.tsx +3 -1
- package/src/admin/ui/src/routes/organizations_.$orgId_.setup.tsx +1 -3
- package/src/admin/ui/src/routes/{zone-key-requests.tsx → zone-delegation-requests.tsx} +228 -172
- package/src/admin/ui/vite.config.ts +14 -1
- package/src/admin/ui.generated.ts +412 -583
- package/src/analytics/tables/requestEvents.test.ts +13 -3
- package/src/analytics/tables/requestEvents.ts +4 -3
- package/src/apps/data/App.test.ts +72 -35
- package/src/apps/data/App.ts +29 -13
- package/src/apps/data/ZoneSelection.test.ts +53 -70
- package/src/apps/data/ZoneSelection.ts +2 -2
- package/src/apps/data/routes/activities.test.ts +140 -6
- package/src/apps/data/routes/activities.ts +124 -18
- package/src/apps/data/routes/balances.ts +5 -1
- package/src/apps/data/routes/blocks.ts +9 -13
- package/src/apps/data/routes/earn-transactions.local.test.ts +6 -1
- package/src/apps/data/routes/earn-transactions.test.ts +7 -3
- package/src/apps/data/routes/earn.bench.ts +0 -14
- package/src/apps/data/routes/earn.discovery.test.ts +4 -39
- package/src/apps/data/routes/earn.local.test.ts +27 -34
- package/src/apps/data/routes/earn.test.ts +172 -490
- package/src/apps/data/routes/earn.ts +103 -652
- package/src/apps/data/routes/exchanges.ts +20 -30
- package/src/apps/data/routes/fee-amm.ts +12 -18
- package/src/apps/data/routes/indexer.test.ts +14 -12
- package/src/apps/data/routes/receipts.ts +1 -1
- package/src/apps/data/routes/rpc.test.ts +23 -18
- package/src/apps/data/routes/rpc.ts +21 -21
- package/src/apps/data/routes/rpc.zone.test.ts +60 -6
- package/src/apps/data/routes/tokenlist.ts +4 -6
- package/src/apps/data/routes/tokens.test.ts +20 -0
- package/src/apps/data/routes/tokens.ts +10 -5
- package/src/apps/data/routes/transactions.ts +3 -3
- package/src/apps/data/routes/transfers.ts +4 -6
- package/src/apps/data/routes/valuation.test.ts +2 -4
- package/src/apps/data/routes/valuation.ts +4 -6
- package/src/apps/data/routes/webhooks.test.ts +1 -5
- package/src/apps/data/routes/webhooks.ts +30 -44
- package/src/apps/data/routes/zones.test.ts +35 -19
- package/src/apps/management/App.ts +3 -1
- package/src/apps/management/routes/api-keys.test.ts +112 -22
- package/src/apps/management/routes/api-keys.ts +30 -15
- package/src/apps/management/routes/billing.ts +17 -25
- package/src/apps/management/routes/cli.ts +12 -18
- package/src/apps/management/routes/fee-payer-usage.ts +5 -4
- package/src/apps/management/routes/invitations.ts +16 -21
- package/src/apps/management/routes/invite-links.ts +18 -26
- package/src/apps/management/routes/me.ts +9 -13
- package/src/apps/management/routes/members.ts +5 -7
- package/src/apps/management/routes/orgs.ts +8 -12
- package/src/apps/management/routes/projects.ts +8 -12
- package/src/apps/management/routes/route-usage.ts +5 -4
- package/src/apps/management/routes/routes-access-requests.test.ts +64 -2
- package/src/apps/management/routes/routes-access-requests.ts +99 -15
- package/src/apps/management/routes/scopes.test.ts +3 -3
- package/src/apps/management/routes/usage.ts +20 -28
- package/src/apps/management/routes/verified-token-requests.ts +9 -13
- package/src/apps/management/routes/zone-delegations.test.ts +3 -5
- package/src/apps/management/routes/zone-delegations.ts +4 -9
- package/src/apps/mpp/App.ts +3 -3
- package/src/apps/mpp/Mpp.zone.test.ts +49 -18
- package/src/apps/mpp/internal/Adapter.ts +1 -1
- package/src/apps/mpp/internal/Error.test.ts +3 -0
- package/src/apps/mpp/internal/Error.ts +1 -0
- package/src/apps/mpp/internal/Policy.test.ts +9 -8
- package/src/apps/mpp/internal/Policy.ts +37 -14
- package/src/apps/relay/App.sandbox.test.ts +15 -14
- package/src/apps/relay/App.test.ts +120 -61
- package/src/apps/relay/App.ts +28 -18
- package/src/apps/relay/App.zone-billing.test.ts +9 -4
- package/src/apps/routes/App.test.ts +21 -22
- package/src/db/Db.ts +34 -3
- package/src/db/Schema.ts +26 -26
- package/src/db/tables/rewardRuns.ts +39 -0
- package/src/db/tables/routesAccessRequests.test.ts +65 -0
- package/src/db/tables/routesAccessRequests.ts +25 -0
- package/src/db/tables/{zoneKeyRequests.test.ts → zoneDelegationRequests.test.ts} +36 -77
- package/src/db/tables/{zoneKeyRequests.ts → zoneDelegationRequests.ts} +69 -52
- package/src/db/tables/zoneDelegations.test.ts +5 -6
- package/src/db/tables/zoneDelegations.ts +25 -8
- package/src/handlers/internal/{multisig.store.test.ts → accounts.store.test.ts} +16 -16
- package/src/handlers/internal/{multisig.test.ts → accounts.test.ts} +85 -82
- package/src/handlers/internal/{multisig.ts → accounts.ts} +9 -9
- package/src/handlers/internal/sponsorship.ts +20 -20
- package/src/handlers/relay.test.ts +48 -46
- package/src/handlers/relay.ts +26 -26
- package/src/internal/Auth.test.ts +6 -2
- package/src/internal/Auth.ts +4 -10
- package/src/internal/EarnRates.ts +4 -6
- package/src/internal/EarnVaults.ts +2 -2
- package/src/internal/Log.test-d.ts +1 -0
- package/src/internal/Log.test.ts +4 -6
- package/src/internal/Log.ts +2 -6
- package/src/internal/Response.ts +0 -3
- package/src/internal/Schema.test.ts +42 -0
- package/src/internal/Schema.ts +36 -1
- package/src/internal/ZoneAccess.test.ts +515 -312
- package/src/internal/ZoneAccess.ts +410 -158
- package/src/internal/ZoneDelegation.test.ts +1 -41
- package/src/internal/ZoneDelegation.ts +10 -48
- package/src/internal/ZoneDelegationApproval.test.ts +22 -0
- package/src/internal/{ZoneKeyApproval.ts → ZoneDelegationApproval.ts} +4 -11
- package/src/internal/ZoneKeyRewrite.ts +231 -0
- package/src/internal/rewards/Campaigns.ts +2 -0
- package/src/internal/rewards/Distributions.ts +8 -12
- package/src/internal/rewards/Monitor.test.ts +64 -0
- package/src/internal/rewards/Monitor.ts +27 -4
- package/src/internal/rewards/Runner.ts +9 -1
- package/src/internal/routes/Action.ts +5 -6
- package/src/internal/routes/Deposit.ts +8 -12
- package/src/internal/routes/DepositAddress.ts +13 -19
- package/src/internal/routes/Policy.ts +2 -1
- package/src/internal/routes/Provider.ts +8 -8
- package/src/internal/routes/Reconciliation.ts +3 -2
- package/src/internal/routes/Transfer.ts +16 -24
- package/src/internal/routes/UniversalDepositAddress.ts +9 -12
- package/src/internal/routes/providers/SquidOrder.ts +3 -2
- package/src/internal/routes/providers/relay.ts +4 -3
- package/dist/admin/apps/zone-key-requests.d.ts +0 -406
- package/dist/admin/apps/zone-key-requests.d.ts.map +0 -1
- package/dist/admin/apps/zone-key-requests.js +0 -183
- package/dist/admin/apps/zone-key-requests.js.map +0 -1
- package/dist/db/tables/zoneKeyRequests.d.ts +0 -289
- package/dist/db/tables/zoneKeyRequests.d.ts.map +0 -1
- package/dist/db/tables/zoneKeyRequests.js.map +0 -1
- package/dist/handlers/internal/multisig.d.ts +0 -7
- package/dist/handlers/internal/multisig.d.ts.map +0 -1
- package/dist/internal/ZoneKeyApproval.d.ts.map +0 -1
- package/dist/internal/ZoneKeyApproval.js +0 -21
- package/dist/internal/ZoneKeyApproval.js.map +0 -1
- package/src/admin/apps/zone-key-requests.test.ts +0 -272
- package/src/admin/apps/zone-key-requests.ts +0 -231
- package/src/internal/ZoneKeyApproval.test.ts +0 -46
|
@@ -1,38 +1,124 @@
|
|
|
1
1
|
import { Context } from 'hono'
|
|
2
2
|
|
|
3
3
|
import * as TestApp from '../../test/App.js'
|
|
4
|
-
import * as
|
|
5
|
-
import * as Projects from '../db/tables/projects.js'
|
|
4
|
+
import * as Db from '../db/Db.js'
|
|
6
5
|
import * as ZoneDelegations from '../db/tables/zoneDelegations.js'
|
|
7
6
|
import * as Metrics from '../Metrics.js'
|
|
8
7
|
import * as Auth from './Auth.js'
|
|
9
8
|
import type * as Log from './Log.js'
|
|
9
|
+
import * as Store from './Store.js'
|
|
10
10
|
import * as Viem from './Viem.js'
|
|
11
11
|
import * as ZoneAccess from './ZoneAccess.js'
|
|
12
12
|
|
|
13
13
|
const chainId = 421_700_001
|
|
14
|
+
const account = `0x${'ab'.repeat(20)}`
|
|
15
|
+
const token = `0x${'cd'.repeat(20)}`
|
|
16
|
+
|
|
17
|
+
/** A whole-zone delegation for the shared test organization. */
|
|
18
|
+
const delegation = {
|
|
19
|
+
accountsAll: true,
|
|
20
|
+
chainId,
|
|
21
|
+
environment: 'production',
|
|
22
|
+
orgId: TestApp.key.orgId,
|
|
23
|
+
tokensAll: true,
|
|
24
|
+
} as const
|
|
14
25
|
|
|
15
26
|
describe('check', () => {
|
|
16
27
|
test.each([
|
|
17
|
-
{ expected: '
|
|
18
|
-
|
|
19
|
-
{ expected: undefined, scopes: [
|
|
20
|
-
{ expected:
|
|
21
|
-
|
|
22
|
-
{ expected: undefined, scopes: [
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
{ delegated: [chainId], expected: 'delegation', scopes: ['zone:read'], verb: 'read', zoneTags: [chainId] }, // prettier-ignore
|
|
29
|
+
// The wildcard grants no Zone; a tagged `*` key reads only through an explicit `zone:read`.
|
|
30
|
+
{ delegated: [chainId], expected: undefined, scopes: ['*'], verb: 'read', zoneTags: [chainId] }, // prettier-ignore
|
|
31
|
+
{ delegated: [chainId], expected: 'delegation', scopes: ['*', 'zone:read'], verb: 'read', zoneTags: [chainId] }, // prettier-ignore
|
|
32
|
+
// Absent tags mean no Zone, whatever the scopes grant.
|
|
33
|
+
{ delegated: [chainId], expected: undefined, scopes: ['zone:read'], verb: 'read', zoneTags: undefined }, // prettier-ignore
|
|
34
|
+
{ delegated: [chainId], expected: undefined, scopes: ['zone:read'], verb: 'read', zoneTags: [chainId + 1] }, // prettier-ignore
|
|
35
|
+
// Tags select among the organization's delegations; they grant nothing alone.
|
|
36
|
+
{
|
|
37
|
+
delegated: [],
|
|
38
|
+
expected: undefined,
|
|
39
|
+
scopes: ['zone:read'],
|
|
40
|
+
verb: 'read',
|
|
41
|
+
zoneTags: [chainId],
|
|
42
|
+
},
|
|
43
|
+
// Delegations never loaded (no `load` ran) fail closed.
|
|
44
|
+
{ delegated: undefined, expected: undefined, scopes: ['zone:read'], verb: 'read', zoneTags: [chainId] }, // prettier-ignore
|
|
45
|
+
// Retired per-Zone read scopes grant nothing, even beside tags and a delegation.
|
|
46
|
+
{ delegated: [chainId], expected: undefined, scopes: [`zone:${chainId}:read`], verb: 'read', zoneTags: [chainId] }, // prettier-ignore
|
|
47
|
+
{ delegated: [chainId], expected: undefined, scopes: ['data:read'], verb: 'read', zoneTags: [chainId] }, // prettier-ignore
|
|
48
|
+
// A stored per-Zone write scope keeps admitting its own Zone this release, without tags or a delegation.
|
|
49
|
+
{ delegated: [], expected: 'scope', scopes: [`zone:${chainId}:write`], verb: 'write', zoneTags: undefined }, // prettier-ignore
|
|
50
|
+
{ delegated: [chainId], expected: undefined, scopes: [`zone:${chainId + 1}:write`], verb: 'write', zoneTags: undefined }, // prettier-ignore
|
|
51
|
+
// The generic write scope broadcasts only to tagged Zones the organization is delegated; revoking the delegation stops it.
|
|
52
|
+
{ delegated: [chainId], expected: 'scope', scopes: ['zone:write'], verb: 'write', zoneTags: [chainId] }, // prettier-ignore
|
|
53
|
+
{ delegated: [], expected: undefined, scopes: ['zone:write'], verb: 'write', zoneTags: [chainId] }, // prettier-ignore
|
|
54
|
+
{ delegated: undefined, expected: undefined, scopes: ['zone:write'], verb: 'write', zoneTags: [chainId] }, // prettier-ignore
|
|
55
|
+
{ delegated: [chainId], expected: undefined, scopes: ['zone:write'], verb: 'write', zoneTags: undefined }, // prettier-ignore
|
|
56
|
+
{ delegated: [chainId], expected: undefined, scopes: ['zone:write'], verb: 'write', zoneTags: [chainId + 1] }, // prettier-ignore
|
|
57
|
+
// The wildcard grants no Zone broadcast, and write scopes never read.
|
|
58
|
+
{ delegated: [chainId], expected: undefined, scopes: ['*'], verb: 'write', zoneTags: [chainId] }, // prettier-ignore
|
|
59
|
+
{ delegated: [chainId], expected: undefined, scopes: ['zone:read'], verb: 'write', zoneTags: [chainId] }, // prettier-ignore
|
|
60
|
+
{ delegated: [chainId], expected: undefined, scopes: ['zone:write'], verb: 'read', zoneTags: [chainId] }, // prettier-ignore
|
|
61
|
+
] as const)(
|
|
62
|
+
'grants $expected to $scopes tagged $zoneTags for $verb with delegations $delegated',
|
|
63
|
+
({ delegated, expected, scopes, verb, zoneTags }) => {
|
|
64
|
+
const c = context({ delegated, scopes, zoneTags })
|
|
65
|
+
const result = ZoneAccess.check(c, { chainId, verb })
|
|
66
|
+
expect(result.allowed ? result.grant : undefined).toBe(expected)
|
|
67
|
+
},
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
test('refuses delegated access as unavailable when the delegation read failed', () => {
|
|
71
|
+
const refusal = (options: {
|
|
72
|
+
scopes: readonly string[]
|
|
73
|
+
verb: ZoneAccess.Verb
|
|
74
|
+
zoneTags?: readonly number[]
|
|
75
|
+
}) => {
|
|
76
|
+
// prettier-ignore
|
|
77
|
+
const c = context(options)
|
|
78
|
+
c.set('zoneDelegations' as never, { failed: true } as never)
|
|
79
|
+
const result = ZoneAccess.check(c, { chainId, verb: options.verb })
|
|
80
|
+
return result.allowed ? result.grant : result.error.status
|
|
81
|
+
}
|
|
82
|
+
expect([
|
|
83
|
+
refusal({ scopes: ['zone:read'], verb: 'read', zoneTags: [chainId] }),
|
|
84
|
+
refusal({ scopes: ['zone:write'], verb: 'write', zoneTags: [chainId] }),
|
|
85
|
+
// Keys no delegation could admit keep their 403.
|
|
86
|
+
refusal({ scopes: ['zone:read'], verb: 'read', zoneTags: [chainId + 1] }),
|
|
87
|
+
refusal({ scopes: ['*'], verb: 'read', zoneTags: [chainId] }),
|
|
88
|
+
// Stored per-Zone write scopes never depend on delegations.
|
|
89
|
+
refusal({ scopes: [`zone:${chainId}:write`], verb: 'write' }),
|
|
90
|
+
]).toEqual([503, 503, 403, 403, 'scope'])
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test('keeps unreadable delegations eligible and fails listings closed with a 503', () => {
|
|
94
|
+
const c = context({ scopes: ['zone:read'], zoneTags: [chainId] })
|
|
95
|
+
c.set('zoneDelegations' as never, { failed: true } as never)
|
|
96
|
+
// Selection keeps the Zone so `authorize` answers the retriable 503 instead of a 403.
|
|
97
|
+
expect(ZoneAccess.eligible(c, { chainId })).toBe(true)
|
|
98
|
+
expect(() => ZoneAccess.visible(c, { chainId })).toThrow(
|
|
99
|
+
expect.objectContaining({ status: 503 }),
|
|
100
|
+
)
|
|
101
|
+
const untagged = context({ scopes: ['zone:read'], zoneTags: [chainId + 1] })
|
|
102
|
+
untagged.set('zoneDelegations' as never, { failed: true } as never)
|
|
103
|
+
expect([ZoneAccess.eligible(untagged, { chainId }), ZoneAccess.visible(untagged, { chainId })]).toEqual([false, false]) // prettier-ignore
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('needs a delegation covering every account and token without the request addresses', () => {
|
|
107
|
+
const c = context({ delegated: [], scopes: ['zone:read'], zoneTags: [chainId] })
|
|
108
|
+
c.set('zoneDelegations' as never, { records: [record({ accountsAll: false })] } as never)
|
|
109
|
+
expect(ZoneAccess.check(c, { chainId, verb: 'read' }).allowed).toBe(false)
|
|
110
|
+
c.set('zoneDelegations' as never, { records: [record({ tokensAll: false })] } as never)
|
|
111
|
+
expect(ZoneAccess.check(c, { chainId, verb: 'read' }).allowed).toBe(false)
|
|
112
|
+
c.set('zoneDelegations' as never, { records: [record({})] } as never)
|
|
113
|
+
expect(ZoneAccess.check(c, { chainId, verb: 'read' }).allowed).toBe(true)
|
|
29
114
|
})
|
|
30
115
|
|
|
31
116
|
test('never records a decision', () => {
|
|
32
|
-
const c = context({ scopes: ['
|
|
117
|
+
const c = context({ delegated: [chainId], scopes: ['zone:read'], zoneTags: [chainId] })
|
|
33
118
|
ZoneAccess.check(c, { chainId, verb: 'read' })
|
|
34
|
-
ZoneAccess.check(context({ scopes: ['*'] }), { chainId, verb: '
|
|
119
|
+
ZoneAccess.check(context({ scopes: ['*'] }), { chainId, verb: 'write' })
|
|
35
120
|
expect(c.get('zoneAccess' as never)).toBeUndefined()
|
|
121
|
+
expect(c.get('zoneAccessDecisions' as never)).toBeUndefined()
|
|
36
122
|
})
|
|
37
123
|
|
|
38
124
|
test('admits a caller Zone token and records it as the grant', () => {
|
|
@@ -68,7 +154,7 @@ describe('check', () => {
|
|
|
68
154
|
"allowed": false,
|
|
69
155
|
"error": {
|
|
70
156
|
"code": "api_key_forbidden",
|
|
71
|
-
"message": "Chain ID 421700001 is a Zone. Use an API key granting \`zone:421700001
|
|
157
|
+
"message": "Chain ID 421700001 is a Zone. Use an API key granting \`zone:read\` and tagged for Zone 421700001, issued to an organization delegated that Zone.",
|
|
72
158
|
"status": 403,
|
|
73
159
|
},
|
|
74
160
|
}
|
|
@@ -91,38 +177,131 @@ describe('check', () => {
|
|
|
91
177
|
denial({ suggestZoneToken: true, verb: 'write' }),
|
|
92
178
|
]).toMatchInlineSnapshot(`
|
|
93
179
|
[
|
|
94
|
-
"Chain ID 421700001 is a Zone. Use an API key granting \`zone:421700001
|
|
95
|
-
"Chain
|
|
96
|
-
"Broadcasting to zone 421700001 requires \`zone:421700001
|
|
97
|
-
"Broadcasting to zone 421700001 requires \`zone:
|
|
180
|
+
"Chain ID 421700001 is a Zone. Use an API key granting \`zone:read\` and tagged for Zone 421700001, issued to an organization delegated that Zone.",
|
|
181
|
+
"Chain ID 421700001 is a Zone. Use an API key granting \`zone:read\` and tagged for Zone 421700001, issued to an organization delegated that Zone, or pass a Zone token via \`X-Authorization-Token\`.",
|
|
182
|
+
"Broadcasting to zone 421700001 requires \`zone:write\` with a tag for Zone 421700001, issued to an organization delegated that Zone.",
|
|
183
|
+
"Broadcasting to zone 421700001 requires \`zone:write\` with a tag for Zone 421700001, issued to an organization delegated that Zone, or a Zone token via \`X-Authorization-Token\`.",
|
|
98
184
|
]
|
|
99
185
|
`)
|
|
100
186
|
})
|
|
101
187
|
})
|
|
102
188
|
|
|
103
189
|
describe('deny', () => {
|
|
104
|
-
test('removes only the grant from the recorded decision', () => {
|
|
105
|
-
const c = context({ scopes: [
|
|
190
|
+
test('removes only the grant from the recorded decision and its metered copy', async () => {
|
|
191
|
+
const c = context({ delegated: [chainId], scopes: ['zone:read'], zoneTags: [chainId] })
|
|
106
192
|
ZoneAccess.deny(c)
|
|
107
193
|
expect(c.get('zoneAccess' as never)).toBeUndefined()
|
|
108
|
-
ZoneAccess.authorize(c, { chainId, verb: 'read' })
|
|
194
|
+
await ZoneAccess.authorize(c, { chainId, verb: 'read' })
|
|
109
195
|
ZoneAccess.deny(c)
|
|
110
196
|
expect(c.get('zoneAccess' as never)).toMatchInlineSnapshot(`
|
|
111
197
|
{
|
|
112
198
|
"accounts": [],
|
|
113
199
|
"chainId": 421700001,
|
|
114
|
-
"
|
|
200
|
+
"delegation": {
|
|
201
|
+
"decision": "allow",
|
|
202
|
+
"id": "zd_421700001",
|
|
203
|
+
},
|
|
115
204
|
"tokens": [],
|
|
116
205
|
"verb": "read",
|
|
117
206
|
}
|
|
118
207
|
`)
|
|
208
|
+
expect(c.get('zoneAccessDecisions' as never)).toEqual([c.get('zoneAccess' as never)])
|
|
209
|
+
})
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
describe('ensure', () => {
|
|
213
|
+
test('loads delegations once for Zone-capable keys and fails closed on read errors', async () => {
|
|
214
|
+
const db = TestApp.database()
|
|
215
|
+
await TestApp.delegateZone(db, { chainIds: [chainId] })
|
|
216
|
+
const counts: string[] = []
|
|
217
|
+
const metrics = Metrics.from({
|
|
218
|
+
count: (name) => void counts.push(name),
|
|
219
|
+
flush() {},
|
|
220
|
+
gauge() {},
|
|
221
|
+
histogram() {},
|
|
222
|
+
})
|
|
223
|
+
const wire = (c: Context, source: Db.Source) => {
|
|
224
|
+
c.set('db' as never, source as never)
|
|
225
|
+
c.set('metrics' as never, metrics as never)
|
|
226
|
+
c.set('store' as never, Store.memory() as never)
|
|
227
|
+
c.set('zones' as never, new Map([[chainId, {}]]) as never)
|
|
228
|
+
return c
|
|
229
|
+
}
|
|
230
|
+
const reader = wire(context({ scopes: ['zone:read'], zoneTags: [chainId] }), db)
|
|
231
|
+
const untagged = wire(context({ scopes: ['zone:read'] }), db)
|
|
232
|
+
const writer = wire(context({ scopes: [`zone:${chainId}:write`] }), db)
|
|
233
|
+
const broken = wire(
|
|
234
|
+
context({ scopes: ['zone:read'], zoneTags: [chainId] }),
|
|
235
|
+
Db.postgres({ connectionString: 'postgresql://unused@127.0.0.1:1/unused' }),
|
|
236
|
+
)
|
|
237
|
+
await Promise.all([reader, untagged, writer, broken].map((c) => ZoneAccess.ensure(c)))
|
|
238
|
+
|
|
239
|
+
expect(
|
|
240
|
+
(reader.get('zoneDelegations' as never) as ZoneAccess.Delegations & { records: unknown[] })
|
|
241
|
+
.records,
|
|
242
|
+
).toMatchObject([{ chainId }])
|
|
243
|
+
expect(untagged.get('zoneDelegations' as never)).toBeUndefined()
|
|
244
|
+
expect(writer.get('zoneDelegations' as never)).toBeUndefined()
|
|
245
|
+
expect(broken.get('zoneDelegations' as never)).toEqual({ failed: true })
|
|
246
|
+
expect(counts).toEqual([ZoneAccess.lookupFailureMetric])
|
|
247
|
+
expect(ZoneAccess.check(broken, { chainId, verb: 'read' }).allowed).toBe(false)
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
test('serves cached delegations while refreshing, and reads Postgres past the maximum age', async () => {
|
|
251
|
+
vi.useFakeTimers({ now: new Date('2026-10-23T20:40:00.000Z'), toFake: ['Date'] })
|
|
252
|
+
try {
|
|
253
|
+
const healthy = TestApp.database()
|
|
254
|
+
await TestApp.delegateZone(healthy, { chainIds: [chainId] })
|
|
255
|
+
const unreachable = Db.postgres({
|
|
256
|
+
connectionString: 'postgresql://unused@127.0.0.1:1/unused',
|
|
257
|
+
})
|
|
258
|
+
let down = false
|
|
259
|
+
const source: Db.Source = () => (down ? unreachable : healthy)
|
|
260
|
+
// One colo cache shared by every request, as `caches.default` is.
|
|
261
|
+
const store = Store.memory()
|
|
262
|
+
const tags: (Metrics.Tags | undefined)[] = []
|
|
263
|
+
const metrics = Metrics.from({
|
|
264
|
+
count: (_name, _value, tag) => void tags.push(tag),
|
|
265
|
+
flush() {},
|
|
266
|
+
gauge() {},
|
|
267
|
+
histogram() {},
|
|
268
|
+
})
|
|
269
|
+
const read = async () => {
|
|
270
|
+
const c = context({ scopes: ['zone:read'], zoneTags: [chainId] })
|
|
271
|
+
c.set('db' as never, source as never)
|
|
272
|
+
c.set('metrics' as never, metrics as never)
|
|
273
|
+
c.set('store' as never, store as never)
|
|
274
|
+
c.set('zones' as never, new Map([[chainId, {}]]) as never)
|
|
275
|
+
await ZoneAccess.ensure(c)
|
|
276
|
+
const result = ZoneAccess.check(c, { chainId, verb: 'read' })
|
|
277
|
+
return result.allowed ? result.grant : result.error.status
|
|
278
|
+
}
|
|
279
|
+
const elapse = (ms: number) => vi.setSystemTime(Date.now() + ms)
|
|
280
|
+
|
|
281
|
+
expect(await read()).toBe('delegation')
|
|
282
|
+
down = true
|
|
283
|
+
// A fresh entry answers without touching Postgres.
|
|
284
|
+
elapse(ZoneAccess.delegationTtl - 1_000)
|
|
285
|
+
expect(await read()).toBe('delegation')
|
|
286
|
+
expect(tags).toEqual([])
|
|
287
|
+
// An aging entry still answers; only its background refresh fails.
|
|
288
|
+
elapse(10_000)
|
|
289
|
+
expect(await read()).toBe('delegation')
|
|
290
|
+
await vi.waitFor(() => expect(tags).toEqual([{ phase: 'refresh' }]))
|
|
291
|
+
// Past the maximum age the request reads Postgres and refuses as unavailable.
|
|
292
|
+
elapse(ZoneAccess.delegationMaxAge)
|
|
293
|
+
expect(await read()).toBe(503)
|
|
294
|
+
expect(tags).toEqual([{ phase: 'refresh' }, { phase: 'request' }])
|
|
295
|
+
} finally {
|
|
296
|
+
vi.useRealTimers()
|
|
297
|
+
}
|
|
119
298
|
})
|
|
120
299
|
})
|
|
121
300
|
|
|
122
301
|
describe('authorize', () => {
|
|
123
|
-
test('records lowercase accounts and tokens, even when denied', () => {
|
|
302
|
+
test('records lowercase accounts and tokens, even when denied', async () => {
|
|
124
303
|
const c = context({ scopes: ['data:read'] })
|
|
125
|
-
ZoneAccess.authorize(c, {
|
|
304
|
+
await ZoneAccess.authorize(c, {
|
|
126
305
|
accounts: [`0x${'AB'.repeat(20)}`],
|
|
127
306
|
chainId,
|
|
128
307
|
tokens: [`0x${'CD'.repeat(20)}`],
|
|
@@ -143,13 +322,9 @@ describe('authorize', () => {
|
|
|
143
322
|
})
|
|
144
323
|
|
|
145
324
|
test('records the accounts a Zone RPC batch reads', async () => {
|
|
146
|
-
const
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
auth: { keys: [reader] },
|
|
150
|
-
logger: (entry) => void entries.push(entry),
|
|
151
|
-
zones: [TestApp.zone({ chainId, rpcUrl: `https://${chainId}.rpc.test` })],
|
|
152
|
-
})
|
|
325
|
+
const reader = TestApp.zoneReader({ ...TestApp.key, scopes: ['data:read'] }, [chainId])
|
|
326
|
+
const { app, db, entries } = await setup([reader])
|
|
327
|
+
await ZoneDelegations.create(db, delegation)
|
|
153
328
|
|
|
154
329
|
await app.request(`/rpc/${chainId}`, {
|
|
155
330
|
body: JSON.stringify([
|
|
@@ -169,25 +344,19 @@ describe('authorize', () => {
|
|
|
169
344
|
"0xabababababababababababababababababababab",
|
|
170
345
|
"0xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
|
|
171
346
|
],
|
|
172
|
-
"zoneGrant": "
|
|
347
|
+
"zoneGrant": "delegation",
|
|
173
348
|
},
|
|
174
349
|
]
|
|
175
350
|
`)
|
|
176
351
|
})
|
|
177
352
|
|
|
178
353
|
test('clears the grant when a later check refuses the request', async () => {
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
scopes: ['data:read', `zone:${chainId + 1}:read`],
|
|
186
|
-
token: 'secret_sandbox',
|
|
187
|
-
}
|
|
188
|
-
const app = TestApp.create({
|
|
189
|
-
auth: { keys: [reader, sandbox] },
|
|
190
|
-
logger: (entry) => void entries.push(entry),
|
|
354
|
+
const reader = TestApp.zoneReader({ ...TestApp.key, scopes: ['data:read'] }, [chainId])
|
|
355
|
+
const sandbox = TestApp.zoneReader(
|
|
356
|
+
{ environment: 'sandbox' as const, id: 'key_sandbox', orgId: TestApp.key.orgId, scopes: ['data:read'], token: 'secret_sandbox' }, // prettier-ignore
|
|
357
|
+
[chainId + 1],
|
|
358
|
+
)
|
|
359
|
+
const { app, db, entries } = await setup([reader, sandbox], {
|
|
191
360
|
zones: [
|
|
192
361
|
TestApp.zone({ chainId, rpcUrl: `https://${chainId}.rpc.test` }),
|
|
193
362
|
TestApp.zone({
|
|
@@ -197,23 +366,26 @@ describe('authorize', () => {
|
|
|
197
366
|
}),
|
|
198
367
|
],
|
|
199
368
|
})
|
|
369
|
+
await ZoneDelegations.create(db, delegation)
|
|
370
|
+
await ZoneDelegations.create(db, { ...delegation, chainId: chainId + 1, environment: 'sandbox' }) // prettier-ignore
|
|
200
371
|
|
|
201
|
-
//
|
|
372
|
+
// The delegation admits the chain, but the method allowlist refuses the broadcast.
|
|
202
373
|
const broadcast = await app.request(`/rpc/${chainId}`, {
|
|
203
374
|
body: JSON.stringify({ id: 1, jsonrpc: '2.0', method: 'eth_sendRawTransaction' }),
|
|
204
375
|
headers: { 'tempo-api-key': reader.token },
|
|
205
376
|
method: 'POST',
|
|
206
377
|
})
|
|
207
|
-
// The sandbox refusal of a mainnet Zone follows the recorded
|
|
378
|
+
// The sandbox refusal of a mainnet Zone follows the recorded grant.
|
|
208
379
|
const mainnet = await app.request(`/v1/tokenlist?chainId=${chainId + 1}`, {
|
|
209
380
|
headers: { 'tempo-api-key': sandbox.token },
|
|
210
381
|
})
|
|
211
382
|
|
|
212
383
|
expect([broadcast.status, mainnet.status]).toStrictEqual([403, 403])
|
|
213
384
|
expect(
|
|
214
|
-
entries.map(({ errorCode, zoneChainId, zoneGrant }) => ({
|
|
385
|
+
entries.map(({ errorCode, zoneChainId, zoneDelegationDecision, zoneGrant }) => ({
|
|
215
386
|
errorCode,
|
|
216
387
|
zoneChainId,
|
|
388
|
+
zoneDelegationDecision,
|
|
217
389
|
zoneGrant,
|
|
218
390
|
})),
|
|
219
391
|
).toMatchInlineSnapshot(`
|
|
@@ -221,11 +393,13 @@ describe('authorize', () => {
|
|
|
221
393
|
{
|
|
222
394
|
"errorCode": "api_key_forbidden",
|
|
223
395
|
"zoneChainId": 421700001,
|
|
396
|
+
"zoneDelegationDecision": undefined,
|
|
224
397
|
"zoneGrant": undefined,
|
|
225
398
|
},
|
|
226
399
|
{
|
|
227
400
|
"errorCode": "api_key_forbidden",
|
|
228
401
|
"zoneChainId": 421700002,
|
|
402
|
+
"zoneDelegationDecision": "allow",
|
|
229
403
|
"zoneGrant": undefined,
|
|
230
404
|
},
|
|
231
405
|
]
|
|
@@ -233,19 +407,15 @@ describe('authorize', () => {
|
|
|
233
407
|
})
|
|
234
408
|
|
|
235
409
|
test('logs Zone reads with the grant, accounts, and trusted client IP', async () => {
|
|
236
|
-
const
|
|
237
|
-
const reader = { ...TestApp.key, scopes: ['data:read', `zone:${chainId}:read`] }
|
|
410
|
+
const reader = TestApp.zoneReader({ ...TestApp.key, scopes: ['data:read'] }, [chainId])
|
|
238
411
|
const outsider = {
|
|
239
412
|
id: 'key_outsider',
|
|
240
413
|
orgId: TestApp.key.orgId,
|
|
241
414
|
scopes: ['data:read'],
|
|
242
415
|
token: 'secret_outsider',
|
|
243
416
|
}
|
|
244
|
-
const app =
|
|
245
|
-
|
|
246
|
-
logger: (entry) => void entries.push(entry),
|
|
247
|
-
zones: [TestApp.zone({ chainId, rpcUrl: `https://${chainId}.rpc.test` })],
|
|
248
|
-
})
|
|
417
|
+
const { app, db, entries } = await setup([outsider, reader])
|
|
418
|
+
await ZoneDelegations.create(db, delegation)
|
|
249
419
|
const granted = new Request(`http://tempo-api.test/v1/tokenlist?chainId=${chainId}`, {
|
|
250
420
|
headers: { 'tempo-api-key': reader.token },
|
|
251
421
|
})
|
|
@@ -297,7 +467,7 @@ describe('authorize', () => {
|
|
|
297
467
|
"status": 200,
|
|
298
468
|
"zoneAccounts": [],
|
|
299
469
|
"zoneChainId": 421700001,
|
|
300
|
-
"zoneGrant": "
|
|
470
|
+
"zoneGrant": "delegation",
|
|
301
471
|
"zoneTokens": [],
|
|
302
472
|
},
|
|
303
473
|
{
|
|
@@ -348,52 +518,119 @@ describe('authorize', () => {
|
|
|
348
518
|
`)
|
|
349
519
|
})
|
|
350
520
|
|
|
351
|
-
test('refuses a
|
|
352
|
-
const
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
logger: (entry) => void entries.push(entry),
|
|
357
|
-
zones: [TestApp.zone({ chainId, rpcUrl: `https://${chainId}.rpc.test` })],
|
|
358
|
-
})
|
|
359
|
-
const read = await app.fetch(
|
|
360
|
-
new Request(`http://tempo-api.test/v1/tokenlist?chainId=${chainId}`, {
|
|
361
|
-
headers: { 'tempo-api-key': generic.token },
|
|
362
|
-
}),
|
|
521
|
+
test('refuses zone:read keys without a tag or without a delegation, and retired per-Zone scopes', async () => {
|
|
522
|
+
const untagged = { ...TestApp.key, scopes: ['data:read', 'zone:read'] }
|
|
523
|
+
const undelegated = TestApp.zoneReader(
|
|
524
|
+
{ ...TestApp.key, id: 'key_undelegated', orgId: 'org_undelegated', scopes: ['data:read'], token: 'secret_undelegated' }, // prettier-ignore
|
|
525
|
+
[chainId],
|
|
363
526
|
)
|
|
527
|
+
const legacy = { ...TestApp.key, id: 'key_legacy', scopes: ['data:read', `zone:${chainId}:read`], token: 'secret_legacy' } // prettier-ignore
|
|
528
|
+
// The additive rewrite left both formats on the record; only the new one grants.
|
|
529
|
+
const both = { ...TestApp.key, id: 'key_both', scopes: ['data:read', `zone:${chainId}:read`, 'zone:read'], token: 'secret_both', zoneTags: [chainId] } // prettier-ignore
|
|
530
|
+
const { app, db, entries } = await setup([untagged, undelegated, legacy, both])
|
|
531
|
+
await ZoneDelegations.create(db, delegation)
|
|
532
|
+
const read = (key: { token: string }) =>
|
|
533
|
+
app.fetch(
|
|
534
|
+
new Request(`http://tempo-api.test/v1/tokenlist?chainId=${chainId}`, {
|
|
535
|
+
headers: { 'tempo-api-key': key.token },
|
|
536
|
+
}),
|
|
537
|
+
)
|
|
364
538
|
const rpc = await app.fetch(
|
|
365
539
|
new Request(`http://tempo-api.test/rpc/${chainId}`, {
|
|
366
540
|
body: JSON.stringify({ id: 1, jsonrpc: '2.0', method: 'eth_chainId' }),
|
|
367
|
-
headers: { 'tempo-api-key':
|
|
541
|
+
headers: { 'tempo-api-key': untagged.token },
|
|
368
542
|
method: 'POST',
|
|
369
543
|
}),
|
|
370
544
|
)
|
|
371
|
-
|
|
372
|
-
|
|
545
|
+
const first = await read(untagged)
|
|
546
|
+
expect([first.status, (await read(undelegated)).status, (await read(legacy)).status, (await read(both)).status, rpc.status]).toEqual([403, 403, 403, 200, 403]) // prettier-ignore
|
|
547
|
+
const { requestId: _requestId, ...error } = (await first.json()).error
|
|
373
548
|
expect(error).toMatchInlineSnapshot(`
|
|
374
549
|
{
|
|
375
550
|
"code": "api_key_forbidden",
|
|
376
|
-
"message": "Chain ID 421700001 is a Zone. Use an API key granting \`zone:421700001
|
|
551
|
+
"message": "Chain ID 421700001 is a Zone. Use an API key granting \`zone:read\` and tagged for Zone 421700001, issued to an organization delegated that Zone.",
|
|
377
552
|
}
|
|
378
553
|
`)
|
|
379
|
-
|
|
554
|
+
// The raw RPC probe logs first; only the rewritten record earns a grant.
|
|
555
|
+
expect(entries.map(({ zoneGrant }) => zoneGrant)).toEqual([undefined, undefined, undefined, undefined, 'delegation']) // prettier-ignore
|
|
556
|
+
})
|
|
557
|
+
|
|
558
|
+
test('refuses reads with a retriable 503 when the delegation read fails', async () => {
|
|
559
|
+
const reader = TestApp.zoneReader({ ...TestApp.key, scopes: ['data:read'] }, [chainId])
|
|
560
|
+
const { app, counts, entries } = await setup([reader], {
|
|
561
|
+
db: Db.postgres({ connectionString: 'postgresql://unused@127.0.0.1:1/unused' }),
|
|
562
|
+
})
|
|
563
|
+
const response = await app.request(`/v1/tokenlist?chainId=${chainId}`, {
|
|
564
|
+
headers: { 'tempo-api-key': reader.token },
|
|
565
|
+
})
|
|
566
|
+
const { requestId: _requestId, ...error } = (await response.json()).error
|
|
567
|
+
expect({ error, status: response.status }).toMatchInlineSnapshot(`
|
|
568
|
+
{
|
|
569
|
+
"error": {
|
|
570
|
+
"code": "zone_access_unavailable",
|
|
571
|
+
"message": "Zone 421700001 access could not be verified. Retry the request.",
|
|
572
|
+
},
|
|
573
|
+
"status": 503,
|
|
574
|
+
}
|
|
575
|
+
`)
|
|
576
|
+
// A query stage on a failed read places the stall in Postgres (through Hyperdrive), not the cache.
|
|
577
|
+
expect(stages(response)).toEqual(expect.arrayContaining(['zone_delegations', 'zone_delegations_cache', 'zone_delegations_query'])) // prettier-ignore
|
|
578
|
+
expect(entries.map(({ errorCode, zoneGrant }) => ({ errorCode, zoneGrant }))).toEqual([{ errorCode: 'zone_access_unavailable', zoneGrant: undefined }]) // prettier-ignore
|
|
579
|
+
expect(counts.filter(({ name }) => name === ZoneAccess.lookupFailureMetric).map(({ tags }) => tags)).toEqual([{ phase: 'request' }]) // prettier-ignore
|
|
580
|
+
})
|
|
581
|
+
|
|
582
|
+
test('refuses raw Zone RPC with a retriable 503 when the delegation read fails', async () => {
|
|
583
|
+
const reader = TestApp.zoneReader({ ...TestApp.key, scopes: ['data:read'] }, [chainId])
|
|
584
|
+
const { app } = await setup([reader], {
|
|
585
|
+
db: Db.postgres({ connectionString: 'postgresql://unused@127.0.0.1:1/unused' }),
|
|
586
|
+
})
|
|
587
|
+
const response = await app.request(`/rpc/${chainId}`, {
|
|
588
|
+
body: JSON.stringify({ id: 1, jsonrpc: '2.0', method: 'eth_chainId' }),
|
|
589
|
+
headers: { 'content-type': 'application/json', 'tempo-api-key': reader.token },
|
|
590
|
+
method: 'POST',
|
|
591
|
+
})
|
|
592
|
+
expect([response.status, (await response.json()).error.code]).toEqual([503, 'zone_access_unavailable']) // prettier-ignore
|
|
593
|
+
})
|
|
594
|
+
|
|
595
|
+
test('refuses Zone broadcasts and inferred Zones with a 503 when the delegation read fails', async () => {
|
|
596
|
+
const reader = TestApp.zoneReader({ ...TestApp.key, scopes: ['data:read'] }, [chainId])
|
|
597
|
+
const writer = { ...TestApp.key, id: 'key_writer', scopes: ['data:read', 'zone:write'], token: 'secret_writer', zoneTags: [chainId] } // prettier-ignore
|
|
598
|
+
const { app } = await setup([reader, writer], {
|
|
599
|
+
db: Db.postgres({ connectionString: 'postgresql://unused@127.0.0.1:1/unused' }),
|
|
600
|
+
})
|
|
601
|
+
const inferred = await app.request(`/v1/addresses/${account}/activities?include=zones`, {
|
|
602
|
+
headers: { 'tempo-api-key': reader.token },
|
|
603
|
+
})
|
|
604
|
+
const rpc = (method: string, params: readonly unknown[]) =>
|
|
605
|
+
app.request(`/rpc/${chainId}`, {
|
|
606
|
+
body: JSON.stringify({ id: 1, jsonrpc: '2.0', method, params }),
|
|
607
|
+
headers: { 'content-type': 'application/json', 'tempo-api-key': writer.token },
|
|
608
|
+
method: 'POST',
|
|
609
|
+
})
|
|
610
|
+
const broadcast = await rpc('eth_sendRawTransaction', [`0x${'02'.repeat(8)}`])
|
|
611
|
+
// A read the write-only key could never perform keeps its 403.
|
|
612
|
+
const read = await rpc('eth_chainId', [])
|
|
613
|
+
const codes = await Promise.all([inferred, broadcast, read].map(async (response) => [response.status, (await response.json()).error.code])) // prettier-ignore
|
|
614
|
+
expect(codes).toEqual([[503, 'zone_access_unavailable'], [503, 'zone_access_unavailable'], [403, 'api_key_forbidden']]) // prettier-ignore
|
|
615
|
+
})
|
|
616
|
+
|
|
617
|
+
test('times the delegation read in Server-Timing', async () => {
|
|
618
|
+
const reader = TestApp.zoneReader({ ...TestApp.key, scopes: ['data:read'] }, [chainId])
|
|
619
|
+
const { app, db } = await setup([reader])
|
|
620
|
+
await ZoneDelegations.create(db, delegation)
|
|
621
|
+
const response = await app.request(`/v1/tokenlist?chainId=${chainId}`, {
|
|
622
|
+
headers: { 'tempo-api-key': reader.token },
|
|
623
|
+
})
|
|
624
|
+
expect(response.status).toBe(200)
|
|
625
|
+
expect(stages(response)).toEqual(expect.arrayContaining(['zone_delegations', 'zone_delegations_cache', 'zone_delegations_query'])) // prettier-ignore
|
|
380
626
|
})
|
|
381
627
|
|
|
382
628
|
test('records a granted Zone for inferred-Zone requests, never a denied sibling', async () => {
|
|
383
|
-
const entries: Log.Entry[] = []
|
|
384
629
|
const other = chainId + 1
|
|
385
|
-
const reader = { ...TestApp.key, scopes: ['data:read',
|
|
386
|
-
const app =
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
zones: [other, chainId].map((id) =>
|
|
390
|
-
TestApp.zone({
|
|
391
|
-
chainId: id,
|
|
392
|
-
rpcUrl: `https://${id}.rpc.test`,
|
|
393
|
-
tidxUrl: `https://${id}.tidx.test`,
|
|
394
|
-
}),
|
|
395
|
-
),
|
|
396
|
-
})
|
|
630
|
+
const reader = TestApp.zoneReader({ ...TestApp.key, scopes: ['data:read'] }, [chainId])
|
|
631
|
+
const { app, db, entries } = await setup([reader], { zones: zonesFor([other, chainId]) })
|
|
632
|
+
await ZoneDelegations.create(db, delegation)
|
|
633
|
+
await ZoneDelegations.create(db, { ...delegation, chainId: other })
|
|
397
634
|
|
|
398
635
|
await app.request(`/v1/addresses/0x${'AB'.repeat(20)}/activities?include=zones`, {
|
|
399
636
|
headers: { 'tempo-api-key': reader.token },
|
|
@@ -412,153 +649,37 @@ describe('authorize', () => {
|
|
|
412
649
|
"0xabababababababababababababababababababab",
|
|
413
650
|
],
|
|
414
651
|
"zoneChainId": 421700001,
|
|
415
|
-
"zoneGrant": "
|
|
652
|
+
"zoneGrant": "delegation",
|
|
416
653
|
},
|
|
417
654
|
]
|
|
418
655
|
`)
|
|
419
656
|
})
|
|
420
|
-
})
|
|
421
657
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
orgId: TestApp.key.orgId,
|
|
430
|
-
projects: 'all',
|
|
431
|
-
tokensAll: true,
|
|
432
|
-
} as const
|
|
658
|
+
test('decides each inferred Zone on its own against the request addresses', async () => {
|
|
659
|
+
const other = chainId + 1
|
|
660
|
+
const reader = TestApp.zoneReader({ ...TestApp.key, scopes: ['data:read'] }, [chainId, other])
|
|
661
|
+
const { app, db, entries, tags } = await setup([reader], { zones: zonesFor([other, chainId]) })
|
|
662
|
+
await ZoneDelegations.create(db, delegation)
|
|
663
|
+
// The sibling Zone lists another account only, so it drops out of the selection.
|
|
664
|
+
await ZoneDelegations.create(db, { ...delegation, accounts: [`0x${'ef'.repeat(20)}`], accountsAll: false, chainId: other }) // prettier-ignore
|
|
433
665
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
const db = TestApp.database()
|
|
437
|
-
await Organizations.create(db, { id: TestApp.key.orgId, name: 'Delegated' })
|
|
438
|
-
const entries: Log.Entry[] = []
|
|
439
|
-
const counts: { name: string; tags: Metrics.Tags | undefined }[] = []
|
|
440
|
-
const app = TestApp.create({
|
|
441
|
-
auth: { keys },
|
|
442
|
-
db,
|
|
443
|
-
logger: (entry) => void entries.push(entry),
|
|
444
|
-
metrics: Metrics.from({
|
|
445
|
-
count: (name, _value, tags) => void counts.push({ name, tags }),
|
|
446
|
-
flush() {},
|
|
447
|
-
gauge() {},
|
|
448
|
-
histogram() {},
|
|
449
|
-
}),
|
|
450
|
-
zones: [TestApp.zone({ chainId, rpcUrl: `https://${chainId}.rpc.test` })],
|
|
666
|
+
const response = await app.request(`/v1/addresses/${account}/activities?include=zones`, {
|
|
667
|
+
headers: { 'tempo-api-key': reader.token },
|
|
451
668
|
})
|
|
452
|
-
const request = (path: string, key: TestApp.kvStore.Key | undefined) =>
|
|
453
|
-
app.request(path, key === undefined ? {} : { headers: { 'tempo-api-key': key.token } })
|
|
454
|
-
const decisions = () =>
|
|
455
|
-
entries.map(
|
|
456
|
-
({
|
|
457
|
-
status,
|
|
458
|
-
zoneDecisionAgreement,
|
|
459
|
-
zoneDelegationDecision,
|
|
460
|
-
zoneDelegationId,
|
|
461
|
-
zoneGrant,
|
|
462
|
-
}) => ({
|
|
463
|
-
// prettier-ignore
|
|
464
|
-
agreement: zoneDecisionAgreement,
|
|
465
|
-
delegation: zoneDelegationDecision,
|
|
466
|
-
delegationId: zoneDelegationId === undefined ? undefined : 'zd',
|
|
467
|
-
grant: zoneGrant,
|
|
468
|
-
status,
|
|
469
|
-
}),
|
|
470
|
-
)
|
|
471
|
-
const tags = () =>
|
|
472
|
-
counts.filter(({ name }) => name === ZoneAccess.decisionMetric).map(({ tags }) => tags)
|
|
473
|
-
return { app, db, decisions, request, tags }
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
test('records agreement and disagreement without changing admission', async () => {
|
|
477
|
-
const reader = { ...TestApp.key, scopes: ['data:read', `zone:${chainId}:read`] }
|
|
478
|
-
const outsider = { ...TestApp.key, id: 'key_outsider', scopes: ['data:read'], token: 'secret_outsider' } // prettier-ignore
|
|
479
|
-
const path = `/v1/tokenlist?chainId=${chainId}`
|
|
480
669
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
// A fresh app, because delegations are cached per colo and client cache directives are ignored.
|
|
487
|
-
const after = await setup([reader, outsider])
|
|
488
|
-
await ZoneDelegations.create(after.db, delegation)
|
|
489
|
-
expect((await after.request(path, reader)).status).toBe(200)
|
|
490
|
-
expect((await after.request(path, outsider)).status).toBe(403)
|
|
491
|
-
// Anonymous callers hold no organization, so no delegation decision is made.
|
|
492
|
-
expect((await after.request(path, undefined)).status).toBe(403)
|
|
493
|
-
|
|
494
|
-
expect([...before.decisions(), ...after.decisions()]).toMatchInlineSnapshot(`
|
|
495
|
-
[
|
|
496
|
-
{
|
|
497
|
-
"agreement": false,
|
|
498
|
-
"delegation": "deny",
|
|
499
|
-
"delegationId": undefined,
|
|
500
|
-
"grant": "scope",
|
|
501
|
-
"status": 200,
|
|
502
|
-
},
|
|
503
|
-
{
|
|
504
|
-
"agreement": true,
|
|
505
|
-
"delegation": "deny",
|
|
506
|
-
"delegationId": undefined,
|
|
507
|
-
"grant": undefined,
|
|
508
|
-
"status": 403,
|
|
509
|
-
},
|
|
510
|
-
{
|
|
511
|
-
"agreement": true,
|
|
512
|
-
"delegation": "allow",
|
|
513
|
-
"delegationId": "zd",
|
|
514
|
-
"grant": "scope",
|
|
515
|
-
"status": 200,
|
|
516
|
-
},
|
|
517
|
-
{
|
|
518
|
-
"agreement": true,
|
|
519
|
-
"delegation": "deny",
|
|
520
|
-
"delegationId": "zd",
|
|
521
|
-
"grant": undefined,
|
|
522
|
-
"status": 403,
|
|
523
|
-
},
|
|
524
|
-
{
|
|
525
|
-
"agreement": undefined,
|
|
526
|
-
"delegation": undefined,
|
|
527
|
-
"delegationId": undefined,
|
|
528
|
-
"grant": undefined,
|
|
529
|
-
"status": 403,
|
|
530
|
-
},
|
|
531
|
-
]
|
|
532
|
-
`)
|
|
533
|
-
expect([...before.tags(), ...after.tags()]).toMatchInlineSnapshot(`
|
|
670
|
+
expect(response.status).not.toBe(403)
|
|
671
|
+
expect(entries.map(({ zoneChainId, zoneGrant }) => ({ zoneChainId, zoneGrant }))).toEqual([
|
|
672
|
+
{ zoneChainId: chainId, zoneGrant: 'delegation' },
|
|
673
|
+
])
|
|
674
|
+
expect(tags()).toMatchInlineSnapshot(`
|
|
534
675
|
[
|
|
535
676
|
{
|
|
536
|
-
"agreement": "disagree",
|
|
537
|
-
"chain_id": 421700001,
|
|
538
|
-
"grant": "scope",
|
|
539
|
-
"outcome": "allow",
|
|
540
|
-
},
|
|
541
|
-
{
|
|
542
|
-
"agreement": "agree",
|
|
543
|
-
"chain_id": 421700001,
|
|
544
|
-
"grant": "none",
|
|
545
|
-
"outcome": "deny",
|
|
546
|
-
},
|
|
547
|
-
{
|
|
548
|
-
"agreement": "agree",
|
|
549
677
|
"chain_id": 421700001,
|
|
550
|
-
"grant": "
|
|
678
|
+
"grant": "delegation",
|
|
551
679
|
"outcome": "allow",
|
|
552
680
|
},
|
|
553
681
|
{
|
|
554
|
-
"
|
|
555
|
-
"chain_id": 421700001,
|
|
556
|
-
"grant": "none",
|
|
557
|
-
"outcome": "deny",
|
|
558
|
-
},
|
|
559
|
-
{
|
|
560
|
-
"agreement": "skipped",
|
|
561
|
-
"chain_id": 421700001,
|
|
682
|
+
"chain_id": 421700002,
|
|
562
683
|
"grant": "none",
|
|
563
684
|
"outcome": "deny",
|
|
564
685
|
},
|
|
@@ -567,68 +688,62 @@ describe('settle', () => {
|
|
|
567
688
|
})
|
|
568
689
|
|
|
569
690
|
test('ignores client cache directives when reading delegations', async () => {
|
|
570
|
-
const reader = { ...TestApp.key, scopes: ['data:read',
|
|
571
|
-
const { app, db
|
|
691
|
+
const reader = TestApp.zoneReader({ ...TestApp.key, scopes: ['data:read'] }, [chainId])
|
|
692
|
+
const { app, db } = await setup([reader])
|
|
572
693
|
const path = `/v1/tokenlist?chainId=${chainId}`
|
|
573
694
|
const headers = { 'cache-control': 'no-cache', 'tempo-api-key': reader.token }
|
|
574
|
-
expect((await app.request(path, { headers })).status).toBe(
|
|
695
|
+
expect((await app.request(path, { headers })).status).toBe(403)
|
|
575
696
|
await ZoneDelegations.create(db, delegation)
|
|
576
697
|
// The cached empty read stands for its TTL even though the caller asked for a refresh.
|
|
577
|
-
expect((await app.request(path, { headers })).status).toBe(
|
|
578
|
-
expect(decisions().map(({ delegation }) => delegation)).toMatchInlineSnapshot(`
|
|
579
|
-
[
|
|
580
|
-
"deny",
|
|
581
|
-
"deny",
|
|
582
|
-
]
|
|
583
|
-
`)
|
|
698
|
+
expect((await app.request(path, { headers })).status).toBe(403)
|
|
584
699
|
})
|
|
585
700
|
|
|
586
|
-
test('narrows by
|
|
587
|
-
const scoped = { ...TestApp.key, projectId: 'prj_a', scopes: ['data:read',
|
|
701
|
+
test('narrows by account; zone-wide routes need every account', async () => {
|
|
702
|
+
const scoped = TestApp.zoneReader({ ...TestApp.key, projectId: 'prj_a', scopes: ['data:read'] }, [chainId]) // prettier-ignore
|
|
703
|
+
// A key of another project reads the same delegation: narrowing is by key tag, never by project.
|
|
588
704
|
const sibling = { ...scoped, id: 'key_b', projectId: 'prj_b', token: 'secret_b' }
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
-
await Projects.create(db, { id: 'prj_a', name: 'A', orgId: TestApp.key.orgId })
|
|
592
|
-
await ZoneDelegations.create(db, { ...delegation, accounts: [account], accountsAll: false, projects: ['prj_a'] }) // prettier-ignore
|
|
705
|
+
const { db, decisions, request } = await setup([scoped, sibling])
|
|
706
|
+
await ZoneDelegations.create(db, { ...delegation, accounts: [account], accountsAll: false })
|
|
593
707
|
const balances = (address: string) => `/v1/addresses/${address}/balances?chainId=${chainId}`
|
|
594
708
|
|
|
595
|
-
// Project narrowing: the named project passes; a sibling project and an organization-wide key do not.
|
|
596
709
|
await request(balances(account), scoped)
|
|
597
710
|
await request(balances(account), sibling)
|
|
598
|
-
await request(balances(account), organizationWide)
|
|
599
711
|
// Account narrowing: another account fails, and a zone-wide list fails without `all`.
|
|
600
712
|
await request(balances(`0x${'ef'.repeat(20)}`), scoped)
|
|
601
713
|
await request(`/v1/tokenlist?chainId=${chainId}`, scoped)
|
|
602
714
|
|
|
603
|
-
expect(decisions().
|
|
604
|
-
.toMatchInlineSnapshot(`
|
|
715
|
+
expect(decisions()).toMatchInlineSnapshot(`
|
|
605
716
|
[
|
|
606
717
|
{
|
|
607
718
|
"delegation": "allow",
|
|
719
|
+
"delegationId": "zd",
|
|
720
|
+
"grant": "delegation",
|
|
608
721
|
"status": 502,
|
|
609
722
|
},
|
|
610
723
|
{
|
|
611
|
-
"delegation": "
|
|
612
|
-
"
|
|
613
|
-
|
|
614
|
-
{
|
|
615
|
-
"delegation": "deny",
|
|
724
|
+
"delegation": "allow",
|
|
725
|
+
"delegationId": "zd",
|
|
726
|
+
"grant": "delegation",
|
|
616
727
|
"status": 502,
|
|
617
728
|
},
|
|
618
729
|
{
|
|
619
730
|
"delegation": "deny",
|
|
620
|
-
"
|
|
731
|
+
"delegationId": "zd",
|
|
732
|
+
"grant": undefined,
|
|
733
|
+
"status": 403,
|
|
621
734
|
},
|
|
622
735
|
{
|
|
623
736
|
"delegation": "deny",
|
|
624
|
-
"
|
|
737
|
+
"delegationId": "zd",
|
|
738
|
+
"grant": undefined,
|
|
739
|
+
"status": 403,
|
|
625
740
|
},
|
|
626
741
|
]
|
|
627
742
|
`)
|
|
628
743
|
})
|
|
629
744
|
|
|
630
745
|
test('treats a raw RPC batch with any zone-wide read as zone-wide', async () => {
|
|
631
|
-
const reader = { ...TestApp.key, scopes: ['data:read',
|
|
746
|
+
const reader = TestApp.zoneReader({ ...TestApp.key, scopes: ['data:read'] }, [chainId])
|
|
632
747
|
const { app, db, decisions } = await setup([reader])
|
|
633
748
|
await ZoneDelegations.create(db, { ...delegation, accounts: [account], accountsAll: false })
|
|
634
749
|
const rpc = (requests: readonly { method: string; params?: unknown[] }[]) =>
|
|
@@ -641,17 +756,28 @@ describe('settle', () => {
|
|
|
641
756
|
// Chain metadata exposes no account, so it keeps the batch account-scoped; logs do not.
|
|
642
757
|
await rpc([{ method: 'eth_getBalance', params: [account, 'latest'] }, { method: 'eth_blockNumber', params: [] }]) // prettier-ignore
|
|
643
758
|
await rpc([{ method: 'eth_getBalance', params: [account, 'latest'] }, { method: 'eth_getLogs', params: [{}] }]) // prettier-ignore
|
|
644
|
-
|
|
759
|
+
// Admitted batches reach body validation (no content type here); refused ones never do.
|
|
760
|
+
expect(decisions().map(({ delegation, status }) => [delegation, status]))
|
|
761
|
+
.toMatchInlineSnapshot(`
|
|
645
762
|
[
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
763
|
+
[
|
|
764
|
+
"allow",
|
|
765
|
+
415,
|
|
766
|
+
],
|
|
767
|
+
[
|
|
768
|
+
"allow",
|
|
769
|
+
415,
|
|
770
|
+
],
|
|
771
|
+
[
|
|
772
|
+
"deny",
|
|
773
|
+
403,
|
|
774
|
+
],
|
|
649
775
|
]
|
|
650
776
|
`)
|
|
651
777
|
})
|
|
652
778
|
|
|
653
779
|
test('narrows by token on token routes', async () => {
|
|
654
|
-
const reader = { ...TestApp.key, scopes: ['data:read',
|
|
780
|
+
const reader = TestApp.zoneReader({ ...TestApp.key, scopes: ['data:read'] }, [chainId])
|
|
655
781
|
const { db, decisions, request } = await setup([reader])
|
|
656
782
|
await ZoneDelegations.create(db, { ...delegation, tokens: [token], tokensAll: false })
|
|
657
783
|
|
|
@@ -660,18 +786,31 @@ describe('settle', () => {
|
|
|
660
786
|
// A list naming no token sees every token, so a token list does not cover it.
|
|
661
787
|
await request(`/v1/tokenlist?chainId=${chainId}`, reader)
|
|
662
788
|
|
|
663
|
-
expect(decisions().map(({ delegation }) => delegation))
|
|
789
|
+
expect(decisions().map(({ delegation, status }) => [delegation, status]))
|
|
790
|
+
.toMatchInlineSnapshot(`
|
|
664
791
|
[
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
792
|
+
[
|
|
793
|
+
"allow",
|
|
794
|
+
404,
|
|
795
|
+
],
|
|
796
|
+
[
|
|
797
|
+
"deny",
|
|
798
|
+
403,
|
|
799
|
+
],
|
|
800
|
+
[
|
|
801
|
+
"deny",
|
|
802
|
+
403,
|
|
803
|
+
],
|
|
668
804
|
]
|
|
669
805
|
`)
|
|
670
806
|
})
|
|
807
|
+
})
|
|
671
808
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
const {
|
|
809
|
+
describe('settle', () => {
|
|
810
|
+
test('meters every recorded decision with its final outcome', async () => {
|
|
811
|
+
const reader = TestApp.zoneReader({ ...TestApp.key, scopes: ['data:read', `zone:${chainId}:write`] }, [chainId]) // prettier-ignore
|
|
812
|
+
const outsider = { ...TestApp.key, id: 'key_outsider', scopes: ['data:read'], token: 'secret_outsider' } // prettier-ignore
|
|
813
|
+
const { app, db, request, tags } = await setup([reader, outsider])
|
|
675
814
|
await ZoneDelegations.create(db, delegation)
|
|
676
815
|
const rpc = (method: string) =>
|
|
677
816
|
app.request(`/rpc/${chainId}`, {
|
|
@@ -679,49 +818,39 @@ describe('settle', () => {
|
|
|
679
818
|
headers: { 'tempo-api-key': reader.token },
|
|
680
819
|
method: 'POST',
|
|
681
820
|
})
|
|
682
|
-
|
|
683
|
-
|
|
821
|
+
expect((await request(`/v1/tokenlist?chainId=${chainId}`, reader)).status).toBe(200)
|
|
822
|
+
expect((await request(`/v1/tokenlist?chainId=${chainId}`, outsider)).status).toBe(403)
|
|
823
|
+
// The delegation admits the chain, the method allowlist refuses the call afterwards.
|
|
684
824
|
expect((await rpc('eth_sign')).status).toBe(403)
|
|
685
|
-
// Broadcasts are user-signed and stay on key scopes
|
|
686
|
-
|
|
687
|
-
expect(
|
|
688
|
-
expect([decisions(), tags()]).toMatchInlineSnapshot(`
|
|
825
|
+
// Broadcasts are user-signed and stay on key scopes.
|
|
826
|
+
expect((await rpc('eth_sendRawTransaction')).status).not.toBe(403)
|
|
827
|
+
expect(tags()).toMatchInlineSnapshot(`
|
|
689
828
|
[
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
"grant": "none",
|
|
711
|
-
"outcome": "deny",
|
|
712
|
-
},
|
|
713
|
-
{
|
|
714
|
-
"agreement": "skipped",
|
|
715
|
-
"chain_id": 421700001,
|
|
716
|
-
"grant": "scope",
|
|
717
|
-
"outcome": "allow",
|
|
718
|
-
},
|
|
719
|
-
],
|
|
829
|
+
{
|
|
830
|
+
"chain_id": 421700001,
|
|
831
|
+
"grant": "delegation",
|
|
832
|
+
"outcome": "allow",
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
"chain_id": 421700001,
|
|
836
|
+
"grant": "none",
|
|
837
|
+
"outcome": "deny",
|
|
838
|
+
},
|
|
839
|
+
{
|
|
840
|
+
"chain_id": 421700001,
|
|
841
|
+
"grant": "none",
|
|
842
|
+
"outcome": "deny",
|
|
843
|
+
},
|
|
844
|
+
{
|
|
845
|
+
"chain_id": 421700001,
|
|
846
|
+
"grant": "scope",
|
|
847
|
+
"outcome": "allow",
|
|
848
|
+
},
|
|
720
849
|
]
|
|
721
850
|
`)
|
|
722
851
|
})
|
|
723
852
|
|
|
724
|
-
test('
|
|
853
|
+
test('meters bare contexts, including forwarded Zone tokens and unloaded delegations', async () => {
|
|
725
854
|
const counts: Metrics.Tags[] = []
|
|
726
855
|
const metrics = Metrics.from({
|
|
727
856
|
count: (_name, _value, tags) => void counts.push(tags ?? {}),
|
|
@@ -731,15 +860,15 @@ describe('settle', () => {
|
|
|
731
860
|
})
|
|
732
861
|
const forwarded = context({ scopes: ['data:read'] })
|
|
733
862
|
forwarded.set('metrics' as never, metrics as never)
|
|
734
|
-
ZoneAccess.authorize(forwarded, { chainId, verb: 'read', zoneToken: true })
|
|
735
|
-
|
|
736
|
-
//
|
|
737
|
-
const bare = context({ scopes: [
|
|
863
|
+
await ZoneAccess.authorize(forwarded, { chainId, verb: 'read', zoneToken: true })
|
|
864
|
+
ZoneAccess.settle(forwarded)
|
|
865
|
+
// Nothing loaded the organization's delegations, so the read fails closed.
|
|
866
|
+
const bare = context({ scopes: ['zone:read'], zoneTags: [chainId] })
|
|
738
867
|
bare.set('metrics' as never, metrics as never)
|
|
739
|
-
ZoneAccess.authorize(bare, { chainId, verb: 'read' })
|
|
740
|
-
|
|
868
|
+
await ZoneAccess.authorize(bare, { chainId, verb: 'read' })
|
|
869
|
+
ZoneAccess.settle(bare)
|
|
741
870
|
const silent = context({ scopes: ['data:read'] })
|
|
742
|
-
|
|
871
|
+
ZoneAccess.settle(silent)
|
|
743
872
|
|
|
744
873
|
expect([
|
|
745
874
|
(forwarded.get('zoneAccess' as never) as ZoneAccess.Access).delegation,
|
|
@@ -753,17 +882,14 @@ describe('settle', () => {
|
|
|
753
882
|
undefined,
|
|
754
883
|
[
|
|
755
884
|
{
|
|
756
|
-
"agreement": "skipped",
|
|
757
885
|
"chain_id": 421700001,
|
|
758
886
|
"grant": "zone_token",
|
|
759
887
|
"outcome": "allow",
|
|
760
888
|
},
|
|
761
|
-
{},
|
|
762
889
|
{
|
|
763
|
-
"agreement": "unavailable",
|
|
764
890
|
"chain_id": 421700001,
|
|
765
|
-
"grant": "
|
|
766
|
-
"outcome": "
|
|
891
|
+
"grant": "none",
|
|
892
|
+
"outcome": "deny",
|
|
767
893
|
},
|
|
768
894
|
],
|
|
769
895
|
]
|
|
@@ -771,12 +897,75 @@ describe('settle', () => {
|
|
|
771
897
|
})
|
|
772
898
|
})
|
|
773
899
|
|
|
900
|
+
/** Builds an app whose Zone requests are logged and metered, over a database holding the key's organization. */
|
|
901
|
+
async function setup(keys: readonly TestApp.kvStore.Key[], options: setup.Options = {}) {
|
|
902
|
+
const db = options.db ?? TestApp.database()
|
|
903
|
+
if (options.db === undefined) await TestApp.delegateZone(db, { chainIds: [] })
|
|
904
|
+
const entries: Log.Entry[] = []
|
|
905
|
+
const counts: { name: string; tags: Metrics.Tags | undefined }[] = []
|
|
906
|
+
const app = TestApp.create({
|
|
907
|
+
auth: { keys },
|
|
908
|
+
db,
|
|
909
|
+
logger: (entry) => void entries.push(entry),
|
|
910
|
+
metrics: Metrics.from({
|
|
911
|
+
count: (name, _value, tags) => void counts.push({ name, tags }),
|
|
912
|
+
flush() {},
|
|
913
|
+
gauge() {},
|
|
914
|
+
histogram() {},
|
|
915
|
+
}),
|
|
916
|
+
zones: options.zones ?? zonesFor([chainId]),
|
|
917
|
+
})
|
|
918
|
+
const request = (path: string, key: TestApp.kvStore.Key | undefined) =>
|
|
919
|
+
app.request(path, key === undefined ? {} : { headers: { 'tempo-api-key': key.token } })
|
|
920
|
+
const decisions = () =>
|
|
921
|
+
entries.map(({ status, zoneDelegationDecision, zoneDelegationId, zoneGrant }) => ({
|
|
922
|
+
delegation: zoneDelegationDecision,
|
|
923
|
+
delegationId: zoneDelegationId === undefined ? undefined : 'zd',
|
|
924
|
+
grant: zoneGrant,
|
|
925
|
+
status,
|
|
926
|
+
}))
|
|
927
|
+
const tags = () =>
|
|
928
|
+
counts.filter(({ name }) => name === ZoneAccess.decisionMetric).map(({ tags }) => tags)
|
|
929
|
+
return { app, counts, db, decisions, entries, request, tags }
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
declare namespace setup {
|
|
933
|
+
type Options = {
|
|
934
|
+
db?: Db.Db | undefined
|
|
935
|
+
zones?: readonly ReturnType<typeof TestApp.zone>[] | undefined
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
function stages(response: globalThis.Response) {
|
|
940
|
+
return (response.headers.get('server-timing') ?? '')
|
|
941
|
+
.split(', ')
|
|
942
|
+
.map((metric) => metric.split(';')[0])
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
function zonesFor(chainIds: readonly number[]) {
|
|
946
|
+
return chainIds.map(
|
|
947
|
+
(id) =>
|
|
948
|
+
TestApp.zone({ chainId: id, rpcUrl: `https://${id}.rpc.test`, tidxUrl: `https://${id}.tidx.test` }), // prettier-ignore
|
|
949
|
+
)
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/** A delegation row as `load` leaves it on the context. */
|
|
953
|
+
function record(options: Partial<ZoneDelegations.Record>): ZoneDelegations.Record {
|
|
954
|
+
return {
|
|
955
|
+
...delegation,
|
|
956
|
+
createdAt: '2026-01-01T00:00:00.000Z',
|
|
957
|
+
id: `zd_${options.chainId ?? chainId}`,
|
|
958
|
+
updatedAt: '2026-01-01T00:00:00.000Z',
|
|
959
|
+
...options,
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
774
963
|
function request() {
|
|
775
964
|
return new Request('http://tempo-api.test/')
|
|
776
965
|
}
|
|
777
966
|
|
|
778
|
-
/** Builds a context authenticated as an API key
|
|
779
|
-
function context(options:
|
|
967
|
+
/** Builds a context authenticated as an API key, with its organization's delegations preloaded when given. */
|
|
968
|
+
function context(options: context.Options) {
|
|
780
969
|
const c = new Context(request())
|
|
781
970
|
const principal = {
|
|
782
971
|
apiKey: {
|
|
@@ -784,12 +973,26 @@ function context(options: { scopes: readonly string[] }) {
|
|
|
784
973
|
id: 'key_test',
|
|
785
974
|
orgId: 'org_test',
|
|
786
975
|
scopes: options.scopes,
|
|
976
|
+
...(options.zoneTags === undefined ? {} : { zoneTags: options.zoneTags }),
|
|
787
977
|
},
|
|
788
978
|
environment: 'production',
|
|
789
979
|
id: 'key_test',
|
|
790
980
|
orgId: 'org_test',
|
|
981
|
+
...(options.projectId === undefined ? {} : { projectId: options.projectId }),
|
|
791
982
|
type: 'api_key',
|
|
983
|
+
...(options.zoneTags === undefined ? {} : { zoneTags: options.zoneTags }),
|
|
792
984
|
}
|
|
793
985
|
c.set('principal' as never, principal as never)
|
|
986
|
+
if (options.delegated)
|
|
987
|
+
c.set('zoneDelegations' as never, { records: options.delegated.map((id) => record({ chainId: id })) } as never) // prettier-ignore
|
|
794
988
|
return c
|
|
795
989
|
}
|
|
990
|
+
|
|
991
|
+
declare namespace context {
|
|
992
|
+
type Options = {
|
|
993
|
+
delegated?: readonly number[] | undefined
|
|
994
|
+
projectId?: string | undefined
|
|
995
|
+
scopes: readonly string[]
|
|
996
|
+
zoneTags?: readonly number[] | undefined
|
|
997
|
+
}
|
|
998
|
+
}
|