tapimo 0.4.0 → 0.4.3

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 (255) hide show
  1. package/README.md +1 -1
  2. package/dist/ApiKey.d.ts +1 -9
  3. package/dist/ApiKey.d.ts.map +1 -1
  4. package/dist/ApiKey.js +2 -3
  5. package/dist/ApiKey.js.map +1 -1
  6. package/dist/ApiKeys.d.ts +53 -27
  7. package/dist/ApiKeys.d.ts.map +1 -1
  8. package/dist/ApiKeys.js +32 -17
  9. package/dist/ApiKeys.js.map +1 -1
  10. package/dist/App.d.ts +2451 -1533
  11. package/dist/App.d.ts.map +1 -1
  12. package/dist/App.js +23 -9
  13. package/dist/App.js.map +1 -1
  14. package/dist/Assets.d.ts +10 -0
  15. package/dist/Assets.d.ts.map +1 -1
  16. package/dist/Assets.js +4 -0
  17. package/dist/Assets.js.map +1 -1
  18. package/dist/Auth.d.ts +2 -1
  19. package/dist/Auth.d.ts.map +1 -1
  20. package/dist/Auth.js +2 -1
  21. package/dist/Auth.js.map +1 -1
  22. package/dist/Scope.d.ts +40 -15
  23. package/dist/Scope.d.ts.map +1 -1
  24. package/dist/Scope.js +44 -13
  25. package/dist/Scope.js.map +1 -1
  26. package/dist/admin/App.d.ts +560 -9
  27. package/dist/admin/App.d.ts.map +1 -1
  28. package/dist/admin/App.js +38 -6
  29. package/dist/admin/App.js.map +1 -1
  30. package/dist/admin/apps/api-keys.d.ts +10 -24
  31. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  32. package/dist/admin/apps/api-keys.js +10 -3
  33. package/dist/admin/apps/api-keys.js.map +1 -1
  34. package/dist/admin/apps/organizations.d.ts +833 -0
  35. package/dist/admin/apps/organizations.d.ts.map +1 -0
  36. package/dist/admin/apps/organizations.js +733 -0
  37. package/dist/admin/apps/organizations.js.map +1 -0
  38. package/dist/admin/apps/scopes.d.ts +8 -0
  39. package/dist/admin/apps/scopes.d.ts.map +1 -1
  40. package/dist/admin/ui.generated.d.ts +1 -1
  41. package/dist/admin/ui.generated.d.ts.map +1 -1
  42. package/dist/admin/ui.generated.js +53 -13
  43. package/dist/admin/ui.generated.js.map +1 -1
  44. package/dist/analytics/Analytics.d.ts +10 -0
  45. package/dist/analytics/Analytics.d.ts.map +1 -1
  46. package/dist/analytics/Analytics.js +8 -0
  47. package/dist/analytics/Analytics.js.map +1 -1
  48. package/dist/analytics/tables/requestEvents.d.ts +9 -0
  49. package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
  50. package/dist/analytics/tables/requestEvents.js +14 -0
  51. package/dist/analytics/tables/requestEvents.js.map +1 -1
  52. package/dist/apps/data/App.d.ts +1354 -984
  53. package/dist/apps/data/App.d.ts.map +1 -1
  54. package/dist/apps/data/App.js +20 -3
  55. package/dist/apps/data/App.js.map +1 -1
  56. package/dist/apps/data/FundingRouteProviders.d.ts +94 -5
  57. package/dist/apps/data/FundingRouteProviders.d.ts.map +1 -1
  58. package/dist/apps/data/FundingRouteProviders.js +196 -5
  59. package/dist/apps/data/FundingRouteProviders.js.map +1 -1
  60. package/dist/apps/data/routes/activities.d.ts +38 -38
  61. package/dist/apps/data/routes/addresses.d.ts +24 -24
  62. package/dist/apps/data/routes/balances.d.ts +45 -45
  63. package/dist/apps/data/routes/blocks.d.ts +39 -39
  64. package/dist/apps/data/routes/coingecko.d.ts +176 -176
  65. package/dist/apps/data/routes/exchanges.d.ts +453 -453
  66. package/dist/apps/data/routes/fee-amm.d.ts +101 -101
  67. package/dist/apps/data/routes/funding.d.ts +730 -138
  68. package/dist/apps/data/routes/funding.d.ts.map +1 -1
  69. package/dist/apps/data/routes/funding.js +234 -0
  70. package/dist/apps/data/routes/funding.js.map +1 -1
  71. package/dist/apps/data/routes/indexer.d.ts +5 -5
  72. package/dist/apps/data/routes/rpc.d.ts +31 -31
  73. package/dist/apps/data/routes/verified-tokens.d.ts +81 -81
  74. package/dist/apps/management/App.d.ts +1156 -615
  75. package/dist/apps/management/App.d.ts.map +1 -1
  76. package/dist/apps/management/App.js +2 -2
  77. package/dist/apps/management/Billing.d.ts +3 -0
  78. package/dist/apps/management/Billing.d.ts.map +1 -1
  79. package/dist/apps/management/Billing.js +7 -2
  80. package/dist/apps/management/Billing.js.map +1 -1
  81. package/dist/apps/management/routes/api-keys.d.ts +106 -90
  82. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  83. package/dist/apps/management/routes/api-keys.js +41 -30
  84. package/dist/apps/management/routes/api-keys.js.map +1 -1
  85. package/dist/apps/management/routes/billing.d.ts +278 -146
  86. package/dist/apps/management/routes/billing.d.ts.map +1 -1
  87. package/dist/apps/management/routes/billing.js +14 -14
  88. package/dist/apps/management/routes/billing.js.map +1 -1
  89. package/dist/apps/management/routes/invitations.d.ts +93 -20
  90. package/dist/apps/management/routes/invitations.d.ts.map +1 -1
  91. package/dist/apps/management/routes/invitations.js +12 -15
  92. package/dist/apps/management/routes/invitations.js.map +1 -1
  93. package/dist/apps/management/routes/me.d.ts +13 -13
  94. package/dist/apps/management/routes/members.d.ts +89 -18
  95. package/dist/apps/management/routes/members.d.ts.map +1 -1
  96. package/dist/apps/management/routes/members.js +10 -10
  97. package/dist/apps/management/routes/members.js.map +1 -1
  98. package/dist/apps/management/routes/orgs.d.ts +130 -70
  99. package/dist/apps/management/routes/orgs.d.ts.map +1 -1
  100. package/dist/apps/management/routes/orgs.js +19 -19
  101. package/dist/apps/management/routes/orgs.js.map +1 -1
  102. package/dist/apps/management/routes/projects.d.ts +137 -48
  103. package/dist/apps/management/routes/projects.d.ts.map +1 -1
  104. package/dist/apps/management/routes/projects.js +12 -13
  105. package/dist/apps/management/routes/projects.js.map +1 -1
  106. package/dist/apps/management/routes/scopes.d.ts +14 -28
  107. package/dist/apps/management/routes/scopes.d.ts.map +1 -1
  108. package/dist/apps/management/routes/scopes.js +6 -7
  109. package/dist/apps/management/routes/scopes.js.map +1 -1
  110. package/dist/apps/management/routes/usage.d.ts +63 -19
  111. package/dist/apps/management/routes/usage.d.ts.map +1 -1
  112. package/dist/apps/management/routes/usage.js +9 -10
  113. package/dist/apps/management/routes/usage.js.map +1 -1
  114. package/dist/apps/mcp.d.ts.map +1 -1
  115. package/dist/apps/mcp.js +35 -3
  116. package/dist/apps/mcp.js.map +1 -1
  117. package/dist/apps/relay/App.d.ts +26 -4
  118. package/dist/apps/relay/App.d.ts.map +1 -1
  119. package/dist/apps/relay/App.js +17 -14
  120. package/dist/apps/relay/App.js.map +1 -1
  121. package/dist/db/Db.d.ts +5 -3
  122. package/dist/db/Db.d.ts.map +1 -1
  123. package/dist/db/Db.js +52 -0
  124. package/dist/db/Db.js.map +1 -1
  125. package/dist/db/Schema.d.ts +13 -0
  126. package/dist/db/Schema.d.ts.map +1 -1
  127. package/dist/db/Schema.js +16 -1
  128. package/dist/db/Schema.js.map +1 -1
  129. package/dist/db/tables/adminAuditLogs.d.ts +27 -0
  130. package/dist/db/tables/adminAuditLogs.d.ts.map +1 -0
  131. package/dist/db/tables/adminAuditLogs.js +19 -0
  132. package/dist/db/tables/adminAuditLogs.js.map +1 -0
  133. package/dist/db/tables/invitations.d.ts +1 -1
  134. package/dist/db/tables/invitations.d.ts.map +1 -1
  135. package/dist/db/tables/memberships.d.ts +32 -4
  136. package/dist/db/tables/memberships.d.ts.map +1 -1
  137. package/dist/db/tables/memberships.js +31 -0
  138. package/dist/db/tables/memberships.js.map +1 -1
  139. package/dist/db/tables/organizations.d.ts +43 -1
  140. package/dist/db/tables/organizations.d.ts.map +1 -1
  141. package/dist/db/tables/organizations.js +103 -0
  142. package/dist/db/tables/organizations.js.map +1 -1
  143. package/dist/db/tables/projects.d.ts +25 -0
  144. package/dist/db/tables/projects.d.ts.map +1 -1
  145. package/dist/db/tables/projects.js +22 -0
  146. package/dist/db/tables/projects.js.map +1 -1
  147. package/dist/db/tables/webhookDeliveries.d.ts +26 -0
  148. package/dist/db/tables/webhookDeliveries.d.ts.map +1 -1
  149. package/dist/db/tables/webhookDeliveries.js +37 -0
  150. package/dist/db/tables/webhookDeliveries.js.map +1 -1
  151. package/dist/handlers/relay.d.ts +13 -10
  152. package/dist/handlers/relay.d.ts.map +1 -1
  153. package/dist/handlers/relay.js +46 -16
  154. package/dist/handlers/relay.js.map +1 -1
  155. package/dist/internal/Auth.d.ts +13 -18
  156. package/dist/internal/Auth.d.ts.map +1 -1
  157. package/dist/internal/Auth.js +78 -58
  158. package/dist/internal/Auth.js.map +1 -1
  159. package/dist/internal/Cursor.d.ts +1 -1
  160. package/dist/internal/Cursor.d.ts.map +1 -1
  161. package/dist/internal/Cursor.js +4 -0
  162. package/dist/internal/Cursor.js.map +1 -1
  163. package/dist/internal/EdgeCache.d.ts.map +1 -1
  164. package/dist/internal/EdgeCache.js +2 -0
  165. package/dist/internal/EdgeCache.js.map +1 -1
  166. package/dist/internal/FundingRoutes.d.ts +170 -0
  167. package/dist/internal/FundingRoutes.d.ts.map +1 -1
  168. package/dist/internal/FundingRoutes.js +68 -6
  169. package/dist/internal/FundingRoutes.js.map +1 -1
  170. package/dist/internal/Log.d.ts +16 -1
  171. package/dist/internal/Log.d.ts.map +1 -1
  172. package/dist/internal/Log.js +5 -3
  173. package/dist/internal/Log.js.map +1 -1
  174. package/package.json +2 -2
  175. package/src/ApiKey.ts +2 -3
  176. package/src/ApiKeys.ts +95 -17
  177. package/src/App.test.ts +44 -2
  178. package/src/App.ts +29 -9
  179. package/src/Assets.ts +8 -0
  180. package/src/Auth.test-d.ts +36 -0
  181. package/src/Auth.ts +20 -1
  182. package/src/Client.test-d.ts +64 -17
  183. package/src/Scope.test.ts +34 -0
  184. package/src/Scope.ts +53 -17
  185. package/src/admin/App.test.ts +33 -3
  186. package/src/admin/App.ts +49 -14
  187. package/src/admin/apps/api-keys.ts +10 -3
  188. package/src/admin/apps/organizations.test.ts +455 -0
  189. package/src/admin/apps/organizations.ts +953 -0
  190. package/src/admin/apps/scopes.test.ts +10 -0
  191. package/src/admin/ui/plugin.ts +1 -1
  192. package/src/admin/ui/src/components/MobileProfile.tsx +2 -0
  193. package/src/admin/ui/src/components/ThemeToggle.tsx +20 -0
  194. package/src/admin/ui/src/lib/api-keys.ts +36 -19
  195. package/src/admin/ui/src/lib/config.ts +2 -3
  196. package/src/admin/ui/src/lib/organizations.test.ts +89 -0
  197. package/src/admin/ui/src/lib/organizations.ts +262 -0
  198. package/src/admin/ui/src/lib/query-client.ts +3 -1
  199. package/src/admin/ui/src/lib/theme.ts +57 -0
  200. package/src/admin/ui/src/routeTree.gen.ts +47 -4
  201. package/src/admin/ui/src/routes/__root.tsx +18 -3
  202. package/src/admin/ui/src/routes/api-keys.tsx +52 -7
  203. package/src/admin/ui/src/routes/index.tsx +1 -1
  204. package/src/admin/ui/src/routes/organizations.tsx +158 -0
  205. package/src/admin/ui/src/routes/organizations_.$orgId.tsx +710 -0
  206. package/src/admin/ui/src/start.ts +6 -0
  207. package/src/admin/ui.generated.ts +63 -13
  208. package/src/analytics/Analytics.ts +25 -0
  209. package/src/analytics/tables/requestEvents.ts +33 -0
  210. package/src/apps/composition.test.ts +82 -2
  211. package/src/apps/data/App.ts +24 -3
  212. package/src/apps/data/FundingRouteProviders.test.ts +204 -0
  213. package/src/apps/data/FundingRouteProviders.ts +294 -9
  214. package/src/apps/data/routes/funding.test.ts +90 -23
  215. package/src/apps/data/routes/funding.ts +315 -0
  216. package/src/apps/data/routes/rpc.test.ts +33 -0
  217. package/src/apps/management/App.ts +2 -2
  218. package/src/apps/management/Billing.ts +10 -2
  219. package/src/apps/management/routes/api-keys.test.ts +137 -1
  220. package/src/apps/management/routes/api-keys.ts +63 -38
  221. package/src/apps/management/routes/billing.ts +14 -14
  222. package/src/apps/management/routes/invitations.test.ts +102 -1
  223. package/src/apps/management/routes/invitations.ts +13 -15
  224. package/src/apps/management/routes/members.ts +10 -10
  225. package/src/apps/management/routes/orgs.test.ts +78 -16
  226. package/src/apps/management/routes/orgs.ts +21 -20
  227. package/src/apps/management/routes/projects.ts +12 -13
  228. package/src/apps/management/routes/scopes.test.ts +16 -0
  229. package/src/apps/management/routes/scopes.ts +6 -7
  230. package/src/apps/management/routes/usage.ts +9 -14
  231. package/src/apps/mcp.test.ts +25 -1
  232. package/src/apps/mcp.ts +43 -3
  233. package/src/apps/relay/App.test.ts +23 -18
  234. package/src/apps/relay/App.ts +35 -14
  235. package/src/db/Db.ts +59 -3
  236. package/src/db/Schema.ts +20 -1
  237. package/src/db/tables/adminAuditLogs.ts +47 -0
  238. package/src/db/tables/invitations.ts +1 -1
  239. package/src/db/tables/memberships.ts +59 -3
  240. package/src/db/tables/organizations.ts +148 -1
  241. package/src/db/tables/projects.ts +47 -0
  242. package/src/db/tables/webhookDeliveries.ts +83 -0
  243. package/src/handlers/relay.test-d.ts +8 -0
  244. package/src/handlers/relay.test.ts +2 -2
  245. package/src/handlers/relay.ts +62 -22
  246. package/src/internal/Auth.test.ts +174 -7
  247. package/src/internal/Auth.ts +90 -62
  248. package/src/internal/Cursor.test.ts +10 -0
  249. package/src/internal/Cursor.ts +5 -1
  250. package/src/internal/EdgeCache.test.ts +11 -0
  251. package/src/internal/EdgeCache.ts +2 -0
  252. package/src/internal/FundingRoutes.test.ts +26 -0
  253. package/src/internal/FundingRoutes.ts +187 -18
  254. package/src/internal/Log.test.ts +41 -3
  255. package/src/internal/Log.ts +23 -4
@@ -8,6 +8,7 @@ import * as FundingRoutes from '../../../internal/FundingRoutes.js'
8
8
  import * as OpenApi from '../../../internal/OpenApi.js'
9
9
  import * as Response from '../../../internal/Response.js'
10
10
  import * as Schema from '../../../internal/Schema.js'
11
+ import * as FundingRouteProviders from '../FundingRouteProviders.js'
11
12
 
12
13
  /** Zod schemas owned by the funding routes resource. */
13
14
  export namespace schema {
@@ -268,6 +269,111 @@ export namespace schema {
268
269
  'Provider-derived quote, limit, and freshness data for a route candidate.',
269
270
  )
270
271
 
272
+ /** Token reference attached to a normalized quote fee. */
273
+ export const QuoteFeeToken = Schema.describe(
274
+ z.object({
275
+ address: z
276
+ .string()
277
+ .check(
278
+ z.describe('Contract address of the fee token.'),
279
+ z.meta({ examples: ['0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'] }),
280
+ ),
281
+ chainId: z
282
+ .string()
283
+ .check(z.describe('CAIP-2 chain id of the fee token.'), z.meta({ examples: ['eip155:8453'] })), // prettier-ignore
284
+ decimals: z
285
+ .number()
286
+ .check(
287
+ z.int(),
288
+ z.nonnegative(),
289
+ z.describe('Number of decimal places used by the fee token.'),
290
+ z.meta({ examples: [6] }),
291
+ ),
292
+ symbol: z
293
+ .string()
294
+ .check(z.describe('Ticker symbol of the fee token.'), z.meta({ examples: ['USDC'] })),
295
+ }),
296
+ 'Token in which a normalized quote fee is denominated.',
297
+ )
298
+
299
+ /** Normalized fee summary for an explicit funding quote. */
300
+ export const QuoteFee = Schema.describe(
301
+ z.object({
302
+ amount: Schema.DecimalString.check(
303
+ z.describe("Fee amount in the fee token's smallest unit."),
304
+ z.meta({ examples: ['1200'] }),
305
+ ),
306
+ amountUsd: z
307
+ .nullable(z.string().check(z.regex(/^\d+(\.\d+)?$/)))
308
+ .check(
309
+ z.describe('Provider-estimated USD value of the fee.'),
310
+ z.meta({ examples: ['0.0012'] }),
311
+ ),
312
+ bps: z
313
+ .nullable(z.number().check(z.int(), z.nonnegative()))
314
+ .check(z.describe('Fee as basis points of the quoted input.'), z.meta({ examples: [12] })),
315
+ token: QuoteFeeToken,
316
+ }),
317
+ 'Provider-neutral fee amount and denomination.',
318
+ )
319
+
320
+ /** Explicit depositor-specific quote for one curated funding route. */
321
+ export const FundingQuote = Schema.describe(
322
+ z.object({
323
+ destinationChain: Chain.check(z.describe('Destination chain for the quoted route.')),
324
+ destinationToken: Token.check(z.describe('Token received on the destination chain.')),
325
+ estimatedSeconds: z
326
+ .nullable(z.number().check(z.int(), z.nonnegative()))
327
+ .check(z.describe('Estimated settlement time in seconds.'), z.meta({ examples: [2] })),
328
+ expiresAt: z.iso
329
+ .datetime()
330
+ .check(
331
+ z.describe('Timestamp after which the quote must not be used.'),
332
+ z.meta({ examples: ['2026-01-01T00:01:00Z'] }),
333
+ ),
334
+ fees: z
335
+ .object({
336
+ total: QuoteFee.check(z.describe('Current estimated total fee.')),
337
+ totalMax: QuoteFee.check(z.describe('Maximum total fee allowed by the quote.')),
338
+ })
339
+ .check(z.describe('Current and maximum provider fee summaries.')),
340
+ inputAmount: Schema.DecimalString.check(
341
+ z.describe('Exact source amount quoted in base units.'),
342
+ z.meta({ examples: ['1000000'] }),
343
+ ),
344
+ minimumOutputAmount: Schema.DecimalString.check(
345
+ z.describe('Slippage-adjusted minimum destination amount in base units.'),
346
+ z.meta({ examples: ['988000'] }),
347
+ ),
348
+ outputAmount: Schema.DecimalString.check(
349
+ z.describe('Expected destination amount in base units.'),
350
+ z.meta({ examples: ['990000'] }),
351
+ ),
352
+ outputAmountFormatted: z
353
+ .string()
354
+ .check(
355
+ z.describe('Expected destination amount rendered with token decimals.'),
356
+ z.meta({ examples: ['0.99'] }),
357
+ ),
358
+ provider: ProviderRef.check(z.describe('Provider that produced the quote.')),
359
+ routeId: z
360
+ .string()
361
+ .check(
362
+ z.describe('Stable id of the curated route that was quoted.'),
363
+ z.meta({ examples: ['base-usdc-tempo-usdce-across'] }),
364
+ ),
365
+ sampledAt: z.iso
366
+ .datetime()
367
+ .check(
368
+ z.describe('Timestamp when Tempo sampled the provider quote.'),
369
+ z.meta({ examples: ['2026-01-01T00:00:00Z'] }),
370
+ ),
371
+ sourceChain: Chain.check(z.describe('Source chain for the quoted route.')),
372
+ sourceToken: Token.check(z.describe('Token spent on the source chain.')),
373
+ }),
374
+ 'Normalized quote information without provider execution payloads.',
375
+ )
376
+
271
377
  /** Static or live funding route candidate. */
272
378
  export const RouteCandidate = Schema.describe(
273
379
  z.object({
@@ -422,6 +528,63 @@ export namespace schema {
422
528
  )
423
529
  }
424
530
 
531
+ /** Schemas for the quoteFundingRoute operation. */
532
+ export namespace quoteFundingRoute {
533
+ /** JSON body for an explicit funding route quote. */
534
+ export const Body = z
535
+ .strictObject({
536
+ depositor: Schema.Address.check(
537
+ z.describe('Address that would submit the funding transaction.'),
538
+ ),
539
+ destinationToken: z
540
+ .optional(z.string())
541
+ .check(
542
+ z.describe('Tempo destination token symbol, address, or token key.'),
543
+ z.meta({ examples: ['USDC.e'] }),
544
+ ),
545
+ provider: z
546
+ .literal('across')
547
+ .check(
548
+ z.describe('Provider id selected from funding route discovery.'),
549
+ z.meta({ examples: ['across'] }),
550
+ ),
551
+ recipient: z
552
+ .optional(Schema.Address)
553
+ .check(z.describe('Optional destination recipient; defaults to the depositor.')),
554
+ sourceAmount: z
555
+ .string()
556
+ .check(
557
+ z.minLength(1),
558
+ z.describe('Exact source amount to quote.'),
559
+ z.meta({ examples: ['1000000'] }),
560
+ ),
561
+ sourceAmountUnits: z
562
+ ._default(z.enum(['baseUnits', 'decimal']), 'baseUnits')
563
+ .check(
564
+ z.describe('Whether `sourceAmount` uses base units or decimal notation.'),
565
+ z.meta({ examples: ['baseUnits'] }),
566
+ ),
567
+ sourceChain: z
568
+ .string()
569
+ .check(
570
+ z.minLength(1),
571
+ z.describe('Source chain alias or CAIP-2 id.'),
572
+ z.meta({ examples: ['base'] }),
573
+ ),
574
+ sourceToken: z
575
+ .string()
576
+ .check(
577
+ z.minLength(1),
578
+ z.describe('Source token symbol, address, mint, or token key.'),
579
+ z.meta({ examples: ['USDC'] }),
580
+ ),
581
+ })
582
+ .check(z.describe('Parameters for a depositor-specific exact-input funding quote.'))
583
+
584
+ /** Normalized funding quote without provider execution payloads. */
585
+ export const Response = FundingQuote
586
+ }
587
+
425
588
  /** Schemas for the listFundingChains operation. */
426
589
  export namespace listFundingChains {
427
590
  /** Non-paginated source-chain inventory. */
@@ -518,6 +681,56 @@ const providersResponseExample = {
518
681
  data: FundingRoutes.listProviders(),
519
682
  } satisfies z.output<typeof schema.listProviders.Response>
520
683
 
684
+ const [fundingQuoteRouteExample] = FundingRoutes.listStaticRoutes({
685
+ destinationToken: 'USDC.e',
686
+ limit: 1,
687
+ provider: 'across',
688
+ sourceAmount: '1000000',
689
+ sourceAmountUnits: 'baseUnits',
690
+ sourceChain: 'base',
691
+ sourceToken: 'USDC',
692
+ })
693
+
694
+ const fundingQuoteResponseExample = {
695
+ destinationChain: fundingQuoteRouteExample!.destinationChain,
696
+ destinationToken: fundingQuoteRouteExample!.destinationToken,
697
+ estimatedSeconds: 2,
698
+ expiresAt: '2026-01-01T00:01:00Z',
699
+ fees: {
700
+ total: {
701
+ amount: '1200',
702
+ amountUsd: '0.0012',
703
+ bps: 12,
704
+ token: {
705
+ address: fundingQuoteRouteExample!.sourceToken.address,
706
+ chainId: fundingQuoteRouteExample!.sourceChain.id,
707
+ decimals: fundingQuoteRouteExample!.sourceToken.decimals,
708
+ symbol: fundingQuoteRouteExample!.sourceToken.symbol,
709
+ },
710
+ },
711
+ totalMax: {
712
+ amount: '1800',
713
+ amountUsd: '0.0018',
714
+ bps: 18,
715
+ token: {
716
+ address: fundingQuoteRouteExample!.sourceToken.address,
717
+ chainId: fundingQuoteRouteExample!.sourceChain.id,
718
+ decimals: fundingQuoteRouteExample!.sourceToken.decimals,
719
+ symbol: fundingQuoteRouteExample!.sourceToken.symbol,
720
+ },
721
+ },
722
+ },
723
+ inputAmount: '1000000',
724
+ minimumOutputAmount: '988000',
725
+ outputAmount: '990000',
726
+ outputAmountFormatted: '0.99',
727
+ provider: fundingQuoteRouteExample!.provider,
728
+ routeId: fundingQuoteRouteExample!.id,
729
+ sampledAt: '2026-01-01T00:00:00Z',
730
+ sourceChain: fundingQuoteRouteExample!.sourceChain,
731
+ sourceToken: fundingQuoteRouteExample!.sourceToken,
732
+ } satisfies z.output<typeof schema.quoteFundingRoute.Response>
733
+
521
734
  /** Mounts funding route discovery and generic provider inventory endpoints. */
522
735
  export function funding(options: funding.Options = {}) {
523
736
  return new Hono<App.Environment>()
@@ -585,6 +798,103 @@ export function funding(options: funding.Options = {}) {
585
798
  }
586
799
  },
587
800
  )
801
+ .post(
802
+ '/v1/funding/routes/quote',
803
+ Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }),
804
+ OpenApi.validate('json', schema.quoteFundingRoute.Body, {
805
+ code: 'body_invalid',
806
+ message: 'Invalid quote parameters',
807
+ }),
808
+ OpenApi.describeRoute({
809
+ description:
810
+ 'Gets a depositor-specific exact-input quote for one curated funding route. The response excludes provider transaction calldata, approvals, and wallet balance details.',
811
+ operationId: 'quoteFundingRoute',
812
+ responses: OpenApi.responses({
813
+ errors: {
814
+ 400: { codes: ['body_invalid'] },
815
+ 404: { codes: ['route_not_found', 'quote_unavailable'] },
816
+ 501: { codes: ['provider_unconfigured'] },
817
+ 502: { codes: ['provider_error', 'provider_rate_limited', 'provider_timeout'] },
818
+ },
819
+ success: {
820
+ description: 'Normalized route quote without provider execution payloads.',
821
+ example: fundingQuoteResponseExample,
822
+ schema: schema.quoteFundingRoute.Response,
823
+ },
824
+ }),
825
+ summary: 'Quote funding route',
826
+ tags: ['Funding'],
827
+ }),
828
+ async (c) => {
829
+ if (Auth.narrowAccess) return Auth.paidAccessError(c)
830
+ if (OpenApi.narrowValidation)
831
+ return OpenApi.validationError(c, {
832
+ code: 'body_invalid',
833
+ message: 'Invalid quote parameters',
834
+ })
835
+
836
+ const body = c.req.valid('json')
837
+ try {
838
+ const quote = await FundingRoutes.quoteRoute({
839
+ depositor: body.depositor,
840
+ destinationToken: body.destinationToken,
841
+ provider: body.provider,
842
+ quote: options.acrossQuote,
843
+ quoteTimeoutMs: options.quoteTimeoutMs,
844
+ recipient: body.recipient,
845
+ sourceAmount: body.sourceAmount,
846
+ sourceAmountUnits: body.sourceAmountUnits,
847
+ sourceChain: body.sourceChain,
848
+ sourceToken: body.sourceToken,
849
+ })
850
+ c.header('Cache-Control', 'no-store')
851
+ return c.json(Response.validated(schema.quoteFundingRoute.Response, quote), 200)
852
+ } catch (cause) {
853
+ const message = requestErrorMessage(cause)
854
+ if (message) return Response.error(c, { code: 'body_invalid', message, status: 400 })
855
+ if (cause instanceof FundingRoutes.FundingRouteNotFoundError)
856
+ return Response.error(c, {
857
+ code: 'route_not_found',
858
+ message: 'No curated funding route matches the request.',
859
+ status: 404,
860
+ })
861
+ if (cause instanceof FundingRoutes.ProviderQuoteUnavailableError)
862
+ return Response.error(c, {
863
+ code: 'quote_unavailable',
864
+ message: 'The provider cannot quote this route.',
865
+ status: 404,
866
+ })
867
+ if (cause instanceof FundingRoutes.ProviderNotConfiguredError)
868
+ return Response.error(c, {
869
+ code: 'provider_unconfigured',
870
+ message: 'The provider quote integration is not configured.',
871
+ status: 501,
872
+ })
873
+ if (cause instanceof FundingRoutes.ProviderRateLimitError)
874
+ return Response.error(c, {
875
+ code: 'provider_rate_limited',
876
+ message: 'The provider temporarily rate-limited this quote.',
877
+ status: 502,
878
+ })
879
+ if (cause instanceof FundingRoutes.ProviderAdapterTimeoutError)
880
+ return Response.error(c, {
881
+ code: 'provider_timeout',
882
+ message: 'The provider quote timed out.',
883
+ status: 502,
884
+ })
885
+ if (cause instanceof Error) {
886
+ const providerFailure = FundingRouteProviders.acrossQuoteFailure({ cause })
887
+ if (providerFailure) c.set('providerFailure', providerFailure)
888
+ return Response.error(c, {
889
+ code: 'provider_error',
890
+ message: 'The provider could not complete the quote.',
891
+ status: 502,
892
+ })
893
+ }
894
+ throw cause
895
+ }
896
+ },
897
+ )
588
898
  .get(
589
899
  '/v1/funding/chains',
590
900
  Auth.policy({ apiKey: { scopes: ['data:read'] }, mpp: true, public: true }),
@@ -658,6 +968,7 @@ export function funding(options: funding.Options = {}) {
658
968
  data: FundingRoutes.listProviders({
659
969
  adapters: options.adapters,
660
970
  capability: query.capability,
971
+ configuredProviders: options.acrossQuote ? ['across'] : [],
661
972
  }),
662
973
  }),
663
974
  200,
@@ -669,10 +980,14 @@ export function funding(options: funding.Options = {}) {
669
980
  export declare namespace funding {
670
981
  /** Options for funding route discovery. */
671
982
  type Options = {
983
+ /** Across explicit quote capability. */
984
+ acrossQuote?: FundingRoutes.ProviderQuote | undefined
672
985
  /** Provider adapters used to enrich static inventory with live data. */
673
986
  adapters?: FundingRoutes.ProviderAdapters | undefined
674
987
  /** Per-provider quote timeout in milliseconds. */
675
988
  adapterTimeoutMs?: number | undefined
989
+ /** Timeout for depositor-specific provider quotes. */
990
+ quoteTimeoutMs?: number | undefined
676
991
  }
677
992
  }
678
993
 
@@ -338,6 +338,39 @@ describe('POST /rpc', () => {
338
338
  fetch.restore()
339
339
  }
340
340
  })
341
+
342
+ test('uses the RPC path for sandbox chain checks', async () => {
343
+ const app = TestApp.create({
344
+ auth: {
345
+ keys: [
346
+ {
347
+ environment: 'sandbox',
348
+ id: 'key_sandbox',
349
+ orgId: 'org_test',
350
+ scopes: ['data:read'],
351
+ token: 'secret_sandbox_key',
352
+ },
353
+ ],
354
+ },
355
+ defaultChainId: 4217,
356
+ })
357
+ const request = (path: string) =>
358
+ app.fetch(
359
+ new Request(`http://tempo-api.test${path}`, {
360
+ body: JSON.stringify({ id: 1, jsonrpc: '2.0', method: 'eth_chainId' }),
361
+ headers: { 'tempo-api-key': 'secret_sandbox_key' },
362
+ method: 'POST',
363
+ }),
364
+ )
365
+
366
+ const rejected = await request('/rpc/4217?chainId=42431')
367
+ const rejectedDefault = await request('/rpc?chainId=42431')
368
+ const rejectedTrailing = await request('/rpc/?chainId=42431')
369
+
370
+ expect(rejected.status).toMatchInlineSnapshot(`403`)
371
+ expect(rejectedDefault.status).toMatchInlineSnapshot(`403`)
372
+ expect(rejectedTrailing.status).toMatchInlineSnapshot(`404`)
373
+ })
341
374
  })
342
375
 
343
376
  describe('POST /rpc/relay', () => {
@@ -87,8 +87,8 @@ export type Environment = {
87
87
  * invitations, and API-key management. Every route reads its dependencies from
88
88
  * request context set by {@link App.create}.
89
89
  *
90
- * The session-auth surface (`/v1/auth/siwe` + `session` capability) is owned by
91
- * {@link App.create}, so management routes gate on a session without owning it.
90
+ * The session-auth surface is owned by {@link App.create}; management routes
91
+ * reference it alongside scoped API-key access.
92
92
  *
93
93
  * ```ts
94
94
  * App.create(options).route('/', management())
@@ -7,6 +7,7 @@ import type * as BillingSettings from '../../db/tables/billingSettings.js'
7
7
  import * as Db from '../../db/Db.js'
8
8
  import * as Fees from '../../internal/Fees.js'
9
9
  import * as RequestUsage from '../../db/tables/requestUsage.js'
10
+ import type * as Scope from '../../Scope.js'
10
11
  import * as SponsoredTransactions from '../../db/tables/sponsoredTransactions.js'
11
12
  import * as StripeCustomers from '../../db/tables/stripeCustomers.js'
12
13
  import * as Viem from '../../internal/Viem.js'
@@ -641,7 +642,7 @@ export async function listActive(
641
642
  export function createKeyBillingSyncer(
642
643
  options: createKeyBillingSyncer.Options,
643
644
  ): createKeyBillingSyncer.Syncer {
644
- const { db, environments = ['sandbox'], kv } = options
645
+ const { db, environments = ['sandbox'], kv, scopeCatalog } = options
645
646
  return {
646
647
  async tick() {
647
648
  // Resolve per tick so Workers factories build a fresh Hyperdrive-pooled
@@ -655,7 +656,12 @@ export function createKeyBillingSyncer(
655
656
  checked += 1
656
657
  // `setBillingActive` writes only drifted records, so a converged
657
658
  // fleet costs list reads and no writes.
658
- updated += await ApiKeys.setBillingActive(kv, { active, environment, orgId })
659
+ updated += await ApiKeys.setBillingActive(kv, {
660
+ active,
661
+ environment,
662
+ orgId,
663
+ scopeCatalog,
664
+ })
659
665
  }
660
666
  }
661
667
  return { checked, updated }
@@ -672,6 +678,8 @@ export declare namespace createKeyBillingSyncer {
672
678
  environments?: readonly StripeCustomers.Record['environment'][] | undefined
673
679
  /** KV state store holding API-key records. */
674
680
  kv: Store.State
681
+ /** Scope catalog accepted while reconciling API-key records. */
682
+ scopeCatalog?: Scope.Catalog | undefined
675
683
  }
676
684
 
677
685
  /** A scheduled snapshot-reconcile job. */
@@ -1,8 +1,13 @@
1
1
  import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
2
2
 
3
3
  import * as ApiKeys from '../../../ApiKeys.js'
4
+ import * as Auth from '../../../internal/Auth.js'
5
+ import * as OpenApi from '../../../internal/OpenApi.js'
6
+ import * as Scope from '../../../Scope.js'
4
7
  import * as TestApp from '../../../../test/App.js'
8
+ import * as Memberships from '../../../db/tables/memberships.js'
5
9
  import * as Keys from './api-keys.js'
10
+ import * as Me from './me.js'
6
11
  import * as Orgs from './orgs.js'
7
12
  import * as Projects from './projects.js'
8
13
 
@@ -33,10 +38,18 @@ function superAdminJson(method: 'PATCH' | 'POST', body: unknown) {
33
38
  // Resolve-cache disabled so revocation assertions observe the store directly.
34
39
  type State = ReturnType<typeof TestApp.kvStore>
35
40
 
36
- function createApp(options: { kv?: { store: State } } = {}) {
41
+ function createApp(
42
+ options: {
43
+ db?: ReturnType<typeof TestApp.database>
44
+ kv?: { store: State }
45
+ scopes?: readonly Scope.Entry[]
46
+ } = {},
47
+ ) {
37
48
  return TestApp.create({
38
49
  auth: { cache: false, superAdmin: { secret } },
50
+ db: options.db ?? TestApp.database(),
39
51
  ...(options.kv ? { kv: options.kv } : {}),
52
+ ...(options.scopes ? { scopes: options.scopes } : {}),
40
53
  session: { wallet: { origin } },
41
54
  })
42
55
  }
@@ -70,6 +83,77 @@ async function fixture(app: ReturnType<typeof createApp>, cookie: string) {
70
83
  }
71
84
 
72
85
  describe('keys', () => {
86
+ test('requires an owner to issue management write keys', async () => {
87
+ const db = TestApp.database()
88
+ const app = createApp({ db })
89
+ const owner = await session(app)
90
+ const { org, project } = await fixture(app, owner)
91
+ const admin = await session(app)
92
+ const user = await TestApp.json(
93
+ await app.request('/v1/me', { headers: { cookie: admin } }),
94
+ Me.schema.User,
95
+ )
96
+ await Memberships.create(db, {
97
+ orgId: org.id,
98
+ role: 'admin',
99
+ userId: user.id,
100
+ })
101
+ const base = `/v1/orgs/${org.id}/projects/${project.id}/api-keys`
102
+
103
+ const read = await app.request(base, json('POST', admin, { scopes: ['management:read'] }))
104
+ expect(read.status).toBe(200)
105
+
106
+ const denied = await app.request(base, json('POST', admin, { scopes: ['management:write'] }))
107
+ expect(denied.status).toBe(403)
108
+ expect(await denied.json()).toMatchObject({ error: { code: 'scope_not_issuable' } })
109
+
110
+ const write = await app.request(base, json('POST', owner, { scopes: ['management:write'] }))
111
+ expect(write.status).toBe(200)
112
+ })
113
+
114
+ test('lets management keys manage org credentials without delegating issuance', async () => {
115
+ const app = createApp()
116
+ const cookie = await session(app)
117
+ const { org, project } = await fixture(app, cookie)
118
+ const other = await TestApp.json(
119
+ await app.request(`/v1/orgs/${org.id}/projects`, json('POST', cookie, { name: 'Payments' })),
120
+ Projects.schema.Project,
121
+ )
122
+ const base = `/v1/orgs/${org.id}/projects/${project.id}/api-keys`
123
+ const siblingBase = `/v1/orgs/${org.id}/projects/${other.id}/api-keys`
124
+ const manager = await TestApp.json(
125
+ await app.request(
126
+ base,
127
+ superAdminJson('POST', {
128
+ scopes: ['management:read', 'management:write'],
129
+ }),
130
+ ),
131
+ Keys.schema.createApiKey.Response,
132
+ )
133
+ const target = await TestApp.json(
134
+ await app.request(siblingBase, superAdminJson('POST', { scopes: ['data:read'] })),
135
+ Keys.schema.createApiKey.Response,
136
+ )
137
+
138
+ const listed = await app.request(siblingBase, {
139
+ headers: { 'tempo-api-key': manager.token },
140
+ })
141
+ expect(listed.status).toBe(200)
142
+
143
+ const delegated = await app.request(base, {
144
+ body: JSON.stringify({ scopes: ['management:read'] }),
145
+ headers: { 'content-type': 'application/json', 'tempo-api-key': manager.token },
146
+ method: 'POST',
147
+ })
148
+ expect(delegated.status).toBe(403)
149
+
150
+ const revoked = await app.request(`${siblingBase}/${target.id}`, {
151
+ headers: { 'tempo-api-key': manager.token },
152
+ method: 'DELETE',
153
+ })
154
+ expect(revoked.status).toBe(200)
155
+ })
156
+
73
157
  test('mints, lists, authenticates, and revokes a project key', async () => {
74
158
  const app = createApp()
75
159
  const cookie = await session(app)
@@ -187,6 +271,58 @@ describe('keys', () => {
187
271
  expect(created.createdBy).not.toBe('super_admin')
188
272
  })
189
273
 
274
+ test('mints and authenticates host scopes', async () => {
275
+ const app = createApp({
276
+ scopes: [
277
+ {
278
+ description: 'Read project accounts.',
279
+ scope: 'accounts:read',
280
+ selfServe: true,
281
+ },
282
+ ],
283
+ }).get(
284
+ '/custom/accounts',
285
+ Auth.policy({ apiKey: { scopes: ['accounts:read'] } }),
286
+ OpenApi.describeRoute({
287
+ operationId: 'getCustomAccounts',
288
+ responses: { 200: { description: 'Custom accounts response.' } },
289
+ summary: 'Get custom accounts',
290
+ }),
291
+ (c) => c.json({ ok: true }, 200),
292
+ )
293
+ const cookie = await session(app)
294
+ const { org, project } = await fixture(app, cookie)
295
+
296
+ const created = await TestApp.json(
297
+ await app.request(
298
+ `/v1/orgs/${org.id}/projects/${project.id}/api-keys`,
299
+ json('POST', cookie, { scopes: ['accounts:read'] }),
300
+ ),
301
+ Keys.schema.createApiKey.Response,
302
+ )
303
+ const response = await app.request('/custom/accounts', {
304
+ headers: { 'tempo-api-key': created.token },
305
+ })
306
+
307
+ expect(created.scopes).toEqual(['accounts:read'])
308
+ expect(response.status).toBe(200)
309
+ expect(await response.json()).toEqual({ ok: true })
310
+ })
311
+
312
+ test('rejects unregistered host scopes', async () => {
313
+ const app = createApp()
314
+ const cookie = await session(app)
315
+ const { org, project } = await fixture(app, cookie)
316
+
317
+ const response = await app.request(
318
+ `/v1/orgs/${org.id}/projects/${project.id}/api-keys`,
319
+ superAdminJson('POST', { scopes: ['accounts:read'] }),
320
+ )
321
+
322
+ expect(response.status).toBe(400)
323
+ expect(await response.json()).toMatchObject({ error: { code: 'body_invalid' } })
324
+ })
325
+
190
326
  test('rejects session mints of non-self-serve scopes (webhooks)', async () => {
191
327
  const app = createApp()
192
328
  const cookie = await session(app)