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
@@ -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
+ }
@@ -338,6 +338,39 @@ describe('POST /rpc', () => {
338
338
  fetch.restore()
339
339
  }
340
340
  })
341
+
342
+ test('uses the RPC path for sandbox chain checks', async () => {
343
+ const app = TestApp.create({
344
+ auth: {
345
+ keys: [
346
+ {
347
+ environment: 'sandbox',
348
+ id: 'key_sandbox',
349
+ orgId: 'org_test',
350
+ scopes: ['data:read'],
351
+ token: 'secret_sandbox_key',
352
+ },
353
+ ],
354
+ },
355
+ defaultChainId: 4217,
356
+ })
357
+ const request = (path: string) =>
358
+ app.fetch(
359
+ new Request(`http://tempo-api.test${path}`, {
360
+ body: JSON.stringify({ id: 1, jsonrpc: '2.0', method: 'eth_chainId' }),
361
+ headers: { 'tempo-api-key': 'secret_sandbox_key' },
362
+ method: 'POST',
363
+ }),
364
+ )
365
+
366
+ const rejected = await request('/rpc/4217?chainId=42431')
367
+ const rejectedDefault = await request('/rpc?chainId=42431')
368
+ const rejectedTrailing = await request('/rpc/?chainId=42431')
369
+
370
+ expect(rejected.status).toMatchInlineSnapshot(`403`)
371
+ expect(rejectedDefault.status).toMatchInlineSnapshot(`403`)
372
+ expect(rejectedTrailing.status).toMatchInlineSnapshot(`404`)
373
+ })
341
374
  })
342
375
 
343
376
  describe('POST /rpc/relay', () => {
@@ -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'
@@ -0,0 +1,8 @@
1
+ import { Handler } from 'tapimo'
2
+
3
+ describe('Handler.relay', () => {
4
+ test('accepts Tempo API defaults', () => {
5
+ expectTypeOf(Handler.relay()).toEqualTypeOf<Handler.Handler>()
6
+ expectTypeOf(Handler.relay({ apiKey: 'secret_api_key' })).toEqualTypeOf<Handler.Handler>()
7
+ })
8
+ })
@@ -16,8 +16,8 @@ const recipient = Relay.accounts[7]!
16
16
 
17
17
  /**
18
18
  * Tokens the relay handler probes for fee-token resolution. The default
19
- * `resolveTokens` fetches `tokenlist.tempo.xyz`, which doesn't know about
20
- * the localnet chain — so tests inject this list explicitly.
19
+ * `resolveTokens` uses the Tempo API for mainnet and testnet, so localnet
20
+ * tests inject this list explicitly.
21
21
  */
22
22
  const localnetTokens = [
23
23
  {
@@ -16,8 +16,10 @@ import {
16
16
  import type { LocalAccount } from 'viem/accounts'
17
17
  import { simulateCalls } from 'viem/actions'
18
18
  import { Abis, Actions, Addresses, Capabilities, VirtualAddress } from 'viem/tempo'
19
+ import { tempoMainnet, tempoTestnet } from 'viem/tempo/chains'
19
20
 
20
21
  import * as ExecutionError from './internal/executionError.js'
22
+ import * as TempoApi from '../Client.js'
21
23
  import type * as RpcSchema from '../internal/RpcSchema.js'
22
24
  import { type Handler, from } from '../Handler.js'
23
25
  import * as Store from '../internal/Store.js'
@@ -25,6 +27,8 @@ import * as Multisig from './internal/multisig.js'
25
27
  import * as Sponsorship from './internal/sponsorship.js'
26
28
  import * as Utils from './internal/utils.js'
27
29
 
30
+ const defaultChains = [tempoMainnet, tempoTestnet] as const
31
+
28
32
  /**
29
33
  * Instantiates a relay handler that proxies `eth_fillTransaction`
30
34
  * with wallet-aware enrichment (fee token resolution, simulation,
@@ -32,9 +36,9 @@ import * as Utils from './internal/utils.js'
32
36
  *
33
37
  * @example
34
38
  * ```ts
35
- * import { Handler } from 'tempo-api/server'
39
+ * import { Handler } from 'tapimo'
36
40
  *
37
- * const handler = Handler.relay({ getClient })
41
+ * const handler = Handler.relay({ apiKey: process.env.TEMPO_API_KEY })
38
42
  *
39
43
  * // Plug handler into your server framework of choice:
40
44
  * createServer(handler.listener) // Node.js
@@ -51,10 +55,9 @@ import * as Utils from './internal/utils.js'
51
55
  *
52
56
  * ```ts
53
57
  * import { privateKeyToAccount } from 'viem/accounts'
54
- * import { Handler } from 'tempo-api/server'
58
+ * import { Handler } from 'tapimo'
55
59
  *
56
60
  * const handler = Handler.relay({
57
- * getClient,
58
61
  * feePayer: {
59
62
  * account: privateKeyToAccount('0x...'),
60
63
  * },
@@ -64,26 +67,26 @@ import * as Utils from './internal/utils.js'
64
67
  * @param options - Options.
65
68
  * @returns Request handler.
66
69
  */
67
- export function relay(options: relay.Options): Handler {
70
+ export function relay(options: relay.Options = {}): Handler {
68
71
  const {
69
- getClient,
72
+ apiKey,
73
+ getClient: getClient_,
70
74
  internal_allowUnsafeUrls = false,
71
75
  onRequest,
72
76
  path = '/',
73
- resolveTokens,
77
+ resolveTokens: resolveTokens_,
74
78
  ...rest
75
79
  } = options
80
+ const getClient = getClient_ ?? defaultGetClient({ apiKey })
81
+ const resolveTokens = resolveTokens_ ?? defaultResolveTokens({ apiKey })
76
82
  const feePayerOptions = options.feePayer
77
83
  // Adapt the state store into the multisig operation store once, up front.
78
84
  const multisigOptions = options.multisig
79
85
  ? { ...options.multisig, store: Multisig.fromStore(options.multisig.store) }
80
86
  : undefined
81
87
 
82
- // Resolves the chain's fee-token candidates through the caller-provided
83
- // resolver. An unconfigured resolver (or a chain it has no list for) yields
84
- // an empty candidate set.
85
88
  const getTokens = async (chainId: number | undefined): Promise<readonly Address[]> => {
86
- if (!resolveTokens || chainId === undefined) return []
89
+ if (chainId === undefined) return []
87
90
  return await resolveTokens(chainId)
88
91
  }
89
92
 
@@ -94,7 +97,7 @@ export function relay(options: relay.Options): Handler {
94
97
 
95
98
  const features = {
96
99
  autoSwap: options.autoSwap ?? options.features === 'all',
97
- feeTokenResolution: options.features === 'all' || Boolean(resolveTokens),
100
+ feeTokenResolution: options.features === 'all' || Boolean(resolveTokens_),
98
101
  simulate: options.features === 'all',
99
102
  }
100
103
 
@@ -175,6 +178,7 @@ export function relay(options: relay.Options): Handler {
175
178
  let filled: Awaited<ReturnType<typeof fill>>
176
179
  let sponsored = false
177
180
  let feeToken = sponsoredFeeToken
181
+ const tokens = getTokens(chainId)
178
182
 
179
183
  // Lazily resolve a swap source token when autoSwap needs one.
180
184
  const resolveFeeTokenForSwap = from
@@ -183,7 +187,7 @@ export function relay(options: relay.Options): Handler {
183
187
  account: from,
184
188
  feeToken: undefined,
185
189
  store,
186
- tokens: (await getTokens(chainId)).filter(
190
+ tokens: (await tokens).filter(
187
191
  (t) => t.toLowerCase() !== insufficientToken.toLowerCase(),
188
192
  ),
189
193
  })
@@ -194,7 +198,7 @@ export function relay(options: relay.Options): Handler {
194
198
  // the transaction is calling — typically the token being
195
199
  // transferred — so a user transferring USDC.e can pay gas in
196
200
  // USDC.e even when the configured list defaults to pathUSD.
197
- const configuredTokens = await getTokens(chainId)
201
+ const configuredTokens = await tokens
198
202
  const unsponsoredTokens = [
199
203
  ...configuredTokens,
200
204
  ...callTargetTokens(baseTx).filter(
@@ -221,7 +225,7 @@ export function relay(options: relay.Options): Handler {
221
225
  // `validate` only governs the wallet's own fee payer).
222
226
  // Default to the chain's first token so the broadcast envelope
223
227
  // always carries a feeToken the chain can charge.
224
- if (!feeToken) feeToken = (await getTokens(chainId))[0]
228
+ if (!feeToken) feeToken = configuredTokens[0]
225
229
  const transaction = {
226
230
  ...baseTx,
227
231
  feePayer: true,
@@ -334,8 +338,7 @@ export function relay(options: relay.Options): Handler {
334
338
  const transaction_filled = filled.transaction
335
339
  const swap = 'swap' in filled ? filled.swap : undefined
336
340
  if (!feeToken)
337
- feeToken =
338
- (transaction_filled.feeToken as Address | undefined) ?? (await getTokens(chainId))[0]
341
+ feeToken = (transaction_filled.feeToken as Address | undefined) ?? configuredTokens[0]
339
342
 
340
343
  // Parallelize: simulate, fee payer signing, and autoSwap metadata.
341
344
  const alreadySigned =
@@ -648,8 +651,46 @@ export function relay(options: relay.Options): Handler {
648
651
  return router
649
652
  }
650
653
 
654
+ function defaultGetClient(options: { apiKey?: string | undefined }) {
655
+ const clients = new Map<number, Client>()
656
+ for (const chain of defaultChains) {
657
+ const transport = options.apiKey
658
+ ? http(new URL(`/rpc/${chain.id}`, TempoApi.defaultUrl).toString(), {
659
+ fetchOptions: { headers: { 'tempo-api-key': options.apiKey } },
660
+ })
661
+ : http()
662
+ clients.set(
663
+ chain.id,
664
+ createClient({ batch: { multicall: { deployless: true } }, chain, transport }),
665
+ )
666
+ }
667
+
668
+ return (chainId: number = tempoMainnet.id): Client => {
669
+ const client = clients.get(chainId)
670
+ if (!client) throw new Error(`Chain ${chainId} not configured`)
671
+ return client
672
+ }
673
+ }
674
+
675
+ function defaultResolveTokens(options: { apiKey?: string | undefined }) {
676
+ const client = TempoApi.create({ apiKey: options.apiKey })
677
+
678
+ return async (chainId: number): Promise<readonly Address[]> => {
679
+ if (!defaultChains.some((chain) => chain.id === chainId)) return []
680
+ const response = await client.v1.tokenlist.$get({ query: { chainId: String(chainId) } })
681
+ if (response.status !== 200) return []
682
+ const { tokens } = await response.json()
683
+ return tokens.map((token) => token.address)
684
+ }
685
+ }
686
+
651
687
  export namespace relay {
652
688
  export type Options = from.Options & {
689
+ /**
690
+ * Tempo API key used by the default RPC clients and token resolver. When
691
+ * omitted, RPC clients use each chain's default transport.
692
+ */
693
+ apiKey?: string | undefined
653
694
  /**
654
695
  * Auto-swap options.
655
696
  */
@@ -663,9 +704,9 @@ export namespace relay {
663
704
  /**
664
705
  * Resolves a viem client for a chain id. The handler resolves the client
665
706
  * from the `chainId` in the incoming transaction (falling back to the
666
- * client's default chain). Called with no argument for the default client.
707
+ * client's default chain). Defaults to Tempo mainnet and testnet clients.
667
708
  */
668
- getClient: (chainId?: number) => Client
709
+ getClient?: ((chainId?: number) => Client) | undefined
669
710
  /**
670
711
  * Fee payer / sponsor configuration. When provided, the relay will
671
712
  * sign `feePayerSignature` on the filled transaction.
@@ -721,9 +762,8 @@ export namespace relay {
721
762
  /** Path to use for the handler. @default "/" */
722
763
  path?: string | undefined
723
764
  /**
724
- * Resolves the known fee-token candidates for a chain. The relay checks
725
- * the caller's balance for each candidate and picks the highest during
726
- * fee-token resolution. Omit to disable fee-token resolution.
765
+ * Resolves the known fee-token candidates for a chain. Defaults to the
766
+ * Tempo API verified token list for mainnet and testnet.
727
767
  */
728
768
  resolveTokens?:
729
769
  | ((chainId: number) => readonly Address[] | Promise<readonly Address[]>)
@@ -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.' }],