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.
- package/README.md +5 -5
- package/dist/App.d.ts +31 -9
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +3 -3
- package/dist/App.js.map +1 -1
- package/dist/admin/App.d.ts +116 -116
- package/dist/apps/data/App.d.ts +30 -5
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +1 -1
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/FundingRouteProviders.d.ts +54 -0
- package/dist/apps/data/FundingRouteProviders.d.ts.map +1 -0
- package/dist/apps/data/FundingRouteProviders.js +355 -0
- package/dist/apps/data/FundingRouteProviders.js.map +1 -0
- package/dist/apps/data/index.d.ts +1 -0
- package/dist/apps/data/index.d.ts.map +1 -1
- package/dist/apps/data/index.js +1 -0
- package/dist/apps/data/index.js.map +1 -1
- package/dist/apps/data/routes/funding.d.ts +79 -1
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +68 -8
- package/dist/apps/data/routes/funding.js.map +1 -1
- package/dist/apps/data/routes/indexer.d.ts +5 -5
- package/dist/apps/docs/App.d.ts +1 -1
- package/dist/apps/docs/App.js +1 -1
- package/dist/apps/index.d.ts +2 -2
- package/dist/apps/index.d.ts.map +1 -1
- package/dist/apps/index.js +2 -2
- package/dist/apps/index.js.map +1 -1
- package/dist/apps/{platform → management}/App.d.ts +11 -11
- package/dist/apps/{platform → management}/App.d.ts.map +1 -1
- package/dist/apps/{platform → management}/App.js +7 -7
- package/dist/apps/management/App.js.map +1 -0
- package/dist/apps/management/Billing.d.ts.map +1 -0
- package/dist/apps/management/Billing.js.map +1 -0
- package/dist/apps/management/index.d.ts +3 -0
- package/dist/apps/management/index.d.ts.map +1 -0
- package/dist/apps/management/index.js +3 -0
- package/dist/apps/management/index.js.map +1 -0
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -0
- package/dist/apps/{platform → management}/routes/api-keys.js +1 -1
- package/dist/apps/management/routes/api-keys.js.map +1 -0
- package/dist/apps/{platform → management}/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js.map +1 -0
- package/dist/apps/{platform → management}/routes/invitations.d.ts +1 -1
- package/dist/apps/management/routes/invitations.d.ts.map +1 -0
- package/dist/apps/{platform → management}/routes/invitations.js +1 -1
- package/dist/apps/management/routes/invitations.js.map +1 -0
- package/dist/apps/management/routes/me.d.ts.map +1 -0
- package/dist/apps/management/routes/me.js.map +1 -0
- package/dist/apps/management/routes/members.d.ts.map +1 -0
- package/dist/apps/management/routes/members.js.map +1 -0
- package/dist/apps/management/routes/orgs.d.ts.map +1 -0
- package/dist/apps/management/routes/orgs.js.map +1 -0
- package/dist/apps/{platform → management}/routes/projects.d.ts.map +1 -1
- package/dist/apps/management/routes/projects.js.map +1 -0
- package/dist/apps/management/routes/scopes.d.ts.map +1 -0
- package/dist/apps/management/routes/scopes.js.map +1 -0
- package/dist/apps/management/routes/usage.d.ts.map +1 -0
- package/dist/apps/management/routes/usage.js.map +1 -0
- package/dist/apps/mcp.js +1 -1
- package/dist/apps/relay/App.js +1 -1
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/internal/Auth.js +1 -1
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/EdgeCache.js +1 -1
- package/dist/internal/EdgeCache.js.map +1 -1
- package/dist/internal/FundingRoutes.d.ts +748 -16
- package/dist/internal/FundingRoutes.d.ts.map +1 -1
- package/dist/internal/FundingRoutes.js +166 -31
- package/dist/internal/FundingRoutes.js.map +1 -1
- package/package.json +5 -5
- package/src/App.ts +10 -10
- package/src/Client.test-d.ts +4 -4
- package/src/apps/composition.test.ts +26 -13
- package/src/apps/data/App.ts +3 -1
- package/src/apps/data/FundingRouteProviders.test.ts +281 -0
- package/src/apps/data/FundingRouteProviders.ts +459 -0
- package/src/apps/data/index.ts +1 -0
- package/src/apps/data/routes/funding.test.ts +102 -2
- package/src/apps/data/routes/funding.ts +122 -8
- package/src/apps/docs/App.ts +1 -1
- package/src/apps/index.ts +2 -2
- package/src/apps/{platform → management}/App.ts +14 -14
- package/src/apps/{platform → management}/index.ts +1 -1
- package/src/apps/{platform → management}/routes/api-keys.ts +1 -1
- package/src/apps/{platform → management}/routes/invitations.ts +1 -1
- package/src/apps/mcp.test.ts +1 -1
- package/src/apps/mcp.ts +1 -1
- package/src/apps/relay/App.test.ts +1 -1
- package/src/apps/relay/App.ts +1 -1
- package/src/internal/Auth.ts +1 -1
- package/src/internal/EdgeCache.ts +1 -1
- package/src/internal/FundingRoutes.test.ts +213 -0
- package/src/internal/FundingRoutes.ts +347 -32
- package/dist/apps/platform/App.js.map +0 -1
- package/dist/apps/platform/Billing.d.ts.map +0 -1
- package/dist/apps/platform/Billing.js.map +0 -1
- package/dist/apps/platform/index.d.ts +0 -3
- package/dist/apps/platform/index.d.ts.map +0 -1
- package/dist/apps/platform/index.js +0 -3
- package/dist/apps/platform/index.js.map +0 -1
- package/dist/apps/platform/routes/api-keys.d.ts.map +0 -1
- package/dist/apps/platform/routes/api-keys.js.map +0 -1
- package/dist/apps/platform/routes/billing.js.map +0 -1
- package/dist/apps/platform/routes/invitations.d.ts.map +0 -1
- package/dist/apps/platform/routes/invitations.js.map +0 -1
- package/dist/apps/platform/routes/me.d.ts.map +0 -1
- package/dist/apps/platform/routes/me.js.map +0 -1
- package/dist/apps/platform/routes/members.d.ts.map +0 -1
- package/dist/apps/platform/routes/members.js.map +0 -1
- package/dist/apps/platform/routes/orgs.d.ts.map +0 -1
- package/dist/apps/platform/routes/orgs.js.map +0 -1
- package/dist/apps/platform/routes/projects.js.map +0 -1
- package/dist/apps/platform/routes/scopes.d.ts.map +0 -1
- package/dist/apps/platform/routes/scopes.js.map +0 -1
- package/dist/apps/platform/routes/usage.d.ts.map +0 -1
- package/dist/apps/platform/routes/usage.js.map +0 -1
- /package/dist/apps/{platform → management}/Billing.d.ts +0 -0
- /package/dist/apps/{platform → management}/Billing.js +0 -0
- /package/dist/apps/{platform → management}/routes/api-keys.d.ts +0 -0
- /package/dist/apps/{platform → management}/routes/billing.d.ts +0 -0
- /package/dist/apps/{platform → management}/routes/billing.js +0 -0
- /package/dist/apps/{platform → management}/routes/me.d.ts +0 -0
- /package/dist/apps/{platform → management}/routes/me.js +0 -0
- /package/dist/apps/{platform → management}/routes/members.d.ts +0 -0
- /package/dist/apps/{platform → management}/routes/members.js +0 -0
- /package/dist/apps/{platform → management}/routes/orgs.d.ts +0 -0
- /package/dist/apps/{platform → management}/routes/orgs.js +0 -0
- /package/dist/apps/{platform → management}/routes/projects.d.ts +0 -0
- /package/dist/apps/{platform → management}/routes/projects.js +0 -0
- /package/dist/apps/{platform → management}/routes/scopes.d.ts +0 -0
- /package/dist/apps/{platform → management}/routes/scopes.js +0 -0
- /package/dist/apps/{platform → management}/routes/usage.d.ts +0 -0
- /package/dist/apps/{platform → management}/routes/usage.js +0 -0
- /package/src/apps/{platform → management}/Billing.test.ts +0 -0
- /package/src/apps/{platform → management}/Billing.ts +0 -0
- /package/src/apps/{platform → management}/routes/api-keys.test.ts +0 -0
- /package/src/apps/{platform → management}/routes/billing.test.ts +0 -0
- /package/src/apps/{platform → management}/routes/billing.ts +0 -0
- /package/src/apps/{platform → management}/routes/invitations.test.ts +0 -0
- /package/src/apps/{platform → management}/routes/me.test.ts +0 -0
- /package/src/apps/{platform → management}/routes/me.ts +0 -0
- /package/src/apps/{platform → management}/routes/members.test.ts +0 -0
- /package/src/apps/{platform → management}/routes/members.ts +0 -0
- /package/src/apps/{platform → management}/routes/orgs.test.ts +0 -0
- /package/src/apps/{platform → management}/routes/orgs.ts +0 -0
- /package/src/apps/{platform → management}/routes/projects.test.ts +0 -0
- /package/src/apps/{platform → management}/routes/projects.ts +0 -0
- /package/src/apps/{platform → management}/routes/scopes.test.ts +0 -0
- /package/src/apps/{platform → management}/routes/scopes.ts +0 -0
- /package/src/apps/{platform → management}/routes/usage.test.ts +0 -0
- /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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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({
|
|
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
|
+
}
|
package/src/apps/docs/App.ts
CHANGED
|
@@ -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('/',
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
91
|
+
* {@link App.create}, so management routes gate on a session without owning it.
|
|
92
92
|
*
|
|
93
93
|
* ```ts
|
|
94
|
-
* App.create(options).route('/',
|
|
94
|
+
* App.create(options).route('/', management())
|
|
95
95
|
* ```
|
|
96
96
|
*/
|
|
97
|
-
export function
|
|
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
|
|
118
|
-
/** Options for the
|
|
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
|
|
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:
|
|
131
|
-
* `session` security scheme. Tagged onto the {@link
|
|
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: '
|
|
155
|
+
name: 'Management API',
|
|
156
156
|
tags: [
|
|
157
157
|
'API Keys',
|
|
158
158
|
'Authentication',
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
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
|
|
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
|
|
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() {
|
package/src/apps/mcp.test.ts
CHANGED
|
@@ -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,
|
|
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.
|
|
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 '../
|
|
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'
|
package/src/apps/relay/App.ts
CHANGED
|
@@ -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 '../
|
|
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'
|
package/src/internal/Auth.ts
CHANGED
|
@@ -882,7 +882,7 @@ export function install(options: install.Options) {
|
|
|
882
882
|
}),
|
|
883
883
|
)
|
|
884
884
|
return Object.assign(
|
|
885
|
-
// The tag joins the `
|
|
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
|
|
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
|
+
})
|