Skip to content

Commit

Permalink
Update top-holder endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
prevostc committed Dec 19, 2024
1 parent d80de6f commit 054be7d
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 130 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ 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/contract/arbitrum/top-holders?contract_addresses=0x0481ad5b536139472af5ce692330dbf00bbd8672&contract_addresses=0x0d1f71170d93121b48a9e8fc7400e8e6a6821500&limit=10
http://localhost:4000/api/v1/contract/arbitrum/top-holders?contract_addresses=0x0481ad5b536139472af5ce692330dbf00bbd8672&contract_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: 0 additions & 32 deletions src/queries/LatestVaultSharesBalances.graphql

This file was deleted.

8 changes: 5 additions & 3 deletions src/queries/VaultSharesBalances.graphql
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
query VaultSharesBalances(
query TokenBalance(
$token_in_1: [Bytes!]!
$token_in_2: [String!]!
$account_not_in: [String!]!
Expand All @@ -7,6 +7,8 @@ query VaultSharesBalances(
$tokenSkip: Int = 0
$first: Int = 1000
$skip: Int = 0
$orderBy: TokenBalance_orderBy = id
$orderDirection: OrderDirection = desc
) {
tokens(first: $tokenFirst, skip: $tokenSkip, where: { id_in: $token_in_1 }) {
id
Expand All @@ -15,8 +17,8 @@ query VaultSharesBalances(
symbol

balances(
orderBy: id
orderDirection: desc
orderBy: $orderBy
orderDirection: $orderDirection
first: $first
skip: $skip
where: {
Expand Down
99 changes: 98 additions & 1 deletion src/routes/v1/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { FastifyInstance, FastifyPluginOptions, FastifySchema } from 'fasti
import { min } from 'lodash';
import type { Hex } from 'viem';
import { type ChainId, chainIdSchema } from '../../config/chains';
import { OrderDirection, TokenBalance_OrderBy } from '../../queries/codegen/sdk';
import { addressSchema } from '../../schema/address';
import { bigintSchema } from '../../schema/bigint';
import { getAsyncCache } from '../../utils/async-lock';
Expand Down Expand Up @@ -65,6 +66,53 @@ export default async function (
);
}

// all holder count list for all chains
{
const urlParamsSchema = Type.Object({
chain: chainIdSchema,
});
type UrlParams = Static<typeof urlParamsSchema>;

const querySchema = Type.Object({
contract_addresses: Type.Array(addressSchema, { minItems: 1, maxItems: 100 }),
limit: Type.Number({ default: 100, minimum: 1, maximum: 1000 }),
});
type QueryParams = Static<typeof querySchema>;

const schema: FastifySchema = {
tags: ['contract'],
params: urlParamsSchema,
querystring: querySchema,
response: {
200: contractHoldersSchema,
},
};

instance.get<{ Params: UrlParams; Querystring: QueryParams }>(
'/:chain/top-holders',
{ schema },
async (request, reply) => {
const { chain } = request.params;
const { contract_addresses, limit } = request.query;

if (
!contract_addresses ||
!Array.isArray(contract_addresses) ||
contract_addresses.length === 0
) {
throw new Error('contract_addresses is required');
}

const result = await asyncCache.wrap(
`vault:${chain}:${contract_addresses.join(',')}:top-holders:${limit}`,
5 * 60 * 1000,
async () => await getTopHolders(chain, contract_addresses as Hex[], limit)
);
reply.send(result);
}
);
}

done();
}

Expand All @@ -91,7 +139,7 @@ const getContractHolders = async (
fetchPage: ({ skip: tokenSkip, first: tokenFirst }) =>
paginate({
fetchPage: ({ skip, first }) =>
sdk.VaultSharesBalances({
sdk.TokenBalance({
tokenSkip,
tokenFirst,
skip,
Expand Down Expand Up @@ -136,3 +184,52 @@ const getContractHolders = async (
)
);
};

const getTopHolders = async (chainId: ChainId, vault_addresses: Hex[], limit: number) => {
const res = (
await Promise.all(
getSdksForChain(chainId).map(sdk =>
paginate({
fetchPage: ({ skip: tokenSkip, first: tokenFirst }) =>
sdk.TokenBalance({
tokenSkip,
tokenFirst,
skip: 0,
first: limit,
account_not_in: ['0x0000000000000000000000000000000000000000'], // providing an empty account_not_in will return 0 holders
token_in_1: vault_addresses,
token_in_2: vault_addresses,
orderBy: TokenBalance_OrderBy.Amount,
orderDirection: OrderDirection.Desc,
}),
count: res => res.data.tokens.length,
})
)
)
).flat();

return res.flatMap(chainRes =>
chainRes.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 {
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,
})),
};
})
);
};
94 changes: 2 additions & 92 deletions src/routes/v1/vault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,49 +19,6 @@ export default async function (
) {
const asyncCache = getAsyncCache();

// all holder count list for all chains
{
const urlParamsSchema = Type.Object({
chain: chainIdSchema,
});
type UrlParams = Static<typeof urlParamsSchema>;

const querySchema = Type.Object({
vault_addresses: Type.Array(addressSchema, { minItems: 1, maxItems: 100 }),
limit: Type.Number({ default: 100, minimum: 1, maximum: 1000 }),
});
type QueryParams = Static<typeof querySchema>;

const schema: FastifySchema = {
tags: ['vault'],
params: urlParamsSchema,
querystring: querySchema,
response: {
200: vaultHoldersSchema,
},
};

instance.get<{ Params: UrlParams; Querystring: QueryParams }>(
'/:chain/top-holders',
{ schema },
async (request, reply) => {
const { chain } = request.params;
const { vault_addresses, limit } = request.query;

if (!vault_addresses || !Array.isArray(vault_addresses) || vault_addresses.length === 0) {
throw new Error('vault_addresses is required');
}

const result = await asyncCache.wrap(
`vault:${chain}:${vault_addresses.join(',')}:top-holders:${limit}`,
5 * 60 * 1000,
async () => await getTopHolders(chain, vault_addresses as Hex[], limit)
);
reply.send(result);
}
);
}

// all holder count list for all chains
{
const urlParamsSchema = Type.Object({
Expand Down Expand Up @@ -294,7 +251,7 @@ const getVaultHolders = async (
fetchPage: ({ skip: tokenSkip, first: tokenFirst }) =>
paginate({
fetchPage: ({ skip, first }) =>
sdk.VaultSharesBalances({
sdk.TokenBalance({
tokenSkip,
tokenFirst,
skip,
Expand Down Expand Up @@ -435,7 +392,7 @@ const _getVaultHoldersAsBaseVaultEquivalent = async (
fetchPage: ({ skip: tokenSkip, first: tokenFirst }) =>
paginate({
fetchPage: ({ skip, first }) =>
sdk.VaultSharesBalances({
sdk.TokenBalance({
tokenSkip,
tokenFirst,
skip,
Expand Down Expand Up @@ -639,50 +596,3 @@ const _getVaultHoldersAsBaseVaultEquivalent = async (
}));
return mergedHolders;
};

const getTopHolders = async (chainId: ChainId, vault_addresses: Hex[], limit: number) => {
const res = (
await Promise.all(
getSdksForChain(chainId).map(sdk =>
paginate({
fetchPage: ({ skip: tokenSkip, first: tokenFirst }) =>
sdk.VaultSharesBalances({
tokenSkip,
tokenFirst,
skip: 0,
first: limit,
account_not_in: ['0x0000000000000000000000000000000000000000'], // providing an empty account_not_in will return 0 holders
token_in_1: vault_addresses,
token_in_2: vault_addresses,
}),
count: res => res.data.tokens.length,
})
)
)
).flat();

return res.flatMap(chainRes =>
chainRes.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 {
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,
})),
};
})
);
};

0 comments on commit 054be7d

Please sign in to comment.