tapimo 0.3.0 → 0.4.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 (153) hide show
  1. package/README.md +5 -5
  2. package/dist/App.d.ts +31 -9
  3. package/dist/App.d.ts.map +1 -1
  4. package/dist/App.js +3 -3
  5. package/dist/App.js.map +1 -1
  6. package/dist/admin/App.d.ts +116 -116
  7. package/dist/apps/data/App.d.ts +30 -5
  8. package/dist/apps/data/App.d.ts.map +1 -1
  9. package/dist/apps/data/App.js +1 -1
  10. package/dist/apps/data/App.js.map +1 -1
  11. package/dist/apps/data/FundingRouteProviders.d.ts +54 -0
  12. package/dist/apps/data/FundingRouteProviders.d.ts.map +1 -0
  13. package/dist/apps/data/FundingRouteProviders.js +355 -0
  14. package/dist/apps/data/FundingRouteProviders.js.map +1 -0
  15. package/dist/apps/data/index.d.ts +1 -0
  16. package/dist/apps/data/index.d.ts.map +1 -1
  17. package/dist/apps/data/index.js +1 -0
  18. package/dist/apps/data/index.js.map +1 -1
  19. package/dist/apps/data/routes/funding.d.ts +79 -1
  20. package/dist/apps/data/routes/funding.d.ts.map +1 -1
  21. package/dist/apps/data/routes/funding.js +68 -8
  22. package/dist/apps/data/routes/funding.js.map +1 -1
  23. package/dist/apps/data/routes/indexer.d.ts +5 -5
  24. package/dist/apps/docs/App.d.ts +1 -1
  25. package/dist/apps/docs/App.js +1 -1
  26. package/dist/apps/index.d.ts +2 -2
  27. package/dist/apps/index.d.ts.map +1 -1
  28. package/dist/apps/index.js +2 -2
  29. package/dist/apps/index.js.map +1 -1
  30. package/dist/apps/{platform → management}/App.d.ts +11 -11
  31. package/dist/apps/{platform → management}/App.d.ts.map +1 -1
  32. package/dist/apps/{platform → management}/App.js +7 -7
  33. package/dist/apps/management/App.js.map +1 -0
  34. package/dist/apps/management/Billing.d.ts.map +1 -0
  35. package/dist/apps/management/Billing.js.map +1 -0
  36. package/dist/apps/management/index.d.ts +3 -0
  37. package/dist/apps/management/index.d.ts.map +1 -0
  38. package/dist/apps/management/index.js +3 -0
  39. package/dist/apps/management/index.js.map +1 -0
  40. package/dist/apps/management/routes/api-keys.d.ts.map +1 -0
  41. package/dist/apps/{platform → management}/routes/api-keys.js +1 -1
  42. package/dist/apps/management/routes/api-keys.js.map +1 -0
  43. package/dist/apps/{platform → management}/routes/billing.d.ts.map +1 -1
  44. package/dist/apps/management/routes/billing.js.map +1 -0
  45. package/dist/apps/{platform → management}/routes/invitations.d.ts +1 -1
  46. package/dist/apps/management/routes/invitations.d.ts.map +1 -0
  47. package/dist/apps/{platform → management}/routes/invitations.js +1 -1
  48. package/dist/apps/management/routes/invitations.js.map +1 -0
  49. package/dist/apps/management/routes/me.d.ts.map +1 -0
  50. package/dist/apps/management/routes/me.js.map +1 -0
  51. package/dist/apps/management/routes/members.d.ts.map +1 -0
  52. package/dist/apps/management/routes/members.js.map +1 -0
  53. package/dist/apps/management/routes/orgs.d.ts.map +1 -0
  54. package/dist/apps/management/routes/orgs.js.map +1 -0
  55. package/dist/apps/{platform → management}/routes/projects.d.ts.map +1 -1
  56. package/dist/apps/management/routes/projects.js.map +1 -0
  57. package/dist/apps/management/routes/scopes.d.ts.map +1 -0
  58. package/dist/apps/management/routes/scopes.js.map +1 -0
  59. package/dist/apps/management/routes/usage.d.ts.map +1 -0
  60. package/dist/apps/management/routes/usage.js.map +1 -0
  61. package/dist/apps/mcp.js +1 -1
  62. package/dist/apps/relay/App.js +1 -1
  63. package/dist/apps/relay/App.js.map +1 -1
  64. package/dist/internal/Auth.js +1 -1
  65. package/dist/internal/Auth.js.map +1 -1
  66. package/dist/internal/EdgeCache.js +1 -1
  67. package/dist/internal/EdgeCache.js.map +1 -1
  68. package/dist/internal/FundingRoutes.d.ts +748 -16
  69. package/dist/internal/FundingRoutes.d.ts.map +1 -1
  70. package/dist/internal/FundingRoutes.js +166 -31
  71. package/dist/internal/FundingRoutes.js.map +1 -1
  72. package/package.json +5 -5
  73. package/src/App.ts +10 -10
  74. package/src/Client.test-d.ts +4 -4
  75. package/src/apps/composition.test.ts +26 -13
  76. package/src/apps/data/App.ts +3 -1
  77. package/src/apps/data/FundingRouteProviders.test.ts +281 -0
  78. package/src/apps/data/FundingRouteProviders.ts +459 -0
  79. package/src/apps/data/index.ts +1 -0
  80. package/src/apps/data/routes/funding.test.ts +102 -2
  81. package/src/apps/data/routes/funding.ts +122 -8
  82. package/src/apps/docs/App.ts +1 -1
  83. package/src/apps/index.ts +2 -2
  84. package/src/apps/{platform → management}/App.ts +14 -14
  85. package/src/apps/{platform → management}/index.ts +1 -1
  86. package/src/apps/{platform → management}/routes/api-keys.ts +1 -1
  87. package/src/apps/{platform → management}/routes/invitations.ts +1 -1
  88. package/src/apps/mcp.test.ts +1 -1
  89. package/src/apps/mcp.ts +1 -1
  90. package/src/apps/relay/App.test.ts +1 -1
  91. package/src/apps/relay/App.ts +1 -1
  92. package/src/internal/Auth.ts +1 -1
  93. package/src/internal/EdgeCache.ts +1 -1
  94. package/src/internal/FundingRoutes.test.ts +213 -0
  95. package/src/internal/FundingRoutes.ts +347 -32
  96. package/dist/apps/platform/App.js.map +0 -1
  97. package/dist/apps/platform/Billing.d.ts.map +0 -1
  98. package/dist/apps/platform/Billing.js.map +0 -1
  99. package/dist/apps/platform/index.d.ts +0 -3
  100. package/dist/apps/platform/index.d.ts.map +0 -1
  101. package/dist/apps/platform/index.js +0 -3
  102. package/dist/apps/platform/index.js.map +0 -1
  103. package/dist/apps/platform/routes/api-keys.d.ts.map +0 -1
  104. package/dist/apps/platform/routes/api-keys.js.map +0 -1
  105. package/dist/apps/platform/routes/billing.js.map +0 -1
  106. package/dist/apps/platform/routes/invitations.d.ts.map +0 -1
  107. package/dist/apps/platform/routes/invitations.js.map +0 -1
  108. package/dist/apps/platform/routes/me.d.ts.map +0 -1
  109. package/dist/apps/platform/routes/me.js.map +0 -1
  110. package/dist/apps/platform/routes/members.d.ts.map +0 -1
  111. package/dist/apps/platform/routes/members.js.map +0 -1
  112. package/dist/apps/platform/routes/orgs.d.ts.map +0 -1
  113. package/dist/apps/platform/routes/orgs.js.map +0 -1
  114. package/dist/apps/platform/routes/projects.js.map +0 -1
  115. package/dist/apps/platform/routes/scopes.d.ts.map +0 -1
  116. package/dist/apps/platform/routes/scopes.js.map +0 -1
  117. package/dist/apps/platform/routes/usage.d.ts.map +0 -1
  118. package/dist/apps/platform/routes/usage.js.map +0 -1
  119. /package/dist/apps/{platform → management}/Billing.d.ts +0 -0
  120. /package/dist/apps/{platform → management}/Billing.js +0 -0
  121. /package/dist/apps/{platform → management}/routes/api-keys.d.ts +0 -0
  122. /package/dist/apps/{platform → management}/routes/billing.d.ts +0 -0
  123. /package/dist/apps/{platform → management}/routes/billing.js +0 -0
  124. /package/dist/apps/{platform → management}/routes/me.d.ts +0 -0
  125. /package/dist/apps/{platform → management}/routes/me.js +0 -0
  126. /package/dist/apps/{platform → management}/routes/members.d.ts +0 -0
  127. /package/dist/apps/{platform → management}/routes/members.js +0 -0
  128. /package/dist/apps/{platform → management}/routes/orgs.d.ts +0 -0
  129. /package/dist/apps/{platform → management}/routes/orgs.js +0 -0
  130. /package/dist/apps/{platform → management}/routes/projects.d.ts +0 -0
  131. /package/dist/apps/{platform → management}/routes/projects.js +0 -0
  132. /package/dist/apps/{platform → management}/routes/scopes.d.ts +0 -0
  133. /package/dist/apps/{platform → management}/routes/scopes.js +0 -0
  134. /package/dist/apps/{platform → management}/routes/usage.d.ts +0 -0
  135. /package/dist/apps/{platform → management}/routes/usage.js +0 -0
  136. /package/src/apps/{platform → management}/Billing.test.ts +0 -0
  137. /package/src/apps/{platform → management}/Billing.ts +0 -0
  138. /package/src/apps/{platform → management}/routes/api-keys.test.ts +0 -0
  139. /package/src/apps/{platform → management}/routes/billing.test.ts +0 -0
  140. /package/src/apps/{platform → management}/routes/billing.ts +0 -0
  141. /package/src/apps/{platform → management}/routes/invitations.test.ts +0 -0
  142. /package/src/apps/{platform → management}/routes/me.test.ts +0 -0
  143. /package/src/apps/{platform → management}/routes/me.ts +0 -0
  144. /package/src/apps/{platform → management}/routes/members.test.ts +0 -0
  145. /package/src/apps/{platform → management}/routes/members.ts +0 -0
  146. /package/src/apps/{platform → management}/routes/orgs.test.ts +0 -0
  147. /package/src/apps/{platform → management}/routes/orgs.ts +0 -0
  148. /package/src/apps/{platform → management}/routes/projects.test.ts +0 -0
  149. /package/src/apps/{platform → management}/routes/projects.ts +0 -0
  150. /package/src/apps/{platform → management}/routes/scopes.test.ts +0 -0
  151. /package/src/apps/{platform → management}/routes/scopes.ts +0 -0
  152. /package/src/apps/{platform → management}/routes/usage.test.ts +0 -0
  153. /package/src/apps/{platform → management}/routes/usage.ts +0 -0
@@ -187,6 +187,12 @@ export namespace schema {
187
187
  z.describe('Explainable route score from 0 to 100, when enough signals are available.'),
188
188
  z.meta({ examples: [91] }),
189
189
  ),
190
+ sourceDetail: z
191
+ .nullable(z.string())
192
+ .check(
193
+ z.describe('Provider-specific source or endpoint detail for the quality signal.'),
194
+ z.meta({ examples: ['providerEndpoint'] }),
195
+ ),
190
196
  tier: z
191
197
  .enum(['liquid', 'thin', 'unknown', 'unavailable', 'error'])
192
198
  .check(
@@ -194,7 +200,72 @@ export namespace schema {
194
200
  z.meta({ examples: ['unknown'] }),
195
201
  ),
196
202
  }),
197
- 'Machine-readable liquidity, fee, and freshness signals for a route candidate.',
203
+ 'Machine-readable liquidity, fee, and source signals for a route candidate.',
204
+ )
205
+
206
+ /** Provider-derived quote data for a route candidate. */
207
+ export const RouteQuote = Schema.describe(
208
+ z.object({
209
+ expiresAt: z
210
+ .nullable(z.iso.datetime())
211
+ .check(
212
+ z.describe('Provider quote expiry timestamp.'),
213
+ z.meta({ examples: ['2026-01-01T00:00:30Z'] }),
214
+ ),
215
+ inputAmount: Schema.DecimalString.check(
216
+ z.describe('Source amount sent to the provider in base units.'),
217
+ z.meta({ examples: ['1000000'] }),
218
+ ),
219
+ limits: z
220
+ .nullable(
221
+ z.object({
222
+ instantSourceAmount: z
223
+ .nullable(Schema.DecimalString)
224
+ .check(
225
+ z.describe('Provider-reported instant source amount capacity.'),
226
+ z.meta({ examples: ['12453940000'] }),
227
+ ),
228
+ maxSourceAmount: z
229
+ .nullable(Schema.DecimalString)
230
+ .check(
231
+ z.describe('Provider-reported maximum source amount.'),
232
+ z.meta({ examples: ['1114274433200'] }),
233
+ ),
234
+ minSourceAmount: z
235
+ .nullable(Schema.DecimalString)
236
+ .check(
237
+ z.describe('Provider-reported minimum source amount.'),
238
+ z.meta({ examples: ['500063'] }),
239
+ ),
240
+ }),
241
+ )
242
+ .check(z.describe('Provider limits expressed in source base units.')),
243
+ minimumOutputAmount: z
244
+ .nullable(Schema.DecimalString)
245
+ .check(
246
+ z.describe('Slippage-adjusted minimum destination amount in base units.'),
247
+ z.meta({ examples: ['988000'] }),
248
+ ),
249
+ outputAmount: z
250
+ .nullable(Schema.DecimalString)
251
+ .check(
252
+ z.describe('Expected destination amount in base units.'),
253
+ z.meta({ examples: ['989102'] }),
254
+ ),
255
+ outputAmountFormatted: z
256
+ .nullable(z.string())
257
+ .check(
258
+ z.describe('Expected destination amount rendered with destination token decimals.'),
259
+ z.meta({ examples: ['0.989102'] }),
260
+ ),
261
+ sampledAt: z.iso
262
+ .datetime()
263
+ .check(
264
+ z.describe('Timestamp for the live quote or route probe.'),
265
+ z.meta({ examples: ['2026-01-01T00:00:00Z'] }),
266
+ ),
267
+ }),
268
+ 'Provider-derived quote, limit, and freshness data for a route candidate.',
198
269
  )
199
270
 
200
271
  /** Static or live funding route candidate. */
@@ -202,6 +273,12 @@ export namespace schema {
202
273
  z.object({
203
274
  destinationChain: Chain.check(z.describe('Destination chain, always Tempo mainnet in V1.')),
204
275
  destinationToken: Token.check(z.describe('Tempo token expected from this route.')),
276
+ errorCode: z
277
+ .nullable(z.string())
278
+ .check(
279
+ z.describe('Stable route error code, when status is `error`.'),
280
+ z.meta({ examples: ['provider_timeout'] }),
281
+ ),
205
282
  id: z
206
283
  .string()
207
284
  .check(
@@ -210,6 +287,9 @@ export namespace schema {
210
287
  ),
211
288
  provider: ProviderRef,
212
289
  quality: Quality,
290
+ quote: z
291
+ .nullable(RouteQuote)
292
+ .check(z.describe('Provider-derived quote data, when a live route probe succeeds.')),
213
293
  sourceAmount: TokenAmount,
214
294
  sourceChain: Chain.check(z.describe('Chain where the caller currently holds funds.')),
215
295
  sourceToken: Token.check(z.describe('Token the caller currently holds.')),
@@ -278,6 +358,12 @@ export namespace schema {
278
358
  ),
279
359
  z.meta({ examples: ['USDC.e'] }),
280
360
  ),
361
+ include: z
362
+ .optional(z.string())
363
+ .check(
364
+ z.describe('Comma-separated response expansions. V1 accepts `quotes`.'),
365
+ z.meta({ examples: ['quotes'] }),
366
+ ),
281
367
  limit: Schema.Limit,
282
368
  provider: z
283
369
  .optional(z.string())
@@ -380,7 +466,7 @@ const fundingRouteResponseExamples = {
380
466
  '`GET /v1/funding/routes?sourceChain=base&sourceToken=USDC&sourceAmount=1000000&provider=across`',
381
467
  summary: 'Base USDC',
382
468
  value: {
383
- data: FundingRoutes.listRoutes({
469
+ data: FundingRoutes.listStaticRoutes({
384
470
  limit: 10,
385
471
  provider: 'across',
386
472
  sourceAmount: '1000000',
@@ -395,7 +481,7 @@ const fundingRouteResponseExamples = {
395
481
  '`GET /v1/funding/routes?sourceChain=solana&sourceToken=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v&sourceAmount=1000000&provider=bridge`',
396
482
  summary: 'Solana USDC',
397
483
  value: {
398
- data: FundingRoutes.listRoutes({
484
+ data: FundingRoutes.listStaticRoutes({
399
485
  limit: 10,
400
486
  provider: 'bridge',
401
487
  sourceAmount: '1000000',
@@ -410,7 +496,7 @@ const fundingRouteResponseExamples = {
410
496
  '`GET /v1/funding/routes?sourceChain=tron&sourceToken=TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t&sourceAmount=1000000&provider=bridge`',
411
497
  summary: 'Tron USDT',
412
498
  value: {
413
- data: FundingRoutes.listRoutes({
499
+ data: FundingRoutes.listStaticRoutes({
414
500
  limit: 10,
415
501
  provider: 'bridge',
416
502
  sourceAmount: '1000000',
@@ -433,7 +519,7 @@ const providersResponseExample = {
433
519
  } satisfies z.output<typeof schema.listProviders.Response>
434
520
 
435
521
  /** Mounts funding route discovery and generic provider inventory endpoints. */
436
- export function funding() {
522
+ export function funding(options: funding.Options = {}) {
437
523
  return new Hono<App.Environment>()
438
524
  .get(
439
525
  '/v1/funding/routes',
@@ -444,7 +530,7 @@ export function funding() {
444
530
  }),
445
531
  OpenApi.describeRoute({
446
532
  description:
447
- 'Lists provider-specific route candidates for moving a source stablecoin amount into Tempo. This endpoint is discovery-only: it does not execute a bridge, reserve liquidity, guarantee settlement, or return live quotes. Static inventory candidates use `status: unknown` and `quality.liquiditySource: staticInventory`.',
533
+ 'Lists provider-specific route candidates for moving a source stablecoin amount into Tempo. This endpoint is discovery-only: it does not execute a bridge, reserve liquidity, or guarantee settlement. Static inventory candidates use `status: unknown`; provider adapters may add best-effort live quote fields.',
448
534
  operationId: 'listFundingRoutes',
449
535
  responses: OpenApi.responses({
450
536
  errors: { 400: { codes: ['query_invalid'] } },
@@ -473,10 +559,15 @@ export function funding() {
473
559
 
474
560
  const query = c.req.valid('query')
475
561
  try {
562
+ const include = FundingRoutes.parseInclude(query.include)
476
563
  return c.json(
477
564
  Response.validated(schema.listFundingRoutes.Response, {
478
- data: FundingRoutes.listRoutes({
565
+ data: await FundingRoutes.listRoutes({
566
+ adapters: options.adapters,
567
+ adapterTimeoutMs: options.adapterTimeoutMs,
568
+ cache: { bypass: cacheBypass(c.req.raw) },
479
569
  destinationToken: query.destinationToken,
570
+ include,
480
571
  limit: query.limit,
481
572
  provider: query.provider,
482
573
  sourceAmount: query.sourceAmount,
@@ -564,7 +655,10 @@ export function funding() {
564
655
  const query = c.req.valid('query')
565
656
  return c.json(
566
657
  Response.validated(schema.listProviders.Response, {
567
- data: FundingRoutes.listProviders({ capability: query.capability }),
658
+ data: FundingRoutes.listProviders({
659
+ adapters: options.adapters,
660
+ capability: query.capability,
661
+ }),
568
662
  }),
569
663
  200,
570
664
  )
@@ -572,9 +666,20 @@ export function funding() {
572
666
  )
573
667
  }
574
668
 
669
+ export declare namespace funding {
670
+ /** Options for funding route discovery. */
671
+ type Options = {
672
+ /** Provider adapters used to enrich static inventory with live data. */
673
+ adapters?: FundingRoutes.ProviderAdapters | undefined
674
+ /** Per-provider quote timeout in milliseconds. */
675
+ adapterTimeoutMs?: number | undefined
676
+ }
677
+ }
678
+
575
679
  // Maps caller-input errors to a specific 400 message; anything else rethrows.
576
680
  function requestErrorMessage(cause: unknown) {
577
681
  if (cause instanceof FundingRoutes.InvalidAmountError) return 'Invalid `sourceAmount`.'
682
+ if (cause instanceof FundingRoutes.UnsupportedIncludeError) return 'Unsupported `include`.'
578
683
  if (cause instanceof FundingRoutes.UnsupportedDestinationTokenError)
579
684
  return 'Unsupported `destinationToken`.'
580
685
  if (cause instanceof FundingRoutes.UnsupportedProviderError) return 'Unsupported `provider`.'
@@ -582,3 +687,12 @@ function requestErrorMessage(cause: unknown) {
582
687
  return 'Unsupported `sourceChain`.'
583
688
  return undefined
584
689
  }
690
+
691
+ function cacheBypass(request: Request) {
692
+ const directives = (
693
+ request.headers.get('Cache-Control') ??
694
+ request.headers.get('Pragma') ??
695
+ ''
696
+ ).toLowerCase()
697
+ return directives.includes('no-cache') || directives.includes('no-store')
698
+ }
@@ -10,7 +10,7 @@ import * as Metadata from '../metadata.js'
10
10
  *
11
11
  * ```ts
12
12
  * import { docs } from 'tapimo/docs'
13
- * App.create(options).route('/', data()).route('/', platform()).route('/', docs())
13
+ * App.create(options).route('/', data()).route('/', management()).route('/', docs())
14
14
  * ```
15
15
  *
16
16
  * The group registers no routes itself: it carries a reference factory (via
package/src/apps/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { data } from './data/index.js'
1
+ export { data, FundingRouteProviders } from './data/index.js'
2
+ export { management } from './management/index.js'
2
3
  export { mcp } from './mcp.js'
3
- export { platform } from './platform/index.js'
4
4
  export { relay } from './relay/index.js'
@@ -22,7 +22,7 @@ import { usage } from './routes/usage.js'
22
22
  export type Email = {
23
23
  /** Console URL embedded in email links (e.g. sign-in URLs). Defaults to the hosted console; self-hosts point it at their own. */
24
24
  consoleUrl?: string | undefined
25
- /** Sender address for platform emails (e.g. `noreply@tempo.xyz`). */
25
+ /** Sender address for management emails (e.g. `noreply@tempo.xyz`). */
26
26
  from: string
27
27
  /** Sends one message. */
28
28
  send: (message: Email.Message) => Promise<unknown>
@@ -69,32 +69,32 @@ export type Billing = {
69
69
  }
70
70
 
71
71
  /**
72
- * Request environment for platform routes: the shared app environment plus the
72
+ * Request environment for management routes: the shared app environment plus the
73
73
  * capabilities this group publishes on context (`billing`, `email`). Owned
74
- * here, not in core `App.Environment`, since these are platform concerns.
74
+ * here, not in core `App.Environment`, since these are management concerns.
75
75
  */
76
76
  export type Environment = {
77
77
  Variables: App.Environment['Variables'] & {
78
78
  /** Stripe billing capability; undefined when billing is unconfigured. */
79
79
  billing: Billing | undefined
80
- /** Transactional email sender for platform emails; undefined when dispatch is disabled. */
80
+ /** Transactional email sender for management emails; undefined when dispatch is disabled. */
81
81
  email: Email | undefined
82
82
  }
83
83
  }
84
84
 
85
85
  /**
86
- * The tenant-platform route group: the current user, orgs, projects, members,
86
+ * The management route group: the current user, orgs, projects, members,
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
90
  * The session-auth surface (`/v1/auth/siwe` + `session` capability) is owned by
91
- * {@link App.create}, so platform routes gate on a session without owning it.
91
+ * {@link App.create}, so management routes gate on a session without owning it.
92
92
  *
93
93
  * ```ts
94
- * App.create(options).route('/', platform())
94
+ * App.create(options).route('/', management())
95
95
  * ```
96
96
  */
97
- export function platform(options: platform.Options = {}) {
97
+ export function management(options: management.Options = {}) {
98
98
  const app = new Hono<Environment>()
99
99
  // Publish this group's capabilities for handlers; undefined disables each.
100
100
  .use('*', async (c, next) => {
@@ -114,21 +114,21 @@ export function platform(options: platform.Options = {}) {
114
114
  return Metadata.attach(app, openapi)
115
115
  }
116
116
 
117
- export declare namespace platform {
118
- /** Options for the platform route group. */
117
+ export declare namespace management {
118
+ /** Options for the management route group. */
119
119
  type Options = {
120
120
  /** Expose the API-key management routes in the OpenAPI document. @default true */
121
121
  apiKeys?: boolean | undefined
122
122
  /** Stripe billing capability; omit to disable billing routes (501) and keep production sponsorship closed. */
123
123
  billing?: Billing | undefined
124
- /** Transactional email sender for platform emails (e.g. invitations); omit to disable dispatch — notified flows still work in-product. */
124
+ /** Transactional email sender for management emails (e.g. invitations); omit to disable dispatch — notified flows still work in-product. */
125
125
  email?: Email | undefined
126
126
  }
127
127
  }
128
128
 
129
129
  /**
130
- * The OpenAPI metadata this group owns: platform operation tags and the
131
- * `session` security scheme. Tagged onto the {@link platform} instance so
130
+ * The OpenAPI metadata this group owns: management operation tags and the
131
+ * `session` security scheme. Tagged onto the {@link management} instance so
132
132
  * `App.create` collects it.
133
133
  */
134
134
  const openapi = {
@@ -152,7 +152,7 @@ const openapi = {
152
152
  ],
153
153
  'x-tagGroups': [
154
154
  {
155
- name: 'Platform API',
155
+ name: 'Management API',
156
156
  tags: [
157
157
  'API Keys',
158
158
  'Authentication',
@@ -1,2 +1,2 @@
1
- export { platform } from './App.js'
1
+ export { management } from './App.js'
2
2
  export * as Billing from './Billing.js'
@@ -494,7 +494,7 @@ export declare namespace apiKeys {
494
494
  }
495
495
  }
496
496
 
497
- /** Projects a persisted record onto the platform key shape (no rate limits, no token). */
497
+ /** Projects a persisted record onto the management key shape (no rate limits, no token). */
498
498
  function serializeKey(record: ApiKeys.Record) {
499
499
  return {
500
500
  createdAt: record.createdAt,
@@ -206,7 +206,7 @@ export namespace schema {
206
206
  * Mounts the invitations surface: org-scoped management
207
207
  * (`/orgs/:orgId/invitations`, admins and owners) plus the invitee's side
208
208
  * (`/invitations`, matched against the session's verified email). Invitation
209
- * emails dispatch through the sender the platform group publishes on context
209
+ * emails dispatch through the sender the management group publishes on context
210
210
  * (`c.get('email')`), best-effort.
211
211
  */
212
212
  export function invitations() {
@@ -114,7 +114,7 @@ test('scopes tools to the data domain', async () => {
114
114
  expect(tools).toContain('docs_search')
115
115
  // RPC is part of the data domain tag group.
116
116
  expect(tools.some((name) => name.startsWith('rpc'))).toBe(true)
117
- // Out of domain: CoinGecko mirrors, session auth, platform, relay.
117
+ // Out of domain: CoinGecko mirrors, session auth, management, relay.
118
118
  expect(tools.some((name) => name.startsWith('gecko_'))).toBe(false)
119
119
  expect(tools.some((name) => name.startsWith('v1_auth_'))).toBe(false)
120
120
  expect(tools.some((name) => name.startsWith('v1_orgs'))).toBe(false)
package/src/apps/mcp.ts CHANGED
@@ -201,7 +201,7 @@ const methods: readonly string[] = ['delete', 'get', 'head', 'options', 'patch',
201
201
 
202
202
  /**
203
203
  * Narrows the document to the data domain: operations tagged inside the
204
- * `Data API` tag group, minus excluded tags. Platform, relay, and session
204
+ * `Data API` tag group, minus excluded tags. Management, relay, and session
205
205
  * operations never surface as tools.
206
206
  */
207
207
  function scope(document: Document): Openapi.OpenAPISpec {
@@ -16,7 +16,7 @@ import * as Fees from '../../internal/Fees.js'
16
16
  import * as Organizations from '../../db/tables/organizations.js'
17
17
  import * as SponsoredTransactions from '../../db/tables/sponsoredTransactions.js'
18
18
  import * as StripeCustomers from '../../db/tables/stripeCustomers.js'
19
- import * as core_Billing from '../platform/Billing.js'
19
+ import * as core_Billing from '../management/Billing.js'
20
20
  import * as RequestListener from '../../handlers/internal/requestListener.js'
21
21
  import * as Viem from '../../internal/Viem.js'
22
22
  import * as Sponsorships from './Sponsorships.js'
@@ -8,7 +8,7 @@ import * as BillingSettings from '../../db/tables/billingSettings.js'
8
8
  import * as Db from '../../db/Db.js'
9
9
  import * as Fees from '../../internal/Fees.js'
10
10
  import * as SponsoredTransactions from '../../db/tables/sponsoredTransactions.js'
11
- import * as Billing from '../platform/Billing.js'
11
+ import * as Billing from '../management/Billing.js'
12
12
  import * as Handler from '../../Handler.js'
13
13
  import * as Auth from '../../internal/Auth.js'
14
14
  import * as OpenApi from '../../internal/OpenApi.js'
@@ -882,7 +882,7 @@ export function install(options: install.Options) {
882
882
  }),
883
883
  )
884
884
  return Object.assign(
885
- // The tag joins the `Platform API` group at `platform()`'s chosen slot;
885
+ // The tag joins the `Management API` group at `management()`'s chosen slot;
886
886
  // group listings filter to defined tags, so it drops out when unmounted.
887
887
  Metadata.attach(app.route('/v1/auth/siwe', handler), {
888
888
  tags: [{ name: 'Authentication', description: 'Authenticate into the Tempo Platform.' }],
@@ -74,7 +74,7 @@ export function middleware<environment extends Env>(
74
74
  return async (c, next) => {
75
75
  const { store } = options
76
76
  // Pay the store lookup only when a matched handler is marked cacheable
77
- // (mounted `Cache.response`); other GETs (authed platform routes, docs,
77
+ // (mounted `Cache.response`); other GETs (authed management routes, docs,
78
78
  // 404s) can never hit, so the read would be a wasted round trip.
79
79
  const eligible =
80
80
  store !== undefined &&
@@ -0,0 +1,213 @@
1
+ import * as FundingRoutes from './FundingRoutes.js'
2
+
3
+ const baseUsdcRequest = {
4
+ limit: 10,
5
+ provider: 'across',
6
+ sourceAmount: '1000000',
7
+ sourceAmountUnits: 'baseUnits',
8
+ sourceChain: 'base',
9
+ sourceToken: 'USDC',
10
+ } as const
11
+
12
+ describe('listRoutes', () => {
13
+ test('enriches static candidates with adapter quote probes', async () => {
14
+ let seen: FundingRoutes.QuoteProbeInput | undefined
15
+ const routes = await FundingRoutes.listRoutes({
16
+ ...baseUsdcRequest,
17
+ adapters: {
18
+ across: {
19
+ async probeQuote(input, signal) {
20
+ seen = input
21
+ expect(signal.aborted).toBe(false)
22
+ expect(input.cache.bypass).toBe(false)
23
+ expect(input.destinationChain.id).toBe('eip155:4217')
24
+ expect(input.destinationToken.symbol).toBe('USDC.e')
25
+ expect(input.include).toEqual([])
26
+ expect(input.request.sourceChain).toBe('base')
27
+ return {
28
+ expiresAt: '2026-01-01T00:00:30Z',
29
+ limits: {
30
+ instantSourceAmount: '1000000000',
31
+ maxSourceAmount: '100000000000',
32
+ minSourceAmount: '500000',
33
+ },
34
+ minimumOutputAmount: '980000',
35
+ outputAmount: '990000',
36
+ outputAmountFormatted: '0.99',
37
+ quality: {
38
+ availableVolumeUsd: '1000.00',
39
+ estimatedSeconds: 4,
40
+ feeBps: 12,
41
+ historicalVolumeUsd24h: '100000.00',
42
+ instantVolumeUsd: '1000.00',
43
+ liquiditySource: 'providerQuote',
44
+ score: 91,
45
+ sourceDetail: 'providerEndpoint',
46
+ tier: 'liquid',
47
+ },
48
+ sampledAt: '2026-01-01T00:00:00Z',
49
+ status: 'available',
50
+ }
51
+ },
52
+ },
53
+ },
54
+ })
55
+
56
+ expect(seen?.route.id).toBe('base-usdc-tempo-usdce-across')
57
+ expect(seen?.sourceAmount.amount).toBe('1000000')
58
+ expect(seen?.sourceChain.id).toBe('eip155:8453')
59
+ expect(seen?.sourceToken.symbol).toBe('USDC')
60
+ expect(routes).toHaveLength(1)
61
+ expect(routes[0]).toMatchObject({
62
+ errorCode: null,
63
+ id: 'base-usdc-tempo-usdce-across',
64
+ quality: {
65
+ availableVolumeUsd: '1000.00',
66
+ estimatedSeconds: 4,
67
+ feeBps: 12,
68
+ historicalVolumeUsd24h: '100000.00',
69
+ instantVolumeUsd: '1000.00',
70
+ liquiditySource: 'providerQuote',
71
+ score: 91,
72
+ sourceDetail: 'providerEndpoint',
73
+ tier: 'liquid',
74
+ },
75
+ quote: {
76
+ expiresAt: '2026-01-01T00:00:30Z',
77
+ inputAmount: '1000000',
78
+ limits: {
79
+ instantSourceAmount: '1000000000',
80
+ maxSourceAmount: '100000000000',
81
+ minSourceAmount: '500000',
82
+ },
83
+ minimumOutputAmount: '980000',
84
+ outputAmount: '990000',
85
+ outputAmountFormatted: '0.99',
86
+ sampledAt: '2026-01-01T00:00:00Z',
87
+ },
88
+ status: 'available',
89
+ })
90
+ })
91
+
92
+ test('leaves candidates static when their provider has no adapter', async () => {
93
+ let calls = 0
94
+ const routes = await FundingRoutes.listRoutes({
95
+ ...baseUsdcRequest,
96
+ adapters: {
97
+ relay: {
98
+ async probeQuote() {
99
+ calls += 1
100
+ throw new Error('unexpected call')
101
+ },
102
+ },
103
+ },
104
+ })
105
+
106
+ expect(calls).toBe(0)
107
+ expect(routes[0]).toMatchObject({
108
+ quality: {
109
+ liquiditySource: 'staticInventory',
110
+ sourceDetail: null,
111
+ tier: 'unknown',
112
+ },
113
+ quote: null,
114
+ status: 'unknown',
115
+ })
116
+ })
117
+
118
+ test('marks candidates as errors when an adapter throws', async () => {
119
+ const routes = await FundingRoutes.listRoutes({
120
+ ...baseUsdcRequest,
121
+ adapters: {
122
+ across: {
123
+ async probeQuote() {
124
+ throw new Error('upstream unavailable')
125
+ },
126
+ },
127
+ },
128
+ })
129
+
130
+ expect(routes[0]).toMatchObject({
131
+ errorCode: 'provider_error',
132
+ quality: {
133
+ liquiditySource: 'providerError',
134
+ sourceDetail: null,
135
+ tier: 'error',
136
+ },
137
+ quote: null,
138
+ status: 'error',
139
+ })
140
+ })
141
+
142
+ test('aborts slow adapters and marks candidates as timed-out errors', async () => {
143
+ let aborted = false
144
+ const routes = await FundingRoutes.listRoutes({
145
+ ...baseUsdcRequest,
146
+ adapterTimeoutMs: 1,
147
+ adapters: {
148
+ across: {
149
+ probeQuote(_input, signal) {
150
+ signal.addEventListener('abort', () => {
151
+ aborted = true
152
+ })
153
+ return new Promise<FundingRoutes.QuoteProbeResult>(() => {})
154
+ },
155
+ },
156
+ },
157
+ })
158
+
159
+ expect(aborted).toBe(true)
160
+ expect(routes[0]).toMatchObject({
161
+ errorCode: 'provider_timeout',
162
+ quality: {
163
+ liquiditySource: 'providerError',
164
+ sourceDetail: null,
165
+ tier: 'error',
166
+ },
167
+ quote: null,
168
+ status: 'error',
169
+ })
170
+ })
171
+
172
+ test('passes include and cache policy through to adapters', async () => {
173
+ let seen: FundingRoutes.QuoteProbeInput | undefined
174
+ const routes = await FundingRoutes.listRoutes({
175
+ ...baseUsdcRequest,
176
+ adapters: {
177
+ across: {
178
+ async probeQuote(input) {
179
+ seen = input
180
+ return {
181
+ errorCode: 'provider_unavailable',
182
+ sampledAt: '2026-01-01T00:00:00Z',
183
+ status: 'error',
184
+ }
185
+ },
186
+ },
187
+ },
188
+ cache: { bypass: true },
189
+ include: ['quotes'],
190
+ })
191
+
192
+ expect(seen?.cache.bypass).toBe(true)
193
+ expect(seen?.include).toEqual(['quotes'])
194
+ expect(seen?.request.include).toEqual(['quotes'])
195
+ expect(routes[0]).toMatchObject({
196
+ errorCode: 'provider_unavailable',
197
+ quote: null,
198
+ status: 'error',
199
+ })
200
+ })
201
+ })
202
+
203
+ describe('parseInclude', () => {
204
+ test('parses comma-separated include values', () => {
205
+ expect(FundingRoutes.parseInclude('quotes,quotes')).toEqual(['quotes'])
206
+ })
207
+
208
+ test('rejects unsupported include values', () => {
209
+ expect(() => FundingRoutes.parseInclude('quotes,debug')).toThrow(
210
+ FundingRoutes.UnsupportedIncludeError,
211
+ )
212
+ })
213
+ })