tapimo 0.4.4 → 0.5.1

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 (229) hide show
  1. package/dist/App.d.ts +4701 -3416
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +24 -2
  4. package/dist/App.js.map +1 -1
  5. package/dist/Assets.js +3 -3
  6. package/dist/Assets.js.map +1 -1
  7. package/dist/Scope.d.ts +18 -1
  8. package/dist/Scope.d.ts.map +1 -1
  9. package/dist/Scope.js +23 -3
  10. package/dist/Scope.js.map +1 -1
  11. package/dist/Webhooks.d.ts +1 -1
  12. package/dist/Webhooks.d.ts.map +1 -1
  13. package/dist/Webhooks.js +5 -10
  14. package/dist/Webhooks.js.map +1 -1
  15. package/dist/admin/App.d.ts +6 -34
  16. package/dist/admin/App.d.ts.map +1 -1
  17. package/dist/admin/apps/api-keys.d.ts +3 -3
  18. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  19. package/dist/admin/apps/api-keys.js +4 -1
  20. package/dist/admin/apps/api-keys.js.map +1 -1
  21. package/dist/admin/apps/scopes.d.ts +6 -34
  22. package/dist/admin/apps/scopes.d.ts.map +1 -1
  23. package/dist/admin/apps/scopes.js +2 -2
  24. package/dist/admin/apps/scopes.js.map +1 -1
  25. package/dist/admin/apps/verified-tokens.d.ts +57 -57
  26. package/dist/admin/ui.generated.d.ts +1 -1
  27. package/dist/admin/ui.generated.js +33 -33
  28. package/dist/admin/ui.generated.js.map +1 -1
  29. package/dist/apps/data/App.d.ts +4574 -4277
  30. package/dist/apps/data/App.d.ts.map +1 -1
  31. package/dist/apps/data/App.js +155 -77
  32. package/dist/apps/data/App.js.map +1 -1
  33. package/dist/apps/data/FundingQuoteProviders.d.ts +109 -0
  34. package/dist/apps/data/FundingQuoteProviders.d.ts.map +1 -0
  35. package/dist/apps/data/FundingQuoteProviders.js +464 -0
  36. package/dist/apps/data/FundingQuoteProviders.js.map +1 -0
  37. package/dist/apps/data/index.d.ts +1 -1
  38. package/dist/apps/data/index.js +1 -1
  39. package/dist/apps/data/routes/activities.d.ts +26 -26
  40. package/dist/apps/data/routes/addresses.d.ts +38 -38
  41. package/dist/apps/data/routes/balances.d.ts +45 -45
  42. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  43. package/dist/apps/data/routes/balances.js +67 -149
  44. package/dist/apps/data/routes/balances.js.map +1 -1
  45. package/dist/apps/data/routes/blocks.d.ts +2 -3
  46. package/dist/apps/data/routes/blocks.d.ts.map +1 -1
  47. package/dist/apps/data/routes/blocks.js +3 -5
  48. package/dist/apps/data/routes/blocks.js.map +1 -1
  49. package/dist/apps/data/routes/coingecko.d.ts +177 -176
  50. package/dist/apps/data/routes/coingecko.d.ts.map +1 -1
  51. package/dist/apps/data/routes/coingecko.js +3 -8
  52. package/dist/apps/data/routes/coingecko.js.map +1 -1
  53. package/dist/apps/data/routes/funding.d.ts +131 -228
  54. package/dist/apps/data/routes/funding.d.ts.map +1 -1
  55. package/dist/apps/data/routes/funding.js +152 -219
  56. package/dist/apps/data/routes/funding.js.map +1 -1
  57. package/dist/apps/data/routes/indexer.d.ts +34 -34
  58. package/dist/apps/data/routes/indexer.d.ts.map +1 -1
  59. package/dist/apps/data/routes/indexer.js +9 -2
  60. package/dist/apps/data/routes/indexer.js.map +1 -1
  61. package/dist/apps/data/routes/receipts.d.ts +228 -228
  62. package/dist/apps/data/routes/rpc.d.ts.map +1 -1
  63. package/dist/apps/data/routes/rpc.js +86 -16
  64. package/dist/apps/data/routes/rpc.js.map +1 -1
  65. package/dist/apps/data/routes/tokenlist.d.ts +12 -12
  66. package/dist/apps/data/routes/verified-tokens.d.ts +5 -5
  67. package/dist/apps/data/routes/webhooks.d.ts +340 -0
  68. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  69. package/dist/apps/data/routes/webhooks.js +13 -1
  70. package/dist/apps/data/routes/webhooks.js.map +1 -1
  71. package/dist/apps/index.d.ts +1 -1
  72. package/dist/apps/index.js +1 -1
  73. package/dist/apps/management/App.d.ts +1833 -851
  74. package/dist/apps/management/App.d.ts.map +1 -1
  75. package/dist/apps/management/App.js +4 -0
  76. package/dist/apps/management/App.js.map +1 -1
  77. package/dist/apps/management/routes/api-keys.d.ts +41 -41
  78. package/dist/apps/management/routes/api-keys.js +2 -2
  79. package/dist/apps/management/routes/api-keys.js.map +1 -1
  80. package/dist/apps/management/routes/billing.d.ts +48 -48
  81. package/dist/apps/management/routes/invitations.d.ts +132 -132
  82. package/dist/apps/management/routes/invite-links.d.ts +1100 -0
  83. package/dist/apps/management/routes/invite-links.d.ts.map +1 -0
  84. package/dist/apps/management/routes/invite-links.js +411 -0
  85. package/dist/apps/management/routes/invite-links.js.map +1 -0
  86. package/dist/apps/management/routes/members.d.ts +41 -41
  87. package/dist/apps/management/routes/orgs.d.ts +92 -92
  88. package/dist/apps/management/routes/projects.d.ts +9 -9
  89. package/dist/apps/management/routes/scopes.d.ts.map +1 -1
  90. package/dist/apps/management/routes/scopes.js +2 -2
  91. package/dist/apps/management/routes/scopes.js.map +1 -1
  92. package/dist/apps/management/routes/usage.d.ts +24 -24
  93. package/dist/apps/mcp.js +2 -2
  94. package/dist/apps/mcp.js.map +1 -1
  95. package/dist/apps/relay/App.d.ts +2 -2
  96. package/dist/apps/relay/App.d.ts.map +1 -1
  97. package/dist/apps/relay/App.js +44 -6
  98. package/dist/apps/relay/App.js.map +1 -1
  99. package/dist/db/Db.d.ts +3 -0
  100. package/dist/db/Db.d.ts.map +1 -1
  101. package/dist/db/Db.js +57 -0
  102. package/dist/db/Db.js.map +1 -1
  103. package/dist/db/Schema.d.ts +31 -0
  104. package/dist/db/Schema.d.ts.map +1 -1
  105. package/dist/db/Schema.js +31 -0
  106. package/dist/db/Schema.js.map +1 -1
  107. package/dist/db/tables/inviteLinks.d.ts +42 -0
  108. package/dist/db/tables/inviteLinks.d.ts.map +1 -0
  109. package/dist/db/tables/inviteLinks.js +148 -0
  110. package/dist/db/tables/inviteLinks.js.map +1 -0
  111. package/dist/db/tables/organizations.d.ts.map +1 -1
  112. package/dist/db/tables/organizations.js +2 -0
  113. package/dist/db/tables/organizations.js.map +1 -1
  114. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  115. package/dist/db/tables/sponsoredTransactions.js +6 -2
  116. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  117. package/dist/handlers/relay.d.ts.map +1 -1
  118. package/dist/handlers/relay.js +13 -3
  119. package/dist/handlers/relay.js.map +1 -1
  120. package/dist/internal/Auth.d.ts +9 -1
  121. package/dist/internal/Auth.d.ts.map +1 -1
  122. package/dist/internal/Auth.js +138 -10
  123. package/dist/internal/Auth.js.map +1 -1
  124. package/dist/internal/EdgeCache.d.ts +3 -1
  125. package/dist/internal/EdgeCache.d.ts.map +1 -1
  126. package/dist/internal/EdgeCache.js +17 -2
  127. package/dist/internal/EdgeCache.js.map +1 -1
  128. package/dist/internal/{FundingRoutes.d.ts → FundingQuotes.d.ts} +240 -369
  129. package/dist/internal/FundingQuotes.d.ts.map +1 -0
  130. package/dist/internal/{FundingRoutes.js → FundingQuotes.js} +144 -183
  131. package/dist/internal/FundingQuotes.js.map +1 -0
  132. package/dist/internal/Log.d.ts +18 -12
  133. package/dist/internal/Log.d.ts.map +1 -1
  134. package/dist/internal/Log.js +2 -0
  135. package/dist/internal/Log.js.map +1 -1
  136. package/dist/internal/Tidx.d.ts +9 -3
  137. package/dist/internal/Tidx.d.ts.map +1 -1
  138. package/dist/internal/Tidx.js +13 -7
  139. package/dist/internal/Tidx.js.map +1 -1
  140. package/dist/internal/Viem.d.ts +17 -2
  141. package/dist/internal/Viem.d.ts.map +1 -1
  142. package/dist/internal/Viem.js +30 -3
  143. package/dist/internal/Viem.js.map +1 -1
  144. package/dist/internal/index.d.ts +1 -1
  145. package/dist/internal/index.js +1 -1
  146. package/package.json +2 -2
  147. package/src/ApiKeys.test.ts +25 -0
  148. package/src/App.test-d.ts +2 -0
  149. package/src/App.test.ts +13 -2
  150. package/src/App.ts +37 -5
  151. package/src/Assets.ts +3 -3
  152. package/src/Client.test-d.ts +21 -11
  153. package/src/Scope.test.ts +1 -0
  154. package/src/Scope.ts +32 -4
  155. package/src/Webhooks.ts +5 -10
  156. package/src/admin/apps/api-keys.test.ts +27 -0
  157. package/src/admin/apps/api-keys.ts +8 -1
  158. package/src/admin/apps/scopes.test.ts +7 -2
  159. package/src/admin/apps/scopes.ts +2 -2
  160. package/src/admin/ui/src/components/Dialog.tsx +8 -2
  161. package/src/admin/ui/src/lib/scopes.test.ts +19 -0
  162. package/src/admin/ui/src/lib/scopes.ts +12 -0
  163. package/src/admin/ui/src/routes/api-keys.tsx +128 -27
  164. package/src/admin/ui.generated.ts +33 -33
  165. package/src/apps/composition.test.ts +77 -0
  166. package/src/apps/data/App.test.ts +354 -0
  167. package/src/apps/data/App.ts +178 -91
  168. package/src/apps/data/FundingQuoteProviders.test.ts +577 -0
  169. package/src/apps/data/FundingQuoteProviders.ts +633 -0
  170. package/src/apps/data/index.ts +1 -1
  171. package/src/apps/data/routes/balances.test.ts +64 -18
  172. package/src/apps/data/routes/balances.ts +76 -177
  173. package/src/apps/data/routes/blocks.ts +3 -5
  174. package/src/apps/data/routes/coingecko.ts +3 -8
  175. package/src/apps/data/routes/funding.test.ts +304 -192
  176. package/src/apps/data/routes/funding.ts +166 -286
  177. package/src/apps/data/routes/indexer.test.ts +173 -15
  178. package/src/apps/data/routes/indexer.ts +8 -2
  179. package/src/apps/data/routes/rpc.test.ts +219 -0
  180. package/src/apps/data/routes/rpc.ts +92 -17
  181. package/src/apps/data/routes/webhooks.test.ts +21 -0
  182. package/src/apps/data/routes/webhooks.ts +13 -1
  183. package/src/apps/index.ts +1 -1
  184. package/src/apps/management/App.ts +4 -0
  185. package/src/apps/management/Billing.test.ts +2 -2
  186. package/src/apps/management/routes/api-keys.test.ts +47 -3
  187. package/src/apps/management/routes/api-keys.ts +2 -2
  188. package/src/apps/management/routes/invite-links.test.ts +385 -0
  189. package/src/apps/management/routes/invite-links.ts +570 -0
  190. package/src/apps/management/routes/scopes.test.ts +3 -2
  191. package/src/apps/management/routes/scopes.ts +4 -2
  192. package/src/apps/mcp.test.ts +18 -0
  193. package/src/apps/mcp.ts +2 -2
  194. package/src/apps/relay/App.test.ts +328 -7
  195. package/src/apps/relay/App.ts +42 -6
  196. package/src/apps/relay/Sponsorships.test.ts +2 -2
  197. package/src/db/Db.test.ts +5 -5
  198. package/src/db/Db.ts +65 -0
  199. package/src/db/Schema.ts +39 -0
  200. package/src/db/tables/billingSettings.test.ts +2 -2
  201. package/src/db/tables/inviteLinks.ts +187 -0
  202. package/src/db/tables/organizations.test.ts +2 -2
  203. package/src/db/tables/organizations.ts +2 -0
  204. package/src/db/tables/sponsoredTransactions.test.ts +19 -2
  205. package/src/db/tables/sponsoredTransactions.ts +21 -14
  206. package/src/db/tables/stripeCustomers.test.ts +2 -2
  207. package/src/handlers/relay.ts +16 -4
  208. package/src/internal/Auth.test.ts +132 -22
  209. package/src/internal/Auth.ts +181 -14
  210. package/src/internal/EdgeCache.test.ts +24 -3
  211. package/src/internal/EdgeCache.ts +18 -2
  212. package/src/internal/FundingQuotes.test.ts +268 -0
  213. package/src/internal/{FundingRoutes.ts → FundingQuotes.ts} +216 -277
  214. package/src/internal/Log.ts +21 -14
  215. package/src/internal/Tidx.test.ts +48 -0
  216. package/src/internal/Tidx.ts +21 -7
  217. package/src/internal/Viem.test.ts +46 -0
  218. package/src/internal/Viem.ts +48 -6
  219. package/src/internal/index.test.ts +1 -1
  220. package/src/internal/index.ts +1 -1
  221. package/dist/apps/data/FundingRouteProviders.d.ts +0 -36
  222. package/dist/apps/data/FundingRouteProviders.d.ts.map +0 -1
  223. package/dist/apps/data/FundingRouteProviders.js +0 -245
  224. package/dist/apps/data/FundingRouteProviders.js.map +0 -1
  225. package/dist/internal/FundingRoutes.d.ts.map +0 -1
  226. package/dist/internal/FundingRoutes.js.map +0 -1
  227. package/src/apps/data/FundingRouteProviders.test.ts +0 -225
  228. package/src/apps/data/FundingRouteProviders.ts +0 -312
  229. package/src/internal/FundingRoutes.test.ts +0 -213
@@ -1,15 +1,19 @@
1
- import { Hono } from 'hono'
1
+ import { type Context, Hono } from 'hono'
2
+ import { matchedRoutes } from 'hono/route'
3
+ import { ZoneRpcAuthentication } from 'ox/tempo'
2
4
  import * as z from 'zod/mini'
3
5
 
4
6
  import type * as Assets from '../../Assets.js'
5
7
  import type * as App from '../../App.js'
6
- import * as Metadata from '../metadata.js'
7
8
  import * as Auth from '../../internal/Auth.js'
9
+ import * as EdgeCache from '../../internal/EdgeCache.js'
8
10
  import * as Path from '../../internal/Path.js'
9
11
  import * as Response from '../../internal/Response.js'
10
12
  import * as Schema from '../../internal/Schema.js'
11
13
  import * as Tidx from '../../internal/Tidx.js'
12
14
  import * as Viem from '../../internal/Viem.js'
15
+ import * as Scope from '../../Scope.js'
16
+ import * as Metadata from '../metadata.js'
13
17
 
14
18
  import { transactions as transactionActivities } from './routes/activities.js'
15
19
  import { addresses } from './routes/addresses.js'
@@ -29,102 +33,111 @@ import { verifiedTokens } from './routes/verified-tokens.js'
29
33
  import { schema as webhookSchema, webhooks } from './routes/webhooks.js'
30
34
 
31
35
  /**
32
- * The chain-data route group: read endpoints over blocks, transactions, tokens,
33
- * the exchange/fee-AMM, and the RPC/indexer passthroughs. It owns all chain
34
- * configuration and publishes the chain clients on request context.
36
+ * The chain-data route group extends app context with TIDX, assets, zones,
37
+ * verified tokens, and webhooks, and exposes indexed and RPC-backed reads.
35
38
  *
36
39
  * ```ts
37
40
  * App.create(options).route('/', data())
38
41
  * ```
39
42
  */
40
43
  export function data(options: data.Options = {}) {
41
- const { assets, defaultChainId = Viem.defaultChainId, rpc: rpc_option, tidx } = options
44
+ const { assets, tidx } = options
42
45
 
43
- // The chain ids this deployment serves (built-in public chains plus
44
- // `defaultChainId` and any explicit `supportedChainIds`). Published on context
45
- // so handlers with a path `chainId` can guard it too.
46
- const supportedChainIds = new Set<number>([
47
- ...Object.keys(Viem.url).map(Number),
48
- ...Object.keys(Tidx.url).map(Number),
49
- defaultChainId,
50
- ...(options.supportedChainIds ?? []),
51
- ])
46
+ // Zone endpoints are explicit trust boundaries; the API never derives them.
47
+ const zones = new Map((options.zones ?? []).map((zone) => [zone.chainId, zone]))
48
+ const dataRouteHandlers = new Set<unknown>()
52
49
 
53
- // Memoize one viem client per resolved upstream (stateless HTTP transports).
54
- // Keying on the resolved output (`url`+`basicAuth`+`chainId`), not the
55
- // principal, bounds cardinality by the number of distinct upstreams.
56
- const clients = new Map<string, ReturnType<typeof Viem.getClient>>()
57
- const getClient = (chainId = defaultChainId, principal: Auth.Principal | null = null) => {
58
- const { url, basicAuth } = Viem.resolve(rpc_option, { chainId, principal })
59
- const cacheKey = `${url}|${basicAuth ?? ''}|${chainId}`
60
- const existing = clients.get(cacheKey)
61
- if (existing) return existing
62
- const client = Viem.getClient({ chainId, principal, rpc: rpc_option })
63
- clients.set(cacheKey, client)
64
- return client
65
- }
66
-
67
- // Memoize TIDX clients per `chainId` so the base URL resolver runs once per
68
- // chain instead of on every request.
69
- const getTidx = Tidx.createGetClient({ defaultChainId, tidx })
50
+ const getTidx = Tidx.createGetClient({ tidx })
70
51
 
71
52
  const app = new Hono<App.Environment>()
72
53
  // Publish the chain-data context vars for this group's handlers. Runs after
73
54
  // `App.create`'s middleware, so `getClient`/`getTidx` can key on the
74
55
  // resolved principal and `getAsset` can read `basePath` off context.
75
56
  .use('*', async (c, next) => {
76
- c.set('chainId', defaultChainId)
57
+ const getClient = c.get('getClient')
58
+ const supportedChainIds = new Set<number>([
59
+ ...c.get('supportedChainIds'),
60
+ ...Object.keys(Tidx.url).map(Number),
61
+ ...zones.keys(),
62
+ ])
77
63
  c.set('getAsset', createGetAsset({ assets, basePath: c.get('basePath'), origin: new URL(c.req.url).origin })) // prettier-ignore
78
- c.set('getClient', (chainId) => getClient(chainId, Auth.getPrincipal(c)))
79
- c.set('getTidx', (chainId) => getTidx(chainId, Auth.getPrincipal(c)))
80
- c.set('rpc', rpc_option)
64
+ c.set('getClient', (chainId) => getClient(chainId ?? c.get('chainId')))
65
+ c.set('getTidx', (chainId) => getTidx(chainId ?? c.get('chainId'), Auth.getPrincipal(c)))
81
66
  c.set('supportedChainIds', supportedChainIds)
82
67
  c.set('tidx', tidx)
83
68
  c.set('verifiedTokens', options.verifiedTokens === false ? undefined : (options.verifiedTokens ?? {})) // prettier-ignore
84
69
  c.set('webhook', options.webhook === false ? undefined : (options.webhook ?? {}))
70
+ c.set('zones', zones)
85
71
  await next()
86
72
  })
87
- // Reject a well-formed but unsupported `chainId` before it reaches an
88
- // upstream, which would otherwise return an opaque `upstream_error`.
73
+ // Resolve one chain for every data route before auth and handlers use it.
74
+ .use('*', async (c, next) => {
75
+ const defaultChainId = c.get('chainId')
76
+ const supportedChainIds = c.get('supportedChainIds')
77
+ const selection = resolveChainSelector({
78
+ c,
79
+ defaultChainId,
80
+ handlers: dataRouteHandlers,
81
+ })
82
+ if (!selection.matched) return next()
83
+ if (selection.conflict || selection.invalid)
84
+ return Response.error(c, {
85
+ code: 'chain_id_invalid',
86
+ message: selection.conflict ? 'Conflicting chain ids' : 'Invalid chain id',
87
+ status: 400,
88
+ })
89
+ if (!supportedChainIds.has(selection.chainId))
90
+ return Response.unsupportedChainId(c, selection.chainId, supportedChainIds)
91
+ c.set('chainId', selection.chainId)
92
+ return next()
93
+ })
94
+ // Zone data requires an issued scope. Raw RPC may instead forward the
95
+ // caller's Zone token to the node that enforces account scoping.
89
96
  .use('*', async (c, next) => {
90
- const raw = resolveChainSelector({
91
- basePath: c.get('basePath') ?? '',
92
- path: c.req.path,
93
- query: c.req.query('chainId') ?? c.req.query('chain_id'),
97
+ if (zones.size === 0) return next()
98
+ const selection = resolveChainSelector({
99
+ c,
100
+ defaultChainId: c.get('chainId'),
101
+ handlers: dataRouteHandlers,
94
102
  })
95
- if (raw === undefined) return next()
96
- // Only reject a well-formed-but-unsupported chain here; malformed values
97
- // (e.g. `abc`, `0`) fall through to the route's own query validation.
98
- const parsed = Schema.ChainId.safeParse(raw)
99
- if (!parsed.success || supportedChainIds.has(parsed.data)) return next()
100
- return Response.unsupportedChainId(c, parsed.data, supportedChainIds)
103
+ if (!selection.matched) return next()
104
+ const chainId = c.get('chainId')
105
+ if (!zones.has(chainId)) return next()
106
+
107
+ const principal = Auth.getPrincipal(c)
108
+ const scopes = principal?.type === 'api_key' ? principal.apiKey.scopes : undefined
109
+ const allowed =
110
+ scopes?.includes(Scope.wildcard) ||
111
+ scopes?.includes(`zone:${chainId}:read`) ||
112
+ (selection.rpc && Boolean(c.req.header(ZoneRpcAuthentication.headerName)?.trim()))
113
+ if (!allowed)
114
+ return Response.error(c, {
115
+ code: 'api_key_forbidden',
116
+ message: selection.rpc
117
+ ? `Chain id ${chainId} is a zone. Use an API key granting \`zone:${chainId}:read\`, or pass a Zone token via \`X-Authorization-Token\`.`
118
+ : `Chain id ${chainId} is a zone. Use an API key granting \`zone:${chainId}:read\`.`,
119
+ status: 403,
120
+ })
121
+
122
+ await next()
123
+ // Defense in depth for cache misses; pre-auth lookups are disabled by
124
+ // the route eligibility predicate below.
125
+ c.set('edgeCache', undefined)
101
126
  })
102
127
  // Sandbox keys are testnet-only. A chain-scoped data route defaults to
103
128
  // mainnet, so a sandbox key that omits `chainId` (or targets mainnet) is
104
129
  // refused with a clear message telling the caller to pass a testnet
105
- // `chainId`. The relay resolves its chain from the request body, not the
106
- // query, so it is skipped and enforces the rule in its sponsor validation.
130
+ // `chainId`.
107
131
  .use('*', async (c, next) => {
108
- const rpc = Path.join(c.get('basePath') ?? '', 'rpc')
109
- const relay = Path.join(rpc, 'relay')
110
- if (c.req.path === relay || c.req.path.startsWith(`${relay}/`)) return next()
111
-
112
132
  const principal = Auth.getPrincipal(c)
113
133
  if (principal?.type !== 'api_key' || principal.environment !== 'sandbox') return next()
114
134
 
115
- const raw = resolveChainSelector({
116
- basePath: c.get('basePath') ?? '',
117
- path: c.req.path,
118
- query: c.req.query('chainId') ?? c.req.query('chain_id'),
135
+ const selection = resolveChainSelector({
136
+ c,
137
+ defaultChainId: c.get('chainId'),
138
+ handlers: dataRouteHandlers,
119
139
  })
120
- // Absent falls back to the group default (mainnet); a malformed value
121
- // stays with the route's own validator rather than the sandbox message.
122
- const chainId = (() => {
123
- if (raw === undefined) return defaultChainId
124
- const parsed = Schema.ChainId.safeParse(raw)
125
- return parsed.success ? parsed.data : undefined
126
- })()
127
- if (chainId === undefined || !Viem.isMainnet(chainId)) return next()
140
+ if (!selection.matched || !Viem.isMainnet(c.get('chainId'))) return next()
128
141
 
129
142
  return Response.error(c, {
130
143
  code: 'api_key_forbidden',
@@ -149,7 +162,7 @@ export function data(options: data.Options = {}) {
149
162
  .route('/', blocks())
150
163
  .route('/', exchanges())
151
164
  .route('/', feeAmm())
152
- .route('/', funding(options.fundingRoutes))
165
+ .route('/', funding(options.funding))
153
166
  .route('/', indexer())
154
167
  .route('/', receipts())
155
168
  .route('/', tokenlist())
@@ -162,19 +175,28 @@ export function data(options: data.Options = {}) {
162
175
  .route('/', rpc())
163
176
  .route('/', coingecko())
164
177
  .route('/', blocksByTimestampCompat())
165
- return Metadata.attach(app, openapi)
166
- }
167
-
168
- function resolveChainSelector(options: {
169
- basePath: string
170
- path: string
171
- query: string | undefined
172
- }) {
173
- const rpc = Path.join(options.basePath, 'rpc')
174
- if (options.path === rpc) return undefined
175
- if (options.path.startsWith(`${rpc}/`))
176
- return options.path.slice(rpc.length + 1).split('/')[0] || undefined
177
- return options.query
178
+ const edgeCacheEligible = (c: Context<App.Environment>) => {
179
+ const selection = resolveChainSelector({
180
+ c,
181
+ defaultChainId: c.get('chainId'),
182
+ handlers: dataRouteHandlers,
183
+ })
184
+ return (
185
+ selection.matched &&
186
+ !selection.conflict &&
187
+ !selection.invalid &&
188
+ !zones.has(selection.chainId)
189
+ )
190
+ }
191
+ for (const route of app.routes) {
192
+ if (route.method === 'ALL') continue
193
+ dataRouteHandlers.add(route.handler)
194
+ if (zones.size > 0) EdgeCache.setEligibility(route.handler, edgeCacheEligible)
195
+ }
196
+ return Metadata.attach(app, {
197
+ ...openapi,
198
+ supportedChainIds: [...Object.keys(Tidx.url).map(Number), ...zones.keys()],
199
+ })
178
200
  }
179
201
 
180
202
  export declare namespace data {
@@ -182,20 +204,27 @@ export declare namespace data {
182
204
  type Options = {
183
205
  /** Static API asset store for the current runtime (e.g. `Assets.cloudflareR2(...)`). Omit to skip static assets — token logo URIs go unresolved. */
184
206
  assets?: Assets.Assets | undefined
185
- /** Default Tempo chain id for requests that omit `chainId`. */
186
- defaultChainId?: Viem.ChainId | undefined
187
- /** Funding route provider adapter configuration. Omit to serve static inventory only. */
188
- fundingRoutes?: funding.Options | undefined
189
- /** Tempo RPC options: a static config, or a per-request resolver that routes by request principal to a separate (e.g. public, no-auth) RPC. */
190
- rpc?: Viem.getClient.Rpc | undefined
191
- /** Extra chain ids this deployment serves, beyond the built-in public chains and `defaultChainId`. A `chainId` outside this set is rejected with a 400. */
192
- supportedChainIds?: readonly number[] | undefined
207
+ /** Funding quote provider configuration. */
208
+ funding?: funding.Options | undefined
193
209
  /** TIDX query client options. */
194
210
  tidx?: Tidx.getClient.Tidx | undefined
195
211
  /** Verified-token feature configuration. Enabled by default (token rows live in `db`; empty until published); pass `false` to always serve an empty verified list. */
196
212
  verifiedTokens?: VerifiedTokens | false | undefined
197
213
  /** Webhook feature configuration. Enabled by default (rows live in `db`); pass `false` to disable, leaving `/webhooks` mounted but returning `404` and hidden from the OpenAPI document. */
198
214
  webhook?: Webhook | false | undefined
215
+ /**
216
+ * Zone chains served by this deployment. REST and TIDX require a
217
+ * `zone:<chainId>:read` scope; raw RPC may forward a Zone token instead.
218
+ */
219
+ zones?: readonly Zone[] | undefined
220
+ }
221
+
222
+ /** One zone chain served by this deployment. */
223
+ type Zone = {
224
+ /** Zone chain id. Consumer-supplied literal; the API never derives zone chain ids. */
225
+ chainId: number
226
+ /** Zone RPC used when forwarding a caller-supplied Zone token. */
227
+ forwardRpcUrl: string
199
228
  }
200
229
 
201
230
  /** Verified-token feature configuration. */
@@ -219,6 +248,66 @@ export declare namespace data {
219
248
  }
220
249
  }
221
250
 
251
+ function resolveChainSelector(options: {
252
+ c: Context<App.Environment>
253
+ defaultChainId: Viem.ChainId
254
+ handlers: ReadonlySet<unknown>
255
+ }) {
256
+ let matched = false
257
+ let path: string | undefined
258
+ let rpc = false
259
+ for (const route of matchedRoutes(options.c)) {
260
+ if (!options.handlers.has(route.handler)) continue
261
+ matched = true
262
+ const selector = pathChainSelector(route.path, options.c.req.path)
263
+ if (selector.name === undefined) continue
264
+ path = selector.value
265
+ rpc = selector.name === 'chain'
266
+ break
267
+ }
268
+
269
+ if (!matched)
270
+ return {
271
+ chainId: options.defaultChainId,
272
+ conflict: false,
273
+ invalid: false,
274
+ matched: false,
275
+ rpc: false,
276
+ }
277
+
278
+ const search = new URL(options.c.req.url).searchParams
279
+ const raw = [
280
+ ...(path === undefined ? [] : [path]),
281
+ ...(rpc ? [] : search.getAll('chainId')),
282
+ ...(rpc ? [] : search.getAll('chain_id')),
283
+ ]
284
+ const parsed = raw.map((value) => Schema.ChainId.safeParse(value))
285
+ const chainIds = [...new Set(parsed.flatMap((result) => (result.success ? [result.data] : [])))]
286
+ return {
287
+ chainId: chainIds[0] ?? options.defaultChainId,
288
+ conflict: chainIds.length > 1,
289
+ invalid: parsed.some((result) => !result.success),
290
+ matched: true,
291
+ rpc,
292
+ }
293
+ }
294
+
295
+ function pathChainSelector(routePath: string, requestPath: string) {
296
+ const route = routePath.split('/')
297
+ const request = requestPath.split('/')
298
+ for (let index = 0; index < route.length; index++) {
299
+ const segment = route[index] ?? ''
300
+ for (const name of ['chainId', 'chain'] as const) {
301
+ const prefix = `:${name}`
302
+ if (!segment.startsWith(prefix)) continue
303
+ const suffix = segment.slice(prefix.length)
304
+ if (suffix !== '' && suffix !== '?' && !suffix.startsWith('{')) continue
305
+ return { name, value: request[index] || undefined }
306
+ }
307
+ }
308
+ return { name: undefined, value: undefined }
309
+ }
310
+
222
311
  /**
223
312
  * The OpenAPI metadata this group owns: its operation tags and the outbound
224
313
  * `webhooks` block. Tagged onto the {@link data} instance so `App.create`
@@ -231,8 +320,7 @@ const openapi = {
231
320
  { name: 'Blocks', description: 'The ordered batches of transactions making up the chain.' },
232
321
  { name: 'Exchange', description: "Tempo's built-in stablecoin exchange: pairs, swaps, orders, prices." }, // prettier-ignore
233
322
  { name: 'Fee AMM', description: 'Pools that convert stablecoins to pay fees.' },
234
- { name: 'Funding', description: 'Routes and inventory for moving stablecoins into Tempo.' },
235
- { name: 'Providers', description: 'Provider metadata, capabilities, and integration status.' },
323
+ { name: 'Funding', description: 'Live quotes and supported inputs for funding Tempo.' },
236
324
  { name: 'Tokens', description: 'TIP-20 token details, supply, and holders.' },
237
325
  { name: 'Transactions', description: 'Transactions submitted to Tempo, and their receipts.' },
238
326
  { name: 'Transfers', description: 'Token movements from executing transactions.' },
@@ -252,7 +340,6 @@ const openapi = {
252
340
  'Exchange',
253
341
  'Fee AMM',
254
342
  'Funding',
255
- 'Providers',
256
343
  'Tokens',
257
344
  'Transactions',
258
345
  'Transfers',