diff --git a/packages/api-derive/src/balances/all.ts b/packages/api-derive/src/balances/all.ts index 72530f6822..583784e5de 100644 --- a/packages/api-derive/src/balances/all.ts +++ b/packages/api-derive/src/balances/all.ts @@ -31,17 +31,23 @@ function calcBalances ([accountId, bestNumber, [freeBalance, reservedBalance, lo } } - // offset = balance locked at genesis, perBlock is the unlock amount + // Calculate the vesting balances, + // - offset = balance locked at genesis, + // - perBlock is the unlock amount const { offset: vestingTotal, perBlock } = vesting.unwrapOr(createType('VestingSchedule')); - const vestedNow = createType('Balance', perBlock.mul(bestNumber)); - const vestedBalance = createType('Balance', vestedNow.gt(vestingTotal) ? new BN(0) : bnMax(new BN(0), freeBalance.sub(vestingTotal).add(vestedNow))); - const availableBalance = createType('Balance', bnMax(new BN(0), (vestedBalance.gtn(0) ? vestedBalance : freeBalance).sub(lockedBalance))); + const vestedBalance = createType('Balance', perBlock.mul(bestNumber)); + const isVesting = vestedBalance.lt(vestingTotal); + + // see what is availble with the current vested amount taken into account + const freeWithVesting = freeBalance.sub(vestingTotal).add(vestedBalance); + const availableBalance = createType('Balance', bnMax(new BN(0), (isVesting ? freeWithVesting : freeBalance).sub(lockedBalance))); return { accountId, accountNonce, availableBalance, freeBalance, + isVesting, lockedBalance, lockedBreakdown, reservedBalance, diff --git a/packages/api-derive/src/session/info.ts b/packages/api-derive/src/session/info.ts index c5b21e1610..82815bbf38 100644 --- a/packages/api-derive/src/session/info.ts +++ b/packages/api-derive/src/session/info.ts @@ -53,8 +53,6 @@ function createDerivedLatest ([[hasBabe, epochDuration, sessionsPerEra], { curre const sessionProgress = currentSlot.sub(epochStartSlot); const eraProgress = currentIndex.sub(currentEraStartSessionIndex).add(sessionProgress); - // console.log(sessionProgress); - return { currentEra, currentIndex, diff --git a/packages/api-derive/src/staking/info.ts b/packages/api-derive/src/staking/info.ts index 1e85208962..fe11e7de32 100644 --- a/packages/api-derive/src/staking/info.ts +++ b/packages/api-derive/src/staking/info.ts @@ -2,11 +2,10 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. +import { ApiInterfaceRx } from '@polkadot/api/types'; import { AccountId, Balance, BlockNumber, Exposure, Keys, RewardDestination, StakingLedger, UnlockChunk, ValidatorPrefs } from '@polkadot/types/interfaces'; import { ITuple } from '@polkadot/types/types'; - -import { ApiInterfaceRx } from '@polkadot/api/types'; -import { DerivedRecentlyOffline, DerivedStaking, DerivedUnlocking } from '../types'; +import { DerivedRecentlyOffline, DerivedSessionInfo, DerivedStaking, DerivedUnlocking } from '../types'; import BN from 'bn.js'; import { combineLatest, Observable, of } from 'rxjs'; @@ -19,13 +18,12 @@ import { memo } from '../util'; interface ParseInput { accountId: AccountId; - bestNumber: BlockNumber; controllerId: AccountId; - eraLength: BlockNumber; recentlyOffline?: DerivedRecentlyOffline; nominators: AccountId[]; rewardDestination: RewardDestination; queuedKeys?: [AccountId, Keys][]; + sessionInfo: DerivedSessionInfo; stakers: Exposure; stakingLedger: Option; stashId: AccountId; @@ -47,46 +45,47 @@ function groupByEra (list: UnlockChunk[]): Record { }, {}); } -// calculate the remining blocks in a specific unlock era -function remainingBlocks (era: BN, eraLength: BN, bestNumber: BlockNumber): BlockNumber { - const remaining = eraLength.mul(era).sub(bestNumber); +// calculate the remaining blocks in a specific unlock era +function remainingBlocks (era: BN, sessionInfo: DerivedSessionInfo): BlockNumber { + const remaining = era.sub(sessionInfo.currentEra); - return createType('BlockNumber', remaining.lten(0) - ? new BN(0) - : remaining + // on the Rust side the current-era >= era-for-unlock (removal done on >) + return createType('BlockNumber', remaining.gtn(0) + ? remaining.subn(1).mul(sessionInfo.eraLength).add(sessionInfo.eraProgress) + : 0 ); } -function calculateUnlocking (stakingLedger: StakingLedger | undefined, eraLength: BN, bestNumber: BlockNumber): DerivedUnlocking[] | undefined { +function calculateUnlocking (stakingLedger: StakingLedger | undefined, sessionInfo: DerivedSessionInfo): DerivedUnlocking[] | undefined { if (isUndefined(stakingLedger)) { return undefined; } const unlockingChunks = stakingLedger.unlocking.filter(({ era }): boolean => - remainingBlocks(era.unwrap(), eraLength, bestNumber).gtn(0) + remainingBlocks(era.unwrap(), sessionInfo).gtn(0) ); if (!unlockingChunks.length) { return undefined; } - // group the Unlockchunks that have the same era and sum their values + // group the unlock chunks that have the same era and sum their values const groupedResult = groupByEra(unlockingChunks); const results = Object.entries(groupedResult).map(([eraString, value]): DerivedUnlocking => ({ value: createType('Balance', value), - remainingBlocks: remainingBlocks(createType('BlockNumber', eraString), eraLength, bestNumber) + remainingBlocks: remainingBlocks(new BN(eraString), sessionInfo) })); return results.length ? results : undefined; } -function redeemableSum (stakingLedger: StakingLedger | undefined, eraLength: BN, bestNumber: BlockNumber): Balance { +function redeemableSum (stakingLedger: StakingLedger | undefined, sessionInfo: DerivedSessionInfo): Balance { if (isUndefined(stakingLedger)) { return createType('Balance'); } return createType('Balance', stakingLedger.unlocking.reduce((total, { era, value }): BN => { - return remainingBlocks(era.unwrap(), eraLength, bestNumber).eqn(0) + return remainingBlocks(era.unwrap(), sessionInfo).eqn(0) ? total.add(value.unwrap()) : total; }, new BN(0))); @@ -117,7 +116,7 @@ function unwrapSessionIds (stashId: AccountId, queuedKeys: Option | [ }; } -function parseResult ({ accountId, controllerId, stashId, eraLength, bestNumber, recentlyOffline = {}, nextKeyFor = createType('Option', null), queuedKeys, stakingLedger, nominators, rewardDestination, stakers, validatorPrefs, nextKeys = createType('Option', null) }: ParseInput): DerivedStaking { +function parseResult ({ accountId, controllerId, stashId, sessionInfo, recentlyOffline = {}, nextKeyFor = createType('Option', null), queuedKeys, stakingLedger, nominators, rewardDestination, stakers, validatorPrefs, nextKeys = createType('Option', null) }: ParseInput): DerivedStaking { const _stakingLedger = stakingLedger.unwrapOr(undefined); return { @@ -125,12 +124,12 @@ function parseResult ({ accountId, controllerId, stashId, eraLength, bestNumber, controllerId, nominators, offline: recentlyOffline[stashId.toString()], - redeemable: redeemableSum(_stakingLedger, eraLength, bestNumber), + redeemable: redeemableSum(_stakingLedger, sessionInfo), rewardDestination, stakers, stakingLedger: _stakingLedger, stashId, - unlocking: calculateUnlocking(_stakingLedger, eraLength, bestNumber), + unlocking: calculateUnlocking(_stakingLedger, sessionInfo), validatorPrefs, ...unwrapSessionIds(stashId, queuedKeys || nextKeyFor, nextKeys) }; @@ -140,8 +139,7 @@ type MultiResultV1 = [Option, Option, [Vec] function retrieveInfoV1 (api: ApiInterfaceRx, accountId: AccountId, stashId: AccountId, controllerId: AccountId): Observable { return combineLatest([ - api.derive.chain.bestNumber(), - api.derive.session.eraLength(), + api.derive.session.info(), api.derive.staking.recentlyOffline(), api.queryMulti([ [api.query.session.nextKeyFor, controllerId], @@ -152,11 +150,11 @@ function retrieveInfoV1 (api: ApiInterfaceRx, accountId: AccountId, stashId: Acc [api.query.staking.validators, stashId] ]) as Observable ]).pipe(map(([ - bestNumber, eraLength, recentlyOffline, + sessionInfo, recentlyOffline, [nextKeyFor, stakingLedger, [nominators], rewardDestination, stakers, [validatorPrefs]] - ]: [BlockNumber, BlockNumber, DerivedRecentlyOffline, MultiResultV1]): DerivedStaking => + ]: [DerivedSessionInfo, DerivedRecentlyOffline, MultiResultV1]): DerivedStaking => parseResult({ - accountId, controllerId, stashId, eraLength, bestNumber, recentlyOffline, nextKeyFor, stakingLedger, nominators, rewardDestination, stakers, validatorPrefs + accountId, controllerId, stashId, sessionInfo, recentlyOffline, nextKeyFor, stakingLedger, nominators, rewardDestination, stakers, validatorPrefs }) )); } @@ -165,8 +163,7 @@ type MultiResultV2 = [Option, [Vec], RewardDestination function retrieveInfoV2 (api: ApiInterfaceRx, accountId: AccountId, stashId: AccountId, controllerId: AccountId): Observable { return combineLatest([ - api.derive.chain.bestNumber(), - api.derive.session.eraLength(), + api.derive.session.info(), api.query.session.queuedKeys>>(), api.queryMulti([ [api.query.staking.ledger, controllerId], @@ -177,11 +174,11 @@ function retrieveInfoV2 (api: ApiInterfaceRx, accountId: AccountId, stashId: Acc [api.query.session.nextKeys, [api.consts.session.dedupKeyPrefix, stashId]] ]) as Observable ]).pipe(map(([ - bestNumber, eraLength, queuedKeys, + sessionInfo, queuedKeys, [stakingLedger, [nominators], rewardDestination, stakers, [validatorPrefs], nextKeys] - ]: [BlockNumber, BlockNumber, [AccountId, Keys][], MultiResultV2]): DerivedStaking => + ]: [DerivedSessionInfo, [AccountId, Keys][], MultiResultV2]): DerivedStaking => parseResult({ - accountId, controllerId, stashId, eraLength, bestNumber, queuedKeys, stakingLedger, nominators, rewardDestination, stakers, validatorPrefs, nextKeys + accountId, controllerId, stashId, sessionInfo, queuedKeys, stakingLedger, nominators, rewardDestination, stakers, validatorPrefs, nextKeys }) )); } diff --git a/packages/api-derive/src/types.ts b/packages/api-derive/src/types.ts index 5a2938c6d3..ba298f21af 100644 --- a/packages/api-derive/src/types.ts +++ b/packages/api-derive/src/types.ts @@ -19,6 +19,7 @@ export interface DerivedBalances { accountId: AccountId; accountNonce: Index; freeBalance: Balance; + isVesting: boolean; lockedBalance: Balance; lockedBreakdown: BalanceLock[]; availableBalance: Balance;