tapimo 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) 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 +21 -4
  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/balances.d.ts +45 -45
  20. package/dist/apps/data/routes/coingecko.d.ts +176 -176
  21. package/dist/apps/data/routes/funding.d.ts +79 -1
  22. package/dist/apps/data/routes/funding.d.ts.map +1 -1
  23. package/dist/apps/data/routes/funding.js +68 -8
  24. package/dist/apps/data/routes/funding.js.map +1 -1
  25. package/dist/apps/data/routes/indexer.d.ts +5 -5
  26. package/dist/apps/data/routes/rpc.d.ts +31 -31
  27. package/dist/apps/data/routes/transactions.d.ts +281 -281
  28. package/dist/apps/data/routes/transfers.d.ts +58 -58
  29. package/dist/apps/docs/App.d.ts +1 -1
  30. package/dist/apps/docs/App.js +1 -1
  31. package/dist/apps/index.d.ts +2 -2
  32. package/dist/apps/index.d.ts.map +1 -1
  33. package/dist/apps/index.js +2 -2
  34. package/dist/apps/index.js.map +1 -1
  35. package/dist/apps/{platform → management}/App.d.ts +11 -11
  36. package/dist/apps/{platform → management}/App.d.ts.map +1 -1
  37. package/dist/apps/{platform → management}/App.js +7 -7
  38. package/dist/apps/management/App.js.map +1 -0
  39. package/dist/apps/management/Billing.d.ts.map +1 -0
  40. package/dist/apps/management/Billing.js.map +1 -0
  41. package/dist/apps/management/index.d.ts +3 -0
  42. package/dist/apps/management/index.d.ts.map +1 -0
  43. package/dist/apps/management/index.js +3 -0
  44. package/dist/apps/management/index.js.map +1 -0
  45. package/dist/apps/management/routes/api-keys.d.ts.map +1 -0
  46. package/dist/apps/{platform → management}/routes/api-keys.js +1 -1
  47. package/dist/apps/management/routes/api-keys.js.map +1 -0
  48. package/dist/apps/{platform → management}/routes/billing.d.ts.map +1 -1
  49. package/dist/apps/management/routes/billing.js.map +1 -0
  50. package/dist/apps/{platform → management}/routes/invitations.d.ts +1 -1
  51. package/dist/apps/management/routes/invitations.d.ts.map +1 -0
  52. package/dist/apps/{platform → management}/routes/invitations.js +1 -1
  53. package/dist/apps/management/routes/invitations.js.map +1 -0
  54. package/dist/apps/{platform → management}/routes/me.d.ts +17 -17
  55. package/dist/apps/management/routes/me.d.ts.map +1 -0
  56. package/dist/apps/management/routes/me.js.map +1 -0
  57. package/dist/apps/management/routes/members.d.ts.map +1 -0
  58. package/dist/apps/management/routes/members.js.map +1 -0
  59. package/dist/apps/management/routes/orgs.d.ts.map +1 -0
  60. package/dist/apps/management/routes/orgs.js.map +1 -0
  61. package/dist/apps/{platform → management}/routes/projects.d.ts.map +1 -1
  62. package/dist/apps/management/routes/projects.js.map +1 -0
  63. package/dist/apps/{platform → management}/routes/scopes.d.ts +11 -11
  64. package/dist/apps/management/routes/scopes.d.ts.map +1 -0
  65. package/dist/apps/management/routes/scopes.js.map +1 -0
  66. package/dist/apps/management/routes/usage.d.ts.map +1 -0
  67. package/dist/apps/management/routes/usage.js.map +1 -0
  68. package/dist/apps/mcp.js +1 -1
  69. package/dist/apps/relay/App.js +1 -1
  70. package/dist/apps/relay/App.js.map +1 -1
  71. package/dist/handlers/relay.d.ts +13 -10
  72. package/dist/handlers/relay.d.ts.map +1 -1
  73. package/dist/handlers/relay.js +46 -16
  74. package/dist/handlers/relay.js.map +1 -1
  75. package/dist/internal/Auth.js +1 -1
  76. package/dist/internal/Auth.js.map +1 -1
  77. package/dist/internal/EdgeCache.js +1 -1
  78. package/dist/internal/EdgeCache.js.map +1 -1
  79. package/dist/internal/FundingRoutes.d.ts +748 -16
  80. package/dist/internal/FundingRoutes.d.ts.map +1 -1
  81. package/dist/internal/FundingRoutes.js +166 -31
  82. package/dist/internal/FundingRoutes.js.map +1 -1
  83. package/package.json +5 -5
  84. package/src/App.ts +10 -10
  85. package/src/Client.test-d.ts +4 -4
  86. package/src/apps/composition.test.ts +26 -13
  87. package/src/apps/data/App.ts +27 -4
  88. package/src/apps/data/FundingRouteProviders.test.ts +281 -0
  89. package/src/apps/data/FundingRouteProviders.ts +459 -0
  90. package/src/apps/data/index.ts +1 -0
  91. package/src/apps/data/routes/funding.test.ts +102 -2
  92. package/src/apps/data/routes/funding.ts +122 -8
  93. package/src/apps/data/routes/rpc.test.ts +33 -0
  94. package/src/apps/docs/App.ts +1 -1
  95. package/src/apps/index.ts +2 -2
  96. package/src/apps/{platform → management}/App.ts +14 -14
  97. package/src/apps/{platform → management}/index.ts +1 -1
  98. package/src/apps/{platform → management}/routes/api-keys.ts +1 -1
  99. package/src/apps/{platform → management}/routes/invitations.ts +1 -1
  100. package/src/apps/mcp.test.ts +1 -1
  101. package/src/apps/mcp.ts +1 -1
  102. package/src/apps/relay/App.test.ts +1 -1
  103. package/src/apps/relay/App.ts +1 -1
  104. package/src/handlers/relay.test-d.ts +8 -0
  105. package/src/handlers/relay.test.ts +2 -2
  106. package/src/handlers/relay.ts +62 -22
  107. package/src/internal/Auth.ts +1 -1
  108. package/src/internal/EdgeCache.ts +1 -1
  109. package/src/internal/FundingRoutes.test.ts +213 -0
  110. package/src/internal/FundingRoutes.ts +347 -32
  111. package/dist/apps/platform/App.js.map +0 -1
  112. package/dist/apps/platform/Billing.d.ts.map +0 -1
  113. package/dist/apps/platform/Billing.js.map +0 -1
  114. package/dist/apps/platform/index.d.ts +0 -3
  115. package/dist/apps/platform/index.d.ts.map +0 -1
  116. package/dist/apps/platform/index.js +0 -3
  117. package/dist/apps/platform/index.js.map +0 -1
  118. package/dist/apps/platform/routes/api-keys.d.ts.map +0 -1
  119. package/dist/apps/platform/routes/api-keys.js.map +0 -1
  120. package/dist/apps/platform/routes/billing.js.map +0 -1
  121. package/dist/apps/platform/routes/invitations.d.ts.map +0 -1
  122. package/dist/apps/platform/routes/invitations.js.map +0 -1
  123. package/dist/apps/platform/routes/me.d.ts.map +0 -1
  124. package/dist/apps/platform/routes/me.js.map +0 -1
  125. package/dist/apps/platform/routes/members.d.ts.map +0 -1
  126. package/dist/apps/platform/routes/members.js.map +0 -1
  127. package/dist/apps/platform/routes/orgs.d.ts.map +0 -1
  128. package/dist/apps/platform/routes/orgs.js.map +0 -1
  129. package/dist/apps/platform/routes/projects.js.map +0 -1
  130. package/dist/apps/platform/routes/scopes.d.ts.map +0 -1
  131. package/dist/apps/platform/routes/scopes.js.map +0 -1
  132. package/dist/apps/platform/routes/usage.d.ts.map +0 -1
  133. package/dist/apps/platform/routes/usage.js.map +0 -1
  134. /package/dist/apps/{platform → management}/Billing.d.ts +0 -0
  135. /package/dist/apps/{platform → management}/Billing.js +0 -0
  136. /package/dist/apps/{platform → management}/routes/api-keys.d.ts +0 -0
  137. /package/dist/apps/{platform → management}/routes/billing.d.ts +0 -0
  138. /package/dist/apps/{platform → management}/routes/billing.js +0 -0
  139. /package/dist/apps/{platform → management}/routes/me.js +0 -0
  140. /package/dist/apps/{platform → management}/routes/members.d.ts +0 -0
  141. /package/dist/apps/{platform → management}/routes/members.js +0 -0
  142. /package/dist/apps/{platform → management}/routes/orgs.d.ts +0 -0
  143. /package/dist/apps/{platform → management}/routes/orgs.js +0 -0
  144. /package/dist/apps/{platform → management}/routes/projects.d.ts +0 -0
  145. /package/dist/apps/{platform → management}/routes/projects.js +0 -0
  146. /package/dist/apps/{platform → management}/routes/scopes.js +0 -0
  147. /package/dist/apps/{platform → management}/routes/usage.d.ts +0 -0
  148. /package/dist/apps/{platform → management}/routes/usage.js +0 -0
  149. /package/src/apps/{platform → management}/Billing.test.ts +0 -0
  150. /package/src/apps/{platform → management}/Billing.ts +0 -0
  151. /package/src/apps/{platform → management}/routes/api-keys.test.ts +0 -0
  152. /package/src/apps/{platform → management}/routes/billing.test.ts +0 -0
  153. /package/src/apps/{platform → management}/routes/billing.ts +0 -0
  154. /package/src/apps/{platform → management}/routes/invitations.test.ts +0 -0
  155. /package/src/apps/{platform → management}/routes/me.test.ts +0 -0
  156. /package/src/apps/{platform → management}/routes/me.ts +0 -0
  157. /package/src/apps/{platform → management}/routes/members.test.ts +0 -0
  158. /package/src/apps/{platform → management}/routes/members.ts +0 -0
  159. /package/src/apps/{platform → management}/routes/orgs.test.ts +0 -0
  160. /package/src/apps/{platform → management}/routes/orgs.ts +0 -0
  161. /package/src/apps/{platform → management}/routes/projects.test.ts +0 -0
  162. /package/src/apps/{platform → management}/routes/projects.ts +0 -0
  163. /package/src/apps/{platform → management}/routes/scopes.test.ts +0 -0
  164. /package/src/apps/{platform → management}/routes/scopes.ts +0 -0
  165. /package/src/apps/{platform → management}/routes/usage.test.ts +0 -0
  166. /package/src/apps/{platform → management}/routes/usage.ts +0 -0
@@ -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
+ })
@@ -288,6 +288,138 @@ type DestinationTokenInput = (typeof destinationTokens)[number]
288
288
  type ProviderInput = (typeof providers)[number]
289
289
  type SourceChainInput = (typeof sourceChains)[number]
290
290
 
291
+ /** Provider ids accepted by the funding route inventory. */
292
+ export type ProviderId = ProviderInput['id']
293
+
294
+ /** Provider-specific route status for the requested amount. */
295
+ export type RouteStatus = 'available' | 'thin' | 'unknown' | 'unavailable' | 'error'
296
+
297
+ /** Machine-readable route quality tier. */
298
+ export type QualityTier = 'liquid' | 'thin' | 'unknown' | 'unavailable' | 'error'
299
+
300
+ /** Response expansions accepted by funding route discovery. */
301
+ export type Include = 'quotes'
302
+
303
+ /** Normalized chain reference passed to provider adapters. */
304
+ export type ChainRef = ReturnType<typeof formatChain> | typeof destinationChain
305
+
306
+ /** Normalized token amount passed to provider adapters. */
307
+ export type TokenAmount = ReturnType<typeof resolveSourceAmount>
308
+
309
+ /** Normalized token reference passed to provider adapters. */
310
+ export type TokenRef = ReturnType<typeof formatDestinationToken> | ReturnType<typeof formatSourceToken>
311
+
312
+ /** A normalized funding route candidate returned by route discovery. */
313
+ export type RouteCandidate = ReturnType<typeof formatRouteCandidate>
314
+
315
+ /** Cache policy for a single funding route discovery request. */
316
+ export type CachePolicy = {
317
+ /** Whether provider adapters should bypass their own quote or inventory caches. */
318
+ bypass: boolean
319
+ }
320
+
321
+ /** Normalized caller request passed to provider adapters. */
322
+ export type QuoteRequest = {
323
+ /** Optional Tempo destination token symbol, address, or token key. */
324
+ destinationToken?: string | undefined
325
+ /** Response expansions requested by the caller. */
326
+ include: readonly Include[]
327
+ /** Maximum number of route candidates requested by the caller. */
328
+ limit: number
329
+ /** Optional provider id filter. */
330
+ provider?: string | undefined
331
+ /** Source amount as supplied by the caller. */
332
+ sourceAmount: string
333
+ /** Unit used by {@link sourceAmount}. */
334
+ sourceAmountUnits: SourceAmountUnits
335
+ /** Chain alias or CAIP-2 source chain id. */
336
+ sourceChain: string
337
+ /** Source token symbol, contract address, mint, or Tempo token key. */
338
+ sourceToken: string
339
+ }
340
+
341
+ /** Input passed to a provider adapter for one static route candidate probe. */
342
+ export type QuoteProbeInput = {
343
+ /** Cache policy for provider-side inventory, quote, and liquidity lookups. */
344
+ cache: CachePolicy
345
+ /** Destination chain, always Tempo mainnet in V1. */
346
+ destinationChain: ChainRef
347
+ /** Tempo token expected from this route. */
348
+ destinationToken: TokenRef
349
+ /** Response expansions requested by the caller. */
350
+ include: readonly Include[]
351
+ /** Normalized caller request that produced this route candidate. */
352
+ request: QuoteRequest
353
+ /** Static candidate being enriched with live provider information. */
354
+ route: RouteCandidate
355
+ /** Source amount in base units and display form. */
356
+ sourceAmount: TokenAmount
357
+ /** Chain where the caller currently holds funds. */
358
+ sourceChain: ChainRef
359
+ /** Token the caller currently holds. */
360
+ sourceToken: TokenRef
361
+ }
362
+
363
+ /** Provider-specific source amount limits, expressed in source base units. */
364
+ export type QuoteProbeLimits = {
365
+ /** Provider-reported instant source amount capacity. */
366
+ instantSourceAmount?: string | null | undefined
367
+ /** Provider-reported maximum source amount. */
368
+ maxSourceAmount?: string | null | undefined
369
+ /** Provider-reported minimum source amount. */
370
+ minSourceAmount?: string | null | undefined
371
+ }
372
+
373
+ /** Quote probe data returned by a provider adapter. */
374
+ export type QuoteProbeResult = {
375
+ /** Stable route error code when status is `error`. */
376
+ errorCode?: string | null | undefined
377
+ /** Provider quote expiry timestamp. */
378
+ expiresAt?: string | null | undefined
379
+ /** Provider-reported source amount limits. */
380
+ limits?: QuoteProbeLimits | null | undefined
381
+ /** Slippage-adjusted minimum destination amount in base units. */
382
+ minimumOutputAmount?: string | null | undefined
383
+ /** Expected destination amount in base units. */
384
+ outputAmount?: string | null | undefined
385
+ /** Expected destination amount rendered with destination token decimals. */
386
+ outputAmountFormatted?: string | null | undefined
387
+ /** Timestamp for the live quote or route probe. */
388
+ sampledAt: string
389
+ /** Provider-specific route status for the requested amount. */
390
+ status: RouteStatus
391
+ /** Provider-backed route quality metadata. */
392
+ quality?: {
393
+ /** Available USD capacity, when available. */
394
+ availableVolumeUsd?: string | null | undefined
395
+ /** Estimated settlement time in seconds, when known. */
396
+ estimatedSeconds?: number | null | undefined
397
+ /** Estimated fee in basis points, when known. */
398
+ feeBps?: number | null | undefined
399
+ /** 24h routed volume in USD, when available. */
400
+ historicalVolumeUsd24h?: string | null | undefined
401
+ /** Instant USD capacity, when available. */
402
+ instantVolumeUsd?: string | null | undefined
403
+ /** Where the live liquidity or support signal came from. */
404
+ liquiditySource: string
405
+ /** Explainable route score from 0 to 100, when enough signals are available. */
406
+ score?: number | null | undefined
407
+ /** Provider-specific quote source or endpoint detail. */
408
+ sourceDetail?: string | null | undefined
409
+ /** Machine-readable route quality tier. */
410
+ tier?: QualityTier | undefined
411
+ }
412
+ }
413
+
414
+ /** Adapter used to enrich a static route candidate with live provider information. */
415
+ export type ProviderAdapter = {
416
+ /** Probes provider-backed quote information for one static candidate. */
417
+ probeQuote(input: QuoteProbeInput, signal: AbortSignal): Promise<QuoteProbeResult>
418
+ }
419
+
420
+ /** Provider adapter registry, keyed by provider id. Missing adapters leave routes static. */
421
+ export type ProviderAdapters = Partial<Record<ProviderId, ProviderAdapter>>
422
+
291
423
  /** Static provider support rows used until live quote adapters are configured. */
292
424
  const inventory: readonly InventoryEntry[] = [
293
425
  { destinationToken: 'usdce', provider: 'across', sourceChains: ['ethereum', 'base', 'bnb'], sourceTokens: ['USDC'] }, // prettier-ignore
@@ -304,6 +436,9 @@ type InventoryEntry = {
304
436
  sourceTokens: readonly string[]
305
437
  }
306
438
 
439
+ const defaultAdapterTimeoutMs = 750
440
+ const includes = ['quotes'] as const
441
+
307
442
  /** Lists normalized chains and source tokens that can fund Tempo. */
308
443
  export function listChains() {
309
444
  return sourceChains.map((chain) => ({
@@ -318,19 +453,61 @@ export function listProviders(options: listProviders.Options = {}) {
318
453
  .filter((provider) =>
319
454
  options.capability === undefined ? true : provider.capabilities.includes(options.capability),
320
455
  )
321
- .map(formatProvider)
456
+ .map((provider) => ({
457
+ ...formatProvider(provider),
458
+ status: options.adapters?.[provider.id] ? ('configured' as const) : provider.status,
459
+ }))
322
460
  }
323
461
 
324
462
  export declare namespace listProviders {
325
463
  /** Options for listing providers. */
326
464
  type Options = {
465
+ /** Provider adapters configured for this deployment. */
466
+ adapters?: ProviderAdapters | undefined
327
467
  /** Provider capability to require. */
328
468
  capability?: Capability | undefined
329
469
  }
330
470
  }
331
471
 
472
+ /** Parses the comma-separated `include` query parameter. */
473
+ export function parseInclude(input: string | undefined) {
474
+ if (input === undefined || input.trim() === '') return []
475
+ const parsed = input.split(',').map((part) => part.trim()).filter(Boolean)
476
+ for (const include of parsed)
477
+ if (!(includes as readonly string[]).includes(include)) throw new UnsupportedIncludeError()
478
+ return [...new Set(parsed)] as readonly Include[]
479
+ }
480
+
481
+ /** Lists funding route candidates, enriching static inventory when adapters are configured. */
482
+ export async function listRoutes(options: listRoutes.Options) {
483
+ const routes = listStaticRoutes(options)
484
+ const adapterEntries = Object.entries(options.adapters ?? {})
485
+ if (adapterEntries.length === 0) return routes
486
+
487
+ return Promise.all(
488
+ routes.map((route) =>
489
+ enrichRoute(route, {
490
+ adapters: options.adapters ?? {},
491
+ cache: options.cache ?? { bypass: false },
492
+ include: options.include ?? [],
493
+ request: {
494
+ destinationToken: options.destinationToken,
495
+ include: options.include ?? [],
496
+ limit: options.limit,
497
+ provider: options.provider,
498
+ sourceAmount: options.sourceAmount,
499
+ sourceAmountUnits: options.sourceAmountUnits,
500
+ sourceChain: options.sourceChain,
501
+ sourceToken: options.sourceToken,
502
+ },
503
+ timeoutMs: options.adapterTimeoutMs ?? defaultAdapterTimeoutMs,
504
+ }),
505
+ ),
506
+ )
507
+ }
508
+
332
509
  /** Lists static funding route candidates for a normalized caller request. */
333
- export function listRoutes(options: listRoutes.Options) {
510
+ export function listStaticRoutes(options: listStaticRoutes.Options) {
334
511
  const sourceChain = resolveSourceChain(options.sourceChain)
335
512
  const sourceToken = resolveSourceToken(sourceChain, options.sourceToken)
336
513
  const destinationToken = resolveDestinationToken(options.destinationToken)
@@ -353,39 +530,19 @@ export function listRoutes(options: listRoutes.Options) {
353
530
  .filter((entry) => entry.destinationToken === destinationToken.slug)
354
531
  .filter((entry) => provider === undefined || entry.provider === provider.id)
355
532
  .slice(0, options.limit)
356
- .map((entry) => {
357
- const entryProvider = providers.find((candidate) => candidate.id === entry.provider)!
358
- return {
359
- destinationChain,
360
- destinationToken: formatDestinationToken(destinationToken),
361
- id: [
362
- sourceChain.slug,
363
- sourceToken.symbol.toLowerCase(),
364
- 'tempo',
365
- destinationToken.slug,
366
- entryProvider.id,
367
- ].join('-'),
368
- provider: formatProviderRef(entryProvider),
369
- quality: {
370
- availableVolumeUsd: null,
371
- estimatedSeconds: null,
372
- feeBps: null,
373
- historicalVolumeUsd24h: null,
374
- instantVolumeUsd: null,
375
- liquiditySource: 'staticInventory',
376
- score: null,
377
- tier: 'unknown' as const,
378
- },
533
+ .map((entry) =>
534
+ formatRouteCandidate({
535
+ destinationToken,
536
+ provider: providers.find((candidate) => candidate.id === entry.provider)!,
379
537
  sourceAmount,
380
- sourceChain: formatChain(sourceChain),
381
- sourceToken: formatSourceToken(sourceChain, sourceToken),
382
- status: 'unknown' as const,
383
- }
384
- })
538
+ sourceChain,
539
+ sourceToken,
540
+ }),
541
+ )
385
542
  }
386
543
 
387
- export declare namespace listRoutes {
388
- /** Options for listing funding routes. */
544
+ export declare namespace listStaticRoutes {
545
+ /** Options for listing static funding routes. */
389
546
  type Options = {
390
547
  /** Optional Tempo destination token symbol, address, or token key. */
391
548
  destinationToken?: string | undefined
@@ -404,6 +561,20 @@ export declare namespace listRoutes {
404
561
  }
405
562
  }
406
563
 
564
+ export declare namespace listRoutes {
565
+ /** Options for listing funding routes. */
566
+ type Options = listStaticRoutes.Options & {
567
+ /** Provider adapters used to enrich static inventory with live data. */
568
+ adapters?: ProviderAdapters | undefined
569
+ /** Per-provider quote timeout in milliseconds. */
570
+ adapterTimeoutMs?: number | undefined
571
+ /** Cache policy for provider-side inventory, quote, and liquidity lookups. */
572
+ cache?: CachePolicy | undefined
573
+ /** Response expansions requested by the caller. */
574
+ include?: readonly Include[] | undefined
575
+ }
576
+ }
577
+
407
578
  /** Provider capability identifiers accepted by the generic provider catalog. */
408
579
  export type Capability = 'fundingRoutes'
409
580
 
@@ -423,6 +594,51 @@ function decimalFromBaseUnits(baseUnits: string, decimals: number) {
423
594
  return fraction ? `${whole}.${fraction}` : whole
424
595
  }
425
596
 
597
+ async function enrichRoute(
598
+ route: RouteCandidate,
599
+ options: {
600
+ adapters: ProviderAdapters
601
+ cache: CachePolicy
602
+ include: readonly Include[]
603
+ request: QuoteRequest
604
+ timeoutMs: number
605
+ },
606
+ ) {
607
+ const adapter = options.adapters[route.provider.id as ProviderId]
608
+ if (!adapter) return route
609
+
610
+ try {
611
+ const quote = await quoteWithTimeout(
612
+ adapter,
613
+ {
614
+ cache: options.cache,
615
+ destinationChain: route.destinationChain,
616
+ destinationToken: route.destinationToken,
617
+ include: options.include,
618
+ request: options.request,
619
+ route,
620
+ sourceAmount: route.sourceAmount,
621
+ sourceChain: route.sourceChain,
622
+ sourceToken: route.sourceToken,
623
+ },
624
+ options.timeoutMs,
625
+ )
626
+ return {
627
+ ...route,
628
+ errorCode: quote.errorCode ?? null,
629
+ quality: {
630
+ ...route.quality,
631
+ ...(quote.status === 'error' ? { tier: 'error' as const } : {}),
632
+ ...quote.quality,
633
+ },
634
+ quote: quote.status === 'error' ? null : formatRouteQuote(route, quote),
635
+ status: quote.status,
636
+ }
637
+ } catch (cause) {
638
+ return markRouteError(route, cause instanceof ProviderAdapterTimeoutError ? 'provider_timeout' : 'provider_error') // prettier-ignore
639
+ }
640
+ }
641
+
426
642
  function formatChain(chain: SourceChainInput) {
427
643
  const { addressFormat, id, kind, name, providerIds } = chain
428
644
  return { addressFormat, id, kind, name, providerIds }
@@ -451,6 +667,44 @@ function formatProviderRef(provider: ProviderInput) {
451
667
  return { id, kind, name, requiresApiKey }
452
668
  }
453
669
 
670
+ function formatRouteCandidate(options: {
671
+ destinationToken: DestinationTokenInput
672
+ provider: ProviderInput
673
+ sourceAmount: ReturnType<typeof resolveSourceAmount>
674
+ sourceChain: SourceChainInput
675
+ sourceToken: TokenInput
676
+ }) {
677
+ return {
678
+ destinationChain,
679
+ destinationToken: formatDestinationToken(options.destinationToken),
680
+ errorCode: null,
681
+ id: [
682
+ options.sourceChain.slug,
683
+ options.sourceToken.symbol.toLowerCase(),
684
+ 'tempo',
685
+ options.destinationToken.slug,
686
+ options.provider.id,
687
+ ].join('-'),
688
+ provider: formatProviderRef(options.provider),
689
+ quality: {
690
+ availableVolumeUsd: null,
691
+ estimatedSeconds: null,
692
+ feeBps: null,
693
+ historicalVolumeUsd24h: null,
694
+ instantVolumeUsd: null,
695
+ liquiditySource: 'staticInventory',
696
+ score: null,
697
+ sourceDetail: null,
698
+ tier: 'unknown',
699
+ },
700
+ quote: null,
701
+ sourceAmount: options.sourceAmount,
702
+ sourceChain: formatChain(options.sourceChain),
703
+ sourceToken: formatSourceToken(options.sourceChain, options.sourceToken),
704
+ status: 'unknown',
705
+ } as const
706
+ }
707
+
454
708
  function formatSourceToken(chain: SourceChainInput, token: TokenInput) {
455
709
  return {
456
710
  address: token.address,
@@ -482,6 +736,57 @@ function parseDecimalUnits(value: string, decimals: number) {
482
736
  return baseUnits.toString()
483
737
  }
484
738
 
739
+ function formatRouteQuote(route: RouteCandidate, quote: QuoteProbeResult) {
740
+ return {
741
+ expiresAt: quote.expiresAt ?? null,
742
+ inputAmount: route.sourceAmount.amount,
743
+ limits: quote.limits
744
+ ? {
745
+ instantSourceAmount: quote.limits.instantSourceAmount ?? null,
746
+ maxSourceAmount: quote.limits.maxSourceAmount ?? null,
747
+ minSourceAmount: quote.limits.minSourceAmount ?? null,
748
+ }
749
+ : null,
750
+ minimumOutputAmount: quote.minimumOutputAmount ?? null,
751
+ outputAmount: quote.outputAmount ?? null,
752
+ outputAmountFormatted: quote.outputAmountFormatted ?? null,
753
+ sampledAt: quote.sampledAt,
754
+ } as const
755
+ }
756
+
757
+ function markRouteError(route: RouteCandidate, errorCode: string) {
758
+ return {
759
+ ...route,
760
+ errorCode,
761
+ quality: {
762
+ ...route.quality,
763
+ liquiditySource: 'providerError',
764
+ score: null,
765
+ sourceDetail: null,
766
+ tier: 'error' as const,
767
+ },
768
+ quote: null,
769
+ status: 'error' as const,
770
+ }
771
+ }
772
+
773
+ function quoteWithTimeout(adapter: ProviderAdapter, input: QuoteProbeInput, timeoutMs: number) {
774
+ const controller = new AbortController()
775
+ const timeout = setTimeout(() => controller.abort(), timeoutMs)
776
+ return Promise.race([
777
+ adapter.probeQuote(input, controller.signal),
778
+ new Promise<never>((_, reject) => {
779
+ if (controller.signal.aborted) {
780
+ reject(new ProviderAdapterTimeoutError())
781
+ return
782
+ }
783
+ controller.signal.addEventListener('abort', () => reject(new ProviderAdapterTimeoutError()), {
784
+ once: true,
785
+ })
786
+ }),
787
+ ]).finally(() => clearTimeout(timeout))
788
+ }
789
+
485
790
  function resolveDestinationToken(input: string | undefined) {
486
791
  if (input === undefined) return destinationTokens.find((token) => token.slug === defaultDestinationToken)! // prettier-ignore
487
792
  const normalized = normalizeInput(input)
@@ -572,11 +877,21 @@ export class InvalidAmountError extends Error {
572
877
  override name = 'FundingRoutes.InvalidAmountError'
573
878
  }
574
879
 
880
+ /** Error thrown internally when a provider adapter exceeds its route quote timeout. */
881
+ export class ProviderAdapterTimeoutError extends Error {
882
+ override name = 'FundingRoutes.ProviderAdapterTimeoutError'
883
+ }
884
+
575
885
  /** Error thrown when the caller filters to an unknown provider. */
576
886
  export class UnsupportedProviderError extends Error {
577
887
  override name = 'FundingRoutes.UnsupportedProviderError'
578
888
  }
579
889
 
890
+ /** Error thrown when the caller requests an unsupported include expansion. */
891
+ export class UnsupportedIncludeError extends Error {
892
+ override name = 'FundingRoutes.UnsupportedIncludeError'
893
+ }
894
+
580
895
  /** Error thrown when the caller supplies an unsupported source chain. */
581
896
  export class UnsupportedSourceChainError extends Error {
582
897
  override name = 'FundingRoutes.UnsupportedSourceChainError'
@@ -1 +0,0 @@
1
- {"version":3,"file":"App.js","sourceRoot":"","sources":["../../../src/apps/platform/App.ts"],"names":[],"mappings":"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"}