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
26 changes: 26 additions & 0 deletions public/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,32 @@
}
}
},
"/api/v1/{network}/token/extendedstats": {
"get": {
"tags": [
"Token"
],
"description": "Retrieves token statistics for a given network.",
"parameters": [
{
"name": "network",
"in": "path",
"required": true,
"type": "string",
"description": "The network name. Supported networks: astar, shiden, shibuya, rocstar",
"enum": [
"astar",
"shiden"
]
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/api/{network}/token/circulation": {
"get": {
"tags": [
Expand Down
2 changes: 1 addition & 1 deletion src/client/BaseApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ export class BaseApi implements IAstarApi {

return await localApi.isReadyOrError.then(
(api: ApiPromise) => {
// Connection suceed
// Connection succeed
this._api = api;
return api;
},
Expand Down
21 changes: 21 additions & 0 deletions src/controllers/TokenStatsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,27 @@ export class TokenStatsController extends ControllerBase implements IControllerB
}
});

/**
* @description Token extended statistics route v1.
*/
app.route('/api/v1/:network/token/extendedstats').get(async (req: Request, res: Response) => {
/*
#swagger.description = 'Retrieves token statistics for a given network.'
#swagger.tags = ['Token']
#swagger.parameters['network'] = {
in: 'path',
description: 'The network name. Supported networks: astar, shiden, shibuya, rocstar',
required: true,
enum: ['astar', 'shiden']
}
*/
try {
res.json(await this._statsService.getTokenStatsExtended(req.params.network as NetworkType));
} catch (err) {
this.handleError(res, err as Error);
}
});

/**
* @description Test route
*/
Expand Down
68 changes: 66 additions & 2 deletions src/services/StatsService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { PalletBalancesAccountData } from '@polkadot/types/lookup';
import { formatBalance, BN } from '@polkadot/util';
import { injectable, inject } from 'inversify';
import { IApiFactory } from '../client/ApiFactory';
Expand All @@ -10,15 +9,29 @@ import { AccountData } from '../models/AccountData';
import { Guard } from '../guard';
import { DappStakingV3IndexerBase } from './DappStakingV3IndexerBase';
import axios from 'axios';
import { IPriceProvider } from './IPriceProvider';

export type TotalSupply = {
block: number;
timestamp: number;
balance: bigint;
};

export type ExtendedTokenStats = {
symbol: string;
currencyCode: string;
marketCap: number;
circulatingSupply: number;
maxSupply: number;
provider: string;
lastUpdatedTimestamp: number;
accTradePrice24h: number | null;
price: number;
};

export interface IStatsService {
getTokenStats(network: NetworkType): Promise<TokenStats>;
getTokenStatsExtended(network: NetworkType): Promise<ExtendedTokenStats>;
getTotalSupply(network: NetworkType): Promise<number>;
getTotalIssuanceHistory(network: NetworkType): Promise<TotalSupply[]>;
}
Expand All @@ -28,7 +41,10 @@ export interface IStatsService {
* Token statistics calculation service.
*/
export class StatsService extends DappStakingV3IndexerBase implements IStatsService {
constructor(@inject(ContainerTypes.ApiFactory) private _apiFactory: IApiFactory) {
constructor(
@inject(ContainerTypes.ApiFactory) private _apiFactory: IApiFactory,
@inject(ContainerTypes.PriceProviderWithFailover) private _priceProvider: IPriceProvider,
) {
super();
}

Expand Down Expand Up @@ -62,6 +78,54 @@ export class StatsService extends DappStakingV3IndexerBase implements IStatsServ
}
}

/**
* Calculates token circulation supply by substracting sum of all token holder accounts
* not in circulation from total token supply.
* @param network NetworkType (astar or shiden) to calculate token supply for.
* @returns Token statistics including total supply and circulating supply.
*/
public async getTokenStatsExtended(network: NetworkType): Promise<ExtendedTokenStats> {
if (network !== 'astar' && network !== 'shiden') {
throw new Error(`This method is not supported for the network ${network}`);
}

try {
const currency = 'usd';

const api = this._apiFactory.getApiInstance(network);
const apiClient = await api.getApiPromise();

const chainTokens = apiClient.registry.chainTokens;
const tokenSymbol = chainTokens[0];

const [chainDecimals, totalSupply, balancesToExclude, price] = await Promise.all([
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we get symbol as well? using api.registry.chainTokens

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, fixed

api.getChainDecimals(),
api.getTotalSupply(),
api.getBalances(addressesToExclude),
this._priceProvider.getPrice(tokenSymbol.toLowerCase(), currency),
]);

const totalBalancesToExclude = this.getTotalBalanceToExclude(balancesToExclude);
const circulatingSupplyWei = totalSupply.sub(totalBalancesToExclude);
const circulatingSupply = this.formatBalance(circulatingSupplyWei, chainDecimals);

return {
symbol: tokenSymbol,
currencyCode: currency.toUpperCase(),
price,
marketCap: circulatingSupply * price,
accTradePrice24h: null,
circulatingSupply,
maxSupply: this.formatBalance(totalSupply, chainDecimals),
provider: 'Stake Technologies Pte Ltd',
lastUpdatedTimestamp: Date.now(),
};
} catch (e) {
console.error(e);
throw new Error('Unable to fetch token statistics from a node.');
}
}

public async getTotalSupply(network: NetworkType): Promise<number> {
Guard.ThrowIfUndefined(network, 'network');
this.GuardNetwork(network);
Expand Down