diff --git a/src/api/bigDipperApi.ts b/src/api/bigDipperApi.ts index 2546bf1d..6c67c2ec 100644 --- a/src/api/bigDipperApi.ts +++ b/src/api/bigDipperApi.ts @@ -1,58 +1,81 @@ import { GraphQLClient } from "../helpers/graphql"; import { Account } from "../types/bigDipper"; -import { Coin } from "../types/node"; +import { Coin, ValidatorAggregateCountResponse } from "../types/node"; export class BigDipperApi { - constructor(public readonly graphql_client: GraphQLClient) { - } - - async get_accounts(addresses: string[]): Promise { - let query = "query Account($addresses: [String], $utc: timestamp) {\n" + - " account(where: { address: { _in: $addresses } }) {\n" + - " address\n" + - " accountBalances: account_balances(limit: 1, order_by: { height: desc }) {\n" + - " coins\n" + - " }\n" + - " delegations {\n" + - " amount\n" + - " }\n" + - " unbonding: unbonding_delegations(\n" + - " where: { completion_timestamp: { _gt: $utc } }\n" + - " ) {\n" + - " amount\n" + - " }\n" + - " redelegations(where: { completion_time: { _gt: $utc } }) {\n" + - " amount\n" + - " }\n" + - " delegationRewards: delegation_rewards {\n" + - " amount\n" + - " }\n" + - " }\n" + - "}\n"; - - let params = { - utc: new Date(), - addresses - } - - let resp = await this.graphql_client.query<{ account: Account[] }>(query, params); - return resp.account; - } - - async get_account(address: string): Promise { - let accounts = await this.get_accounts([address]); - return accounts[0]; - } - - async get_total_supply(): Promise { - let query = "query Supply {\n" + - " supply(order_by: {height:desc} limit: 1) {\n" + - " coins\n" + - " height\n" + - " }\n" + - "}\n"; - - let resp = await this.graphql_client.query<{ supply: { coins: Coin[] }[] }>(query); - return resp.supply[0].coins; - } + constructor(public readonly graphql_client: GraphQLClient) { + } + + async get_accounts(addresses: string[]): Promise { + let query = `query Account($addresses: [String], $utc: timestamp) { + account(where: { address: { _in: $addresses } }) { + address + accountBalances: account_balances(limit: 1, order_by: { height: desc }) { + coins + } + delegations { + amount + } + unbonding: unbonding_delegations( + where: { completion_timestamp: { _gt: $utc } } + ) { + amount + } + redelegations(where: { completion_time: { _gt: $utc } }) { + amount + } + delegationRewards: delegation_rewards { + amount + } + } + }` + + let params = { + utc: new Date(), + addresses + } + + let resp = await this.graphql_client.query<{ account: Account[] }>(query, params); + return resp.account; + } + + async get_account(address: string): Promise { + let accounts = await this.get_accounts([address]); + return accounts[0]; + } + + async get_total_supply(): Promise { + let query = `query Supply { + supply(order_by: {height:desc} limit: 1) { + coins + height + } + }`; + + let resp = await this.graphql_client.query<{ supply: { coins: Coin[] }[] }>(query); + return resp.supply[0].coins; + } + + get_delegator_count_for_validator = async (address: string): Promise => { + let query = `query ValidatorDetails($address: String) { + validator(where: {validator_info: {operator_address: {_eq: $address}}}) { + delegations_aggregate { + aggregate { + count + } + } + } + }` + + const params = { + address: address, + } + + const resp = await this.graphql_client.query(query, params); + if (!resp.validator || !resp.validator.length) { + return 0; + } + + return resp.validator[0].delegations_aggregate.aggregate.count; + } } diff --git a/src/handlers/delegatorCount.ts b/src/handlers/delegatorCount.ts new file mode 100644 index 00000000..5f18c7f3 --- /dev/null +++ b/src/handlers/delegatorCount.ts @@ -0,0 +1,17 @@ +import { Request } from "itty-router"; +import { BigDipperApi } from "../api/bigDipperApi"; +import { GraphQLClient } from "../helpers/graphql"; + +export async function handler(request: Request): Promise { + const address = request.params?.['validator_address']; + + if (!address) { + throw new Error("No address specified or wrong address format."); + } + + let gql_client = new GraphQLClient(GRAPHQL_API); + let bd_api = new BigDipperApi(gql_client); + + let delegators = await bd_api.get_delegator_count_for_validator(address); + return new Response(delegators.toString()); +} diff --git a/src/index.ts b/src/index.ts index d25dc1e5..851f9098 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,27 +5,30 @@ import { handler as circulatingSupplyHandler } from "./handlers/circulatingSuppl import { handler as liquidBalanceHandler } from "./handlers/liquidBalance"; import { handler as vestingBalanceHandler } from "./handlers/vestingBalance"; import { handler as vestedBalanceHandler } from "./handlers/vestedBalance"; +import { handler as delegatorCount } from './handlers/delegatorCount'; addEventListener('fetch', (event: FetchEvent) => { - const router = Router() - registerRoutes(router); + const router = Router() + registerRoutes(router); - event.respondWith(router.handle(event.request).catch(handleError)) + event.respondWith(router.handle(event.request).catch(handleError)) }) function registerRoutes(router: Router) { - router.get('/', totalSupplyHandler); - router.get('/supply/total', totalSupplyHandler); - router.get('/supply/circulating', circulatingSupplyHandler); - router.get('/balances/total/:address', totalBalanceHandler); - router.get('/balances/liquid/:address', liquidBalanceHandler); - router.get('/balances/vesting/:address', vestingBalanceHandler); - router.get('/balances/vested/:address', vestedBalanceHandler); + router.get('/', totalSupplyHandler); + router.get('/supply/total', totalSupplyHandler); + router.get('/supply/circulating', circulatingSupplyHandler); + router.get('/balances/total/:address', totalBalanceHandler); + router.get('/balances/liquid/:address', liquidBalanceHandler); + router.get('/balances/vesting/:address', vestingBalanceHandler); + router.get('/balances/vested/:address', vestedBalanceHandler); + router.get('/staking/delegators/:validator_address', delegatorCount); - // 404 for all other requests - router.all('*', () => new Response('Not Found.', {status: 404})) + + // 404 for all other requests + router.all('*', () => new Response('Not Found.', { status: 404 })) } function handleError(error: Error): Response { - return new Response(error.message || 'Server Error', {status: 500}) + return new Response(error.message || 'Server Error', { status: 500 }) } diff --git a/src/types/node.ts b/src/types/node.ts index b451024f..e07193ec 100644 --- a/src/types/node.ts +++ b/src/types/node.ts @@ -1,27 +1,49 @@ -export type Account ={ - '@type': string; - start_time: number; - base_vesting_account: { base_account: BaseAccount, original_vesting: Coin[], delegated_free?: Coin[], delegated_vesting?: Coin[], end_time: number }; +export type Account = { + '@type': string; + start_time: number; + base_vesting_account: { base_account: BaseAccount, original_vesting: Coin[], delegated_free?: Coin[], delegated_vesting?: Coin[], end_time: number }; } export type BaseAccount = { - address: string; - pub_key: PublicKey; - account_number: string; - sequence: string; + address: string; + pub_key: PublicKey; + account_number: string; + sequence: string; } export type PublicKey = { - '@type': string; - key: string; + '@type': string; + key: string; } export class Coin { - public denom: string; - public amount: string; + public denom: string; + public amount: string; - constructor(denom: string, amount: string) { - this.denom = denom; - this.amount = amount; - } + constructor(denom: string, amount: string) { + this.denom = denom; + this.amount = amount; + } +} + +export class Delegation { + public amount: Coin; + public delegatorAddress: string; + + constructor(amount: Coin, delegatorAddress: string) { + this.delegatorAddress = delegatorAddress; + this.amount = amount; + } +} + +export interface ValidatorAggregateCountResponse { + validator: [ + { + delegations_aggregate: { + aggregate: { + count: number + } + } + } + ] }