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,4 +1,5 @@
1
1
  import { Hono, type Context, type TypedResponse } from 'hono'
2
+ import { ZoneRpcAuthentication } from 'ox/tempo'
2
3
  import * as z from 'zod/mini'
3
4
 
4
5
  import type * as App from '../../../App.js'
@@ -19,10 +20,52 @@ const strippedHeaders = new Set([
19
20
  'keep-alive',
20
21
  'proxy-authorization',
21
22
  'te',
23
+ 'tempo-api-key',
22
24
  'trailer',
23
25
  'transfer-encoding',
24
26
  'upgrade',
25
- 'tempo-api-key',
27
+ ZoneRpcAuthentication.headerName.toLowerCase(),
28
+ ])
29
+
30
+ // API-issued Zone read scopes never reach stateful or privileged RPC modules.
31
+ const zoneReadMethods = new Set([
32
+ 'eth_blobBaseFee',
33
+ 'eth_blockNumber',
34
+ 'eth_call',
35
+ 'eth_chainId',
36
+ 'eth_createAccessList',
37
+ 'eth_estimateGas',
38
+ 'eth_feeHistory',
39
+ 'eth_fillTransaction',
40
+ 'eth_gasPrice',
41
+ 'eth_getBalance',
42
+ 'eth_getBlockByHash',
43
+ 'eth_getBlockByNumber',
44
+ 'eth_getBlockReceipts',
45
+ 'eth_getBlockTransactionCountByHash',
46
+ 'eth_getBlockTransactionCountByNumber',
47
+ 'eth_getCode',
48
+ 'eth_getLogs',
49
+ 'eth_getProof',
50
+ 'eth_getRawTransactionByHash',
51
+ 'eth_getStorageAt',
52
+ 'eth_getTransactionByBlockHashAndIndex',
53
+ 'eth_getTransactionByBlockNumberAndIndex',
54
+ 'eth_getTransactionByHash',
55
+ 'eth_getTransactionCount',
56
+ 'eth_getTransactionReceipt',
57
+ 'eth_getUncleByBlockHashAndIndex',
58
+ 'eth_getUncleByBlockNumberAndIndex',
59
+ 'eth_getUncleCountByBlockHash',
60
+ 'eth_getUncleCountByBlockNumber',
61
+ 'eth_maxPriorityFeePerGas',
62
+ 'eth_simulateV1',
63
+ 'eth_syncing',
64
+ 'net_listening',
65
+ 'net_peerCount',
66
+ 'net_version',
67
+ 'web3_clientVersion',
68
+ 'web3_sha3',
26
69
  ])
27
70
 
28
71
  /** Never reached; carries typed passthrough response variants into Hono client inference. */
@@ -105,7 +148,7 @@ export function rpc() {
105
148
  Auth.policy({ apiKey: { scopes: ['data:read'] }, public: true }),
106
149
  OpenApi.describeRoute({
107
150
  description:
108
- 'Sends a single or batch Ethereum JSON-RPC request directly to Tempo. Use `/rpc/testnet`, `/rpc/mainnet`, or `/rpc/:chain` to choose a chain. This passthrough is not part of the stable API contract and is supported on a best-effort basis only. It has no compatibility, latency, availability, or data-freshness guarantees, and breaking changes may happen with limited notice.',
151
+ 'Sends a single or batch Ethereum JSON-RPC request directly to Tempo. Use `/rpc/testnet`, `/rpc/mainnet`, or `/rpc/:chain` to choose a chain. Zone-scoped API keys may call read-only methods. A Zone token in `X-Authorization-Token` is forwarded to the authenticated Zone RPC. This passthrough is not part of the stable API contract and is supported on a best-effort basis only. It has no compatibility, latency, availability, or data-freshness guarantees, and breaking changes may happen with limited notice.',
109
152
  operationId: 'rpcRequest',
110
153
  parameters: [
111
154
  {
@@ -146,19 +189,6 @@ export function rpc() {
146
189
  async (c) => {
147
190
  if (Auth.narrowAccess) return Auth.accessError(c)
148
191
 
149
- if (requiresApiKey(c)) {
150
- const body = await c.req.raw
151
- .clone()
152
- .json()
153
- .catch(() => undefined)
154
- if (!isPublicRequest(body))
155
- return Response.error(c, {
156
- code: 'api_key_missing',
157
- message: 'Missing API key',
158
- status: 401,
159
- })
160
- }
161
-
162
192
  const chainId = resolveChainId(c)
163
193
  if (!chainId)
164
194
  return Response.error(c, {
@@ -167,6 +197,31 @@ export function rpc() {
167
197
  status: 400,
168
198
  })
169
199
 
200
+ const zone = c.get('zones').get(chainId)
201
+ const zoneToken = getZoneToken(c)
202
+ const apiKeyRequired = requiresApiKey(c)
203
+ const inspectBody = apiKeyRequired || Boolean(zone && !zoneToken)
204
+ const body = inspectBody
205
+ ? await c.req.raw
206
+ .clone()
207
+ .json()
208
+ .catch(() => undefined)
209
+ : undefined
210
+
211
+ if (apiKeyRequired && !isPublicRequest(body))
212
+ return Response.error(c, {
213
+ code: 'api_key_missing',
214
+ message: 'Missing API key',
215
+ status: 401,
216
+ })
217
+ if (zone && !zoneToken && !isZoneReadRequest(body))
218
+ return Response.error(c, {
219
+ code: 'api_key_forbidden',
220
+ message:
221
+ 'Zone API keys may only call read-only RPC methods. Pass a Zone token via `X-Authorization-Token` for other methods.',
222
+ status: 403,
223
+ })
224
+
170
225
  if (narrowPassthrough)
171
226
  return c.json(Response.validated(schema.ResponseBody, { jsonrpc: '2.0' }), 200)
172
227
  return passthrough(c, chainId)
@@ -180,6 +235,11 @@ function requiresApiKey(c: Context<App.Environment>) {
180
235
  return principal?.type !== 'api_key' && principal?.type !== 'super_admin'
181
236
  }
182
237
 
238
+ function getZoneToken(c: Context<App.Environment>) {
239
+ const value = c.req.header(ZoneRpcAuthentication.headerName)
240
+ return value?.trim() ? value : undefined
241
+ }
242
+
183
243
  function isPublicRequest(body: unknown): boolean {
184
244
  const requests = Array.isArray(body) ? body : [body]
185
245
  return requests.length > 0 && requests.every((request) => isPublicMethod(request))
@@ -191,13 +251,27 @@ function isPublicMethod(request: unknown): boolean {
191
251
  return typeof method === 'string' && method.startsWith('eth_')
192
252
  }
193
253
 
254
+ function isZoneReadRequest(body: unknown): boolean {
255
+ const requests = Array.isArray(body) ? body : [body]
256
+ return requests.length > 0 && requests.every((request) => isZoneReadMethod(request))
257
+ }
258
+
259
+ function isZoneReadMethod(request: unknown): boolean {
260
+ if (!request || typeof request !== 'object') return false
261
+ const method = (request as Record<string, unknown>)['method']
262
+ return typeof method === 'string' && zoneReadMethods.has(method)
263
+ }
264
+
194
265
  async function passthrough(
195
266
  c: Context<App.Environment>,
196
267
  chainId: z.output<typeof Schema.ChainId>,
197
268
  ): Promise<globalThis.Response & TypedResponse<never, never, never>> {
198
- const rpc = c.get('rpc')
269
+ const zone = c.get('zones').get(chainId)
270
+ const zoneToken = getZoneToken(c)
271
+ const rpc = zone && zoneToken ? { url: () => zone.forwardRpcUrl } : c.get('rpc')
199
272
  const principal = Auth.getPrincipal(c)
200
- const upstream_rpc = principal?.type === 'public' && typeof rpc !== 'function' ? undefined : rpc
273
+ const upstream_rpc =
274
+ !zone && principal?.type === 'public' && typeof rpc !== 'function' ? undefined : rpc
201
275
  // Anonymous callers use the built-in public RPC unless a resolver chooses a
202
276
  // different public upstream.
203
277
  const { basicAuth, url } = Viem.resolve(upstream_rpc, { chainId, principal })
@@ -215,6 +289,7 @@ async function passthrough(
215
289
  for (const [name, value] of c.req.raw.headers)
216
290
  if (!strippedHeaders.has(name.toLowerCase())) headers.set(name, value)
217
291
  if (basicAuth) headers.set('authorization', `Basic ${btoa(basicAuth)}`)
292
+ if (zone && zoneToken) headers.set(ZoneRpcAuthentication.headerName, zoneToken)
218
293
 
219
294
  // `duplex` is required when streaming a request body, but is not yet in
220
295
  // the DOM `RequestInit` type.
@@ -112,6 +112,27 @@ describe('access matrix', () => {
112
112
  expect(body.error?.code).toMatchInlineSnapshot(`"api_key_forbidden"`)
113
113
  })
114
114
 
115
+ test('rejects Zone webhook subscriptions', async () => {
116
+ const zone = 421_700_001
117
+ const app = TestApp.client({
118
+ auth: { keys: [writer] },
119
+ zones: [
120
+ {
121
+ chainId: zone,
122
+ forwardRpcUrl: 'https://zone.rpc.test',
123
+ },
124
+ ],
125
+ })
126
+ const response = await app.v1.webhooks.$post(
127
+ { json: { ...validBody, chainId: zone } },
128
+ as(writer),
129
+ )
130
+ const body = (await response.json()) as { error?: { code?: string } }
131
+
132
+ expect(response.status).toBe(400)
133
+ expect(body.error?.code).toBe('chain_id_unsupported')
134
+ })
135
+
115
136
  test('lists event types without authentication (public catalog)', async () => {
116
137
  // `GET /webhooks/event-types` is intentionally public: anonymous callers are
117
138
  // served within the public quota, no key or scope required.
@@ -1294,7 +1294,13 @@ export function webhooks(options: webhooks.Options = {}) {
1294
1294
  hide: hidden,
1295
1295
  operationId: 'createWebhook',
1296
1296
  responses: OpenApi.responses({
1297
- errors: { 403: 'You have reached the webhook subscription limit.' },
1297
+ errors: {
1298
+ 400: {
1299
+ codes: ['chain_id_unsupported'],
1300
+ description: 'Webhook subscriptions do not support Zone chains.',
1301
+ },
1302
+ 403: 'You have reached the webhook subscription limit.',
1303
+ },
1298
1304
  success: {
1299
1305
  description:
1300
1306
  'The created webhook subscription, including the one-time signing secret.',
@@ -1320,6 +1326,12 @@ export function webhooks(options: webhooks.Options = {}) {
1320
1326
 
1321
1327
  const body = c.req.valid('json')
1322
1328
  const chainId = body.chainId ?? c.get('chainId')
1329
+ if (c.get('zones').has(chainId))
1330
+ return Response.error(c, {
1331
+ code: 'chain_id_unsupported',
1332
+ message: 'Webhook subscriptions do not support Zone chains.',
1333
+ status: 400,
1334
+ })
1323
1335
 
1324
1336
  try {
1325
1337
  const subscription = await Webhooks.createSubscription(
package/src/apps/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { data, FundingRouteProviders } from './data/index.js'
1
+ export { data, FundingQuoteProviders } from './data/index.js'
2
2
  export { management } from './management/index.js'
3
3
  export { mcp } from './mcp.js'
4
4
  export { relay } from './relay/index.js'
@@ -7,6 +7,7 @@ import * as Metadata from '../metadata.js'
7
7
  import { apiKeys } from './routes/api-keys.js'
8
8
  import { billing } from './routes/billing.js'
9
9
  import { invitations } from './routes/invitations.js'
10
+ import { inviteLinks } from './routes/invite-links.js'
10
11
  import { me } from './routes/me.js'
11
12
  import { members } from './routes/members.js'
12
13
  import { orgs } from './routes/orgs.js'
@@ -105,6 +106,7 @@ export function management(options: management.Options = {}) {
105
106
  .route('/', apiKeys({ enabled: options.apiKeys !== false }))
106
107
  .route('/', billing())
107
108
  .route('/', invitations())
109
+ .route('/', inviteLinks())
108
110
  .route('/', me())
109
111
  .route('/', members())
110
112
  .route('/', orgs())
@@ -144,6 +146,7 @@ const openapi = {
144
146
  { name: 'API Keys', description: 'Credentials for accessing the Tempo API.' },
145
147
  { name: 'Billing', description: 'Organization billing backed by Stripe.' },
146
148
  { name: 'Invitations', description: 'Pending organization invitations.' },
149
+ { name: 'Invite Links', description: 'Reusable organization invite links.' },
147
150
  { name: 'Members', description: 'Organization team membership.' },
148
151
  { name: 'Organizations', description: 'Teams that own application workspaces and members.' },
149
152
  { name: 'Projects', description: 'Application workspaces within an organization.' },
@@ -158,6 +161,7 @@ const openapi = {
158
161
  'Authentication',
159
162
  'Billing',
160
163
  'Invitations',
164
+ 'Invite Links',
161
165
  'Members',
162
166
  'Organizations',
163
167
  'Projects',
@@ -1,7 +1,7 @@
1
1
  import { nanoid } from 'nanoid'
2
2
  import type Stripe from 'stripe'
3
3
 
4
- import { postgresUrl } from '../../../test/containers.js'
4
+ import * as Runtime from '../../../test/runtime.js'
5
5
  import * as ApiKeys from '../../ApiKeys.js'
6
6
  import * as Db from '../../db/Db.js'
7
7
  import * as RequestUsage from '../../db/tables/requestUsage.js'
@@ -11,7 +11,7 @@ import * as Store from '../../internal/Store.js'
11
11
  import * as Viem from '../../internal/Viem.js'
12
12
  import * as Billing from './Billing.js'
13
13
 
14
- const create = () => Db.postgres({ connectionString: postgresUrl, schema: `t_${nanoid()}` })
14
+ const create = () => Db.postgres({ connectionString: Runtime.postgresUrl, schema: `t_${nanoid()}` })
15
15
 
16
16
  /** Baseline billable mainnet sponsorship input; tests override per case. */
17
17
  const input = {
@@ -111,7 +111,20 @@ describe('keys', () => {
111
111
  expect(write.status).toBe(200)
112
112
  })
113
113
 
114
- test('lets management keys manage org credentials without delegating issuance', async () => {
114
+ test('zone scopes are never self-serve', async () => {
115
+ const app = createApp()
116
+ const owner = await session(app)
117
+ const { org, project } = await fixture(app, owner)
118
+ const base = `/v1/orgs/${org.id}/projects/${project.id}/api-keys`
119
+
120
+ // Even the org owner cannot self-mint zone access: membership is
121
+ // controlled zone-side, so zone scopes are super-admin-issued.
122
+ const denied = await app.request(base, json('POST', owner, { scopes: ['zone:421700001:read'] }))
123
+ expect(denied.status).toBe(403)
124
+ expect(await denied.json()).toMatchObject({ error: { code: 'scope_not_issuable' } })
125
+ })
126
+
127
+ test('restricts project management keys without delegating issuance', async () => {
115
128
  const app = createApp()
116
129
  const cookie = await session(app)
117
130
  const { org, project } = await fixture(app, cookie)
@@ -135,10 +148,28 @@ describe('keys', () => {
135
148
  Keys.schema.createApiKey.Response,
136
149
  )
137
150
 
151
+ const own = await app.request(base, {
152
+ headers: { 'tempo-api-key': manager.token },
153
+ })
154
+ expect(own.status).toMatchInlineSnapshot(`200`)
155
+
138
156
  const listed = await app.request(siblingBase, {
139
157
  headers: { 'tempo-api-key': manager.token },
140
158
  })
141
- expect(listed.status).toBe(200)
159
+ const { requestId: listedRequestId, ...listedBody } = (await listed.json()) as {
160
+ requestId?: string
161
+ }
162
+
163
+ expect(listed.status).toMatchInlineSnapshot(`404`)
164
+ expect(listedRequestId).toBeDefined()
165
+ expect(listedBody).toMatchInlineSnapshot(`
166
+ {
167
+ "error": {
168
+ "code": "project_not_found",
169
+ "message": "Project not found",
170
+ },
171
+ }
172
+ `)
142
173
 
143
174
  const delegated = await app.request(base, {
144
175
  body: JSON.stringify({ scopes: ['management:read'] }),
@@ -151,7 +182,20 @@ describe('keys', () => {
151
182
  headers: { 'tempo-api-key': manager.token },
152
183
  method: 'DELETE',
153
184
  })
154
- expect(revoked.status).toBe(200)
185
+ const { requestId: revokedRequestId, ...revokedBody } = (await revoked.json()) as {
186
+ requestId?: string
187
+ }
188
+
189
+ expect(revoked.status).toMatchInlineSnapshot(`404`)
190
+ expect(revokedRequestId).toBeDefined()
191
+ expect(revokedBody).toMatchInlineSnapshot(`
192
+ {
193
+ "error": {
194
+ "code": "project_not_found",
195
+ "message": "Project not found",
196
+ },
197
+ }
198
+ `)
155
199
  })
156
200
 
157
201
  test('mints, lists, authenticates, and revokes a project key', async () => {
@@ -60,7 +60,7 @@ export namespace schema {
60
60
  .array(z.string().check(z.minLength(1)))
61
61
  .check(
62
62
  z.describe(
63
- "Granted scopes. '*' (grant-all) appears only on keys minted by the super admin.",
63
+ "Granted scopes. '*' and non-self-serve scopes appear only on keys minted by the super admin.",
64
64
  ),
65
65
  z.meta({ examples: [['data:read']] }),
66
66
  ),
@@ -218,7 +218,7 @@ export namespace schema {
218
218
  }
219
219
 
220
220
  /**
221
- * Mounts project-attributed API key mint, list, and revoke routes plus the org-wide list. Access follows the caller's organization ownership.
221
+ * Mounts project-attributed API key mint, list, and revoke routes plus the org-wide list. Project routes enforce caller organization and project attribution.
222
222
  */
223
223
  export function apiKeys(options: apiKeys.Options = {}) {
224
224
  const hidden = !options.enabled