Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 32 additions & 18 deletions src/routes/holders/evm.sql
Original file line number Diff line number Diff line change
@@ -1,33 +1,47 @@
/* get the latest balance for each account */
WITH balances AS (
SELECT address, contract, balance, timestamp, block_num
FROM {db_balances:Identifier}.erc20_balances FINAL
WHERE contract = {contract:String} AND balance > 0
ORDER BY balance DESC, address
LIMIT {limit:UInt64}
OFFSET {offset:UInt64}
/* Materialize top-N balances once into a row array, then expand via arrayJoin.
The address-array is reused to pre-filter the contracts lookup via primary key,
avoiding the full-table hash build a plain JOIN would trigger. */
WITH rows_arr AS (
SELECT groupArray(tuple(address, balance, timestamp, block_num)) AS r
FROM (
SELECT address, balance, timestamp, block_num
FROM {db_balances:Identifier}.erc20_balances FINAL
WHERE contract = {contract:String} AND balance > 0
ORDER BY balance DESC, address
LIMIT {limit:UInt64} OFFSET {offset:UInt64}
)
)
SELECT
/* timestamps */
b.timestamp AS last_update,
b.block_num AS last_update_block_num,
toUnixTimestamp(b.timestamp) AS last_update_timestamp,
t.3 AS last_update,
t.4 AS last_update_block_num,
toUnixTimestamp(t.3) AS last_update_timestamp,

/* identifiers */
address,
contract,
t.1 AS address,
{contract:String} AS contract,

/* amounts */
toString(b.balance) AS amount,
b.balance / pow(10, m.decimals) AS value,
toString(t.2) AS amount,
t.2 / pow(10, m.decimals) AS value,

/* holder type */
toBool(t.1 IN (
SELECT address FROM {db_contracts:Identifier}.contracts
WHERE address IN (
SELECT arrayJoin(arrayMap(x -> x.1, (SELECT r FROM rows_arr)))
)
)) AS is_contract,

/* decimals and metadata */
nullIf(m.name, '') AS name,
nullIf(m.symbol, '') AS symbol,
m.decimals AS decimals,

/* network */
{network:String} as network
FROM balances b
{network:String} AS network
FROM (
SELECT arrayJoin((SELECT r FROM rows_arr)) AS t
) AS expanded
LEFT JOIN metadata.metadata AS m FINAL ON m.network = {network:String} AND {contract:String} = m.contract
ORDER BY b.balance DESC, address
ORDER BY t.2 DESC, t.1
8 changes: 7 additions & 1 deletion src/routes/holders/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ const responseSchema = apiUsageResponseSchema.extend({
amount: z.string(),
value: z.number(),

// -- holder type --
is_contract: z.boolean(),

// -- contract --
name: z.string().nullable(),
symbol: z.string().nullable(),
Expand Down Expand Up @@ -73,6 +76,7 @@ const openapi = describeRoute(
contract: '0xdac17f958d2ee523a2206206994597c13d831ec7',
amount: '20000000000000000',
value: 20000000000,
is_contract: false,
name: 'Tether USD',
symbol: 'USDT',
decimals: 6,
Expand All @@ -99,14 +103,16 @@ route.get('/', openapi, zValidator('query', querySchema, validatorHook), validat
const params = c.req.valid('query');

const dbBalances = config.balancesDatabases[params.network];
const dbContracts = config.contractsDatabases[params.network];

if (!dbBalances) {
if (!dbBalances || !dbContracts) {
return c.json({ error: `Network not found: ${params.network}` }, 400);
}

const response = await makeUsageQueryJson(c, [query], {
...params,
db_balances: dbBalances.database,
db_contracts: dbContracts.database,
});
return handleUsageQueryError(c, response);
});
Expand Down
54 changes: 36 additions & 18 deletions src/routes/holders/evm_native.sql
Original file line number Diff line number Diff line change
Expand Up @@ -21,37 +21,55 @@ addresses AS (
SELECT address FROM {db_balances:Identifier}.native_balances
WHERE balance >= (SELECT * FROM cutoff)
),
/* get the latest balance for each account */
balances AS (
SELECT address, argMax(balance, b.block_num) as balance, max(b.timestamp) as timestamp, max(block_num) as block_num
FROM {db_balances:Identifier}.native_balances b
WHERE address IN (SELECT address FROM addresses)
GROUP BY address
ORDER BY balance DESC, address
LIMIT {limit:UInt64}
OFFSET {offset:UInt64}
/* Materialize top-N balances once into a row array, then expand via arrayJoin.
The address-array is reused to pre-filter the contracts lookup via primary key,
avoiding the full-table hash build a plain JOIN would trigger. */
rows_arr AS (
SELECT groupArray(tuple(address, balance, timestamp, block_num)) AS r
FROM (
SELECT
address,
argMax(balance, b.block_num) AS balance,
max(b.timestamp) AS timestamp,
max(block_num) AS block_num
FROM {db_balances:Identifier}.native_balances b
WHERE address IN (SELECT address FROM addresses)
GROUP BY address
ORDER BY balance DESC, address
LIMIT {limit:UInt64} OFFSET {offset:UInt64}
)
)
SELECT
/* timestamps */
b.timestamp AS last_update,
b.block_num AS last_update_block_num,
toUnixTimestamp(b.timestamp) AS last_update_timestamp,
t.3 AS last_update,
t.4 AS last_update_block_num,
toUnixTimestamp(t.3) AS last_update_timestamp,

/* identifiers */
address,
t.1 AS address,

/* amounts */
toString(b.balance) AS amount,
b.balance / pow(10, m.decimals) AS value,
toString(t.2) AS amount,
t.2 / pow(10, m.decimals) AS value,

/* holder type */
toBool(t.1 IN (
SELECT address FROM {db_contracts:Identifier}.contracts
WHERE address IN (
SELECT arrayJoin(arrayMap(x -> x.1, (SELECT r FROM rows_arr)))
)
)) AS is_contract,

/* decimals and metadata */
nullIf(m.name, '') AS name,
nullIf(m.symbol, '') AS symbol,
m.decimals AS decimals,

/* network */
{network:String} as network
FROM balances b
{network:String} AS network
FROM (
SELECT arrayJoin((SELECT r FROM rows_arr)) AS t
) AS expanded
LEFT JOIN metadata.metadata AS m FINAL ON m.network = {network:String} AND '0x0000000000000000000000000000000000000000' = m.contract
ORDER BY b.balance DESC, address
ORDER BY t.2 DESC, t.1
SETTINGS use_skip_indexes_for_top_k = 1, use_top_k_dynamic_filtering = 1
8 changes: 7 additions & 1 deletion src/routes/holders/evm_native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ const responseSchema = apiUsageResponseSchema.extend({
amount: z.string(),
value: z.number(),

// -- holder type --
is_contract: z.boolean(),

// -- contract --
name: z.string().nullable(),
symbol: z.string().nullable(),
Expand Down Expand Up @@ -67,6 +70,7 @@ const openapi = describeRoute(
address: '0x00000000219ab540356cbb839cbe05303d7705fa',
amount: '78761803578844096172899779',
value: 78761803.5788441,
is_contract: true,
name: 'Ethereum',
symbol: 'ETH',
decimals: 18,
Expand All @@ -89,8 +93,9 @@ route.get('/', openapi, zValidator('query', querySchema, validatorHook), validat
const params = c.req.valid('query');

const dbBalances = config.balancesDatabases[params.network];
const dbContracts = config.contractsDatabases[params.network];

if (!dbBalances) {
if (!dbBalances || !dbContracts) {
return c.json({ error: `Network not found: ${params.network}` }, 400);
}

Expand All @@ -100,6 +105,7 @@ route.get('/', openapi, zValidator('query', querySchema, validatorHook), validat
{
...params,
db_balances: dbBalances.database,
db_contracts: dbContracts.database,
},
{
clickhouse_settings: {
Expand Down
4 changes: 2 additions & 2 deletions src/routes/routes.perf.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,8 +406,8 @@ const PERF_ROUTES: PerfRoute[] = [
lookup('/v1/evm/balances/historical/native', 'evm', ['balances'], `address=${EVM_ADDRESS_VITALIK_EXAMPLE}`),
lookup('/v1/svm/balances', 'svm', ['balances'], `owner=${SVM_OWNER_USER_EXAMPLE}`),
lookup('/v1/svm/balances/native', 'svm', ['balances'], `address=${SVM_ADDRESS_OWNER_EXAMPLE}`),
lookup('/v1/evm/holders', 'evm', ['balances'], (n) => `contract=${getEvmExamples(n).contract}`),
lookup('/v1/evm/holders/native', 'evm', ['balances']),
lookup('/v1/evm/holders', 'evm', ['balances', 'contracts'], (n) => `contract=${getEvmExamples(n).contract}`),
lookup('/v1/evm/holders/native', 'evm', ['balances', 'contracts']),
lookup('/v1/svm/holders', 'svm', ['balances'], `mint=${SVM_MINT_WSOL_EXAMPLE}`),
lookup('/v1/evm/dexes', 'evm', ['dex'], '', BUDGET.heavyLookup),
lookup('/v1/svm/dexes', 'svm', ['dex']),
Expand Down
Loading