From 4797aec0bbfbeaeb820cf245c5b605c7a683fe64 Mon Sep 17 00:00:00 2001 From: Gregory Luneau Date: Wed, 31 Jan 2024 08:36:32 -0500 Subject: [PATCH] feat: stake amount for one participant (#131) * draft of stake info * entries * period check * evmToNative and etmToAddress * proper types --- public/swagger.json | 34 +++++++++ src/client/BaseApi.ts | 85 ++++++++++++++++++++- src/controllers/DappsStakingV3Controller.ts | 24 ++++++ src/services/DappsStakingEvents.ts | 24 +++++- 4 files changed, 164 insertions(+), 3 deletions(-) diff --git a/public/swagger.json b/public/swagger.json index d38ff81..18c4425 100644 --- a/public/swagger.json +++ b/public/swagger.json @@ -813,6 +813,40 @@ } } }, + "/api/v3/{network}/dapps-staking/stake-info/{address}": { + "get": { + "tags": [ + "Dapps Staking" + ], + "description": "Retrieves the amount of stake of participant", + "parameters": [ + { + "name": "network", + "in": "path", + "required": true, + "type": "string", + "description": "The network name. Supported networks: astar, shiden, shibuya", + "enum": [ + "astar", + "shiden", + "shibuya" + ] + }, + { + "name": "address", + "in": "path", + "required": true, + "type": "string", + "description": "Participant address to get stats for" + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/api/v3/{network}/dapps-staking/stats/dapp/{contractAddress}": { "get": { "tags": [ diff --git a/src/client/BaseApi.ts b/src/client/BaseApi.ts index f8b6dad..716f115 100644 --- a/src/client/BaseApi.ts +++ b/src/client/BaseApi.ts @@ -1,9 +1,11 @@ // TODO remove use of any /* eslint-disable @typescript-eslint/no-explicit-any */ import { ApiPromise, WsProvider } from '@polkadot/api'; -import { isEthereumAddress, checkAddress, decodeAddress, encodeAddress } from '@polkadot/util-crypto'; +import { isEthereumAddress, checkAddress, decodeAddress, encodeAddress, evmToAddress } from '@polkadot/util-crypto'; +import { ASTAR_SS58_FORMAT } from '../services/TxQueryService'; import { hexToU8a, isHex } from '@polkadot/util'; -import { u32, u128, Option, Struct, Enum } from '@polkadot/types'; +import { u32, u128, Option, Struct, Enum, Compact, bool, StorageKey } from '@polkadot/types'; +import { AnyTuple, Codec } from '@polkadot/types/types'; import { Header, AccountId, DispatchError, Call } from '@polkadot/types/interfaces'; import { SubmittableExtrinsic } from '@polkadot/api/types'; import { ISubmittableResult, ITuple } from '@polkadot/types/types'; @@ -14,6 +16,15 @@ import { networks } from '../networks'; import { EraRewardAndStake } from '../types/DappsStaking'; import { AccountData } from '../models/AccountData'; +declare global { + interface BigInt { + toJSON: () => string; + } +} +BigInt.prototype.toJSON = function () { + return this.toString(); +}; + export interface DappInfo extends Struct { developer: AccountId; state: string; @@ -24,6 +35,37 @@ export interface DappInfoV3 extends Struct { state: string; } +export interface PalletDappStakingV3ProtocolState extends Struct { + era: Compact; + nextEraStart: Compact; + periodInfo: PalletDappStakingV3PeriodInfo; + maintenance: bool; +} + +interface PalletDappStakingV3PeriodInfo extends Struct { + number: Compact; + subperiod: PalletDappStakingV3PeriodType; + nextSubperiodStartEra: Compact; +} + +interface PalletDappStakingV3PeriodType extends Enum { + isVoting: boolean; + isBuildAndEarn: boolean; + type: 'Voting' | 'BuildAndEarn'; +} + +export interface PalletDappStakingV3SingularStakingInfo extends Struct { + staked: PalletDappStakingV3StakeAmount; + loyalStaker: bool; +} + +export interface PalletDappStakingV3StakeAmount extends Struct { + voting: Compact; + buildAndEarn: Compact; + era: Compact; + period: Compact; +} + export interface RegisteredDapp { developer: string; state: string; @@ -50,6 +92,7 @@ export interface IAstarApi { getRegisteredDapp(dappAddress: string): Promise; getCurrentEra(): Promise; getApiPromise(): Promise; + getStakerInfo(address: string): Promise; } export class BaseApi implements IAstarApi { @@ -189,6 +232,44 @@ export class BaseApi implements IAstarApi { } } + public async getStakerInfo(address: string): Promise { + await this.ensureConnection(); + let ss558Address = address; + + if (isEthereumAddress(address)) { + ss558Address = evmToAddress(address, ASTAR_SS58_FORMAT); + + if (Object.prototype.hasOwnProperty.call(this._api.query, 'unifiedAccounts')) { + const unifiedAccount = await this._api.query.unifiedAccounts.evmToNative(address); + if (!unifiedAccount.isEmpty) { + ss558Address = unifiedAccount.toString(); + } + } + } + + const [state, result] = await Promise.all([ + this._api.query.dappStaking.activeProtocolState(), + this._api.query.dappStaking.stakerInfo.entries(ss558Address), + ]); + const period = state.periodInfo.number.toNumber(); + + const total = result.reduce((sum, [key, value]) => { + const singularStakingInfo = >value; + const unwrapped = singularStakingInfo.unwrapOrDefault(); + + if (unwrapped.staked.period.toNumber() !== period) { + return sum; + } + + const buildAndEarn = unwrapped.staked.buildAndEarn.toBigInt(); + const voting = unwrapped.staked.voting.toBigInt(); + + return sum + buildAndEarn + voting; + }, BigInt(0)); + + return total; + } + public async getCurrentEra(): Promise { await this.ensureConnection(); const era = await this._api.query.dappsStaking.currentEra(); diff --git a/src/controllers/DappsStakingV3Controller.ts b/src/controllers/DappsStakingV3Controller.ts index 28daf81..b6e442a 100644 --- a/src/controllers/DappsStakingV3Controller.ts +++ b/src/controllers/DappsStakingV3Controller.ts @@ -142,6 +142,30 @@ export class DappsStakingV3Controller extends ControllerBase implements IControl res.json(await this._dappsStakingEvents.getDapps(req.params.network as NetworkType)); }); + app.route('/api/v3/:network/dapps-staking/stake-info/:address').get(async (req: Request, res: Response) => { + /* + #swagger.description = 'Retrieves the amount of stake of participant' + #swagger.tags = ['Dapps Staking'] + #swagger.parameters['network'] = { + in: 'path', + description: 'The network name. Supported networks: astar, shiden, shibuya', + required: true, + enum: ['astar', 'shiden', 'shibuya'] + } + #swagger.parameters['address'] = { + in: 'path', + description: 'Participant address to get stats for', + required: true + } + */ + res.json( + await this._dappsStakingEvents.getParticipantStake( + req.params.network as NetworkType, + req.params.address as string, + ), + ); + }); + app.route('/api/v3/:network/dapps-staking/stats/dapp/:contractAddress').get( async (req: Request, res: Response) => { /* diff --git a/src/services/DappsStakingEvents.ts b/src/services/DappsStakingEvents.ts index d3a0750..bb2d034 100644 --- a/src/services/DappsStakingEvents.ts +++ b/src/services/DappsStakingEvents.ts @@ -1,8 +1,10 @@ -import { injectable } from 'inversify'; +import { injectable, inject } from 'inversify'; import axios from 'axios'; import { NetworkType } from '../networks'; import { Guard } from '../guard'; import { Pair, PeriodType, ServiceBase, List } from './ServiceBase'; +import { IApiFactory } from '../client/ApiFactory'; +import { ContainerTypes } from '../containertypes'; import { DappStakingEventData, DappStakingEventResponse, @@ -23,6 +25,7 @@ export interface IDappsStakingEvents { getAggregatedData(network: NetworkType, period: PeriodType): Promise; getDappStakingTvl(network: NetworkType, period: PeriodType): Promise; getDappStakingStakersCount(network: NetworkType, contractAddress: string, period: PeriodType): Promise; + getParticipantStake(network: NetworkType, address: string): Promise; getDappStakingStakersCountTotal(network: NetworkType, period: PeriodType): Promise; getDappStakingRewards(network: NetworkType, period: PeriodType, transaction: RewardEventType): Promise; getDappStakingRewardsAggregated(network: NetworkType, address: string, period: PeriodType): Promise; @@ -42,6 +45,10 @@ BigInt.prototype.toJSON = function () { @injectable() export class DappsStakingEvents extends ServiceBase implements IDappsStakingEvents { + constructor(@inject(ContainerTypes.ApiFactory) private _apiFactory: IApiFactory) { + super(); + } + public async getStakingEvents( network: NetworkType, contractAddress: string, @@ -89,6 +96,21 @@ export class DappsStakingEvents extends ServiceBase implements IDappsStakingEven return result.data.data.stakingEvents; } + public async getParticipantStake(network: NetworkType, address: string): Promise { + Guard.ThrowIfUndefined(network, 'network'); + Guard.ThrowIfUndefined(address, 'address'); + + try { + const api = this._apiFactory.getApiInstance(network); + const stakerInfo = await api.getStakerInfo(address); + + return stakerInfo; + } catch (e) { + console.error(e); + throw new Error('Unable to fetch token statistics from a node.'); + } + } + public async getAggregatedData(network: NetworkType, period: PeriodType): Promise { Guard.ThrowIfUndefined('network', network);