diff --git a/api/src/fetchers/adapters/db-fetcher.ts b/api/src/fetchers/adapters/db-fetcher.ts index 955fdc7..b5314a3 100644 --- a/api/src/fetchers/adapters/db-fetcher.ts +++ b/api/src/fetchers/adapters/db-fetcher.ts @@ -20,6 +20,15 @@ import { searchNamesFuzzy } from '@fetchers/search'; import { inscriptionBuffToId } from '@fetchers/inscriptions'; import { bytesToHex, hexToBytes } from 'micro-stacks/common'; +interface InscribedNamesRow { + inscriptionId: string; + id: bigint; + name: string; + namespace: string; + blockHeight: number; + txid: Buffer; +} + export class DbFetcher implements BaseFetcher { bnsDb: BnsDb; stacksDb: StacksDb; @@ -249,33 +258,54 @@ export class DbFetcher implements BaseFetcher { } } + async fetchInscribedNamesRows(cursor?: number) { + let rows: InscribedNamesRow[]; + if (typeof cursor === 'undefined') { + rows = await this.bnsDb.$queryRaw` + select i.id + , i.inscription_id as "inscriptionId" + , i.block_height as "blockHeight" + , i.txid + , n.name + , n.namespace + from public.inscribed_names i + join public.names n on n.id = i.id + order by i.id desc + limit 100 + offset 0 + `; + } else { + rows = await this.bnsDb.$queryRaw` + select i.id + , i.inscription_id as "inscriptionId" + , i.block_height as "blockHeight" + , i.txid + , n.name + , n.namespace + from public.inscribed_names i + join public.names n on n.id = i.id + where i.id < ${cursor} + order by i.id desc + limit 100 + offset 0 + `; + } + return rows; + } + async fetchInscribedNames(cursor?: number) { - const rows = await this.bnsDb.inscribedNames.findMany({ - include: { - name: true, - }, - orderBy: { - id: 'desc', - }, - cursor: - typeof cursor === 'undefined' - ? undefined - : { - id: cursor, - }, - take: -100, - skip: typeof cursor === 'undefined' ? undefined : 1, - }); + const rows = await this.fetchInscribedNamesRows(cursor); return rows .map(row => { if (row.name === null) return null; - const name = convertDbName(row.name); + const { name, namespace, id } = row; + const _name = convertDbName({ name, namespace, id }); return { - inscriptionId: inscriptionBuffToId(hexToBytes(row.inscription_id)), + inscriptionId: inscriptionBuffToId(hexToBytes(row.inscriptionId)), id: Number(row.id), - name: name.combined, + name: _name.combined, blockHeight: row.blockHeight, txid: bytesToHex(row.txid), }; diff --git a/api/src/prisma-plugin.ts b/api/src/prisma-plugin.ts index 36f8150..b266303 100644 --- a/api/src/prisma-plugin.ts +++ b/api/src/prisma-plugin.ts @@ -52,6 +52,45 @@ function getStacksDb() { return new StacksDb(); } +function getBnsDb() { + if (process.env.DEBUG_SLOW_QUERIES_BNSX === 'true') { + const bnsPrisma = new BnsDb({ + log: [ + { + emit: 'event', + level: 'query', + }, + { + emit: 'stdout', + level: 'error', + }, + { + emit: 'stdout', + level: 'info', + }, + { + emit: 'stdout', + level: 'warn', + }, + ], + }); + bnsPrisma.$on('query', e => { + if (e.duration > 500) { + logger.warn( + { + query: e.query, + duration: e.duration, + params: e.params, + }, + 'slow query' + ); + } + }); + return bnsPrisma; + } + return new BnsDb(); +} + export const prismaPlugin: FastifyPluginAsync = fp(async server => { const dbEnv = process.env.STACKS_API_POSTGRES; if (typeof dbEnv === 'undefined' || dbEnv === '') { @@ -65,7 +104,7 @@ export const prismaPlugin: FastifyPluginAsync = fp(async server => { logger.debug({ params }, 'Stacks DB connection query parameters'); const promises = [stacksPrisma.$connect()]; const prismaDbEnv = process.env.BNSX_DB_URL; - const prisma = new BnsDb(); + const prisma = getBnsDb(); if (typeof prismaDbEnv !== 'undefined') { promises.push(prisma.$connect()); server.decorate('prisma', prisma); diff --git a/web/common/store/bridge.ts b/web/common/store/bridge.ts index c1a5763..8acdb1d 100644 --- a/web/common/store/bridge.ts +++ b/web/common/store/bridge.ts @@ -1,7 +1,7 @@ import { atom } from 'jotai'; import type { BridgeSignerResponse, BridgeSignerResponseOk } from '../../pages/api/bridge-sig'; import { hashAtom, txidQueryAtom } from '@store/migration'; -import { atomsWithQuery } from 'jotai-tanstack-query'; +import { atomsWithInfiniteQuery, atomsWithQuery } from 'jotai-tanstack-query'; import { trpc } from '@store/api'; import { atomFamily } from 'jotai/utils'; import { makeClarityHash } from 'micro-stacks/connect'; @@ -77,6 +77,8 @@ export type InscribedNamesResult = Awaited< ReturnType >['results'][0]; +export const inscribedNamesCursorAtom = atom(undefined); + export const inscribedNamesAtom = atomsWithQuery(_get => ({ queryKey: ['inscribedNames'], queryFn: async () => { @@ -85,6 +87,23 @@ export const inscribedNamesAtom = atomsWithQuery(_get => ({ }, }))[0]; +export const inscribedNamesInfiniteAtom = atomsWithInfiniteQuery(_get => ({ + queryKey: ['inscribedNamesInfinite'], + queryFn: async ({ pageParam }: { pageParam?: number }) => { + const { results } = await trpc.bridgeRouter.inscribedNames.query({ cursor: pageParam }); + return results; + // return { data: results, nextCursor: results.at(-1)?.id }; + }, + getNextPageParam: lastPage => { + return lastPage.at(-1)?.id; + }, +})); + +export const lastInscribedNameIdState = atom(get => { + const names = get(inscribedNamesAtom); + return names.at(-1)?.id; +}); + export const inscriptionForNameAtom = atomFamily((fqn: string) => { return atomsWithQuery(() => ({ queryKey: ['inscriptionForName', fqn], diff --git a/web/components/p/bridge/names.tsx b/web/components/p/bridge/names.tsx index d8a920d..f257cce 100644 --- a/web/components/p/bridge/names.tsx +++ b/web/components/p/bridge/names.tsx @@ -1,27 +1,26 @@ -import React, { useMemo } from 'react'; +import React from 'react'; import { Text } from '@components/ui/text'; -import { - Table, - TableBody, - TableCaption, - TableCell, - TableHead, - TableHeader, - TableRow, -} from '@components/ui/table'; -import { inscribedNamesAtom } from '@store/bridge'; -import { useAtomValue } from 'jotai'; +import { Table, TableBody, TableHead, TableHeader, TableRow } from '@components/ui/table'; +import { inscribedNamesInfiniteAtom } from '@store/bridge'; +import { useAtom } from 'jotai'; import { Button } from '@components/ui/button'; import { BridgedNameRow } from '@components/p/bridge/names/name-row'; +import { useDeepMemo } from '@common/hooks/use-deep-memo'; export const BridgeNames: React.FC<{ children?: React.ReactNode }> = () => { - const names = useAtomValue(inscribedNamesAtom); + const [names, dispatch] = useAtom(inscribedNamesInfiniteAtom[0]); + const fetchNextPage = React.useCallback(() => { + dispatch({ type: 'fetchNextPage' }); + }, [dispatch]); - const nameRows = useMemo(() => { - return names.map(name => { - return ; - }); + const nameRows = useDeepMemo(() => { + return [ + ...names.pages.map(page => page.map(name => )), + ]; }, [names]); + + const hasMore = (names.pages.at(-1)?.length ?? 0) === 100; + return (
BNS on L1 @@ -29,11 +28,13 @@ export const BridgeNames: React.FC<{ children?: React.ReactNode }> = () => { Name + Time View {nameRows} +
{hasMore && }
); }; diff --git a/web/components/p/bridge/names/name-row.tsx b/web/components/p/bridge/names/name-row.tsx index eb0aae4..f05b5c3 100644 --- a/web/components/p/bridge/names/name-row.tsx +++ b/web/components/p/bridge/names/name-row.tsx @@ -4,7 +4,6 @@ import { Button } from '@components/ui/button'; import type { InscribedNamesResult } from '@store/bridge'; import { useTxUrl } from '@common/hooks/use-tx-url'; import { ordinalsBaseUrl } from '@common/constants'; -import { Tooltip, TooltipContent, TooltipTrigger, TooltipProvider } from '@components/ui/tooltip'; import { Text } from '@components/ui/text'; import { DropdownMenu, @@ -19,6 +18,26 @@ import { useCopyToClipboard } from 'usehooks-ts'; import { useToast } from '@common/hooks/use-toast'; import { BoxLink } from '@components/link'; import { useAccountPath } from '@common/hooks/use-account-path'; +import { useAtomValue } from 'jotai'; +import { coreNodeInfoAtom } from '@store/api'; +import formatDistance from 'date-fns/formatDistance'; + +const NameTimestamp: React.FC<{ blockHeight: number }> = ({ blockHeight }) => { + const coreInfo = useAtomValue(coreNodeInfoAtom); + const curHeight = coreInfo.stacks_tip_height; + const diff = curHeight - blockHeight; + + const now = new Date(); + const distance = formatDistance(new Date(now.getTime() - diff * 10 * 60 * 1000), now, { + addSuffix: true, + }); + + return ( + + about {distance} + + ); +}; export const BridgedNameRow: React.FC<{ children?: React.ReactNode; @@ -46,6 +65,11 @@ export const BridgedNameRow: React.FC<{ return ( {name.name} + + }> + + +