diff --git a/packages/api-derive/src/balances/all.ts b/packages/api-derive/src/balances/all.ts index 48e8cda7db..2f6e04bd22 100644 --- a/packages/api-derive/src/balances/all.ts +++ b/packages/api-derive/src/balances/all.ts @@ -12,8 +12,41 @@ import { idAndIndex } from '../accounts/idAndIndex'; import { DerivedBalances } from '../types'; import { drr } from '../util/drr'; +type Result = [AccountId | undefined, BlockNumber | undefined, [Balance?, Balance?, Array?, Option?]]; + const EMPTY_ACCOUNT = new AccountId(); +function calcBalances ([accountId = EMPTY_ACCOUNT, bestNumber = new BlockNumber(0), [freeBalance = new Balance(0), reservedBalance = new Balance(0), locks = [], vesting = new Option(VestingSchedule, null)]]: Result): DerivedBalances { + 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 = totals[0] ? bnMax(...totals.map(({ amount }) => amount)) as Balance : new Balance(0); + } + + 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 = 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 + }; +} + /** * @name all * @param {( ccountIndex | AccountId | Address | string )} address - An accounts Id in different formats. @@ -38,46 +71,17 @@ export function all (api: ApiInterface$Rx) { ? combineLatest([ of(accountId), api.derive.chain.bestNumber(), - api.query.balances.freeBalance(accountId), - api.query.balances.reservedBalance(accountId), - api.query.balances.locks(accountId), - api.query.balances.vesting(accountId) + api.queryMulti([ + [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?]> + : of([undefined, undefined, [undefined, undefined, undefined, undefined]]) + ) as any as Observable ), - 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 = totals[0] ? bnMax(...totals.map(({ amount }) => amount)) as Balance : new Balance(0); - } - - 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 - }; - }), + map(calcBalances), drr() ); }; diff --git a/packages/api-derive/src/democracy/referendumInfo.ts b/packages/api-derive/src/democracy/referendumInfo.ts index 12bfaf7211..bd66018d61 100644 --- a/packages/api-derive/src/democracy/referendumInfo.ts +++ b/packages/api-derive/src/democracy/referendumInfo.ts @@ -45,20 +45,26 @@ export class ReferendumInfoExtended extends ReferendumInfo { } } +export function constructInfo (index: BN | number, optionInfo?: Option): Option { + const info = optionInfo + ? optionInfo.unwrapOr(null) + : null; + + return new Option( + ReferendumInfoExtended, + isNull(info) + ? null + : new ReferendumInfoExtended(info, index) + ); +} + export function referendumInfo (api: ApiInterface$Rx) { return (index: BN | number): Observable> => { return (api.query.democracy.referendumInfoOf(index) as Observable>) .pipe( - map((optionInfo) => { - const info = optionInfo.unwrapOr(null); - - return new Option( - ReferendumInfoExtended, - isNull(info) - ? null - : new ReferendumInfoExtended(info, index) - ); - }), + map((optionInfo) => + constructInfo(index, optionInfo) + ), drr() ); }; diff --git a/packages/api-derive/src/democracy/referendumInfos.ts b/packages/api-derive/src/democracy/referendumInfos.ts index 0ec889860f..4b9daad2e8 100644 --- a/packages/api-derive/src/democracy/referendumInfos.ts +++ b/packages/api-derive/src/democracy/referendumInfos.ts @@ -3,22 +3,26 @@ // of the Apache-2.0 license. See the LICENSE file for details. import BN from 'bn.js'; -import { combineLatest, Observable, of } from 'rxjs'; +import { Observable, of } from 'rxjs'; +import { map } from 'rxjs/operators'; import { ApiInterface$Rx } from '@plugnet/api/types'; -import { Option } from '@plugnet/types'; +import { Option, ReferendumInfo } from '@plugnet/types'; import { drr } from '../util/drr'; -import { referendumInfo, ReferendumInfoExtended } from './referendumInfo'; +import { constructInfo, ReferendumInfoExtended } from './referendumInfo'; export function referendumInfos (api: ApiInterface$Rx) { - const referendumInfoOf = referendumInfo(api); - return (ids: Array = []): Observable>> => { - return !ids || !ids.length - ? of([]).pipe(drr()) - : combineLatest( - ids.map((id) => referendumInfoOf(id)) - ).pipe( + return ( + !ids || !ids.length + ? of([]) + : api.query.democracy.referendumInfoOf.multi(ids) as any as Observable>> + ).pipe( + map((infos) => + ids.map((id, index) => + constructInfo(id, infos[index]) + ) + ), drr() ); }; diff --git a/packages/api-derive/src/democracy/referendums.ts b/packages/api-derive/src/democracy/referendums.ts index 6bcb039560..ec44fa48a1 100644 --- a/packages/api-derive/src/democracy/referendums.ts +++ b/packages/api-derive/src/democracy/referendums.ts @@ -2,7 +2,7 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import { combineLatest, Observable, of } from 'rxjs'; +import { Observable, of } from 'rxjs'; import { switchMap } from 'rxjs/operators'; import { ApiInterface$Rx } from '@plugnet/api/types'; import { Option, ReferendumIndex } from '@plugnet/types'; @@ -13,10 +13,10 @@ import { referendumInfos } from './referendumInfos'; export function referendums (api: ApiInterface$Rx) { return (): Observable>> => - (combineLatest([ - api.query.democracy.nextTally(), - api.query.democracy.referendumCount() - ]) as Observable<[ReferendumIndex?, ReferendumIndex?]>).pipe( + (api.queryMulti([ + api.query.democracy.nextTally, + api.query.democracy.referendumCount + ]) as any as Observable<[ReferendumIndex?, ReferendumIndex?]>).pipe( switchMap(([nextTally, referendumCount]) => referendumCount && nextTally && referendumCount.gt(nextTally) && referendumCount.gtn(0) ? referendumInfos(api)( diff --git a/packages/api-derive/src/session/eraLength.ts b/packages/api-derive/src/session/eraLength.ts index 9913019d0c..246ecda6be 100644 --- a/packages/api-derive/src/session/eraLength.ts +++ b/packages/api-derive/src/session/eraLength.ts @@ -3,23 +3,17 @@ // of the Apache-2.0 license. See the LICENSE file for details. import BN from 'bn.js'; -import { combineLatest, Observable } from 'rxjs'; +import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { ApiInterface$Rx } from '@plugnet/api/types'; -import { BlockNumber } from '@plugnet/types'; import { drr } from '../util/drr'; +import { info } from './info'; export function eraLength (api: ApiInterface$Rx) { return (): Observable => - (combineLatest([ - api.query.session.sessionLength(), - api.query.staking.sessionsPerEra() - ]) as Observable<[BlockNumber?, BlockNumber?]>).pipe( - map( - ([sessionLength, sessionsPerEra]) => - (sessionLength || new BN(1)).mul(sessionsPerEra || new BN(1)) - ), + info(api)().pipe( + map(({ eraLength }) => eraLength), drr() ); } diff --git a/packages/api-derive/src/session/eraProgress.ts b/packages/api-derive/src/session/eraProgress.ts index d6eec242a9..872e965380 100644 --- a/packages/api-derive/src/session/eraProgress.ts +++ b/packages/api-derive/src/session/eraProgress.ts @@ -3,33 +3,17 @@ // of the Apache-2.0 license. See the LICENSE file for details. import BN from 'bn.js'; -import { combineLatest, Observable } from 'rxjs'; +import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { ApiInterface$Rx } from '@plugnet/api/types'; -import { BlockNumber } from '@plugnet/types'; -import { sessionProgress } from './sessionProgress'; import { drr } from '../util/drr'; +import { info } from './info'; export function eraProgress (api: ApiInterface$Rx) { return (): Observable => - (combineLatest([ - sessionProgress(api)(), - api.query.session.currentIndex(), - api.query.session.sessionLength(), - api.query.staking.lastEraLengthChange(), - api.query.staking.sessionsPerEra() - ]) as Observable<[BN, BlockNumber?, BlockNumber?, BlockNumber?, BlockNumber?]>).pipe( - map( - ([sessionProgress, currentIndex, sessionLength, lastEraLengthChange, sessionsPerEra]) => - (currentIndex || new BN(0)) - .sub(lastEraLengthChange || new BN(0)) - .mod(sessionsPerEra || new BN(1)) - .mul(sessionLength || new BN(1)) - .add(sessionProgress || new BN(0) - ) - - ), + info(api)().pipe( + map(({ eraProgress }) => eraProgress), drr() ); } diff --git a/packages/api-derive/src/session/index.ts b/packages/api-derive/src/session/index.ts index 47474007b8..5569c879cf 100644 --- a/packages/api-derive/src/session/index.ts +++ b/packages/api-derive/src/session/index.ts @@ -4,4 +4,5 @@ export * from './eraLength'; export * from './eraProgress'; +export * from './info'; export * from './sessionProgress'; diff --git a/packages/api-derive/src/session/info.ts b/packages/api-derive/src/session/info.ts new file mode 100644 index 0000000000..4b925c060b --- /dev/null +++ b/packages/api-derive/src/session/info.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 { DerivedSessionInfo } from '../types'; + +import BN from 'bn.js'; +import { Observable, combineLatest } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { ApiInterface$Rx } from '@polkadot/api/types'; +import { BlockNumber, Option } from '@polkadot/types'; + +import { drr } from '../util/drr'; +import { bestNumber } from '../chain'; + +type Result = [BN, [BlockNumber, Option, BlockNumber, BlockNumber, BlockNumber]]; + +const ZERO = new BN(0); + +// internal helper to just split the logic - take all inputs, do the calculations and combine +function createDerived ([bestNumber, [currentIndex, _lastLengthChange, sessionLength, lastEraLengthChange, sessionsPerEra]]: Result): DerivedSessionInfo { + const eraLength = sessionLength.mul(sessionsPerEra); + const lastLengthChange = _lastLengthChange + ? _lastLengthChange.unwrapOr(ZERO) + : ZERO; + const sessionProgress = bestNumber + .sub(lastLengthChange) + .add(sessionLength) + .mod(sessionLength); + const eraProgress = (currentIndex) + .sub(lastEraLengthChange) + .mod(sessionsPerEra) + .mul(sessionLength) + .add(sessionProgress); + + return { + currentIndex, + eraLength, + eraProgress, + lastEraLengthChange, + lastLengthChange, + sessionLength, + sessionsPerEra, + sessionProgress + }; +} + +/** + * @description Retrieves all the session and era info and calculates specific valus on it sunh as the length of the session and eras + */ +export function info (api: ApiInterface$Rx) { + return (): Observable => + // This is a much more optimal way to calculate since we only make a single call to the RPC backend + // instead of making a subscription for each of the params (this means all others in session use) + (combineLatest([ + bestNumber(api)(), + api.queryMulti([ + api.query.session.currentIndex, + api.query.session.lastLengthChange, + api.query.session.sessionLength, + api.query.staking.lastEraLengthChange, + api.query.staking.sessionsPerEra + ]) + ]) as any as Observable).pipe( + map(createDerived), + drr() + ); +} diff --git a/packages/api-derive/src/session/sessionProgress.ts b/packages/api-derive/src/session/sessionProgress.ts index 0f33a868f7..1419d4f835 100644 --- a/packages/api-derive/src/session/sessionProgress.ts +++ b/packages/api-derive/src/session/sessionProgress.ts @@ -3,28 +3,17 @@ // of the Apache-2.0 license. See the LICENSE file for details. import BN from 'bn.js'; -import { combineLatest, Observable } from 'rxjs'; +import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { ApiInterface$Rx } from '@plugnet/api/types'; -import { BlockNumber, Option } from '@plugnet/types'; -import { bestNumber } from '../chain'; import { drr } from '../util/drr'; +import { info } from './info'; export function sessionProgress (api: ApiInterface$Rx) { return (): Observable => - combineLatest([ - bestNumber(api)(), - api.query.session.sessionLength(), - api.query.session.lastLengthChange() - ]).pipe( - map( - ([bestNumber, sessionLength, lastLengthChange]) => - (bestNumber || new BN(0)) - .sub((lastLengthChange as Option).unwrapOr(new BN(0))) - .add(sessionLength as BlockNumber || new BN(1)) - .mod(sessionLength as BlockNumber || new BN(1)) - ), + info(api)().pipe( + map(({ sessionProgress }) => sessionProgress), drr() ); } diff --git a/packages/api-derive/src/types.ts b/packages/api-derive/src/types.ts index a5f45ce337..9858bec396 100644 --- a/packages/api-derive/src/types.ts +++ b/packages/api-derive/src/types.ts @@ -3,7 +3,7 @@ // of the Apache-2.0 license. See the LICENSE file for details. import BN from 'bn.js'; -import { AccountId, Balance, Vote } from '@plugnet/types'; +import { AccountId, Balance, BlockNumber, Vote } from '@plugnet/types'; export type DerivedBalances = { accountId: AccountId, @@ -45,3 +45,14 @@ export type DerivedContractFees = { transferFee: BN, tombstoneDeposit: BN }; + +export type DerivedSessionInfo = { + currentIndex: BlockNumber, + eraLength: BN, + eraProgress: BN, + lastEraLengthChange: BlockNumber, + lastLengthChange: BN, + sessionLength: BlockNumber, + sessionsPerEra: BlockNumber, + sessionProgress: BN +}; diff --git a/packages/api-derive/test/e2e/promise.spec.ts b/packages/api-derive/test/e2e/promise.spec.ts index 07e70d18ee..f946a10ea1 100644 --- a/packages/api-derive/test/e2e/promise.spec.ts +++ b/packages/api-derive/test/e2e/promise.spec.ts @@ -47,4 +47,27 @@ describe.skip('derive e2e', () => { done(); }); }); + + it('retrieves all session info', (done) => { + let count = 0; + + return api.derive.session.info((info) => { + console.error(JSON.stringify(info)); + + // 5 blocks only, then go our merry way + if (++count === 5) { + done(); + } + }); + }); + + it('retrieves the balances', (done) => { + return api.derive.balances.all('5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y', (balance) => { + console.error(JSON.stringify(balance)); + + if (balance.freeBalance.gtn(1)) { + done(); + } + }); + }); }); diff --git a/packages/api-derive/test/e2e/rx.spec.ts b/packages/api-derive/test/e2e/rx.spec.ts index 86b6cde827..0c0862aff4 100644 --- a/packages/api-derive/test/e2e/rx.spec.ts +++ b/packages/api-derive/test/e2e/rx.spec.ts @@ -31,7 +31,6 @@ describe.skip('derive e2e', () => { // These derive.accounts tests only work on localhost, not the poc-3 URL // (and it is assuming it sent at least 1 tx) describe('derive.accounts', () => { - describe('idAndIndex', () => { it('looks up AccountId & AccountIndex from AccountId', async (done) => { // @ts-ignore silence warning until we have static types here diff --git a/packages/api/src/Base.ts b/packages/api/src/Base.ts index 9ac1b244b0..40f6687f65 100644 --- a/packages/api/src/Base.ts +++ b/packages/api/src/Base.ts @@ -495,9 +495,10 @@ export default abstract class ApiBase implement private decorateMulti (onCall: OnCallDefinition): QueryableStorageMulti { return ((calls: QueryableStorageMultiArgs, callback?: CodecCallback): S => { const mapped = calls.map((arg: QueryableStorageMultiArg): [QueryableStorageFunction, ...Array] => + // the input is a QueryableStorageFunction, convert to StorageFunction Array.isArray(arg) - ? arg - : [arg] as any + ? [arg[0].creator, ...arg.slice(1)] + : [arg.creator] as any ); return onCall( @@ -543,7 +544,7 @@ export default abstract class ApiBase implement }, {} as QueryableStorage); } - private decorateStorageEntry (method: StorageFunction, onCall: OnCallDefinition): QueryableStorageFunction { + private decorateStorageEntry (creator: StorageFunction, onCall: OnCallDefinition): QueryableStorageFunction { // These signatures are allowed and exposed here - // (arg?: CodecArg): CodecResult; // (arg: CodecArg, callback: CodecCallback): SubscriptionResult; @@ -557,13 +558,13 @@ export default abstract class ApiBase implement params = args.slice(0, args.length - 1); } - if (method.headKey && params.length === 0) { - return this.decorateStorageEntryLinked(method, onCall, callback); + if (creator.headKey && params.length === 0) { + return this.decorateStorageEntryLinked(creator, onCall, callback); } return onCall( (arg: CodecArg) => this._rpcRx.state - .subscribeStorage([[method, arg]]) + .subscribeStorage([[creator, arg]]) .pipe( // state_storage returns an array of values, since we have just subscribed to // a single entry, we pull that from the array and return it as-is @@ -576,9 +577,11 @@ export default abstract class ApiBase implement ); }) as QueryableStorageFunction; + decorated.creator = creator; + decorated.at = (hash: Hash | Uint8Array | string, arg?: CodecArg): C => onCall( - (arg: CodecArg) => this._rpcRx.state.getStorage([method, arg], hash), + (arg: CodecArg) => this._rpcRx.state.getStorage([creator, arg], hash), [arg] ) as C; @@ -586,27 +589,27 @@ export default abstract class ApiBase implement // be converted from C to the actual result required decorated.hash = (arg?: CodecArg): HashResult => onCall( - (arg: CodecArg) => this._rpcRx.state.getStorageHash([method, arg]), + (arg: CodecArg) => this._rpcRx.state.getStorageHash([creator, arg]), [arg] ) as unknown as HashResult; decorated.key = (arg?: CodecArg): string => - u8aToHex(compactStripLength(method(arg))[1]); + u8aToHex(compactStripLength(creator(arg))[1]); decorated.multi = (args: Array, callback?: CodecCallback): S => onCall( - () => this._rpcRx.state.subscribeStorage(args.map((arg) => [method, arg])), + () => this._rpcRx.state.subscribeStorage(args.map((arg) => [creator, arg])), [], callback ) as unknown as S; decorated.size = (arg?: CodecArg): U64Result => onCall( - (arg: CodecArg) => this._rpcRx.state.getStorageSize([method, arg]), + (arg: CodecArg) => this._rpcRx.state.getStorageSize([creator, arg]), [arg] ) as unknown as U64Result; - return this.decorateFunctionMeta(method, decorated) as QueryableStorageFunction; + return this.decorateFunctionMeta(creator, decorated) as QueryableStorageFunction; } private decorateStorageEntryLinked (method: StorageFunction, onCall: OnCallDefinition, callback: CodecCallback | undefined): C | S { diff --git a/packages/api/src/types.ts b/packages/api/src/types.ts index dd741271fa..f6e9e3396a 100644 --- a/packages/api/src/types.ts +++ b/packages/api/src/types.ts @@ -50,6 +50,7 @@ export type U64Result = export interface QueryableStorageFunctionBase extends StorageFunction { (arg?: CodecArg): CodecResult; at: (hash: Hash | Uint8Array | string, arg?: CodecArg) => CodecResult; + creator: StorageFunction; hash: (arg?: CodecArg) => HashResult; key: (arg?: CodecArg) => string; multi: (args: Array, callback?: CodecCallback) => SubscriptionResult; diff --git a/packages/api/test/e2e/promise-queries.spec.js b/packages/api/test/e2e/promise-queries.spec.js index 0006cc5c6b..12198398ac 100644 --- a/packages/api/test/e2e/promise-queries.spec.js +++ b/packages/api/test/e2e/promise-queries.spec.js @@ -98,13 +98,28 @@ describe.skip('e2e queries', () => { api.query.balances.freeBalance.multi([ keyring.alice.address(), keyring.bob.address(), - keyring.ferdie.address(), - '5DTestUPts3kjeXSTMyerHihn1uwMfLj8vU8sqF7qYrFabHE' + '5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y', + keyring.ferdie.address() ], (balances) => { + console.error(balances); + expect(balances).toHaveLength(4); + done(); + }); + }); + + it('subscribes to multiple results (api.queryMulti)', (done) => { + return api.queryMulti([ + [api.query.balances.freeBalance, keyring.alice.address()], + [api.query.balances.freeBalance, keyring.bob.address()], + [api.query.balances.freeBalance, '5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y'], + [api.query.balances.freeBalance, keyring.ferdie.address()] + ], (balances) => { console.error(balances); + expect(balances).toHaveLength(4); + done(); }); }); diff --git a/packages/types/src/primitive/StorageKey.ts b/packages/types/src/primitive/StorageKey.ts index 8f790df398..3eb160362d 100644 --- a/packages/types/src/primitive/StorageKey.ts +++ b/packages/types/src/primitive/StorageKey.ts @@ -4,7 +4,7 @@ import { AnyU8a } from '../types'; -import { isFunction } from '@plugnet/util'; +import { assert, isFunction, isString, isU8a } from '@plugnet/util'; import Bytes from './Bytes'; import { StorageFunctionMetadata as MetaV0 } from '../Metadata/v0/Storage'; @@ -29,25 +29,28 @@ export default class StorageKey extends Bytes { private _meta: MetaV0 | MetaV4 | null; private _outputType: string | null; - constructor (value: AnyU8a | StorageKey | StorageFunction | [StorageFunction, any]) { + constructor (value?: AnyU8a | StorageKey | StorageFunction | [StorageFunction, any]) { super(StorageKey.decodeStorageKey(value)); this._meta = StorageKey.getMeta(value as StorageKey); this._outputType = StorageKey.getType(value as StorageKey); } - static decodeStorageKey (value: AnyU8a | StorageKey | StorageFunction | [StorageFunction, any]): Uint8Array { - if (isFunction(value)) { + static decodeStorageKey (value?: AnyU8a | StorageKey | StorageFunction | [StorageFunction, any]): Uint8Array | string | undefined { + if (!value || isU8a(value) || isString(value)) { + // let Bytes handle these inputs + return value; + } else if (isFunction(value)) { return value(); } else if (Array.isArray(value)) { const [fn, ...arg] = value; - if (isFunction(fn)) { - return fn(...arg); - } + assert(isFunction(fn), 'Expected function input for key construction'); + + return (fn as Function)(...arg); } - return value as Uint8Array; + throw new Error(`Unable to convert input ${value} to StorageKey`); } static getMeta (value: StorageKey | StorageFunction | [StorageFunction, any]): MetaV0 | MetaV4 | null {