Skip to content

Commit

Permalink
feat: stake amount for one participant (#131)
Browse files Browse the repository at this point in the history
* draft of stake info

* entries

* period check

* evmToNative and etmToAddress

* proper types
  • Loading branch information
gluneau authored Jan 31, 2024
1 parent d6458a5 commit 4797aec
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 3 deletions.
34 changes: 34 additions & 0 deletions public/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
85 changes: 83 additions & 2 deletions src/client/BaseApi.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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;
Expand All @@ -24,6 +35,37 @@ export interface DappInfoV3 extends Struct {
state: string;
}

export interface PalletDappStakingV3ProtocolState extends Struct {
era: Compact<u32>;
nextEraStart: Compact<u32>;
periodInfo: PalletDappStakingV3PeriodInfo;
maintenance: bool;
}

interface PalletDappStakingV3PeriodInfo extends Struct {
number: Compact<u32>;
subperiod: PalletDappStakingV3PeriodType;
nextSubperiodStartEra: Compact<u32>;
}

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<u128>;
buildAndEarn: Compact<u128>;
era: Compact<u32>;
period: Compact<u32>;
}

export interface RegisteredDapp {
developer: string;
state: string;
Expand All @@ -50,6 +92,7 @@ export interface IAstarApi {
getRegisteredDapp(dappAddress: string): Promise<RegisteredDapp | undefined>;
getCurrentEra(): Promise<number>;
getApiPromise(): Promise<ApiPromise>;
getStakerInfo(address: string): Promise<bigint>;
}

export class BaseApi implements IAstarApi {
Expand Down Expand Up @@ -189,6 +232,44 @@ export class BaseApi implements IAstarApi {
}
}

public async getStakerInfo(address: string): Promise<bigint> {
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<AccountId>(address);
if (!unifiedAccount.isEmpty) {
ss558Address = unifiedAccount.toString();
}
}
}

const [state, result] = await Promise.all([
this._api.query.dappStaking.activeProtocolState<PalletDappStakingV3ProtocolState>(),
this._api.query.dappStaking.stakerInfo.entries(ss558Address),
]);
const period = state.periodInfo.number.toNumber();

const total = result.reduce((sum, [key, value]) => {
const singularStakingInfo = <Option<PalletDappStakingV3SingularStakingInfo>>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<number> {
await this.ensureConnection();
const era = await this._api.query.dappsStaking.currentEra<u32>();
Expand Down
24 changes: 24 additions & 0 deletions src/controllers/DappsStakingV3Controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
/*
Expand Down
24 changes: 23 additions & 1 deletion src/services/DappsStakingEvents.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -23,6 +25,7 @@ export interface IDappsStakingEvents {
getAggregatedData(network: NetworkType, period: PeriodType): Promise<DappStakingAggregatedData[]>;
getDappStakingTvl(network: NetworkType, period: PeriodType): Promise<Pair[]>;
getDappStakingStakersCount(network: NetworkType, contractAddress: string, period: PeriodType): Promise<Pair[]>;
getParticipantStake(network: NetworkType, address: string): Promise<bigint>;
getDappStakingStakersCountTotal(network: NetworkType, period: PeriodType): Promise<Pair[]>;
getDappStakingRewards(network: NetworkType, period: PeriodType, transaction: RewardEventType): Promise<Pair[]>;
getDappStakingRewardsAggregated(network: NetworkType, address: string, period: PeriodType): Promise<Pair[]>;
Expand All @@ -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,
Expand Down Expand Up @@ -89,6 +96,21 @@ export class DappsStakingEvents extends ServiceBase implements IDappsStakingEven
return result.data.data.stakingEvents;
}

public async getParticipantStake(network: NetworkType, address: string): Promise<bigint> {
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<DappStakingAggregatedData[]> {
Guard.ThrowIfUndefined('network', network);

Expand Down

0 comments on commit 4797aec

Please sign in to comment.