diff --git a/src/routes/hyperliquid/markets.sql b/src/routes/hyperliquid/markets.sql index a667ddf6..368a62c4 100644 --- a/src/routes/hyperliquid/markets.sql +++ b/src/routes/hyperliquid/markets.sql @@ -11,8 +11,8 @@ WITH FROM {db_hypercore:Identifier}.state_ohlcv_fills WHERE interval_min = 60 AND timestamp >= now() - INTERVAL 24 HOUR - AND (isNull({coin:Nullable(String)}) OR coin = {coin:Nullable(String)}) - AND (isNull({dex:Nullable(String)}) OR dex_from_coin(coin) = {dex:Nullable(String)}) + AND (empty({coin:Array(String)}) OR coin IN {coin:Array(String)}) + AND (empty({dex:Array(String)}) OR dex_from_coin(coin) IN {dex:Array(String)}) GROUP BY coin ), uu_24h AS ( @@ -22,8 +22,8 @@ WITH FROM {db_hypercore:Identifier}.state_ohlcv_fills_uniq_user FINAL WHERE interval_min = 1440 AND timestamp >= now() - INTERVAL 48 HOUR - AND (isNull({coin:Nullable(String)}) OR coin = {coin:Nullable(String)}) - AND (isNull({dex:Nullable(String)}) OR dex_from_coin(coin) = {dex:Nullable(String)}) + AND (empty({coin:Array(String)}) OR coin IN {coin:Array(String)}) + AND (empty({dex:Array(String)}) OR dex_from_coin(coin) IN {dex:Array(String)}) GROUP BY coin ), day_prev AS ( @@ -34,8 +34,8 @@ WITH WHERE interval_min = 60 AND timestamp >= now() - INTERVAL 48 HOUR AND timestamp < now() - INTERVAL 24 HOUR - AND (isNull({coin:Nullable(String)}) OR coin = {coin:Nullable(String)}) - AND (isNull({dex:Nullable(String)}) OR dex_from_coin(coin) = {dex:Nullable(String)}) + AND (empty({coin:Array(String)}) OR coin IN {coin:Array(String)}) + AND (empty({dex:Array(String)}) OR dex_from_coin(coin) IN {dex:Array(String)}) GROUP BY coin ), oi_latest AS ( @@ -47,7 +47,7 @@ WITH FROM {db_hypercore:Identifier}.open_interest WHERE interval_min = 60 AND timestamp >= now() - INTERVAL 6 HOUR - AND (isNull({coin:Nullable(String)}) OR coin = {coin:Nullable(String)}) + AND (empty({coin:Array(String)}) OR coin IN {coin:Array(String)}) GROUP BY coin ) SELECT @@ -72,8 +72,8 @@ LEFT JOIN day_prev prev ON prev.coin = cur.coin LEFT JOIN oi_latest oi ON oi.coin = cur.coin LEFT JOIN uu_24h uu ON uu.coin = cur.coin LEFT JOIN {db_hypercore:Identifier}.state_spot_pair_names AS n FINAL ON n.coin = cur.coin -WHERE (isNull({base_token:Nullable(String)}) OR n.base_token = {base_token:Nullable(String)}) - AND (isNull({quote_token:Nullable(String)}) OR n.quote_token = {quote_token:Nullable(String)}) +WHERE (empty({base_token:Array(String)}) OR n.base_token IN {base_token:Array(String)}) + AND (empty({quote_token:Array(String)}) OR n.quote_token IN {quote_token:Array(String)}) ORDER BY cur.volume_24h DESC LIMIT {limit:UInt64} OFFSET {offset:UInt64} diff --git a/src/routes/hyperliquid/markets.ts b/src/routes/hyperliquid/markets.ts index e57cf15f..a6e25002 100644 --- a/src/routes/hyperliquid/markets.ts +++ b/src/routes/hyperliquid/markets.ts @@ -17,10 +17,10 @@ import { validatorHook, withErrorResponses } from '../../utils.js'; import baseQuery from './markets.sql' with { type: 'text' }; const querySchema = createQuerySchema({ - coin: { schema: hyperliquidCoinSchema, optional: true }, - dex: { schema: hyperliquidDexIdSchema, optional: true }, - base_token: { schema: hyperliquidTokenSchema, optional: true }, - quote_token: { schema: hyperliquidTokenSchema, optional: true }, + coin: { schema: hyperliquidCoinSchema, batched: true, optional: true }, + dex: { schema: hyperliquidDexIdSchema, batched: true, optional: true }, + base_token: { schema: hyperliquidTokenSchema, batched: true, optional: true }, + quote_token: { schema: hyperliquidTokenSchema, batched: true, optional: true }, }); const responseSchema = apiUsageResponseSchema.extend({ diff --git a/src/routes/hyperliquid/markets_activity.sql b/src/routes/hyperliquid/markets_activity.sql index 691c4bea..492c0081 100644 --- a/src/routes/hyperliquid/markets_activity.sql +++ b/src/routes/hyperliquid/markets_activity.sql @@ -61,9 +61,9 @@ FROM ( FROM {db_hypercore:Identifier}.fills WHERE timestamp >= (SELECT t FROM start_ts) AND timestamp < (SELECT t FROM end_ts) - AND (isNull({user:Nullable(String)}) OR user = {user:Nullable(String)}) - AND (isNull({coin:Nullable(String)}) OR coin = {coin:Nullable(String)}) - AND (isNull({dex:Nullable(String)}) OR dex = {dex:Nullable(String)}) + AND (empty({user:Array(String)}) OR user IN {user:Array(String)}) + AND (empty({coin:Array(String)}) OR coin IN {coin:Array(String)}) + AND (empty({dex:Array(String)}) OR dex IN {dex:Array(String)}) ORDER BY timestamp DESC, block_num DESC, event_index DESC LIMIT {limit:UInt64} OFFSET {offset:UInt64} diff --git a/src/routes/hyperliquid/markets_activity.ts b/src/routes/hyperliquid/markets_activity.ts index 83dce8f9..a0971fcc 100644 --- a/src/routes/hyperliquid/markets_activity.ts +++ b/src/routes/hyperliquid/markets_activity.ts @@ -18,9 +18,9 @@ import { validatorHook, withErrorResponses } from '../../utils.js'; import query from './markets_activity.sql' with { type: 'text' }; const querySchema = createQuerySchema({ - coin: { schema: hyperliquidCoinSchema, optional: true }, - dex: { schema: hyperliquidDexIdSchema, optional: true }, - user: { schema: evmAddressSchema, optional: true }, + coin: { schema: hyperliquidCoinSchema, batched: true, optional: true }, + dex: { schema: hyperliquidDexIdSchema, batched: true, optional: true }, + user: { schema: evmAddressSchema, batched: true, optional: true }, start_time: { schema: timestampSchema, optional: true }, end_time: { schema: timestampSchema, optional: true }, }); @@ -110,7 +110,7 @@ const route = new Hono<{ Variables: { validatedData: z.infer route.get('/', openapi, zValidator('query', querySchema, validatorHook), validator('query', querySchema), async (c) => { const params = c.req.valid('query'); - if (!params.coin && !params.dex && !params.user) { + if (!params.coin.length && !params.dex.length && !params.user.length) { return c.json( { status: 400, code: 'bad_query_input', message: 'Provide at least one of coin, dex, or user' }, 400 diff --git a/src/routes/hyperliquid/markets_liquidations.sql b/src/routes/hyperliquid/markets_liquidations.sql index 64a149a7..5dbd4914 100644 --- a/src/routes/hyperliquid/markets_liquidations.sql +++ b/src/routes/hyperliquid/markets_liquidations.sql @@ -19,9 +19,9 @@ SELECT FROM {db_hypercore:Identifier}.fills_liquidation AS f LEFT JOIN {db_hypercore:Identifier}.state_spot_pair_names AS n FINAL ON n.coin = f.coin WHERE f.user = f.liquidated_user - AND (isNull({coin:Nullable(String)}) OR f.coin = {coin:Nullable(String)}) - AND (isNull({dex:Nullable(String)}) OR dex_from_coin(f.coin) = {dex:Nullable(String)}) - AND (isNull({liquidated_user:Nullable(String)}) OR f.liquidated_user = {liquidated_user:Nullable(String)}) + AND (empty({coin:Array(String)}) OR f.coin IN {coin:Array(String)}) + AND (empty({dex:Array(String)}) OR dex_from_coin(f.coin) IN {dex:Array(String)}) + AND (empty({liquidated_user:Array(String)}) OR f.liquidated_user IN {liquidated_user:Array(String)}) AND (isNull({start_time:Nullable(UInt64)}) OR f.timestamp >= toDateTime({start_time:Nullable(UInt64)})) AND (isNull({end_time:Nullable(UInt64)}) OR f.timestamp < toDateTime({end_time:Nullable(UInt64)})) GROUP BY f.event_hash, f.liquidated_user, f.coin, f.direction diff --git a/src/routes/hyperliquid/markets_liquidations.ts b/src/routes/hyperliquid/markets_liquidations.ts index 49f0c0cb..acb72c8f 100644 --- a/src/routes/hyperliquid/markets_liquidations.ts +++ b/src/routes/hyperliquid/markets_liquidations.ts @@ -19,9 +19,9 @@ import { validatorHook, withErrorResponses } from '../../utils.js'; import baseQuery from './markets_liquidations.sql' with { type: 'text' }; const querySchema = createQuerySchema({ - coin: { schema: hyperliquidCoinSchema, optional: true }, - dex: { schema: hyperliquidDexIdSchema, optional: true }, - liquidated_user: { schema: evmAddressSchema, optional: true }, + coin: { schema: hyperliquidCoinSchema, batched: true, optional: true }, + dex: { schema: hyperliquidDexIdSchema, batched: true, optional: true }, + liquidated_user: { schema: evmAddressSchema, batched: true, optional: true }, sort_by: { schema: hyperliquidLiquidationSortBySchema, prefault: 'notional' }, start_time: { schema: timestampSchema, optional: true }, end_time: { schema: timestampSchema, optional: true }, diff --git a/src/routes/hyperliquid/markets_oi.sql b/src/routes/hyperliquid/markets_oi.sql index 32a04fb1..b2a0c8eb 100644 --- a/src/routes/hyperliquid/markets_oi.sql +++ b/src/routes/hyperliquid/markets_oi.sql @@ -17,9 +17,9 @@ SELECT sum(oi.funding_events) AS funding_events FROM {db_hypercore:Identifier}.state_open_interest AS oi LEFT JOIN {db_hypercore:Identifier}.state_spot_pair_names AS n FINAL ON n.coin = oi.coin -WHERE oi.coin = {coin:String} +WHERE oi.coin IN {coin:Array(String)} AND oi.interval_min = {interval:UInt32} - AND (isNull({dex:Nullable(String)}) OR dex_from_coin(oi.coin) = {dex:Nullable(String)}) + AND (empty({dex:Array(String)}) OR dex_from_coin(oi.coin) IN {dex:Array(String)}) AND (isNull({start_time:Nullable(UInt64)}) OR oi.timestamp >= toDateTime({start_time:Nullable(UInt64)})) AND (isNull({end_time:Nullable(UInt64)}) OR oi.timestamp < toDateTime({end_time:Nullable(UInt64)})) GROUP BY oi.interval_min, oi.coin, oi.timestamp diff --git a/src/routes/hyperliquid/markets_oi.ts b/src/routes/hyperliquid/markets_oi.ts index 3bf80ecc..9a97ef2e 100644 --- a/src/routes/hyperliquid/markets_oi.ts +++ b/src/routes/hyperliquid/markets_oi.ts @@ -18,8 +18,8 @@ import { validatorHook, withErrorResponses } from '../../utils.js'; import query from './markets_oi.sql' with { type: 'text' }; const querySchema = createQuerySchema({ - coin: { schema: hyperliquidCoinSchema }, - dex: { schema: hyperliquidDexIdSchema, optional: true }, + coin: { schema: hyperliquidCoinSchema, batched: true }, + dex: { schema: hyperliquidDexIdSchema, batched: true, optional: true }, interval: { schema: evmIntervalSchema, prefault: '1h' }, start_time: { schema: timestampSchema, optional: true }, end_time: { schema: timestampSchema, optional: true }, diff --git a/src/routes/hyperliquid/users.sql b/src/routes/hyperliquid/users.sql index d64edff3..f6542503 100644 --- a/src/routes/hyperliquid/users.sql +++ b/src/routes/hyperliquid/users.sql @@ -1,27 +1,27 @@ SELECT - user, - {coin:Nullable(String)} AS coin, - {dex:Nullable(String)} AS dex, + u.user AS user, + if(length({coin:Array(String)}) = 1, {coin:Array(String)}[1], NULL) AS coin, + if(length({dex:Array(String)}) = 1, {dex:Array(String)}[1], NULL) AS dex, {interval:Nullable(String)} AS interval, - sum(transactions) AS transactions, - sum(buys) AS buys, - sum(sells) AS sells, - sum(volume_bought) AS volume_bought, - sum(volume_sold) AS volume_sold, - sum(total_volume) AS total_volume, - sum(total_fees) AS total_fees, - sum(realized_pnl) AS realized_pnl, - sum(total_funding) AS total_funding, - sum(liquidation_fills) AS liquidation_fills, + sum(u.transactions) AS transactions, + sum(u.buys) AS buys, + sum(u.sells) AS sells, + sum(u.volume_bought) AS volume_bought, + sum(u.volume_sold) AS volume_sold, + sum(u.total_volume) AS total_volume, + sum(u.total_fees) AS total_fees, + sum(u.realized_pnl) AS realized_pnl, + sum(u.total_funding) AS total_funding, + sum(u.liquidation_fills) AS liquidation_fills, count() AS coins_traded, - min(first_trade) AS first_trade, - max(last_trade) AS last_trade -FROM {db_hypercore:Identifier}.state_user_by_coin FINAL -WHERE interval_min = {interval_min:UInt32} - AND (isNull({user:Nullable(String)}) OR user = {user:Nullable(String)}) - AND (isNull({coin:Nullable(String)}) OR coin = {coin:Nullable(String)}) - AND (isNull({dex:Nullable(String)}) OR dex = {dex:Nullable(String)}) -GROUP BY user + min(u.first_trade) AS first_trade, + max(u.last_trade) AS last_trade +FROM {db_hypercore:Identifier}.state_user_by_coin AS u FINAL +WHERE u.interval_min = {interval_min:UInt32} + AND (empty({user:Array(String)}) OR u.user IN {user:Array(String)}) + AND (empty({coin:Array(String)}) OR u.coin IN {coin:Array(String)}) + AND (empty({dex:Array(String)}) OR u.dex IN {dex:Array(String)}) +GROUP BY u.user ORDER BY total_volume DESC LIMIT {limit:UInt64} OFFSET {offset:UInt64} diff --git a/src/routes/hyperliquid/users.ts b/src/routes/hyperliquid/users.ts index 8053a671..231315cd 100644 --- a/src/routes/hyperliquid/users.ts +++ b/src/routes/hyperliquid/users.ts @@ -20,11 +20,11 @@ import byCoinQuery from './users.sql' with { type: 'text' }; import leaderboardQuery from './users_leaderboard.sql' with { type: 'text' }; const querySchema = createQuerySchema({ - user: { schema: evmAddressSchema, optional: true }, + user: { schema: evmAddressSchema, batched: true, optional: true }, interval: { schema: userLookbackIntervalSchema, optional: true }, sort_by: { schema: hyperliquidUserSortBySchema, prefault: 'total_volume' }, - coin: { schema: hyperliquidCoinSchema, optional: true }, - dex: { schema: hyperliquidDexIdSchema, optional: true }, + coin: { schema: hyperliquidCoinSchema, batched: true, optional: true }, + dex: { schema: hyperliquidDexIdSchema, batched: true, optional: true }, }); const responseSchema = apiUsageResponseSchema.extend({ @@ -148,7 +148,7 @@ route.get('/', openapi, zValidator('query', querySchema, validatorHook), validat // state_user_leaderboard is pre-aggregated across coins/dexes and keyed by // (interval_min, user) — use it whenever the request doesn't scope to a // specific coin or dex. Falls back to state_user_by_coin FINAL otherwise. - const usesLeaderboard = !params.coin && !params.dex; + const usesLeaderboard = !params.coin.length && !params.dex.length; const baseSql = usesLeaderboard ? leaderboardQuery : byCoinQuery; const sql = baseSql.replace('ORDER BY total_volume DESC', `ORDER BY ${sortColumn} DESC`); diff --git a/src/routes/hyperliquid/users_leaderboard.sql b/src/routes/hyperliquid/users_leaderboard.sql index a6be3386..4a433f3c 100644 --- a/src/routes/hyperliquid/users_leaderboard.sql +++ b/src/routes/hyperliquid/users_leaderboard.sql @@ -1,24 +1,24 @@ SELECT - user, - {coin:Nullable(String)} AS coin, - {dex:Nullable(String)} AS dex, + u.user AS user, + if(length({coin:Array(String)}) = 1, {coin:Array(String)}[1], NULL) AS coin, + if(length({dex:Array(String)}) = 1, {dex:Array(String)}[1], NULL) AS dex, {interval:Nullable(String)} AS interval, - transactions, - buys, - sells, - volume_bought, - volume_sold, - total_volume, - total_fees, - realized_pnl, - total_funding, - liquidation_fills, - coins_traded, - first_trade, - last_trade -FROM {db_hypercore:Identifier}.state_user_leaderboard FINAL -WHERE interval_min = {interval_min:UInt32} - AND (isNull({user:Nullable(String)}) OR user = {user:Nullable(String)}) + u.transactions AS transactions, + u.buys AS buys, + u.sells AS sells, + u.volume_bought AS volume_bought, + u.volume_sold AS volume_sold, + u.total_volume AS total_volume, + u.total_fees AS total_fees, + u.realized_pnl AS realized_pnl, + u.total_funding AS total_funding, + u.liquidation_fills AS liquidation_fills, + u.coins_traded AS coins_traded, + u.first_trade AS first_trade, + u.last_trade AS last_trade +FROM {db_hypercore:Identifier}.state_user_leaderboard AS u FINAL +WHERE u.interval_min = {interval_min:UInt32} + AND (empty({user:Array(String)}) OR u.user IN {user:Array(String)}) ORDER BY total_volume DESC LIMIT {limit:UInt64} OFFSET {offset:UInt64} diff --git a/src/routes/hyperliquid/users_positions.sql b/src/routes/hyperliquid/users_positions.sql index b2b26587..fa7a6b2e 100644 --- a/src/routes/hyperliquid/users_positions.sql +++ b/src/routes/hyperliquid/users_positions.sql @@ -9,9 +9,9 @@ SELECT FROM {db_hypercore:Identifier}.funding_deltas AS f LEFT JOIN {db_hypercore:Identifier}.state_spot_pair_names AS n FINAL ON n.coin = f.coin WHERE f.timestamp >= now() - INTERVAL 2 DAY - AND f.user = {user:String} - AND (isNull({coin:Nullable(String)}) OR f.coin = {coin:Nullable(String)}) - AND (isNull({dex:Nullable(String)}) OR f.dex = {dex:Nullable(String)}) + AND f.user IN {user:Array(String)} + AND (empty({coin:Array(String)}) OR f.coin IN {coin:Array(String)}) + AND (empty({dex:Array(String)}) OR f.dex IN {dex:Array(String)}) GROUP BY f.user, f.coin HAVING position_size != 0 ORDER BY abs(position_size) DESC diff --git a/src/routes/hyperliquid/users_positions.ts b/src/routes/hyperliquid/users_positions.ts index 928286e3..5ea80b5a 100644 --- a/src/routes/hyperliquid/users_positions.ts +++ b/src/routes/hyperliquid/users_positions.ts @@ -17,9 +17,9 @@ import { validatorHook, withErrorResponses } from '../../utils.js'; import query from './users_positions.sql' with { type: 'text' }; const querySchema = createQuerySchema({ - user: { schema: evmAddressSchema }, - coin: { schema: hyperliquidCoinSchema, optional: true }, - dex: { schema: hyperliquidDexIdSchema, optional: true }, + user: { schema: evmAddressSchema, batched: true }, + coin: { schema: hyperliquidCoinSchema, batched: true, optional: true }, + dex: { schema: hyperliquidDexIdSchema, batched: true, optional: true }, }); const responseSchema = apiUsageResponseSchema.extend({ diff --git a/src/routes/hyperliquid/vaults.sql b/src/routes/hyperliquid/vaults.sql index e29471a5..cd684bcd 100644 --- a/src/routes/hyperliquid/vaults.sql +++ b/src/routes/hyperliquid/vaults.sql @@ -7,7 +7,7 @@ WITH uniqExact(splitByChar(',', users)[1]) AS depositor_count, max(timestamp) AS last_deposit_at FROM {db_hypercore:Identifier}.ledger_vault_deposits - WHERE (isNull({vault:Nullable(String)}) OR vault = {vault:Nullable(String)}) + WHERE (empty({vault:Array(String)}) OR vault IN {vault:Array(String)}) GROUP BY vault ), withdrawals AS ( @@ -18,7 +18,7 @@ WITH count() AS withdrawal_count, max(timestamp) AS last_withdrawal_at FROM {db_hypercore:Identifier}.ledger_vault_withdrawals - WHERE (isNull({vault:Nullable(String)}) OR vault = {vault:Nullable(String)}) + WHERE (empty({vault:Array(String)}) OR vault IN {vault:Array(String)}) GROUP BY vault ), distributions AS ( @@ -26,7 +26,7 @@ WITH vault, sum(usdc_num) AS lifetime_distributions FROM {db_hypercore:Identifier}.ledger_vault_distributions - WHERE (isNull({vault:Nullable(String)}) OR vault = {vault:Nullable(String)}) + WHERE (empty({vault:Array(String)}) OR vault IN {vault:Array(String)}) GROUP BY vault ), creates AS ( @@ -37,7 +37,7 @@ WITH argMin(usdc_num, timestamp) AS initial_deposit, argMin(fee_num, timestamp) AS create_fee FROM {db_hypercore:Identifier}.ledger_vault_creates - WHERE (isNull({vault:Nullable(String)}) OR vault = {vault:Nullable(String)}) + WHERE (empty({vault:Array(String)}) OR vault IN {vault:Array(String)}) GROUP BY vault ) SELECT diff --git a/src/routes/hyperliquid/vaults.ts b/src/routes/hyperliquid/vaults.ts index 550ce900..262b12fe 100644 --- a/src/routes/hyperliquid/vaults.ts +++ b/src/routes/hyperliquid/vaults.ts @@ -16,7 +16,7 @@ import { validatorHook, withErrorResponses } from '../../utils.js'; import baseQuery from './vaults.sql' with { type: 'text' }; const querySchema = createQuerySchema({ - vault: { schema: evmAddressSchema, optional: true }, + vault: { schema: evmAddressSchema, batched: true, optional: true }, sort_by: { schema: hyperliquidVaultSortBySchema, prefault: 'lifetime_deposits' }, }); diff --git a/src/routes/hyperliquid/vaults_depositors.sql b/src/routes/hyperliquid/vaults_depositors.sql index 29de528c..f036d863 100644 --- a/src/routes/hyperliquid/vaults_depositors.sql +++ b/src/routes/hyperliquid/vaults_depositors.sql @@ -1,35 +1,38 @@ WITH deposits AS ( SELECT + vault, splitByChar(',', users)[1] AS user, sum(usdc_num) AS deposits, count() AS deposit_count, max(timestamp) AS last_deposit_at FROM {db_hypercore:Identifier}.ledger_vault_deposits - WHERE vault = {vault:String} - GROUP BY user + WHERE vault IN {vault:Array(String)} + GROUP BY vault, user ), withdrawals AS ( SELECT + vault, user, sum(net_withdrawn_usd_num) AS withdrawals, count() AS withdrawal_count, max(timestamp) AS last_withdrawal_at FROM {db_hypercore:Identifier}.ledger_vault_withdrawals - WHERE vault = {vault:String} - GROUP BY user + WHERE vault IN {vault:Array(String)} + GROUP BY vault, user ), distributions AS ( SELECT + vault, splitByChar(',', users)[1] AS user, sum(usdc_num) AS distributions_received FROM {db_hypercore:Identifier}.ledger_vault_distributions - WHERE vault = {vault:String} - GROUP BY user + WHERE vault IN {vault:Array(String)} + GROUP BY vault, user ) SELECT deposits.user AS user, - {vault:String} AS vault, + deposits.vault AS vault, deposits.deposits AS deposits, deposits.deposit_count AS deposit_count, coalesce(withdrawals.withdrawals, 0) AS withdrawals, @@ -37,8 +40,8 @@ SELECT coalesce(distributions.distributions_received, 0) AS distributions_received, greatest(deposits.last_deposit_at, withdrawals.last_withdrawal_at) AS last_activity_at FROM deposits -LEFT JOIN withdrawals USING (user) -LEFT JOIN distributions USING (user) +LEFT JOIN withdrawals USING (vault, user) +LEFT JOIN distributions USING (vault, user) ORDER BY deposits DESC LIMIT {limit:UInt64} OFFSET {offset:UInt64} diff --git a/src/routes/hyperliquid/vaults_depositors.ts b/src/routes/hyperliquid/vaults_depositors.ts index 87c5667a..75b16889 100644 --- a/src/routes/hyperliquid/vaults_depositors.ts +++ b/src/routes/hyperliquid/vaults_depositors.ts @@ -16,7 +16,7 @@ import { validatorHook, withErrorResponses } from '../../utils.js'; import baseQuery from './vaults_depositors.sql' with { type: 'text' }; const querySchema = createQuerySchema({ - vault: { schema: evmAddressSchema }, + vault: { schema: evmAddressSchema, batched: true }, sort_by: { schema: hyperliquidVaultDepositorSortBySchema, prefault: 'deposits' }, }); diff --git a/src/types/zod.ts b/src/types/zod.ts index 32c2cc58..0b50b441 100644 --- a/src/types/zod.ts +++ b/src/types/zod.ts @@ -948,7 +948,7 @@ export const hyperliquidDexIdSchema = z.coerce .meta({ type: 'string', description: - 'DEX identifier. `perps` for core perps, `spot` for `@N` spot pairs, or a builder DEX name (e.g. `xyz`, `cash`). New builder DEXs are added on Hyperliquid permissionlessly — call `/v1/hyperliquid/dexes` for the live set.', + 'DEX identifier. `perps` for core perps, `spot` for `@N` spot pairs, or a builder DEX name (`xyz`, `cash`, …). Call `/v1/hyperliquid/dexes` for the live set.', examples: knownHyperliquidDexes, }); @@ -1018,6 +1018,7 @@ export const hyperliquidUserSortBySchema = z.enum(hyperliquidUserSortFields).met export const hyperliquidTokenSchema = z.coerce .string() .min(1) + .refine((val) => !/[,\s]/.test(val), 'Invalid token symbol (no commas or whitespace)') .meta({ type: 'string', description: @@ -1025,9 +1026,13 @@ export const hyperliquidTokenSchema = z.coerce examples: ['HYPE', 'USDC', 'PURR'], }); -export const hyperliquidCoinSchema = z.coerce.string().min(1).meta({ - type: 'string', - description: - 'Hyperliquid coin identifier. Core perps have no prefix (`BTC`, `HYPE`); spot pairs use `@N` (`@107`); builder DEXs prefix the symbol with the DEX name (`xyz:SILVER`).', - example: 'BTC', -}); +export const hyperliquidCoinSchema = z.coerce + .string() + .min(1) + .refine((val) => !/[,\s]/.test(val), 'Invalid coin identifier (no commas or whitespace)') + .meta({ + type: 'string', + description: + 'Hyperliquid coin identifier. Core perps have no prefix (`BTC`, `HYPE`); spot pairs use `@N` (`@107`); builder DEXs prefix the symbol with the DEX name (`xyz:SILVER`).', + example: 'BTC', + });