Skip to content

Commit 7939b10

Browse files
committed
feat(explorer): migrate Tempo API amount fields
1 parent 2b28a74 commit 7939b10

6 files changed

Lines changed: 4676 additions & 524 deletions

File tree

apps/explorer/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"abitype": "catalog:",
5353
"animejs": "catalog:",
5454
"cva": "catalog:",
55-
"hono": "catalog:",
55+
"hono": "^4.12.31",
5656
"midcut": "catalog:",
5757
"ox": "catalog:",
5858
"react": "catalog:",
@@ -81,11 +81,11 @@
8181
"@types/react": "catalog:",
8282
"@types/react-dom": "catalog:",
8383
"@vitejs/plugin-react": "catalog:",
84-
"cadent": "https://pkg.pr.new/tempoxyz/cadent@57613c4",
8584
"eruda": "catalog:",
8685
"rollup-plugin-visualizer": "catalog:",
8786
"shiki": "catalog:",
8887
"sonda": "catalog:",
88+
"tapimo": "https://pkg.pr.new/tempoxyz/api/tapimo@500",
8989
"tailwind-merge": "catalog:",
9090
"tw-animate-css": "catalog:",
9191
"typed-query-selector": "catalog:",

apps/explorer/src/lib/server/fee-amm.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ export type FeeAmmPool = {
1717
reserveUserToken: bigint
1818
reserveValidatorToken: bigint
1919
liquidityUsd: number
20-
userTokenSymbol?: string | undefined
21-
userTokenName?: string | undefined
22-
userTokenDecimals?: number | undefined
23-
validatorTokenSymbol?: string | undefined
24-
validatorTokenName?: string | undefined
25-
validatorTokenDecimals?: number | undefined
20+
userTokenSymbol: string
21+
userTokenName: string
22+
userTokenDecimals: number
23+
validatorTokenSymbol: string
24+
validatorTokenName: string
25+
validatorTokenDecimals: number
2626
}
2727

2828
type PoolsResponse = InferResponseType<
@@ -47,11 +47,11 @@ export function mapFeeAmmPools(pools: PoolsResponse['data']): FeeAmmPool[] {
4747
createdAt: parseTimestamp(pool.createdAt) ?? null,
4848
latestMintAt: parseTimestamp(pool.lastMintAt) ?? null,
4949
mintCount: pool.mintCount,
50-
reserveUserToken: BigInt(pool.userToken.amount ?? 0),
51-
reserveValidatorToken: BigInt(pool.validatorToken.amount ?? 0),
50+
reserveUserToken: BigInt(pool.userAmount?.baseUnits ?? 0),
51+
reserveValidatorToken: BigInt(pool.validatorAmount?.baseUnits ?? 0),
5252
liquidityUsd:
53-
Number(pool.userToken.formatted ?? 0) +
54-
Number(pool.validatorToken.formatted ?? 0),
53+
Number(pool.userAmount?.formatted ?? 0) +
54+
Number(pool.validatorAmount?.formatted ?? 0),
5555
userTokenName: pool.userToken.name,
5656
userTokenSymbol: pool.userToken.symbol,
5757
userTokenDecimals: pool.userToken.decimals,

apps/explorer/src/lib/server/tempo-api.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import type * as cadent from 'cadent'
21
import * as Sentry from '@sentry/cloudflare'
3-
import { hc } from 'hono/client'
2+
import { create } from 'tapimo/client'
43
import { serverEnv, tempoApiUrl } from './env.ts'
54

65
const ALERTABLE_STATUSES = new Set([402, 403, 429])
@@ -50,20 +49,17 @@ const instrumentedFetch: typeof fetch = async (input, init) => {
5049
/**
5150
* Typed client for the Tempo API. Server-side only.
5251
*
53-
* Built with hono's `hc` + the API's `App` type (instead of its
54-
* `Client.create`) so the package stays a type-only import: its runtime barrel
55-
* pulls the whole API server, including Node-only transitive dependencies
56-
* that can't load in the Workers runtime.
52+
* Uses the API's client-only entrypoint, which keeps its Node-only server
53+
* dependencies out of the Workers runtime.
5754
*
5855
* Consume with hono's `parseResponse` (throws on non-2xx, returns the typed
5956
* success body), or narrow manually with `response.status === 200`.
6057
*
6158
* Anonymous requests are rate-limited; deployments set `TEMPO_API_KEY`
6259
* (scopes: `data:read`, `indexer:query`).
6360
*/
64-
export const api = hc<cadent.App.App>(tempoApiUrl, {
61+
export const api = create({
62+
apiKey: serverEnv.TEMPO_API_KEY,
6563
fetch: instrumentedFetch,
66-
headers: serverEnv.TEMPO_API_KEY
67-
? { 'tempo-api-key': serverEnv.TEMPO_API_KEY }
68-
: undefined,
64+
url: tempoApiUrl,
6965
})

apps/explorer/src/lib/server/token.ts

Lines changed: 18 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
import { createServerFn } from '@tanstack/react-start'
22
import { type InferResponseType, parseResponse } from 'hono/client'
33
import type { Address, Hex } from 'ox'
4-
import type { Config } from 'wagmi'
54
import { getChainId } from 'wagmi/actions'
6-
import { Actions } from 'wagmi/tempo'
75
import * as z from 'zod/mini'
86
import { TOKEN_COUNT_MAX } from '#lib/constants'
97
import { api } from '#lib/server/tempo-api'
10-
import { getVerifiedTokens } from '#lib/server/verified-tokens'
118
import { zAddress } from '#lib/zod'
129
import { getWagmiConfig } from '#wagmi.config.ts'
1310

@@ -217,9 +214,9 @@ export type AccountTransfersApiResponse = {
217214
timestamp: string | null
218215
token: {
219216
address: Address.Address
220-
symbol?: string | undefined
221-
decimals?: number | undefined
222-
currency?: string | undefined
217+
symbol: string
218+
decimals: number
219+
currency: string
223220
}
224221
}>
225222
total: number
@@ -232,70 +229,6 @@ const EMPTY_ACCOUNT_TRANSFERS_RESPONSE: AccountTransfersApiResponse = {
232229
totalCapped: false,
233230
}
234231

235-
type TransferTokenMeta = {
236-
symbol?: string | undefined
237-
decimals?: number | undefined
238-
currency?: string | undefined
239-
}
240-
241-
const transferTokenMetaCache = new Map<string, CacheEntry<TransferTokenMeta>>()
242-
243-
/**
244-
* Display metadata (symbol/decimals) for transfer-row tokens: the cached
245-
* verified list covers nearly every row for free; unknown tokens fall back to
246-
* one RPC metadata read each, memoized. (The API's `include=token` does the
247-
* same upstream but adds ~5s per page — resolve locally instead.)
248-
*/
249-
async function getTransferTokenMeta(
250-
chainId: number,
251-
tokens: readonly Address.Address[],
252-
): Promise<Map<string, TransferTokenMeta>> {
253-
const verified = await getVerifiedTokens(chainId)
254-
const verifiedByAddress = new Map(
255-
verified.map((token) => [token.address.toLowerCase(), token]),
256-
)
257-
258-
const meta = new Map<string, TransferTokenMeta>()
259-
const misses: Address.Address[] = []
260-
for (const token of new Set(tokens.map((t) => t.toLowerCase()))) {
261-
const entry = verifiedByAddress.get(token)
262-
if (entry) {
263-
meta.set(token, {
264-
symbol: entry.symbol,
265-
decimals: entry.decimals,
266-
currency: entry.currency,
267-
})
268-
continue
269-
}
270-
const cached = transferTokenMetaCache.get(`${chainId}-${token}`)
271-
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
272-
meta.set(token, cached.data)
273-
continue
274-
}
275-
misses.push(token as Address.Address)
276-
}
277-
278-
const config = getWagmiConfig()
279-
await Promise.all(
280-
misses.map(async (token) => {
281-
const resolved = await Actions.token
282-
.getMetadata(config as Config, { token })
283-
.then(
284-
(m): TransferTokenMeta => ({
285-
symbol: m.symbol,
286-
decimals: m.decimals,
287-
currency: m.currency,
288-
}),
289-
)
290-
.catch((): TransferTokenMeta => ({}))
291-
meta.set(token.toLowerCase(), resolved)
292-
setCached(transferTokenMetaCache, `${chainId}-${token}`, resolved)
293-
}),
294-
)
295-
296-
return meta
297-
}
298-
299232
/**
300233
* Token transfers where the account is sender or recipient, across all
301234
* tokens (the address page's account-scoped Transfers tab). Rows span
@@ -321,31 +254,21 @@ export const fetchAccountTransfers = createServerFn({ method: 'POST' })
321254
}),
322255
)
323256

324-
const tokenMeta = await getTransferTokenMeta(
325-
chainId,
326-
page.data.map(
327-
(transfer) => transfer.sourceToken.address as Address.Address,
328-
),
329-
)
330-
331257
return {
332-
transfers: page.data.map((transfer) => {
333-
const meta = tokenMeta.get(transfer.sourceToken.address.toLowerCase())
334-
return {
335-
from: transfer.sender as Address.Address,
336-
to: transfer.recipient as Address.Address,
337-
value: transfer.sourceToken.amount,
338-
transactionHash: transfer.transactionHash as Hex.Hex,
339-
blockNumber: String(transfer.blockNumber),
340-
timestamp: transfer.timestamp ?? null,
341-
token: {
342-
address: transfer.sourceToken.address as Address.Address,
343-
symbol: meta?.symbol,
344-
decimals: meta?.decimals,
345-
currency: meta?.currency,
346-
},
347-
}
348-
}),
258+
transfers: page.data.map((transfer) => ({
259+
from: transfer.sender as Address.Address,
260+
to: transfer.recipient as Address.Address,
261+
value: transfer.sourceAmount.baseUnits,
262+
transactionHash: transfer.transactionHash as Hex.Hex,
263+
blockNumber: String(transfer.blockNumber),
264+
timestamp: transfer.timestamp ?? null,
265+
token: {
266+
address: transfer.sourceToken.address as Address.Address,
267+
symbol: transfer.sourceToken.symbol,
268+
decimals: transfer.sourceToken.decimals,
269+
currency: transfer.sourceToken.currency,
270+
},
271+
})),
349272
...resolveTotal({
350273
exactCount: page.meta?.totalCount,
351274
exactCountCapped: page.meta?.totalCountCapped,
@@ -389,7 +312,7 @@ export const fetchTransfers = createServerFn({ method: 'POST' })
389312
transfers: page.data.map((transfer) => ({
390313
from: transfer.sender as Address.Address,
391314
to: transfer.recipient as Address.Address,
392-
value: transfer.sourceToken.amount,
315+
value: transfer.sourceAmount.baseUnits,
393316
transactionHash: transfer.transactionHash as Hex.Hex,
394317
blockNumber: String(transfer.blockNumber),
395318
timestamp: transfer.timestamp ?? null,
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { mapFeeAmmPools } from '#lib/server/fee-amm'
3+
4+
describe('mapFeeAmmPools', () => {
5+
it('maps reserve amounts separately from token metadata', () => {
6+
const [pool] = mapFeeAmmPools([
7+
{
8+
createdAt: '2026-07-22T00:00:00.000Z',
9+
id: `0x${'11'.repeat(32)}`,
10+
lastMintAt: '2026-07-22T01:00:00.000Z',
11+
mintCount: 2,
12+
poolId: `0x${'11'.repeat(32)}`,
13+
userAmount: {
14+
baseUnits: '1250000',
15+
currency: 'USD',
16+
decimals: 6,
17+
formatted: '1.25',
18+
},
19+
userToken: {
20+
address: `0x${'22'.repeat(20)}`,
21+
currency: 'USD',
22+
decimals: 6,
23+
name: 'Alpha USD',
24+
symbol: 'aUSD',
25+
},
26+
validatorAmount: {
27+
baseUnits: '2500000',
28+
currency: 'USD',
29+
decimals: 6,
30+
formatted: '2.5',
31+
},
32+
validatorToken: {
33+
address: `0x${'33'.repeat(20)}`,
34+
currency: 'USD',
35+
decimals: 6,
36+
name: 'Beta USD',
37+
symbol: 'bUSD',
38+
},
39+
},
40+
])
41+
42+
expect(pool).toMatchInlineSnapshot(`
43+
{
44+
"createdAt": 1784678400,
45+
"latestMintAt": 1784682000,
46+
"liquidityUsd": 3.75,
47+
"mintCount": 2,
48+
"poolId": "0x1111111111111111111111111111111111111111111111111111111111111111",
49+
"reserveUserToken": 1250000n,
50+
"reserveValidatorToken": 2500000n,
51+
"userToken": "0x2222222222222222222222222222222222222222",
52+
"userTokenDecimals": 6,
53+
"userTokenName": "Alpha USD",
54+
"userTokenSymbol": "aUSD",
55+
"validatorToken": "0x3333333333333333333333333333333333333333",
56+
"validatorTokenDecimals": 6,
57+
"validatorTokenName": "Beta USD",
58+
"validatorTokenSymbol": "bUSD",
59+
}
60+
`)
61+
})
62+
})

0 commit comments

Comments
 (0)