From 40756be07e1a42af1611eb0a3b9af9e95844059e Mon Sep 17 00:00:00 2001 From: o-az Date: Sat, 2 May 2026 23:35:14 -0700 Subject: [PATCH 1/2] feat: replace Index Supply with `tidx` --- .env.example | 3 +- env.d.ts | 3 +- package.json | 1 + pnpm-lock.yaml | 27 ++++ src/components/SignatureSelector.tsx | 4 +- .../{IndexSupplyQuery.tsx => TidxQuery.tsx} | 68 +++++---- src/components/lib/IndexSupply.ts | 84 ----------- src/components/lib/Tidx.ts | 53 +++++++ ...xSupplySignatures.ts => TidxSignatures.ts} | 28 +--- src/pages/_api/api/index-supply.ts | 137 ------------------ src/pages/_api/api/tidx.ts | 123 ++++++++++++++++ .../stablecoin-dex/view-the-orderbook.mdx | 67 +++++---- src/wagmi.config.ts | 2 +- tsconfig.node.json | 2 +- 14 files changed, 295 insertions(+), 307 deletions(-) rename src/components/{IndexSupplyQuery.tsx => TidxQuery.tsx} (89%) delete mode 100644 src/components/lib/IndexSupply.ts create mode 100644 src/components/lib/Tidx.ts rename src/components/lib/{IndexSupplySignatures.ts => TidxSignatures.ts} (77%) delete mode 100644 src/pages/_api/api/index-supply.ts create mode 100644 src/pages/_api/api/tidx.ts diff --git a/.env.example b/.env.example index fa329d7d..ce9f91ce 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,5 @@ -INDEXSUPPLY_API_KEY= +TIDX_BASIC_AUTH= # e.g. tidx:... +TIDX_URL=https://tidx.tempo.xyz SLACK_FEEDBACK_WEBHOOK= # e.g. https://hooks.slack.com/services/... VITE_BASE_URL= # e.g. https://docs.tempo.xyz VITE_GA_MEASUREMENT_ID= diff --git a/env.d.ts b/env.d.ts index 5a86c157..22ac6500 100644 --- a/env.d.ts +++ b/env.d.ts @@ -6,7 +6,8 @@ interface EnvironmentVariables { readonly VITE_POSTHOG_HOST: string readonly VITE_TEMPO_ENV: 'localnet' | 'devnet' | 'moderato' - readonly INDEXSUPPLY_API_KEY: string + readonly TIDX_BASIC_AUTH: string + readonly TIDX_URL: string readonly SLACK_FEEDBACK_WEBHOOK: string readonly VERCEL_URL: string diff --git a/package.json b/package.json index 563a4612..c5d6fb2f 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "sql-formatter": "^15.7.3", "tailwind-merge": "^3.5.0", "tailwindcss": "^4.2.2", + "tidx.ts": "^0.1.1", "unplugin-auto-import": "^21.0.0", "unplugin-icons": "^23.0.1", "viem": "^2.48.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 14670c0c..80a45fcc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -93,6 +93,9 @@ importers: tailwindcss: specifier: ^4.2.2 version: 4.2.2 + tidx.ts: + specifier: ^0.1.1 + version: 0.1.1(typescript@5.9.3) unplugin-auto-import: specifier: ^21.0.0 version: 21.0.0 @@ -2603,6 +2606,10 @@ packages: khroma@2.1.0: resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} + kysely@0.28.16: + resolution: {integrity: sha512-3i5pmOiZvMDj00qhrIVbH0AnioVTx22DMP7Vn5At4yJO46iy+FM8Y/g61ltenLVSo3fiO8h8Q3QOFgf/gQ72ww==} + engines: {node: '>=20.0.0'} + langium@4.2.2: resolution: {integrity: sha512-JUshTRAfHI4/MF9dH2WupvjSXyn8JBuUEWazB8ZVJUtXutT0doDlAv1XKbZ1Pb5sMexa8FF4CFBc0iiul7gbUQ==} engines: {node: '>=20.10.0', npm: '>=10.2.3'} @@ -2955,6 +2962,9 @@ packages: typescript: optional: true + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + mlly@1.8.2: resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} @@ -3653,6 +3663,9 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + tidx.ts@0.1.1: + resolution: {integrity: sha512-XFoDuwCQwkERGRYB+QGQvzH//Y6kDknttWSts3durreALgkrKOCM9mUDfgRVg3zVisuQgx5RfvoJmO8x8oXm+g==} + tinyexec@1.1.1: resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} engines: {node: '>=18'} @@ -6559,6 +6572,8 @@ snapshots: khroma@2.1.0: {} + kysely@0.28.16: {} + langium@4.2.2: dependencies: '@chevrotain/regexp-to-ast': 12.0.0 @@ -7184,6 +7199,8 @@ snapshots: optionalDependencies: typescript: 5.9.3 + mitt@3.0.1: {} + mlly@1.8.2: dependencies: acorn: 8.16.0 @@ -7918,6 +7935,16 @@ snapshots: dependencies: any-promise: 1.3.0 + tidx.ts@0.1.1(typescript@5.9.3): + dependencies: + abitype: 1.2.3(typescript@5.9.3)(zod@4.3.6) + kysely: 0.28.16 + mitt: 3.0.1 + ox: 0.14.20(typescript@5.9.3)(zod@4.3.6) + zod: 4.3.6 + transitivePeerDependencies: + - typescript + tinyexec@1.1.1: {} tinyglobby@0.2.16: diff --git a/src/components/SignatureSelector.tsx b/src/components/SignatureSelector.tsx index 358e95e6..b19eaa34 100644 --- a/src/components/SignatureSelector.tsx +++ b/src/components/SignatureSelector.tsx @@ -1,6 +1,6 @@ 'use client' import * as React from 'react' -import { getAllSignatures, type SignatureInfo } from './lib/IndexSupplySignatures' +import { getAllSignatures, type SignatureInfo } from './lib/TidxSignatures' type SignatureSelectorProps = { value: string[] @@ -194,7 +194,7 @@ export function SignatureSelector(props: SignatureSelectorProps) {
[0] +type QueryResult = QueryResponse -// Default EVM tables and their columns from IndexSupply +// Default EVM tables and their columns from tidx. const EVM_TABLE_COLUMNS = { blocks: [ - 'chain', 'num', 'timestamp', - 'size', 'gas_limit', 'gas_used', - 'nonce', 'hash', - 'receipts_root', - 'state_root', + 'parent_hash', 'extra_data', 'miner', ], @@ -36,7 +31,10 @@ const EVM_TABLE_COLUMNS = { 'idx', 'type', 'gas', - 'gas_price', + 'gas_limit', + 'gas_used', + 'max_fee_per_gas', + 'max_priority_fee_per_gas', 'nonce', 'hash', 'from', @@ -51,22 +49,43 @@ const EVM_TABLE_COLUMNS = { 'log_idx', 'tx_hash', 'address', - 'topics', + 'selector', + 'topic0', + 'topic1', + 'topic2', + 'topic3', 'data', ], + receipts: [ + 'block_num', + 'block_timestamp', + 'tx_idx', + 'tx_hash', + 'from', + 'to', + 'contract_address', + 'gas_used', + 'cumulative_gas_used', + 'effective_gas_price', + 'status', + 'fee_payer', + ], } as const -type IndexSupplyQueryProps = { +type TidxQueryProps = { chainId: number signatures?: string[] query?: string title?: string - signatureFilter?: 'all' | 'events' | 'functions' } function getExplorerHost() { const { VITE_TEMPO_ENV, VITE_EXPLORER_OVERRIDE } = import.meta.env - if (VITE_TEMPO_ENV !== 'testnet' && VITE_EXPLORER_OVERRIDE !== undefined) { + if ( + VITE_TEMPO_ENV !== 'testnet' && + VITE_TEMPO_ENV !== 'moderato' && + VITE_EXPLORER_OVERRIDE !== undefined + ) { return VITE_EXPLORER_OVERRIDE } return tempo.blockExplorers.default.url @@ -133,7 +152,7 @@ function renderCellValue(cell: string | number | boolean | null): React.ReactNod ) } -export function IndexSupplyQuery(props: IndexSupplyQueryProps) { +export function TidxQuery(props: TidxQueryProps) { const isReadOnly = props.query !== undefined const allSignatures = React.useMemo(() => getAllSignatures(), []) @@ -212,7 +231,7 @@ export function IndexSupplyQuery(props: IndexSupplyQueryProps) { chainId: props.chainId, ...(signatures.length > 0 ? { signatures } : {}), } - const queryResult = await runIndexSupplyQuery(queryToRun, options) + const queryResult = await runTidxQuery(queryToRun, options) setResult(queryResult) } catch (err) { setError(err instanceof Error ? err.message : 'Unknown error occurred') @@ -234,7 +253,7 @@ export function IndexSupplyQuery(props: IndexSupplyQueryProps) { setError(null) setResult(null) - runIndexSupplyQuery(queryToRun, { + runTidxQuery(queryToRun, { chainId: props.chainId, ...(signatures.length > 0 ? { signatures } : {}), }) @@ -254,7 +273,7 @@ export function IndexSupplyQuery(props: IndexSupplyQueryProps) { - {props.title || 'IndexSupply SQL Query'} + {props.title || 'tidx SQL Query'} } headerRight={ @@ -269,7 +288,7 @@ export function IndexSupplyQuery(props: IndexSupplyQueryProps) {
Signatures
) : ( - + )}
- -export function toBigInt(value: RowValue | null | undefined): bigint { - if (value === null || value === undefined) return 0n - if (typeof value === 'number') return BigInt(value) - const normalized = value.trim() - if (normalized === '') return 0n - return BigInt(normalized) -} - -export function toQuantityHex(value: RowValue | null | undefined, fallback: bigint = 0n) { - return Hex.fromNumber(value === null || value === undefined ? fallback : toBigInt(value)) -} - -export function toHexData(value: RowValue | null | undefined): Hex.Hex { - if (typeof value !== 'string' || value.length === 0) return '0x' - Hex.assert(value) - return value -} - -export function toAddressValue(value: RowValue | null | undefined): Address.Address | null { - if (typeof value !== 'string' || value.length === 0) return null - Address.assert(value) - return value -} - -type RunQueryOptions = { - chainId: number - signatures?: string[] -} - -export async function runIndexSupplyQuery(query: string, options: RunQueryOptions) { - const response = await fetch('/api/index-supply', { - method: 'POST', - headers: { - 'content-type': 'application/json', - }, - body: JSON.stringify({ - chainId: options.chainId, - query: query.replace(/\s+/g, ' ').trim(), - signatures: options.signatures, - }), - }) - - let json: unknown - try { - json = await response.json() - } catch { - throw new Error('API returned invalid JSON') - } - - if (!response.ok) { - const message = - typeof json === 'object' && - json !== null && - 'error' in json && - typeof (json as { error?: string }).error === 'string' - ? (json as { error: string }).error - : response.statusText - throw new Error(`API error (${response.status}): ${message}`) - } - - const result = json as z.infer[0] - if (!result) throw new Error('API returned an empty result set') - return result -} diff --git a/src/components/lib/Tidx.ts b/src/components/lib/Tidx.ts new file mode 100644 index 00000000..1ef4b322 --- /dev/null +++ b/src/components/lib/Tidx.ts @@ -0,0 +1,53 @@ +import * as z from 'zod/mini' + +export const rowValueSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]) + +export const responseSchema = z.object({ + columns: z.array( + z.object({ + name: z.string(), + }), + ), + rows: z.array(z.array(rowValueSchema)), +}) + +export type QueryResponse = z.infer + +type RunQueryOptions = { + chainId: number + signatures?: string[] +} + +export async function runTidxQuery(query: string, options: RunQueryOptions) { + const response = await fetch('/api/tidx', { + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify({ + chainId: options.chainId, + query: query.replace(/\s+/g, ' ').trim(), + signatures: options.signatures, + }), + }) + + let json: unknown + try { + json = await response.json() + } catch { + throw new Error('API returned invalid JSON') + } + + if (!response.ok) { + const message = + typeof json === 'object' && + json !== null && + 'error' in json && + typeof (json as { error?: string }).error === 'string' + ? (json as { error: string }).error + : response.statusText + throw new Error(`API error (${response.status}): ${message}`) + } + + return z.parse(responseSchema, json) +} diff --git a/src/components/lib/IndexSupplySignatures.ts b/src/components/lib/TidxSignatures.ts similarity index 77% rename from src/components/lib/IndexSupplySignatures.ts rename to src/components/lib/TidxSignatures.ts index e1c3c116..3c7707f4 100644 --- a/src/components/lib/IndexSupplySignatures.ts +++ b/src/components/lib/TidxSignatures.ts @@ -1,26 +1,27 @@ import type { Abi, AbiEvent, AbiFunction } from 'abitype' +import type { Tidx } from 'tidx.ts' import { Abis } from 'viem/tempo' export type SignatureInfo = { - signature: string + signature: Tidx.Signature name: string contract: string type: 'event' | 'function' } -function formatEventSignature(event: AbiEvent): string { +function formatEventSignature(event: AbiEvent): Tidx.Signature { const params = event.inputs .map((input) => { const indexed = input.indexed ? ' indexed' : '' return `${input.type}${indexed} ${input.name || ''}` }) .join(', ') - return `${event.name}(${params})` + return `event ${event.name}(${params})` } -function formatFunctionSignature(fn: AbiFunction): string { +function formatFunctionSignature(fn: AbiFunction): Tidx.Signature { const inputs = fn.inputs.map((input) => `${input.type} ${input.name || ''}`).join(', ') - return `${fn.name}(${inputs})` + return `function ${fn.name}(${inputs})` } function extractSignaturesFromAbi(abi: Abi, contractName: string): SignatureInfo[] { @@ -66,23 +67,6 @@ export function getAllSignatures(): SignatureInfo[] { return allSignatures } -export function getSignaturesByContract(): Record { - const allSignatures = getAllSignatures() - const byContract: Record = {} - - for (const sig of allSignatures) { - if (!byContract[sig.contract]) { - byContract[sig.contract] = [] - } - const contractSigs = byContract[sig.contract] - if (contractSigs) { - contractSigs.push(sig) - } - } - - return byContract -} - export function extractParameterNames(signature: string): string[] { // Extract parameters from signature like // "Transfer(address indexed from, address indexed to, uint256 amount)" diff --git a/src/pages/_api/api/index-supply.ts b/src/pages/_api/api/index-supply.ts deleted file mode 100644 index ed8f4782..00000000 --- a/src/pages/_api/api/index-supply.ts +++ /dev/null @@ -1,137 +0,0 @@ -type QueryRequest = { - chainId: number - query: string - signatures?: string[] -} - -type QueryResponse = { - cursor?: string - columns: Array<{ - name: string - pgtype: string - }> - rows: Array> -} - -export async function POST(request: Request): Promise { - const origin = request.headers.get('origin') - const corsHeaders = cors(origin) - - let body: QueryRequest - try { - body = await request.json() - } catch { - return Response.json( - { error: 'Invalid request: could not parse JSON' }, - { status: 400, headers: corsHeaders }, - ) - } - - if (!body || typeof body.query !== 'string') { - return Response.json( - { error: 'Invalid request: query is required' }, - { status: 400, headers: corsHeaders }, - ) - } - - if (!Number.isInteger(body.chainId) || body.chainId <= 0) { - return Response.json( - { error: 'Invalid request: chainId is required and must be a positive integer' }, - { status: 400, headers: corsHeaders }, - ) - } - - const apiKey = process.env.INDEXSUPPLY_API_KEY - if (!apiKey) { - console.error('INDEXSUPPLY_API_KEY is not configured') - return Response.json( - { error: 'Server configuration error: API key not found' }, - { status: 500, headers: corsHeaders }, - ) - } - - try { - const endpoint = 'https://api.indexsupply.net/v2/query' - const url = new URL(endpoint) - url.searchParams.set('api-key', apiKey) - - const signatures = body.signatures && body.signatures.length > 0 ? body.signatures : [''] - - const chainCursor = `${body.chainId}-0` - - const response = await fetch(url.toString(), { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify([ - { - cursor: chainCursor, - signatures, - query: body.query.replace(/\s+/g, ' ').trim(), - }, - ]), - }) - - let json: unknown - try { - json = await response.json() - } catch { - return Response.json( - { error: 'Index Supply API returned invalid JSON' }, - { status: 502, headers: corsHeaders }, - ) - } - - if (!response.ok) { - const message = - typeof json === 'object' && - json !== null && - 'message' in json && - typeof (json as { message?: string }).message === 'string' - ? (json as { message: string }).message - : response.statusText - return Response.json( - { error: `Index Supply API error: ${message}` }, - { status: response.status, headers: corsHeaders }, - ) - } - - const data = json as QueryResponse[] - const [result] = data - if (!result) { - return Response.json( - { error: 'Index Supply returned an empty result set' }, - { status: 500, headers: corsHeaders }, - ) - } - - return Response.json(result, { headers: corsHeaders }) - } catch (error) { - console.error('Error querying Index Supply:', error) - return Response.json( - { error: error instanceof Error ? error.message : 'Unknown error occurred' }, - { status: 500, headers: corsHeaders }, - ) - } -} - -export async function OPTIONS(request: Request): Promise { - const origin = request.headers.get('origin') - return new Response(null, { status: 200, headers: cors(origin) }) -} - -function cors(origin: string | null): Record { - const allowedOrigins = ['https://docs.tempo.xyz', 'https://mainnet.docs.tempo.xyz'] - - if (origin?.includes('vercel.app')) allowedOrigins.push(origin) - if (process.env.NODE_ENV === 'development') allowedOrigins.push('http://localhost:5173') - - const headers: Record = { - 'Access-Control-Allow-Methods': 'POST, OPTIONS', - 'Access-Control-Allow-Headers': 'Content-Type, x-api-token', - } - - if (origin && allowedOrigins.some((allowed) => origin.startsWith(allowed))) - headers['Access-Control-Allow-Origin'] = origin - - return headers -} diff --git a/src/pages/_api/api/tidx.ts b/src/pages/_api/api/tidx.ts new file mode 100644 index 00000000..63d59afa --- /dev/null +++ b/src/pages/_api/api/tidx.ts @@ -0,0 +1,123 @@ +import { Tidx } from 'tidx.ts' + +type QueryRequest = { + chainId: number + query: string + signatures?: Tidx.Signature[] +} + +type RowValue = string | number | boolean | null + +type QueryResponse = { + columns: Array<{ + name: string + }> + rows: Array +} + +export async function POST(request: Request): Promise { + const origin = request.headers.get('origin') + const corsHeaders = cors(origin) + + let body: QueryRequest + try { + body = await request.json() + } catch { + return Response.json( + { error: 'Invalid request: could not parse JSON' }, + { status: 400, headers: corsHeaders }, + ) + } + + if (!body || typeof body.query !== 'string') { + return Response.json( + { error: 'Invalid request: query is required' }, + { status: 400, headers: corsHeaders }, + ) + } + + if (!Number.isInteger(body.chainId) || body.chainId <= 0) { + return Response.json( + { + error: 'Invalid request: chainId is required and must be a positive integer', + }, + { status: 400, headers: corsHeaders }, + ) + } + + const basicAuth = process.env.TIDX_BASIC_AUTH + if (!basicAuth) { + console.error('TIDX_BASIC_AUTH is not configured') + return Response.json( + { error: 'Server configuration error: tidx credentials not found' }, + { status: 500, headers: corsHeaders }, + ) + } + + try { + const tidx = Tidx.create({ + basicAuth, + baseUrl: process.env.TIDX_URL ?? 'https://tidx.tempo.xyz', + chainId: body.chainId, + }) + + const result = await tidx.fetch({ + query: body.query.replace(/\s+/g, ' ').trim(), + signatures: body.signatures, + }) + + return Response.json(toQueryResponse(result.rows), { + headers: corsHeaders, + }) + } catch (error) { + console.error('Error querying tidx:', error) + return Response.json( + { + error: error instanceof Error ? error.message : 'Unknown error occurred', + }, + { status: 500, headers: corsHeaders }, + ) + } +} + +export async function OPTIONS(request: Request): Promise { + const origin = request.headers.get('origin') + return new Response(null, { status: 200, headers: cors(origin) }) +} + +function cors(origin: string | null): Record { + const allowedOrigins = new Set(['https://docs.tempo.xyz', 'https://mainnet.docs.tempo.xyz']) + + if (origin?.includes('vercel.app')) allowedOrigins.add(origin) + if (process.env.NODE_ENV === 'development' && process.env.VITE_BASE_URL) + allowedOrigins.add(process.env.VITE_BASE_URL) + + const headers: Record = { + 'Access-Control-Allow-Methods': 'POST, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, x-api-token', + } + + if (origin && Array.from(allowedOrigins).some((allowed) => origin.startsWith(allowed))) + headers['Access-Control-Allow-Origin'] = origin + + return headers +} + +function toQueryResponse(rows: Record[]): QueryResponse { + const columns = Array.from(new Set(rows.flatMap((row) => Object.keys(row)))).map((name) => ({ + name, + })) + + return { + columns, + rows: rows.map((row) => columns.map((column) => toRowValue(row[column.name]))), + } +} + +function toRowValue(value: unknown): RowValue { + if (value === null || value === undefined) return null + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') + return value + if (typeof value === 'bigint') return value.toString() + return JSON.stringify(value) +} diff --git a/src/pages/guide/stablecoin-dex/view-the-orderbook.mdx b/src/pages/guide/stablecoin-dex/view-the-orderbook.mdx index c151d7b7..9d2529c2 100644 --- a/src/pages/guide/stablecoin-dex/view-the-orderbook.mdx +++ b/src/pages/guide/stablecoin-dex/view-the-orderbook.mdx @@ -2,7 +2,7 @@ description: Inspect Tempo's onchain orderbook using SQL queries. View spreads, order depth, individual orders, and recent trade prices with indexed data. --- -import { IndexSupplyQuery } from '../../../components/IndexSupplyQuery' +import { TidxQuery } from '../../../components/TidxQuery' # View the Orderbook @@ -13,7 +13,7 @@ Query and inspect the orderbook to see available liquidity, price levels, and in We recommend using indexed data to query the orderbook for better performance and ease of use. While you can query logs and transactions directly from an RPC node, indexed data providers offer structured SQL interfaces that make complex queries simpler and more efficient. -In this guide, we use [Index Supply](https://www.indexsupply.net) as our indexing provider, but you're free to choose your own indexing solution or query the chain directly based on your needs. +In this guide, we use Tempo's [tidx](https://github.com/tempoxyz/tidx) indexer for structured SQL access to onchain data. ## Recipes @@ -23,14 +23,15 @@ Query the best bid and ask prices to calculate the current spread for a token pa These examples use the mainnet `USDC.e / pathUSD` orderbook; `pathUSD` is the quote token for `USDC.e` — see [Quote Tokens](/protocol/exchange/quote-tokens). -Find the highest bid prices (buyers) for the `USDC.e / pathUSD` book. This query filters out fully filled and cancelled orders, groups by price level (tick), and shows the top 5 bid prices with their total liquidity. +Find the highest bid prices (buyers) for the `USDC.e / pathUSD` book. This query filters out fully filled and cancelled orders, groups by tick, and shows the top 5 bid levels with their computed price and raw token liquidity. - Find the lowest ask prices (sellers) for the `USDC.e / pathUSD` book. The spread is the difference between the highest bid and lowest ask price. - ### Inspect order depth -View aggregated liquidity at each price level to understand the orderbook structure. +View aggregated liquidity at each tick level to understand the orderbook structure. This query shows all active orders in the `USDC.e / pathUSD` book, including both regular and flip orders. - @@ -112,16 +115,17 @@ This query shows all active orders in the `USDC.e / pathUSD` book, including bot Get detailed information about a specific order including its placement details, fill history, and cancellation status. -This query inspects the details of the most recent order in the `USDC.e / pathUSD` book. It shows when the order was created, at what price (tick), the order size, whether it's a flip order, and who placed it. +This query inspects the details of the most recent order in the `USDC.e / pathUSD` book. It shows when the order was created, its tick and computed price, the raw order size, whether it's a flip order, and who placed it. - Date: Sun, 3 May 2026 01:47:33 -0700 Subject: [PATCH 2/2] chore: use better hooks --- src/components/SignatureSelector.tsx | 22 ++-- src/components/TidxQuery.tsx | 100 ++++++------------ .../guides/steps/exchange/PlaceOrder.tsx | 2 +- .../guides/steps/exchange/QueryOrder.tsx | 17 ++- 4 files changed, 48 insertions(+), 93 deletions(-) diff --git a/src/components/SignatureSelector.tsx b/src/components/SignatureSelector.tsx index b19eaa34..72dcac41 100644 --- a/src/components/SignatureSelector.tsx +++ b/src/components/SignatureSelector.tsx @@ -17,6 +17,12 @@ export function SignatureSelector(props: SignatureSelectorProps) { const allSignatures = React.useMemo(() => getAllSignatures(), []) + const handleClickOutside = React.useEffectEvent((event: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + setIsOpen(false) + } + }) + const filteredSignatures = React.useMemo(() => { let signatures = allSignatures @@ -56,12 +62,6 @@ export function SignatureSelector(props: SignatureSelectorProps) { React.useEffect(() => { if (!isOpen) return - function handleClickOutside(event: MouseEvent) { - if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { - setIsOpen(false) - } - } - document.addEventListener('mousedown', handleClickOutside) return () => document.removeEventListener('mousedown', handleClickOutside) }, [isOpen]) @@ -115,7 +115,7 @@ export function SignatureSelector(props: SignatureSelectorProps) { onChange={(e) => setSearchQuery(e.target.value)} onFocus={() => !disabled && setIsOpen(true)} placeholder={placeholderText} - className="h-[34px] w-full rounded-lg border border-gray4 px-3 text-[13px] focus:outline-none focus:ring-1 focus:ring-accent disabled:cursor-not-allowed disabled:opacity-50" + className="h-8.5 w-full rounded-lg border border-gray4 px-3 text-[13px] focus:outline-none focus:ring-1 focus:ring-accent disabled:cursor-not-allowed disabled:opacity-50" disabled={disabled} /> {value.length > 0 && !disabled && ( @@ -131,7 +131,7 @@ export function SignatureSelector(props: SignatureSelectorProps) {
{isOpen && ( -
+
{Object.keys(groupedSignatures).length === 0 ? (
No signatures found
) : ( @@ -159,7 +159,7 @@ export function SignatureSelector(props: SignatureSelectorProps) { {sig.signature} @@ -219,9 +219,7 @@ export function SignatureSelector(props: SignatureSelectorProps) { isEvent ? 'bg-blue9' : 'bg-purple9' }`} /> - - {sigInfo?.name || sig} - + {sigInfo?.name || sig} {!disabled && (