|
@@ -4,6 +4,7 @@ import { ApiPromise, WsProvider } from '@polkadot/api';
|
|
|
import { formatBalance } from '@polkadot/util';
|
|
|
import { Balance, Hash } from '@polkadot/types/interfaces';
|
|
|
import { KeyringPair } from '@polkadot/keyring/types';
|
|
|
+import { Codec } from '@polkadot/types/types';
|
|
|
import { AccountBalances, CouncilInfoObj, CouncilInfoTuple, AccountSummary, createCouncilInfoObj } from './Types';
|
|
|
import { DerivedFees, DerivedBalances } from '@polkadot/api-derive/types';
|
|
|
import { CLIError } from '@oclif/errors';
|
|
@@ -19,10 +20,13 @@ const TOKEN_SYMBOL = 'JOY';
|
|
|
// Api wrapper for handling most common api calls and allowing easy API implementation switch in the future
|
|
|
|
|
|
export default class Api {
|
|
|
- private _api: ApiPromise | null = null;
|
|
|
- private initializing: boolean = false;
|
|
|
+ private _api: ApiPromise;
|
|
|
|
|
|
- private async initApi(): Promise<ApiPromise> {
|
|
|
+ private constructor(originalApi:ApiPromise) {
|
|
|
+ this._api = originalApi;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static async initApi(): Promise<ApiPromise> {
|
|
|
formatBalance.setDefaults({ unit: TOKEN_SYMBOL });
|
|
|
const wsProvider:WsProvider = new WsProvider(API_URL);
|
|
|
registerJoystreamTypes();
|
|
@@ -30,42 +34,42 @@ export default class Api {
|
|
|
return await ApiPromise.create({ provider: wsProvider });
|
|
|
}
|
|
|
|
|
|
- private async getApi(): Promise<ApiPromise> {
|
|
|
- while (this.initializing) {
|
|
|
- await new Promise((res,rej) => setTimeout(res, 10));
|
|
|
- }
|
|
|
- if (!this._api) {
|
|
|
- this.initializing = true;
|
|
|
- this._api = await this.initApi();
|
|
|
- this.initializing = false;
|
|
|
- }
|
|
|
+ static async create(): Promise<Api> {
|
|
|
+ const originalApi: ApiPromise = await Api.initApi();
|
|
|
+ return new Api(originalApi);
|
|
|
+ }
|
|
|
|
|
|
- return this._api;
|
|
|
+ private async queryMultiOnce(queries: Parameters<typeof ApiPromise.prototype.queryMulti>[0]): Promise<Codec[]> {
|
|
|
+ let results: Codec[] = [];
|
|
|
+
|
|
|
+ const unsub = await this._api.queryMulti(
|
|
|
+ queries,
|
|
|
+ (res) => { results = res }
|
|
|
+ );
|
|
|
+ unsub();
|
|
|
+
|
|
|
+ if (!results.length || results.length !== queries.length) {
|
|
|
+ throw new CLIError('API querying issue', { exit: ExitCodes.ApiError });
|
|
|
+ }
|
|
|
+ return results;
|
|
|
}
|
|
|
|
|
|
async getAccountsBalancesInfo(accountAddresses:string[]): Promise<AccountBalances[]> {
|
|
|
- const api: ApiPromise = await this.getApi();
|
|
|
-
|
|
|
let apiQueries:any = [];
|
|
|
for (let address of accountAddresses) {
|
|
|
- apiQueries.push([api.query.balances.freeBalance, address]);
|
|
|
- apiQueries.push([api.query.balances.reservedBalance, address]);
|
|
|
+ apiQueries.push([this._api.query.balances.freeBalance, address]);
|
|
|
+ apiQueries.push([this._api.query.balances.reservedBalance, address]);
|
|
|
}
|
|
|
|
|
|
let balances: AccountBalances[] = [];
|
|
|
- const unsub = await api.queryMulti(
|
|
|
- apiQueries,
|
|
|
- (balancesRes) => {
|
|
|
- for (let key in accountAddresses) {
|
|
|
- let numKey: number = parseInt(key);
|
|
|
- const free: Balance = <Balance> balancesRes[numKey*2];
|
|
|
- const reserved: Balance = <Balance> balancesRes[numKey*2 + 1];
|
|
|
- const total: Balance = api.createType('Balance', free.add(reserved));
|
|
|
- balances[key] = { free, reserved, total };
|
|
|
- }
|
|
|
- }
|
|
|
- );
|
|
|
- unsub();
|
|
|
+ let balancesRes = await this.queryMultiOnce(apiQueries);
|
|
|
+ for (let key in accountAddresses) {
|
|
|
+ let numKey: number = parseInt(key);
|
|
|
+ const free: Balance = <Balance> balancesRes[numKey*2];
|
|
|
+ const reserved: Balance = <Balance> balancesRes[numKey*2 + 1];
|
|
|
+ const total: Balance = this._api.createType('Balance', free.add(reserved));
|
|
|
+ balances[key] = { free, reserved, total };
|
|
|
+ }
|
|
|
|
|
|
return balances;
|
|
|
}
|
|
@@ -73,51 +77,40 @@ export default class Api {
|
|
|
// Get on-chain data related to given account.
|
|
|
// For now it's just account balances
|
|
|
async getAccountSummary(accountAddresses:string): Promise<AccountSummary> {
|
|
|
- const api: ApiPromise = await this.getApi();
|
|
|
- const balances: DerivedBalances = await api.derive.balances.all(accountAddresses);
|
|
|
+ const balances: DerivedBalances = await this._api.derive.balances.all(accountAddresses);
|
|
|
|
|
|
return { balances };
|
|
|
}
|
|
|
|
|
|
async getCouncilInfo(): Promise<CouncilInfoObj> {
|
|
|
- const api: ApiPromise = await this.getApi();
|
|
|
- let infoObj: CouncilInfoObj | null = null;
|
|
|
- const unsub = await api.queryMulti(
|
|
|
- [
|
|
|
- api.query.council.activeCouncil,
|
|
|
- api.query.council.termEndsAt,
|
|
|
- api.query.councilElection.autoStart,
|
|
|
- api.query.councilElection.newTermDuration,
|
|
|
- api.query.councilElection.candidacyLimit,
|
|
|
- api.query.councilElection.councilSize,
|
|
|
- api.query.councilElection.minCouncilStake,
|
|
|
- api.query.councilElection.minVotingStake,
|
|
|
- api.query.councilElection.announcingPeriod,
|
|
|
- api.query.councilElection.votingPeriod,
|
|
|
- api.query.councilElection.revealingPeriod,
|
|
|
- api.query.councilElection.round,
|
|
|
- api.query.councilElection.stage
|
|
|
- ],
|
|
|
- ((res) => {
|
|
|
- infoObj = createCouncilInfoObj(...(<CouncilInfoTuple> res));
|
|
|
- })
|
|
|
- );
|
|
|
- unsub();
|
|
|
-
|
|
|
- if (infoObj) return infoObj;
|
|
|
- // Probably never happens, but otherwise TS will see it as error
|
|
|
- else throw new CLIError('Unexpected API error', { exit: ExitCodes.ApiError });
|
|
|
+ const results = await this.queryMultiOnce([
|
|
|
+ this._api.query.council.activeCouncil,
|
|
|
+ this._api.query.council.termEndsAt,
|
|
|
+ this._api.query.councilElection.autoStart,
|
|
|
+ this._api.query.councilElection.newTermDuration,
|
|
|
+ this._api.query.councilElection.candidacyLimit,
|
|
|
+ this._api.query.councilElection.councilSize,
|
|
|
+ this._api.query.councilElection.minCouncilStake,
|
|
|
+ this._api.query.councilElection.minVotingStake,
|
|
|
+ this._api.query.councilElection.announcingPeriod,
|
|
|
+ this._api.query.councilElection.votingPeriod,
|
|
|
+ this._api.query.councilElection.revealingPeriod,
|
|
|
+ this._api.query.councilElection.round,
|
|
|
+ this._api.query.councilElection.stage
|
|
|
+ ]);
|
|
|
+
|
|
|
+ let infoObj: CouncilInfoObj = createCouncilInfoObj(...(<CouncilInfoTuple> results));
|
|
|
+
|
|
|
+ return infoObj;
|
|
|
}
|
|
|
|
|
|
// TODO: This formula is probably not too good, so some better implementation will be required in the future
|
|
|
async estimateFee(account: KeyringPair, recipientAddr: string, amount: BN): Promise<BN> {
|
|
|
- const api: ApiPromise = await this.getApi();
|
|
|
-
|
|
|
- const transfer = api.tx.balances.transfer(recipientAddr, amount);
|
|
|
+ const transfer = this._api.tx.balances.transfer(recipientAddr, amount);
|
|
|
const signature = account.sign(transfer.toU8a());
|
|
|
const transactionByteSize:BN = new BN(transfer.encodedLength + signature.length);
|
|
|
|
|
|
- const fees: DerivedFees = await api.derive.balances.fees();
|
|
|
+ const fees: DerivedFees = await this._api.derive.balances.fees();
|
|
|
|
|
|
const estimatedFee = fees.transactionBaseFee.add(fees.transactionByteFee.mul(transactionByteSize));
|
|
|
|
|
@@ -125,9 +118,7 @@ export default class Api {
|
|
|
}
|
|
|
|
|
|
async transfer(account: KeyringPair, recipientAddr: string, amount: BN): Promise<Hash> {
|
|
|
- const api: ApiPromise = await this.getApi();
|
|
|
-
|
|
|
- const txHash = await api.tx.balances
|
|
|
+ const txHash = await this._api.tx.balances
|
|
|
.transfer(recipientAddr, amount)
|
|
|
.signAndSend(account);
|
|
|
return txHash;
|