diff --git a/packages/api-derive/src/balances/all.ts b/packages/api-derive/src/balances/all.ts new file mode 100644 index 0000000000..7f91a34cbf --- /dev/null +++ b/packages/api-derive/src/balances/all.ts @@ -0,0 +1,68 @@ +// 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 { combineLatest, of, Observable } from 'rxjs'; +import { map, switchMap } from 'rxjs/operators'; +import { ApiInterface$Rx } from '@polkadot/api/types'; +import { AccountId, AccountIndex, Address, Balance, BalanceLock, BlockNumber, Option, VestingSchedule } from '@polkadot/types'; +import { bnMax } from '@polkadot/util'; + +import { idAndIndex } from '../accounts/idAndIndex'; +import { DerivedBalances } from '../types'; +import { drr } from '../util/drr'; + +const EMPTY_ACCOUNT = new AccountId(); + +export function all (api: ApiInterface$Rx) { + return (address: AccountIndex | AccountId | Address | string): Observable => { + return idAndIndex(api)(address).pipe( + switchMap(([accountId]) => + (accountId + ? combineLatest([ + of(accountId), + api.query.chain.bestNumber(), + api.query.balances.freeBalance(accountId), + api.query.balances.reservedBalance(accountId), + api.query.balances.locks(accountId), + api.query.balances.vesting(accountId) + ]) + : of([undefined, undefined, undefined, undefined, undefined, undefined]) + ) as Observable<[AccountId?, BlockNumber?, Balance?, Balance?, Array?, Option?]> + ), + map(([accountId = EMPTY_ACCOUNT, bestNumber = new BlockNumber(0), freeBalance = new Balance(0), reservedBalance = new Balance(0), locks = [], vesting = new Option(VestingSchedule, null)]) => { + let lockedBalance = new Balance(0); + + if (Array.isArray(locks)) { + // only get the locks that are valid until passed the current block + const totals = locks.filter((value) => 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 = bnMax(...totals.map(({ amount }) => amount)) as Balance; + } + + const { offset, perBlock } = vesting.unwrapOr({ + offset: new Balance(0), + perBlock: new Balance(0) + } as VestingSchedule); + + const vestedBalance = new Balance(perBlock.mul(bestNumber).add(freeBalance.sub(offset))); + + // const availableBalance = lockedBalance.eq(new BlockNumber('0xffffffffffffffffffffffffffffffff')) ? new Balance(0) : freeBalance && new Balance(freeBalance.sub(lockedBalance)); + const availableBalance = new Balance(vestedBalance.sub(lockedBalance)); + + return { + accountId, + freeBalance, + reservedBalance, + votingBalance: new Balance( + freeBalance.add(reservedBalance) + ), + availableBalance, + lockedBalance, + vestedBalance: vestedBalance.lt(new Balance(0)) ? new Balance(0) : vestedBalance + }; + }), + drr() + ); + }; +} diff --git a/packages/api-derive/src/balances/index.ts b/packages/api-derive/src/balances/index.ts index ff52076e80..054b575ac1 100644 --- a/packages/api-derive/src/balances/index.ts +++ b/packages/api-derive/src/balances/index.ts @@ -2,6 +2,7 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. +export * from './all'; export * from './fees'; export * from './validatingBalance'; export * from './validatingBalances'; diff --git a/packages/api-derive/src/balances/votingBalance.ts b/packages/api-derive/src/balances/votingBalance.ts index c0a7cefc02..9efa1d0659 100644 --- a/packages/api-derive/src/balances/votingBalance.ts +++ b/packages/api-derive/src/balances/votingBalance.ts @@ -35,7 +35,10 @@ export function votingBalance (api: ApiInterface$Rx) { stakingBalance: new Balance(0), votingBalance: new Balance( freeBalance.add(reservedBalance) - ) + ), + availableBalance: new Balance(0), + lockedBalance: new Balance(0), + vestedBalance: new Balance(0) }; }), drr() diff --git a/packages/api-derive/src/index.spec.ts b/packages/api-derive/src/index.spec.ts index 8dd1319754..3f566e409c 100644 --- a/packages/api-derive/src/index.spec.ts +++ b/packages/api-derive/src/index.spec.ts @@ -39,6 +39,7 @@ describe('derive', () => { testFunction(api)('accounts', 'indexes', []); testFunction(api)('accounts', 'indexToId', []); + testFunction(api)('balances', 'all', []); testFunction(api)('balances', 'fees', []); testFunction(api)('balances', 'validatingBalance', []); testFunction(api)('balances', 'validatingBalances', []); diff --git a/packages/api-derive/src/types.ts b/packages/api-derive/src/types.ts index 77c2490072..2f22334f03 100644 --- a/packages/api-derive/src/types.ts +++ b/packages/api-derive/src/types.ts @@ -8,11 +8,11 @@ import { AccountId, Balance, Vote } from '@polkadot/types'; export type DerivedBalances = { accountId: AccountId, freeBalance: Balance, - nominatedBalance: Balance, + lockedBalance: Balance, + availableBalance: Balance, reservedBalance: Balance, votingBalance: Balance, - stakingBalance: Balance, - nominators?: Array + vestedBalance: Balance }; export type DerivedFees = { diff --git a/packages/api/test/e2e/promise-queries.spec.js b/packages/api/test/e2e/promise-queries.spec.js index af6b6191a2..2dafdcd46d 100644 --- a/packages/api/test/e2e/promise-queries.spec.js +++ b/packages/api/test/e2e/promise-queries.spec.js @@ -102,6 +102,24 @@ describe.skip('e2e queries', () => { }); }); + it('subscribes to derived balances (balances.all)', (done) => { + api.derive.balances.all( + keyring.alice.address(), + (all) => { + expect(all.accountId).toEqual(keyring.alice.address()); + expect(all.freeBalance).toBeDefined(); + expect(all.reservedBalance).toBeDefined(); + expect(all.availableBalance).toBeDefined(); + expect(all.lockedBalance).toBeDefined(); + expect(all.vestedBalance).toBeDefined(); + + console.error(all); + + done(); + } + ); + }); + it('makes a query at a specific block', async () => { const header = await api.rpc.chain.getHeader(); const events = await api.query.system.events.at(header.hash);