// Copyright 2017-2019 @polkadot/api-derive authors & contributors // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. import { AccountId, AccountIndex, Address, Balance, BalanceLock, BlockNumber, Index, VestingSchedule } from '@polkadot/types/interfaces'; import BN from 'bn.js'; import { combineLatest, of, Observable } from 'rxjs'; import { map, switchMap } from 'rxjs/operators'; import { ApiInterfaceRx } from '@polkadot/api/types'; import { ClassOf, Option, createType } from '@polkadot/types'; import { bnMax } from '@polkadot/util'; import { idAndIndex } from '../accounts/idAndIndex'; import { bestNumber } from '../chain/bestNumber'; import { DerivedBalances } from '../types'; import { drr } from '../util/drr'; type Result = [AccountId | undefined, BlockNumber | undefined, [Balance?, Balance?, BalanceLock[]?, Option?, Index?]]; function calcBalances ([accountId = createType('AccountId'), bestNumber = createType('Balance'), [freeBalance = createType('Balance'), reservedBalance = createType('Balance'), locks = [], vesting = new Option(ClassOf('VestingSchedule'), null), accountNonce = createType('Balance')]]: Result): DerivedBalances { let lockedBalance = createType('Balance'); if (Array.isArray(locks)) { // only get the locks that are valid until passed the current block const totals = locks.filter((value): boolean => bestNumber && value.until.gt(bestNumber)); // get the maximum of the locks according to https://github.com/paritytech/substrate/blob/master/srml/balances/src/lib.rs#L699 lockedBalance = totals[0] ? bnMax(...totals.map(({ amount }): Balance => amount)) as Balance : createType('Balance'); } // offset = balance locked at genesis, perBlock is the unlock amount const { offset, perBlock } = vesting.unwrapOr(createType('VestingSchedule')); const vestedNow: BN = perBlock.mul(bestNumber); const vestedBalance: BN = vestedNow.gt(offset) ? freeBalance : freeBalance.sub(offset).add(vestedNow); // NOTE Workaround for this account on Alex (one of a couple reported) - // 5F7BJL6Z4m8RLtK7nXEqqpEqhBbd535Z3CZeYF6ccvaQAY6N // The locked is > the vested and ended up with the locked > free, // i.e. related to https://github.com/paritytech/polkadot/issues/225 // (most probably due to movements from stash -> controller -> free) const availableBalance: BN = bnMax(createType('Balance'), vestedBalance.sub(lockedBalance)); return { accountId, accountNonce, availableBalance, freeBalance, lockedBalance, reservedBalance, vestedBalance, votingBalance: freeBalance.add(reservedBalance) } as DerivedBalances; } /** * @name all * @param {( AccountIndex | AccountId | Address | string )} address - An accounts Id in different formats. * @returns An object containing the combined results of the storage queries for * all relevant fees as declared in the substrate chain spec. * @example *
* * ```javascript * const ALICE = 'F7Hs'; * * api.derive.balances.all(ALICE, ([accountId, lockedBalance]) => { * console.log(`The account ${accountId} has a locked balance ${lockedBalance} units.`); * }); * ``` */ export function all (api: ApiInterfaceRx): (address: AccountIndex | AccountId | Address | string) => Observable { return (address: AccountIndex | AccountId | Address | string): Observable => { return idAndIndex(api)(address).pipe( switchMap(([accountId]): Observable => (accountId ? combineLatest([ of(accountId), bestNumber(api)(), api.queryMulti([ [api.query.balances.freeBalance, accountId], [api.query.balances.reservedBalance, accountId], [api.query.balances.locks, accountId], [api.query.balances.vesting, accountId], [api.query.system.accountNonce, accountId] ]) ]) : of([undefined, undefined, [undefined, undefined, undefined, undefined, undefined]]) ) as Observable ), map(calcBalances), drr() ); }; }