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
129 changes: 76 additions & 53 deletions src/api/bigDipperApi.ts
Original file line number Diff line number Diff line change
@@ -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<Account[]> {
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<Account> {
let accounts = await this.get_accounts([address]);
return accounts[0];
}

async get_total_supply(): Promise<Coin[]> {
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<Account[]> {
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<Account> {
let accounts = await this.get_accounts([address]);
return accounts[0];
}

async get_total_supply(): Promise<Coin[]> {
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<Number> => {
let query = `query ValidatorDetails($address: String) {
Comment thread
ankurdotb marked this conversation as resolved.
validator(where: {validator_info: {operator_address: {_eq: $address}}}) {
delegations_aggregate {
aggregate {
count
}
}
}
}`

const params = {
address: address,
}

const resp = await this.graphql_client.query<ValidatorAggregateCountResponse>(query, params);
if (!resp.validator || !resp.validator.length) {
return 0;
}

return resp.validator[0].delegations_aggregate.aggregate.count;
}
}
17 changes: 17 additions & 0 deletions src/handlers/delegatorCount.ts
Original file line number Diff line number Diff line change
@@ -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<Response> {
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());
}
29 changes: 16 additions & 13 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Request, IHTTPMethods>()
registerRoutes(router);
const router = Router<Request, IHTTPMethods>()
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 })
}
54 changes: 38 additions & 16 deletions src/types/node.ts
Original file line number Diff line number Diff line change
@@ -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
}
}
}
]
}