diff --git a/public/swagger.json b/public/swagger.json index 74906ec..6c1545f 100644 --- a/public/swagger.json +++ b/public/swagger.json @@ -76,6 +76,34 @@ } } }, + "/api/{network}/token/circulation": { + "get": { + "tags": [ + "Token" + ], + "description": "Retrieves a token circulation supply 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", + "shibuya", + "rocstar" + ] + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/api/v1/{network}/token/circulation": { "get": { "tags": [ @@ -104,6 +132,34 @@ } } }, + "/api/v1/{network}/token/supply": { + "get": { + "tags": [ + "Token" + ], + "description": "Retrieves a token total supply 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", + "shibuya", + "rocstar" + ] + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/api/v1/{network}/token/price/{period}": { "get": { "tags": [ diff --git a/src/controllers/TokenStatsController.ts b/src/controllers/TokenStatsController.ts index 7e1efe1..61645a6 100644 --- a/src/controllers/TokenStatsController.ts +++ b/src/controllers/TokenStatsController.ts @@ -28,7 +28,7 @@ export class TokenStatsController extends ControllerBase implements IControllerB #swagger.description = 'Test endpoint, returns token stats for Astar Network' */ try { - res.json(await this._statsService.getTokenStats()); + res.json(await this._statsService.getTokenStats('astar')); } catch (err) { this.handleError(res, err as Error); } @@ -98,7 +98,7 @@ export class TokenStatsController extends ControllerBase implements IControllerB #swagger.ignore = true */ try { - res.json(await (await this._statsService.getTokenStats()).circulatingSupply); + res.json(await (await this._statsService.getTokenStats('astar')).circulatingSupply); } catch (err) { this.handleError(res, err as Error); } @@ -109,7 +109,14 @@ export class TokenStatsController extends ControllerBase implements IControllerB */ app.route('/api/:network/token/circulation').get(async (req: Request, res: Response) => { /* - #swagger.ignore = true + #swagger.description = 'Retrieves a token circulation supply 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', 'shibuya', 'rocstar'] + } */ try { res.json( @@ -147,6 +154,27 @@ export class TokenStatsController extends ControllerBase implements IControllerB } }); + /** + * @description Token total supply. + */ + app.route('/api/v1/:network/token/supply').get(async (req: Request, res: Response) => { + /* + #swagger.description = 'Retrieves a token total supply 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', 'shibuya', 'rocstar'] + } + */ + try { + res.json(await this._statsService.getTotalSupply(req.params.network as NetworkType)); + } catch (err) { + this.handleError(res, err as Error); + } + }); + /** * @description Token price route v1. */ diff --git a/src/services/StatsService.ts b/src/services/StatsService.ts index ade2bcf..4217c82 100644 --- a/src/services/StatsService.ts +++ b/src/services/StatsService.ts @@ -7,9 +7,11 @@ import { TokenStats } from '../models/TokenStats'; import { NetworkType } from '../networks'; import { addressesToExclude } from './AddressesToExclude'; import { AccountData } from '../models/AccountData'; +import { Guard } from '../guard'; export interface IStatsService { - getTokenStats(network?: NetworkType): Promise; + getTokenStats(network: NetworkType): Promise; + getTotalSupply(network: NetworkType): Promise; } @injectable() @@ -22,10 +24,13 @@ export class StatsService implements IStatsService { /** * Calculates token circulation supply by substracting sum of all token holder accounts * not in circulation from total token supply. - * @param network Network (astar or shiden) to calculate token supply for. + * @param network NetworkType (astar or shiden) to calculate token supply for. * @returns Token statistics including total supply and circulating supply. */ - public async getTokenStats(network = 'astar'): Promise { + public async getTokenStats(network: NetworkType): Promise { + Guard.ThrowIfUndefined(network, 'network'); + this.throwIfNetworkIsNotSupported(network); + try { const api = this._apiFactory.getApiInstance(network); const chainDecimals = await api.getChainDecimals(); @@ -46,6 +51,22 @@ export class StatsService implements IStatsService { } } + public async getTotalSupply(network: NetworkType): Promise { + Guard.ThrowIfUndefined(network, 'network'); + this.throwIfNetworkIsNotSupported(network); + + try { + const api = this._apiFactory.getApiInstance(network); + const chainDecimals = await api.getChainDecimals(); + const totalSupply = await api.getTotalSupply(); + + return this.formatBalance(totalSupply, chainDecimals); + } catch (e) { + console.error(e); + throw new Error('Unable to fetch token total supply from a node.'); + } + } + private getTotalBalanceToExclude(balances: AccountData[]): BN { const sum = balances .map((balance) => { @@ -61,4 +82,10 @@ export class StatsService implements IStatsService { return parseInt(result.replaceAll(',', '')); } + + private throwIfNetworkIsNotSupported(network: NetworkType): void { + if (network !== 'astar' && network !== 'shiden' && network !== 'shibuya' && network !== 'rocstar') { + throw new Error(`Network ${network} is not supported.`); + } + } } diff --git a/tests/services/StatsService.test.ts b/tests/services/StatsService.test.ts index 391cebc..a0df467 100644 --- a/tests/services/StatsService.test.ts +++ b/tests/services/StatsService.test.ts @@ -5,7 +5,7 @@ import { AstarApiMock } from './AstarApiMock'; /** * Tests are writen adhering to AAA (Arange, Act, Assert) pattern. */ -describe('getTokeStats', () => { +describe('getTokenStats', () => { let apiFactory: IApiFactory; beforeEach(() => { @@ -16,7 +16,7 @@ describe('getTokeStats', () => { it('calculates circulating supply', async () => { const service = new StatsService(apiFactory); - const result = await service.getTokenStats(''); + const result = await service.getTokenStats('astar'); expect(result.circulatingSupply).toBe(83); }); @@ -24,7 +24,7 @@ describe('getTokeStats', () => { it('returns valid total supply', async () => { const service = new StatsService(apiFactory); - const result = await service.getTokenStats(''); + const result = await service.getTokenStats('astar'); expect(result.totalSupply).toBe(100); });