Skip to content

Commit

Permalink
Add top holder endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
prevostc committed Dec 18, 2024
1 parent 0499969 commit 2e12b78
Show file tree
Hide file tree
Showing 5 changed files with 268 additions and 134 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ http://localhost:4000/api/v1/status
https://balance-api.beefy.finance/api/v1/holders/counts/all
http://localhost:4000/api/v1/holders/counts/all
https://balance-api.beefy.finance/api/v1/vault/arbitrum/top-holders?vault_addresses=0x0481ad5b536139472af5ce692330dbf00bbd8672&vault_addresses=0x0d1f71170d93121b48a9e8fc7400e8e6a6821500&limit=10
http://localhost:4000/api/v1/vault/arbitrum/top-holders?vault_addresses=0x0481ad5b536139472af5ce692330dbf00bbd8672&vault_addresses=0x0d1f71170d93121b48a9e8fc7400e8e6a6821500&limit=10
https://balance-api.beefy.finance/api/v1/vault/base/baseswap-cow-weth-cbbtc/20449610/share-tokens-balances
http://localhost:4000/api/v1/vault/base/baseswap-cow-weth-cbbtc/20449610/share-tokens-balances
Expand Down
32 changes: 32 additions & 0 deletions src/queries/LatestVaultSharesBalances.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
query LatestVaultSharesBalances(
$token_in_1: [Bytes!]!
$token_in_2: [String!]!
$account_not_in: [String!]!
$tokenFirst: Int = 1000
$tokenSkip: Int = 0
$first: Int = 1000
$skip: Int = 0
) {
tokens(first: $tokenFirst, skip: $tokenSkip, where: { id_in: $token_in_1 }) {
id
name
decimals
symbol

balances(
orderBy: id
orderDirection: desc
first: $first
skip: $skip
where: {
amount_gt: 0
account_not_in: $account_not_in
}
) {
account {
id
}
amount
}
}
}
41 changes: 20 additions & 21 deletions src/queries/VaultSharesBalances.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,32 @@ query VaultSharesBalances(
$token_in_2: [String!]!
$account_not_in: [String!]!
$block: Int
$tokenFirst: Int = 1000
$tokenSkip: Int = 0
$first: Int = 1000
$skip: Int = 0
) {
tokens(first: $first, skip: $skip, where: { id_in: $token_in_1 }) {
tokens(first: $tokenFirst, skip: $tokenSkip, where: { id_in: $token_in_1 }) {
id
name
decimals
symbol
}
tokenBalances(
block: { number: $block }
orderBy: id
orderDirection: desc
first: $first
skip: $skip
where: {
amount_gt: 0
token_in: $token_in_2
account_not_in: $account_not_in
}
) {
token {
id
}
account {
id

balances(
orderBy: id
orderDirection: desc
first: $first
skip: $skip
where: {
amount_gt: 0
token_in: $token_in_2
account_not_in: $account_not_in
}
) {
account {
id
}
amount
}
amount
}
}
}
78 changes: 41 additions & 37 deletions src/routes/v1/contract.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { type Static, Type } from '@sinclair/typebox';
import type { FastifyInstance, FastifyPluginOptions, FastifySchema } from 'fastify';
import { min } from 'lodash';
import type { Hex } from 'viem';
import { type ChainId, chainIdSchema } from '../../config/chains';
import { addressSchema } from '../../schema/address';
Expand Down Expand Up @@ -87,48 +88,51 @@ const getContractHolders = async (
await Promise.all(
getSdksForChain(chainId).map(sdk =>
paginate({
fetchPage: ({ skip, first }) =>
sdk.VaultSharesBalances({
skip,
first,
block: Number(block),
account_not_in: ['0x0000000000000000000000000000000000000000'], // empty list returns nothing
token_in_1: [contract_address],
token_in_2: [contract_address],
fetchPage: ({ skip: tokenSkip, first: tokenFirst }) =>
paginate({
fetchPage: ({ skip, first }) =>
sdk.VaultSharesBalances({
tokenSkip,
tokenFirst,
skip,
first,
block: Number(block),
account_not_in: ['0x0000000000000000000000000000000000000000'], // empty list returns nothing
token_in_1: [contract_address],
token_in_2: [contract_address],
}),
count: res => min(res.data.tokens.map(token => token.balances.length)) ?? 0,
}),
count: res => res.data.tokenBalances.length,
count: res => min(res.map(chainRes => chainRes.data.tokens.length)) ?? 0,
})
)
)
).flat();

return res.flatMap(chainRes => {
const tokens = chainRes.data.tokens;
const balances = chainRes.data.tokenBalances;
return res.flatMap(chainRes =>
chainRes.flatMap(tokenPage =>
tokenPage.data.tokens.map(token => {
if (!token.symbol) {
throw new Error(`Token ${token.id} has no symbol`);
}
if (!token.decimals) {
throw new Error(`Token ${token.id} has no decimals`);
}
if (!token.name) {
throw new Error(`Token ${token.id} has no name`);
}

return tokens.map(token => {
const tokenBalances = balances.filter(balance => balance.token.id === token.id);

if (!token.symbol) {
throw new Error(`Token ${token.id} has no symbol`);
}
if (!token.decimals) {
throw new Error(`Token ${token.id} has no decimals`);
}
if (!token.name) {
throw new Error(`Token ${token.id} has no name`);
}

return {
id: token.id,
name: token.name,
symbol: token.symbol,
decimals: Number.parseInt(token.decimals, 10),
balances: tokenBalances.map(balance => ({
balance: balance.amount,
holder: balance.account.id,
})),
};
});
});
return {
id: token.id,
name: token.name,
symbol: token.symbol,
decimals: Number.parseInt(token.decimals, 10),
balances: token.balances.map(balance => ({
balance: balance.amount,
holder: balance.account.id,
})),
};
})
)
);
};
Loading

0 comments on commit 2e12b78

Please sign in to comment.