tapimo 0.7.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (294) hide show
  1. package/README.md +10 -7
  2. package/dist/ApiKey.d.ts +3 -0
  3. package/dist/ApiKey.d.ts.map +1 -1
  4. package/dist/ApiKey.js +10 -0
  5. package/dist/ApiKey.js.map +1 -1
  6. package/dist/ApiKeys.d.ts +7 -3
  7. package/dist/ApiKeys.d.ts.map +1 -1
  8. package/dist/ApiKeys.js +9 -2
  9. package/dist/ApiKeys.js.map +1 -1
  10. package/dist/App.d.ts +20434 -19484
  11. package/dist/App.d.ts.map +1 -1
  12. package/dist/App.js +62 -4
  13. package/dist/App.js.map +1 -1
  14. package/dist/admin/App.d.ts +84 -74
  15. package/dist/admin/App.d.ts.map +1 -1
  16. package/dist/admin/apps/api-keys.d.ts +14 -1
  17. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  18. package/dist/admin/apps/api-keys.js +6 -2
  19. package/dist/admin/apps/api-keys.js.map +1 -1
  20. package/dist/admin/apps/organizations.d.ts +2 -0
  21. package/dist/admin/apps/organizations.d.ts.map +1 -1
  22. package/dist/admin/apps/verified-tokens.d.ts +168 -168
  23. package/dist/admin/ui.generated.d.ts +1 -1
  24. package/dist/admin/ui.generated.js +67 -67
  25. package/dist/admin/ui.generated.js.map +1 -1
  26. package/dist/analytics/Analytics.d.ts +2 -0
  27. package/dist/analytics/Analytics.d.ts.map +1 -1
  28. package/dist/analytics/Analytics.js +3 -0
  29. package/dist/analytics/Analytics.js.map +1 -1
  30. package/dist/apps/data/App.d.ts +10305 -12785
  31. package/dist/apps/data/App.d.ts.map +1 -1
  32. package/dist/apps/data/App.js +4 -8
  33. package/dist/apps/data/App.js.map +1 -1
  34. package/dist/apps/data/routes/activities.d.ts +24 -24
  35. package/dist/apps/data/routes/addresses.d.ts +288 -288
  36. package/dist/apps/data/routes/balances.d.ts +6 -6
  37. package/dist/apps/data/routes/blocks.d.ts +39 -39
  38. package/dist/apps/data/routes/coingecko.d.ts +170 -170
  39. package/dist/apps/data/routes/earn.d.ts +436 -6
  40. package/dist/apps/data/routes/earn.d.ts.map +1 -1
  41. package/dist/apps/data/routes/earn.js +631 -4
  42. package/dist/apps/data/routes/earn.js.map +1 -1
  43. package/dist/apps/data/routes/exchanges.d.ts +10 -10
  44. package/dist/apps/data/routes/fee-amm.d.ts +2 -2
  45. package/dist/apps/data/routes/indexer.d.ts +6 -6
  46. package/dist/apps/data/routes/indexer.js +4 -1
  47. package/dist/apps/data/routes/indexer.js.map +1 -1
  48. package/dist/apps/data/routes/receipts.d.ts +235 -235
  49. package/dist/apps/data/routes/rpc.d.ts +1 -1
  50. package/dist/apps/data/routes/rpc.js +4 -1
  51. package/dist/apps/data/routes/rpc.js.map +1 -1
  52. package/dist/apps/data/routes/tokenlist.d.ts +36 -36
  53. package/dist/apps/data/routes/tokens.d.ts +6 -6
  54. package/dist/apps/data/routes/transactions.d.ts +2 -2
  55. package/dist/apps/data/routes/transfers.d.ts +1 -1
  56. package/dist/apps/data/routes/valuation.d.ts +1 -1
  57. package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
  58. package/dist/apps/data/routes/webhooks.d.ts +9 -9
  59. package/dist/apps/data/routes/zones.d.ts +14 -14
  60. package/dist/apps/funding/Access.d.ts +16 -0
  61. package/dist/apps/funding/Access.d.ts.map +1 -0
  62. package/dist/apps/funding/Access.js +22 -0
  63. package/dist/apps/funding/Access.js.map +1 -0
  64. package/dist/apps/{data/routes/funding.d.ts → funding/App.d.ts} +1717 -3035
  65. package/dist/apps/funding/App.d.ts.map +1 -0
  66. package/dist/apps/funding/App.js +46 -0
  67. package/dist/apps/funding/App.js.map +1 -0
  68. package/dist/apps/funding/Inventory.d.ts +5 -0
  69. package/dist/apps/funding/Inventory.d.ts.map +1 -0
  70. package/dist/apps/funding/Inventory.js +25 -0
  71. package/dist/apps/funding/Inventory.js.map +1 -0
  72. package/dist/apps/funding/ProviderAttempts.d.ts +5 -0
  73. package/dist/apps/funding/ProviderAttempts.d.ts.map +1 -0
  74. package/dist/apps/funding/ProviderAttempts.js +24 -0
  75. package/dist/apps/funding/ProviderAttempts.js.map +1 -0
  76. package/dist/apps/funding/index.d.ts +2 -0
  77. package/dist/apps/funding/index.d.ts.map +1 -0
  78. package/dist/apps/funding/index.js +2 -0
  79. package/dist/apps/funding/index.js.map +1 -0
  80. package/dist/apps/funding/routes/chains.d.ts +169 -0
  81. package/dist/apps/funding/routes/chains.d.ts.map +1 -0
  82. package/dist/apps/funding/routes/chains.js +80 -0
  83. package/dist/apps/funding/routes/chains.js.map +1 -0
  84. package/dist/apps/funding/routes/deposit-addresses.d.ts +2388 -0
  85. package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -0
  86. package/dist/apps/funding/routes/deposit-addresses.js +860 -0
  87. package/dist/apps/funding/routes/deposit-addresses.js.map +1 -0
  88. package/dist/apps/funding/routes/providers.d.ts +91 -0
  89. package/dist/apps/funding/routes/providers.d.ts.map +1 -0
  90. package/dist/apps/funding/routes/providers.js +56 -0
  91. package/dist/apps/funding/routes/providers.js.map +1 -0
  92. package/dist/apps/funding/routes/quotes.d.ts +426 -0
  93. package/dist/apps/funding/routes/quotes.d.ts.map +1 -0
  94. package/dist/apps/funding/routes/quotes.js +188 -0
  95. package/dist/apps/funding/routes/quotes.js.map +1 -0
  96. package/dist/apps/funding/routes/transfers.d.ts +1999 -0
  97. package/dist/apps/funding/routes/transfers.d.ts.map +1 -0
  98. package/dist/apps/funding/routes/transfers.js +614 -0
  99. package/dist/apps/funding/routes/transfers.js.map +1 -0
  100. package/dist/apps/index.d.ts +1 -0
  101. package/dist/apps/index.d.ts.map +1 -1
  102. package/dist/apps/index.js +1 -0
  103. package/dist/apps/index.js.map +1 -1
  104. package/dist/apps/management/App.d.ts +430 -165
  105. package/dist/apps/management/App.d.ts.map +1 -1
  106. package/dist/apps/management/routes/api-keys.d.ts +298 -21
  107. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  108. package/dist/apps/management/routes/api-keys.js +75 -1
  109. package/dist/apps/management/routes/api-keys.js.map +1 -1
  110. package/dist/apps/management/routes/billing.d.ts +6 -6
  111. package/dist/apps/management/routes/faucet.d.ts +35 -35
  112. package/dist/apps/management/routes/invitations.d.ts +48 -48
  113. package/dist/apps/management/routes/invite-links.d.ts +38 -38
  114. package/dist/apps/management/routes/members.d.ts +69 -69
  115. package/dist/apps/management/routes/orgs.d.ts +37 -37
  116. package/dist/apps/management/routes/projects.d.ts +25 -25
  117. package/dist/apps/management/routes/scopes.d.ts +1 -1
  118. package/dist/apps/management/routes/usage.d.ts +72 -72
  119. package/dist/apps/management/routes/verified-token-requests.d.ts +18 -18
  120. package/dist/apps/management/routes/webhooks.d.ts +128 -128
  121. package/dist/apps/mcp.d.ts +2 -3
  122. package/dist/apps/mcp.d.ts.map +1 -1
  123. package/dist/apps/mcp.js +16 -14
  124. package/dist/apps/mcp.js.map +1 -1
  125. package/dist/apps/metadata.d.ts +4 -0
  126. package/dist/apps/metadata.d.ts.map +1 -1
  127. package/dist/apps/metadata.js +10 -0
  128. package/dist/apps/metadata.js.map +1 -1
  129. package/dist/apps/mpp/App.d.ts +2 -2
  130. package/dist/cloudflare.d.ts +50 -0
  131. package/dist/cloudflare.d.ts.map +1 -1
  132. package/dist/cloudflare.js +178 -0
  133. package/dist/cloudflare.js.map +1 -1
  134. package/dist/db/Db.d.ts +2 -0
  135. package/dist/db/Db.d.ts.map +1 -1
  136. package/dist/db/Db.js +37 -0
  137. package/dist/db/Db.js.map +1 -1
  138. package/dist/db/Schema.d.ts +12 -0
  139. package/dist/db/Schema.d.ts.map +1 -1
  140. package/dist/db/Schema.js +20 -0
  141. package/dist/db/Schema.js.map +1 -1
  142. package/dist/db/tables/fundingDepositAddresses.d.ts +13 -0
  143. package/dist/db/tables/fundingDepositAddresses.d.ts.map +1 -1
  144. package/dist/db/tables/fundingDepositAddresses.js +12 -0
  145. package/dist/db/tables/fundingDepositAddresses.js.map +1 -1
  146. package/dist/db/tables/fundingDepositRequestObservations.d.ts +45 -0
  147. package/dist/db/tables/fundingDepositRequestObservations.d.ts.map +1 -0
  148. package/dist/db/tables/fundingDepositRequestObservations.js +46 -0
  149. package/dist/db/tables/fundingDepositRequestObservations.js.map +1 -0
  150. package/dist/db/tables/fundingDeposits.d.ts +57 -1
  151. package/dist/db/tables/fundingDeposits.d.ts.map +1 -1
  152. package/dist/db/tables/fundingDeposits.js +82 -0
  153. package/dist/db/tables/fundingDeposits.js.map +1 -1
  154. package/dist/internal/Auth.d.ts +11 -11
  155. package/dist/internal/Auth.d.ts.map +1 -1
  156. package/dist/internal/Auth.js +41 -15
  157. package/dist/internal/Auth.js.map +1 -1
  158. package/dist/internal/Cache.js +1 -1
  159. package/dist/internal/Cache.js.map +1 -1
  160. package/dist/internal/EdgeCache.d.ts +2 -2
  161. package/dist/internal/EdgeCache.d.ts.map +1 -1
  162. package/dist/internal/EdgeCache.js +10 -4
  163. package/dist/internal/EdgeCache.js.map +1 -1
  164. package/dist/internal/IpAllowlist.d.ts +15 -0
  165. package/dist/internal/IpAllowlist.d.ts.map +1 -0
  166. package/dist/internal/IpAllowlist.js +27 -0
  167. package/dist/internal/IpAllowlist.js.map +1 -0
  168. package/dist/internal/OpenApi.d.ts +1 -1
  169. package/dist/internal/OpenApi.js +1 -1
  170. package/dist/internal/OpenApi.js.map +1 -1
  171. package/dist/internal/Tidx.d.ts +6 -0
  172. package/dist/internal/Tidx.d.ts.map +1 -1
  173. package/dist/internal/Tidx.js +13 -3
  174. package/dist/internal/Tidx.js.map +1 -1
  175. package/dist/internal/Viem.d.ts +8 -0
  176. package/dist/internal/Viem.d.ts.map +1 -1
  177. package/dist/internal/Viem.js +12 -5
  178. package/dist/internal/Viem.js.map +1 -1
  179. package/dist/internal/funding/Deposit.d.ts +42 -0
  180. package/dist/internal/funding/Deposit.d.ts.map +1 -1
  181. package/dist/internal/funding/Deposit.js +37 -5
  182. package/dist/internal/funding/Deposit.js.map +1 -1
  183. package/dist/internal/funding/Evidence.d.ts +1 -5
  184. package/dist/internal/funding/Evidence.d.ts.map +1 -1
  185. package/dist/internal/funding/Evidence.js +4 -3
  186. package/dist/internal/funding/Evidence.js.map +1 -1
  187. package/dist/internal/funding/Provider.d.ts +102 -0
  188. package/dist/internal/funding/Provider.d.ts.map +1 -1
  189. package/dist/internal/funding/Provider.js +5 -0
  190. package/dist/internal/funding/Provider.js.map +1 -1
  191. package/dist/internal/funding/Reconciliation.d.ts +16 -2
  192. package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
  193. package/dist/internal/funding/Reconciliation.js +257 -48
  194. package/dist/internal/funding/Reconciliation.js.map +1 -1
  195. package/dist/internal/funding/SourceObservation.d.ts +108 -0
  196. package/dist/internal/funding/SourceObservation.d.ts.map +1 -0
  197. package/dist/internal/funding/SourceObservation.js +143 -0
  198. package/dist/internal/funding/SourceObservation.js.map +1 -0
  199. package/dist/internal/funding/Subsidy.d.ts +2 -2
  200. package/dist/internal/funding/Subsidy.d.ts.map +1 -1
  201. package/dist/internal/funding/Subsidy.js +1 -1
  202. package/dist/internal/funding/Subsidy.js.map +1 -1
  203. package/dist/internal/funding/index.d.ts +1 -0
  204. package/dist/internal/funding/index.d.ts.map +1 -1
  205. package/dist/internal/funding/index.js +1 -0
  206. package/dist/internal/funding/index.js.map +1 -1
  207. package/dist/internal/funding/providers/relay.d.ts +43 -1
  208. package/dist/internal/funding/providers/relay.d.ts.map +1 -1
  209. package/dist/internal/funding/providers/relay.js +434 -27
  210. package/dist/internal/funding/providers/relay.js.map +1 -1
  211. package/package.json +3 -3
  212. package/src/ApiKey.test.ts +35 -0
  213. package/src/ApiKey.ts +27 -0
  214. package/src/ApiKeys.test.ts +29 -0
  215. package/src/ApiKeys.ts +11 -3
  216. package/src/App.test.ts +23 -5
  217. package/src/App.ts +76 -5
  218. package/src/Client.test-d.ts +171 -19
  219. package/src/admin/apps/api-keys.test.ts +25 -1
  220. package/src/admin/apps/api-keys.ts +6 -2
  221. package/src/admin/ui.generated.ts +67 -67
  222. package/src/analytics/Analytics.ts +4 -0
  223. package/src/apps/composition.test.ts +100 -2
  224. package/src/apps/data/App.test.ts +0 -12
  225. package/src/apps/data/App.ts +4 -8
  226. package/src/apps/data/routes/earn.test.ts +283 -0
  227. package/src/apps/data/routes/earn.ts +800 -3
  228. package/src/apps/data/routes/indexer.ts +2 -1
  229. package/src/apps/data/routes/rpc.ts +2 -0
  230. package/src/apps/data/routes/tokens.test.ts +6 -8
  231. package/src/apps/funding/Access.ts +26 -0
  232. package/src/apps/funding/App.test.ts +78 -0
  233. package/src/apps/funding/App.ts +63 -0
  234. package/src/apps/funding/Inventory.ts +30 -0
  235. package/src/apps/funding/ProviderAttempts.ts +26 -0
  236. package/src/apps/funding/index.ts +1 -0
  237. package/src/apps/funding/routes/chains.test.ts +78 -0
  238. package/src/apps/funding/routes/chains.ts +101 -0
  239. package/src/apps/funding/routes/deposit-addresses.test.ts +1628 -0
  240. package/src/apps/funding/routes/deposit-addresses.ts +1015 -0
  241. package/src/apps/funding/routes/providers.test.ts +165 -0
  242. package/src/apps/funding/routes/providers.ts +73 -0
  243. package/src/apps/funding/routes/quotes.test.ts +511 -0
  244. package/src/apps/funding/routes/quotes.ts +205 -0
  245. package/src/apps/funding/routes/transfers.test.ts +1255 -0
  246. package/src/apps/funding/routes/transfers.ts +704 -0
  247. package/src/apps/index.ts +1 -0
  248. package/src/apps/management/routes/api-keys.test.ts +98 -0
  249. package/src/apps/management/routes/api-keys.ts +96 -1
  250. package/src/apps/mcp.test.ts +17 -2
  251. package/src/apps/mcp.ts +18 -13
  252. package/src/apps/metadata.ts +12 -0
  253. package/src/apps/mpp/internal/Policy.test.ts +7 -1
  254. package/src/cloudflare.ts +279 -0
  255. package/src/db/Db.ts +62 -0
  256. package/src/db/Schema.ts +24 -0
  257. package/src/db/tables/fundingDepositAddresses.ts +28 -0
  258. package/src/db/tables/fundingDepositRequestObservations.test.ts +106 -0
  259. package/src/db/tables/fundingDepositRequestObservations.ts +99 -0
  260. package/src/db/tables/fundingDeposits.test.ts +138 -0
  261. package/src/db/tables/fundingDeposits.ts +145 -1
  262. package/src/internal/Auth.test.ts +177 -5
  263. package/src/internal/Auth.ts +48 -18
  264. package/src/internal/Cache.test.ts +37 -3
  265. package/src/internal/Cache.ts +1 -1
  266. package/src/internal/EdgeCache.test.ts +7 -7
  267. package/src/internal/EdgeCache.ts +13 -4
  268. package/src/internal/IpAllowlist.test.ts +52 -0
  269. package/src/internal/IpAllowlist.ts +39 -0
  270. package/src/internal/Log.test.ts +7 -1
  271. package/src/internal/OpenApi.ts +1 -1
  272. package/src/internal/Tidx.test.ts +28 -0
  273. package/src/internal/Tidx.ts +19 -3
  274. package/src/internal/Viem.test.ts +28 -0
  275. package/src/internal/Viem.ts +21 -5
  276. package/src/internal/funding/Deposit.ts +74 -5
  277. package/src/internal/funding/Evidence.test.ts +2 -2
  278. package/src/internal/funding/Evidence.ts +4 -9
  279. package/src/internal/funding/Provider.ts +115 -0
  280. package/src/internal/funding/Reconciliation.local.test.ts +7 -0
  281. package/src/internal/funding/Reconciliation.test.ts +1052 -35
  282. package/src/internal/funding/Reconciliation.ts +328 -56
  283. package/src/internal/funding/SourceObservation.test.ts +301 -0
  284. package/src/internal/funding/SourceObservation.ts +267 -0
  285. package/src/internal/funding/Subsidy.ts +2 -2
  286. package/src/internal/funding/index.test.ts +1 -0
  287. package/src/internal/funding/index.ts +1 -0
  288. package/src/internal/funding/providers/relay.test.ts +94 -4
  289. package/src/internal/funding/providers/relay.ts +528 -27
  290. package/dist/apps/data/routes/funding.d.ts.map +0 -1
  291. package/dist/apps/data/routes/funding.js +0 -1557
  292. package/dist/apps/data/routes/funding.js.map +0 -1
  293. package/src/apps/data/routes/funding.test.ts +0 -2994
  294. package/src/apps/data/routes/funding.ts +0 -1821
package/src/apps/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { data, FxOracle } from './data/index.js'
2
+ export { funding } from './funding/index.js'
2
3
  export { management } from './management/index.js'
3
4
  export { mcp } from './mcp.js'
4
5
  export { mpp } from './mpp/index.js'
@@ -256,6 +256,99 @@ describe('keys', () => {
256
256
  expect(empty).toEqual({ data: [] })
257
257
  })
258
258
 
259
+ test('creates, reads, updates, enforces, and clears an IP allowlist', async () => {
260
+ const state = TestApp.kvStore()
261
+ const app = TestApp.create({
262
+ // Avoid waiting for the accepted KV and isolate-cache propagation delay.
263
+ auth: { cache: false, superAdmin: { secret } },
264
+ kv: { store: state },
265
+ session: { wallet: { origin } },
266
+ }).get(
267
+ '/custom/allowlist-test',
268
+ Auth.policy({ apiKey: { scopes: ['data:read'] } }),
269
+ OpenApi.describeRoute({
270
+ operationId: 'getAllowlistTest',
271
+ responses: { 200: { description: 'Allowlist test response.' } },
272
+ summary: 'Test allowlist',
273
+ }),
274
+ (c) => c.json({ ok: true }, 200),
275
+ )
276
+ const cookie = await session(app)
277
+ const { org, project } = await fixture(app, cookie)
278
+ const base = `/v1/orgs/${org.id}/projects/${project.id}/api-keys`
279
+ const created = await TestApp.json(
280
+ await app.request(
281
+ base,
282
+ superAdminJson('POST', {
283
+ allowedIps: ['203.0.113.0/24'],
284
+ scopes: ['data:read'],
285
+ }),
286
+ ),
287
+ Keys.schema.createApiKey.Response,
288
+ )
289
+
290
+ expect(created.allowedIps).toEqual(['203.0.113.0/24'])
291
+ const authenticate = (address?: string) =>
292
+ app.request('/custom/allowlist-test', {
293
+ headers: {
294
+ ...(address ? { 'cf-connecting-ip': address } : {}),
295
+ 'tempo-api-key': created.token,
296
+ },
297
+ })
298
+ const listed = await TestApp.json(
299
+ await app.request(base, { headers: { cookie } }),
300
+ Keys.schema.listApiKeys.Response,
301
+ )
302
+ expect(listed.data[0]?.allowedIps).toEqual(['203.0.113.0/24'])
303
+
304
+ expect((await authenticate('203.0.113.7')).status).toBe(200)
305
+ const denied = await authenticate('198.51.100.7')
306
+ expect(denied.status).toBe(403)
307
+ expect(await denied.json()).toMatchObject({ error: { code: 'api_key_ip_forbidden' } })
308
+
309
+ const updated = await TestApp.json(
310
+ await app.request(
311
+ `${base}/${created.id}`,
312
+ json('PATCH', cookie, { allowedIps: ['198.51.100.7'] }),
313
+ ),
314
+ Keys.schema.Key,
315
+ )
316
+ expect(updated.allowedIps).toEqual(['198.51.100.7'])
317
+
318
+ expect((await authenticate('203.0.113.7')).status).toBe(403)
319
+ expect((await authenticate('198.51.100.7')).status).toBe(200)
320
+
321
+ const cleared = await TestApp.json(
322
+ await app.request(`${base}/${created.id}`, json('PATCH', cookie, { allowedIps: [] })),
323
+ Keys.schema.Key,
324
+ )
325
+ expect(cleared.allowedIps).toEqual([])
326
+ expect((await authenticate()).status).toBe(200)
327
+ })
328
+
329
+ test('rejects malformed IP allowlists on create and update', async () => {
330
+ const app = createApp()
331
+ const cookie = await session(app)
332
+ const { org, project } = await fixture(app, cookie)
333
+ const base = `/v1/orgs/${org.id}/projects/${project.id}/api-keys`
334
+
335
+ const invalidCreate = await app.request(
336
+ base,
337
+ json('POST', cookie, { allowedIps: ['203.0.113.0/33'], scopes: ['data:read'] }),
338
+ )
339
+ expect(invalidCreate.status).toBe(400)
340
+
341
+ const created = await TestApp.json(
342
+ await app.request(base, json('POST', cookie, { scopes: ['data:read'] })),
343
+ Keys.schema.createApiKey.Response,
344
+ )
345
+ const invalidUpdate = await app.request(
346
+ `${base}/${created.id}`,
347
+ json('PATCH', cookie, { allowedIps: ['*'] }),
348
+ )
349
+ expect(invalidUpdate.status).toBe(400)
350
+ })
351
+
259
352
  test('rejects the wildcard scope at mint', async () => {
260
353
  const app = createApp()
261
354
  const cookie = await session(app)
@@ -427,6 +520,11 @@ describe('keys', () => {
427
520
  { headers: { cookie: owner }, method: 'DELETE' },
428
521
  )
429
522
  expect(crossRevoke.status).toBe(404)
523
+ const crossUpdate = await app.request(
524
+ `/v1/orgs/${org.id}/projects/${other.id}/api-keys/${minted.id}`,
525
+ json('PATCH', owner, { allowedIps: ['203.0.113.0/24'] }),
526
+ )
527
+ expect(crossUpdate.status).toBe(404)
430
528
 
431
529
  // Foreign session: the whole surface 404s at the org-ownership link.
432
530
  const stranger = await session(app)
@@ -3,6 +3,7 @@ import * as z from 'zod/mini'
3
3
 
4
4
  import * as Analytics from '../../../analytics/Analytics.js'
5
5
  import * as RequestEvents from '../../../analytics/tables/requestEvents.js'
6
+ import * as ApiKey from '../../../ApiKey.js'
6
7
  import * as ApiKeys from '../../../ApiKeys.js'
7
8
  import type * as App from '../../../App.js'
8
9
  import * as Db from '../../../db/Db.js'
@@ -19,6 +20,12 @@ export namespace schema {
19
20
  /** One API key: metadata only — the token appears once, at mint. */
20
21
  export const Key = Schema.describe(
21
22
  z.object({
23
+ allowedIps: ApiKey.schema.AllowedIps.check(
24
+ z.describe(
25
+ 'Client IP addresses and CIDR ranges allowed to use this key. An empty list means unrestricted.',
26
+ ),
27
+ z.meta({ examples: [['203.0.113.0/24', '2001:db8::1']] }),
28
+ ),
22
29
  createdAt: z
23
30
  .string()
24
31
  .check(
@@ -140,6 +147,14 @@ export namespace schema {
140
147
  /** Request body minting a key. The wildcard scope is not issuable here. */
141
148
  export const KeyBody = z
142
149
  .object({
150
+ allowedIps: z
151
+ .optional(ApiKey.schema.AllowedIps)
152
+ .check(
153
+ z.describe(
154
+ 'Client IP addresses and CIDR ranges allowed to use the key. Omit or use an empty list for unrestricted access.',
155
+ ),
156
+ z.meta({ examples: [['203.0.113.0/24']] }),
157
+ ),
143
158
  environment: z
144
159
  ._default(z.enum(['production', 'sandbox']), 'production')
145
160
  .check(z.describe('Key environment.'), z.meta({ examples: ['sandbox'] })),
@@ -224,10 +239,25 @@ export namespace schema {
224
239
  })
225
240
  .check(z.describe('Confirmation that the key was revoked.'))
226
241
  }
242
+
243
+ /** Schemas for the updateApiKey operation. */
244
+ export namespace updateApiKey {
245
+ /** Request body replacing or clearing a key's IP allowlist. */
246
+ export const Body = z
247
+ .object({
248
+ allowedIps: ApiKey.schema.AllowedIps.check(
249
+ z.describe(
250
+ 'Replacement client IP/CIDR allowlist. Use an empty list for unrestricted access.',
251
+ ),
252
+ z.meta({ examples: [['203.0.113.0/24']] }),
253
+ ),
254
+ })
255
+ .check(z.describe("Fields for updating an API key's network access."))
256
+ }
227
257
  }
228
258
 
229
259
  /**
230
- * Mounts project-attributed API key mint, list, and revoke routes plus the org-wide list. Project routes enforce caller organization and project attribution.
260
+ * Mounts project-attributed API key mint, list, update, and revoke routes plus the org-wide list. Project routes enforce caller organization and project attribution.
231
261
  */
232
262
  export function apiKeys(options: apiKeys.Options = {}) {
233
263
  const hidden = !options.enabled
@@ -313,6 +343,7 @@ export function apiKeys(options: apiKeys.Options = {}) {
313
343
  {
314
344
  createdBy,
315
345
  environment: body.environment,
346
+ ...(body.allowedIps === undefined ? {} : { allowedIps: body.allowedIps }),
316
347
  ...(billingActive === undefined ? {} : { billingActive }),
317
348
  ...(body.name === undefined ? {} : { name: body.name }),
318
349
  orgId,
@@ -453,6 +484,69 @@ export function apiKeys(options: apiKeys.Options = {}) {
453
484
  }
454
485
  },
455
486
  )
487
+ .patch(
488
+ '/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/projects/:projectId{prj_[A-Za-z0-9_-]+}/api-keys/:keyId{key_[A-Za-z0-9_-]+}',
489
+ Auth.policy({ apiKey: { scopes: ['management:write'] }, session: true }),
490
+ Auth.ensureOrg({ role: 'admin' }),
491
+ Auth.ensureProject(),
492
+ OpenApi.validate('param', schema.KeyParams, {
493
+ code: 'param_invalid',
494
+ message: 'Check the path parameters and try again.',
495
+ }),
496
+ OpenApi.validate('json', schema.updateApiKey.Body, {
497
+ code: 'body_invalid',
498
+ message: 'Check the request body and try again.',
499
+ }),
500
+ OpenApi.describeRoute({
501
+ description:
502
+ "Replace an API key's client IP/CIDR allowlist. An empty list removes the restriction.",
503
+ hide: hidden,
504
+ operationId: 'updateApiKey',
505
+ responses: OpenApi.responses({
506
+ errors: {
507
+ 404: 'No accessible API key was found for this id.',
508
+ },
509
+ success: {
510
+ description: 'The updated API key metadata.',
511
+ schema: schema.Key,
512
+ },
513
+ }),
514
+ summary: 'Update API key',
515
+ tags: ['API Keys'],
516
+ }),
517
+ async (c) => {
518
+ if (Auth.narrowAccess) return Auth.accessError(c)
519
+ if (Auth.narrowScope) return Auth.ensureProjectError(c)
520
+ if (OpenApi.narrowValidation)
521
+ return OpenApi.validationError(c, {
522
+ code: 'param_invalid',
523
+ message: 'Check the path parameters and try again.',
524
+ })
525
+ if (OpenApi.narrowValidation)
526
+ return OpenApi.validationError(c, {
527
+ code: 'body_invalid',
528
+ message: 'Check the request body and try again.',
529
+ })
530
+
531
+ const kv = c.get('kv')
532
+ if (!kv) return notEnabled(c)
533
+ const { keyId } = c.req.valid('param')
534
+ try {
535
+ const record = await ApiKeys.get(kv.store, keyId, {
536
+ scopeCatalog: c.get('scopeCatalog'),
537
+ })
538
+ if (!record || record.orgId !== Auth.org(c).id || record.projectId !== Auth.project(c).id)
539
+ return keyNotFound(c)
540
+ const updated = await ApiKeys.update(kv.store, keyId, c.req.valid('json'), {
541
+ scopeCatalog: c.get('scopeCatalog'),
542
+ })
543
+ if (!updated) return keyNotFound(c)
544
+ return c.json(Response.validated(schema.Key, serializeKey(updated)), 200)
545
+ } catch (cause) {
546
+ return Response.upstream(c, cause)
547
+ }
548
+ },
549
+ )
456
550
  .delete(
457
551
  '/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/projects/:projectId{prj_[A-Za-z0-9_-]+}/api-keys/:keyId{key_[A-Za-z0-9_-]+}',
458
552
  Auth.policy({ apiKey: { scopes: ['management:write'] }, session: true }),
@@ -532,6 +626,7 @@ async function serializeKeys(c: Context<App.Environment>, records: readonly ApiK
532
626
  /** Projects a persisted record onto the management key shape (no rate limits, no token). */
533
627
  function serializeKey(record: ApiKeys.Record, lastUsedAt?: string) {
534
628
  return {
629
+ allowedIps: [...record.allowedIps],
535
630
  createdAt: record.createdAt,
536
631
  ...(record.createdBy === undefined ? {} : { createdBy: record.createdBy }),
537
632
  environment: record.environment,
@@ -1,4 +1,6 @@
1
1
  import { Cli as incur_Cli, z } from 'incur'
2
+ import { App } from 'tapimo'
3
+ import { data } from 'tapimo/apps'
2
4
 
3
5
  import * as TestApp from '../../test/App.js'
4
6
  import * as Runtime from '../../test/runtime.js'
@@ -17,7 +19,7 @@ const reader = {
17
19
  /** Composes a test app with the MCP group mounted, dispatching in-process. */
18
20
  function createHost(
19
21
  options: {
20
- app?: ReturnType<typeof TestApp.create> | undefined
22
+ app?: Host | undefined
21
23
  docs?: mcp.Options['docs'] | undefined
22
24
  } = {},
23
25
  ) {
@@ -149,7 +151,7 @@ test('identifies as Tempo MCP', async () => {
149
151
  })
150
152
  })
151
153
 
152
- test('scopes tools to the data domain', async () => {
154
+ test('scopes tools to the data and inbound funding domains', async () => {
153
155
  const { app, host } = createHost({ docs: docsStub() })
154
156
 
155
157
  // The domain filter must drop paths that actually exist in the spec.
@@ -170,6 +172,8 @@ test('scopes tools to the data domain', async () => {
170
172
  const tools = (await toolsSearch(host)).map((tool) => tool.name)
171
173
 
172
174
  expect(tools).toContain('v1_tokens_get')
175
+ expect(tools).toContain('v1_funding_quotes')
176
+ expect(tools).toContain('v1_funding_transfers_post')
173
177
  expect(tools).toContain('docs_search')
174
178
  // RPC is part of the data domain tag group.
175
179
  expect(tools.some((name) => name.startsWith('rpc'))).toBe(true)
@@ -180,6 +184,17 @@ test('scopes tools to the data domain', async () => {
180
184
  expect(tools.some((name) => name.startsWith('v1_me'))).toBe(false)
181
185
  })
182
186
 
187
+ test('keeps the data tools functional when funding is omitted', async () => {
188
+ const app = App.create({ auth: false, db: TestApp.database() }).route('/', data())
189
+ const { host } = createHost({ app })
190
+
191
+ await initialize(host)
192
+ const tools = (await toolsSearch(host)).map((tool) => tool.name)
193
+
194
+ expect(tools).toContain('v1_tokens_get')
195
+ expect(tools.some((name) => name.startsWith('v1_funding_'))).toBe(false)
196
+ })
197
+
183
198
  test('keeps tool input schemas compact', async () => {
184
199
  const { host } = createHost()
185
200
  await initialize(host)
package/src/apps/mcp.ts CHANGED
@@ -7,15 +7,18 @@ import * as Auth from '../internal/Auth.js'
7
7
  import * as OpenApi from '../internal/OpenApi.js'
8
8
  import * as Metadata from './metadata.js'
9
9
 
10
- /** Document tag group whose operations surface as MCP tools (the data domain). */
11
- const tagGroup = 'Data API'
10
+ /** Required document tag group whose operations surface as MCP tools. */
11
+ const dataTagGroup = 'Data API'
12
+
13
+ /** Optional document tag group added when the funding app is mounted. */
14
+ const fundingTagGroup = 'Funding & Bridge API'
12
15
 
13
16
  /** Tags inside the group hidden from MCP: partner-shaped CoinGecko mirrors and the MCP endpoint itself. */
14
17
  const excludedTags = ['CoinGecko', 'MCP']
15
18
 
16
19
  /** Usage guidance surfaced to MCP clients on initialize. */
17
20
  const instructions =
18
- "Tempo MCP provides access to the Tempo API's data domain and documentation. Authenticated tools accept a Tempo API key sent as an `Authorization: Bearer` header on the HTTP request. List endpoints paginate via `cursor`/`nextCursor`. `docs_*` tools search the Tempo documentation."
21
+ "Tempo MCP provides access to the Tempo API's data and inbound funding domains, plus documentation. Authenticated tools accept a Tempo API key sent as an `Authorization: Bearer` header on the HTTP request. List endpoints paginate via `cursor`/`nextCursor`. `docs_*` tools search the Tempo documentation."
19
22
 
20
23
  /** Zod schemas owned by the MCP endpoint's OpenAPI description. */
21
24
  export namespace schema {
@@ -56,9 +59,8 @@ export namespace schema {
56
59
 
57
60
  /**
58
61
  * The MCP route group: a stateless streamable-HTTP MCP server at `/mcp` whose
59
- * tools mirror the API's data domain (scoped via the OpenAPI document's
60
- * `Data API` tag group), plus the remote Tempo docs tools. Mount alongside
61
- * the other route groups:
62
+ * tools mirror the API's data domain and its optional inbound funding domain,
63
+ * plus the remote Tempo docs tools. Mount alongside the other route groups:
62
64
  *
63
65
  * ```ts
64
66
  * const app = App.create(options)
@@ -208,7 +210,7 @@ const openapi = {
208
210
  'A hosted Model Context Protocol server: the data domain and Tempo docs exposed as tools.',
209
211
  },
210
212
  ],
211
- 'x-tagGroups': [{ name: tagGroup, tags: ['MCP'] }],
213
+ 'x-tagGroups': [{ name: dataTagGroup, tags: ['MCP'] }],
212
214
  } satisfies App.create.Metadata
213
215
 
214
216
  /** The slice of the OpenAPI document the domain filter reads. */
@@ -221,14 +223,17 @@ type Document = Openapi.OpenAPISpec & {
221
223
  const methods: readonly string[] = ['delete', 'get', 'head', 'options', 'patch', 'post', 'put', 'trace'] // prettier-ignore
222
224
 
223
225
  /**
224
- * Narrows the document to the data domain: operations tagged inside the
225
- * `Data API` tag group, minus excluded tags. Management, relay, and session
226
- * operations never surface as tools.
226
+ * Narrows the document to the data and optional inbound funding domains.
227
+ * Management, relay, and session operations never surface as tools.
227
228
  */
228
229
  function scope(document: Document): Openapi.OpenAPISpec {
229
- const group = document['x-tagGroups']?.find((entry) => entry.name === tagGroup)
230
- if (!group) throw new Error(`openapi.json is missing the "${tagGroup}" tag group`)
231
- const tags = new Set(group.tags.filter((tag) => !excludedTags.includes(tag)))
230
+ const groups = document['x-tagGroups'] ?? []
231
+ const dataGroup = groups.find((entry) => entry.name === dataTagGroup)
232
+ if (!dataGroup) throw new Error(`openapi.json is missing the "${dataTagGroup}" tag group`)
233
+ const fundingGroup = groups.find((entry) => entry.name === fundingTagGroup)
234
+ const tags = new Set(
235
+ [...dataGroup.tags, ...(fundingGroup?.tags ?? [])].filter((tag) => !excludedTags.includes(tag)),
236
+ )
232
237
  const paths: Document['paths'] = {}
233
238
  for (const [path, item] of Object.entries(document.paths)) {
234
239
  // Keep in-domain operations; non-method keys (parameters, summary) ride along.
@@ -7,6 +7,7 @@ import type * as App from '../App.js'
7
7
  // `App.create` reads it back on mount. The `WeakMap` keeps the association off
8
8
  // the app's type, so `testClient`/`hc` inference is untouched.
9
9
  const registry = new WeakMap<Hono<any, any>, App.create.Metadata>()
10
+ const defaultChainRegistry = new WeakSet<Hono<any, any>>()
10
11
 
11
12
  /** Attaches a group's OpenAPI metadata to its app instance for `create` to collect on mount. */
12
13
  export function attach<const app extends Hono<any, any>>(
@@ -21,3 +22,14 @@ export function attach<const app extends Hono<any, any>>(
21
22
  export function read(app: Hono<any, any>): App.create.Metadata | undefined {
22
23
  return registry.get(app)
23
24
  }
25
+
26
+ /** Marks a group whose routes use the root app's default chain access policy. */
27
+ export function defaultChain<const app extends Hono<any, any>>(app: app): app {
28
+ defaultChainRegistry.add(app)
29
+ return app
30
+ }
31
+
32
+ /** Returns whether a group uses the root app's default chain access policy. */
33
+ export function usesDefaultChain(app: Hono<any, any>): boolean {
34
+ return defaultChainRegistry.has(app)
35
+ }
@@ -20,7 +20,13 @@ const input = {
20
20
  } satisfies Mpp.RelayInput
21
21
 
22
22
  const principal = {
23
- apiKey: { environment: 'production', id: 'key_mpp', orgId: 'org_mpp', scopes: ['mpp:write'] },
23
+ apiKey: {
24
+ allowedIps: [],
25
+ environment: 'production',
26
+ id: 'key_mpp',
27
+ orgId: 'org_mpp',
28
+ scopes: ['mpp:write'],
29
+ },
24
30
  environment: 'production',
25
31
  id: 'key_mpp',
26
32
  orgId: 'org_mpp',