|
@@ -1,480 +1,469 @@
|
|
|
-import BN from 'bn.js';
|
|
|
-import { registerJoystreamTypes } from '@joystream/types/';
|
|
|
-import { ApiPromise, WsProvider } from '@polkadot/api';
|
|
|
-import { QueryableStorageMultiArg } from '@polkadot/api/types';
|
|
|
-import { formatBalance } from '@polkadot/util';
|
|
|
-import { Hash, Balance } from '@polkadot/types/interfaces';
|
|
|
-import { KeyringPair } from '@polkadot/keyring/types';
|
|
|
-import { Codec } from '@polkadot/types/types';
|
|
|
-import { Option, Vec } from '@polkadot/types';
|
|
|
-import { u32 } from '@polkadot/types/primitive';
|
|
|
+import BN from 'bn.js'
|
|
|
+import { registerJoystreamTypes } from '@joystream/types/'
|
|
|
+import { ApiPromise, WsProvider } from '@polkadot/api'
|
|
|
+import { QueryableStorageMultiArg } from '@polkadot/api/types'
|
|
|
+import { formatBalance } from '@polkadot/util'
|
|
|
+import { Hash, Balance, Moment } from '@polkadot/types/interfaces'
|
|
|
+import { KeyringPair } from '@polkadot/keyring/types'
|
|
|
+import { Codec } from '@polkadot/types/types'
|
|
|
+import { Option, Vec } from '@polkadot/types'
|
|
|
+import { u32 } from '@polkadot/types/primitive'
|
|
|
import {
|
|
|
- AccountSummary,
|
|
|
- CouncilInfoObj, CouncilInfoTuple, createCouncilInfoObj,
|
|
|
- WorkingGroups,
|
|
|
- Reward,
|
|
|
- GroupMember,
|
|
|
- OpeningStatus,
|
|
|
- GroupOpeningStage,
|
|
|
- GroupOpening,
|
|
|
- GroupApplication
|
|
|
-} from './Types';
|
|
|
-import { DerivedFees, DerivedBalances } from '@polkadot/api-derive/types';
|
|
|
-import { CLIError } from '@oclif/errors';
|
|
|
-import ExitCodes from './ExitCodes';
|
|
|
+ AccountSummary,
|
|
|
+ CouncilInfoObj,
|
|
|
+ CouncilInfoTuple,
|
|
|
+ createCouncilInfoObj,
|
|
|
+ WorkingGroups,
|
|
|
+ Reward,
|
|
|
+ GroupMember,
|
|
|
+ OpeningStatus,
|
|
|
+ GroupOpeningStage,
|
|
|
+ GroupOpening,
|
|
|
+ GroupApplication,
|
|
|
+} from './Types'
|
|
|
+import { DerivedFees, DerivedBalances } from '@polkadot/api-derive/types'
|
|
|
+import { CLIError } from '@oclif/errors'
|
|
|
+import ExitCodes from './ExitCodes'
|
|
|
import {
|
|
|
- Worker, WorkerId,
|
|
|
- RoleStakeProfile,
|
|
|
- Opening as WGOpening,
|
|
|
- Application as WGApplication
|
|
|
-} from '@joystream/types/working-group';
|
|
|
+ Worker,
|
|
|
+ WorkerId,
|
|
|
+ RoleStakeProfile,
|
|
|
+ Opening as WGOpening,
|
|
|
+ Application as WGApplication,
|
|
|
+} from '@joystream/types/working-group'
|
|
|
import {
|
|
|
- Opening,
|
|
|
- Application,
|
|
|
- OpeningStage,
|
|
|
- ApplicationStageKeys,
|
|
|
- ApplicationId,
|
|
|
- OpeningId
|
|
|
-} from '@joystream/types/hiring';
|
|
|
-import { MemberId, Profile } from '@joystream/types/members';
|
|
|
-import { RewardRelationship, RewardRelationshipId } from '@joystream/types/recurring-rewards';
|
|
|
-import { Stake, StakeId } from '@joystream/types/stake';
|
|
|
-import { LinkageResult } from '@polkadot/types/codec/Linkage';
|
|
|
-import { Moment } from '@polkadot/types/interfaces';
|
|
|
-import { InputValidationLengthConstraint } from '@joystream/types/common';
|
|
|
-
|
|
|
-export const DEFAULT_API_URI = 'wss://rome-rpc-endpoint.joystream.org:9944/';
|
|
|
-const DEFAULT_DECIMALS = new u32(12);
|
|
|
+ Opening,
|
|
|
+ Application,
|
|
|
+ OpeningStage,
|
|
|
+ ApplicationStageKeys,
|
|
|
+ ApplicationId,
|
|
|
+ OpeningId,
|
|
|
+} from '@joystream/types/hiring'
|
|
|
+import { MemberId, Profile } from '@joystream/types/members'
|
|
|
+import { RewardRelationship, RewardRelationshipId } from '@joystream/types/recurring-rewards'
|
|
|
+import { Stake, StakeId } from '@joystream/types/stake'
|
|
|
+import { LinkageResult } from '@polkadot/types/codec/Linkage'
|
|
|
+
|
|
|
+import { InputValidationLengthConstraint } from '@joystream/types/common'
|
|
|
+
|
|
|
+export const DEFAULT_API_URI = 'wss://rome-rpc-endpoint.joystream.org:9944/'
|
|
|
+const DEFAULT_DECIMALS = new u32(12)
|
|
|
|
|
|
// Mapping of working group to api module
|
|
|
export const apiModuleByGroup: { [key in WorkingGroups]: string } = {
|
|
|
- [WorkingGroups.StorageProviders]: 'storageWorkingGroup'
|
|
|
-};
|
|
|
+ [WorkingGroups.StorageProviders]: 'storageWorkingGroup',
|
|
|
+}
|
|
|
|
|
|
// Api wrapper for handling most common api calls and allowing easy API implementation switch in the future
|
|
|
export default class Api {
|
|
|
- private _api: ApiPromise;
|
|
|
+ private _api: ApiPromise
|
|
|
|
|
|
- private constructor(originalApi: ApiPromise) {
|
|
|
- this._api = originalApi;
|
|
|
- }
|
|
|
+ private constructor(originalApi: ApiPromise) {
|
|
|
+ this._api = originalApi
|
|
|
+ }
|
|
|
|
|
|
- public getOriginalApi(): ApiPromise {
|
|
|
- return this._api;
|
|
|
- }
|
|
|
+ public getOriginalApi(): ApiPromise {
|
|
|
+ return this._api
|
|
|
+ }
|
|
|
+
|
|
|
+ private static async initApi(apiUri: string = DEFAULT_API_URI): Promise<ApiPromise> {
|
|
|
+ const wsProvider: WsProvider = new WsProvider(apiUri)
|
|
|
+ registerJoystreamTypes()
|
|
|
+ const api = await ApiPromise.create({ provider: wsProvider })
|
|
|
+
|
|
|
+ // Initializing some api params based on pioneer/packages/react-api/Api.tsx
|
|
|
+ const [properties] = await Promise.all([api.rpc.system.properties()])
|
|
|
+
|
|
|
+ const tokenSymbol = properties.tokenSymbol.unwrapOr('DEV').toString()
|
|
|
+ const tokenDecimals = properties.tokenDecimals.unwrapOr(DEFAULT_DECIMALS).toNumber()
|
|
|
+
|
|
|
+ // formatBlanace config
|
|
|
+ formatBalance.setDefaults({
|
|
|
+ decimals: tokenDecimals,
|
|
|
+ unit: tokenSymbol,
|
|
|
+ })
|
|
|
|
|
|
- private static async initApi(apiUri: string = DEFAULT_API_URI): Promise<ApiPromise> {
|
|
|
- const wsProvider: WsProvider = new WsProvider(apiUri);
|
|
|
- registerJoystreamTypes();
|
|
|
- const api = await ApiPromise.create({ provider: wsProvider });
|
|
|
+ return api
|
|
|
+ }
|
|
|
|
|
|
- // Initializing some api params based on pioneer/packages/react-api/Api.tsx
|
|
|
- const [properties] = await Promise.all([
|
|
|
- api.rpc.system.properties()
|
|
|
- ]);
|
|
|
+ static async create(apiUri: string = DEFAULT_API_URI): Promise<Api> {
|
|
|
+ const originalApi: ApiPromise = await Api.initApi(apiUri)
|
|
|
+ return new Api(originalApi)
|
|
|
+ }
|
|
|
|
|
|
- const tokenSymbol = properties.tokenSymbol.unwrapOr('DEV').toString();
|
|
|
- const tokenDecimals = properties.tokenDecimals.unwrapOr(DEFAULT_DECIMALS).toNumber();
|
|
|
+ private async queryMultiOnce(queries: Parameters<typeof ApiPromise.prototype.queryMulti>[0]): Promise<Codec[]> {
|
|
|
+ let results: Codec[] = []
|
|
|
|
|
|
- // formatBlanace config
|
|
|
- formatBalance.setDefaults({
|
|
|
- decimals: tokenDecimals,
|
|
|
- unit: tokenSymbol
|
|
|
- });
|
|
|
+ const unsub = await this._api.queryMulti(queries, (res) => {
|
|
|
+ results = res
|
|
|
+ })
|
|
|
+ unsub()
|
|
|
|
|
|
- return api;
|
|
|
+ if (!results.length || results.length !== queries.length) {
|
|
|
+ throw new CLIError('API querying issue', { exit: ExitCodes.ApiError })
|
|
|
}
|
|
|
|
|
|
- static async create(apiUri: string = DEFAULT_API_URI): Promise<Api> {
|
|
|
- const originalApi: ApiPromise = await Api.initApi(apiUri);
|
|
|
- return new Api(originalApi);
|
|
|
- }
|
|
|
+ return results
|
|
|
+ }
|
|
|
+
|
|
|
+ async getAccountsBalancesInfo(accountAddresses: string[]): Promise<DerivedBalances[]> {
|
|
|
+ const accountsBalances: DerivedBalances[] = await this._api.derive.balances.votingBalances(accountAddresses)
|
|
|
|
|
|
- private async queryMultiOnce(queries: Parameters<typeof ApiPromise.prototype.queryMulti>[0]): Promise<Codec[]> {
|
|
|
- let results: Codec[] = [];
|
|
|
+ return accountsBalances
|
|
|
+ }
|
|
|
|
|
|
- const unsub = await this._api.queryMulti(
|
|
|
- queries,
|
|
|
- (res) => { results = res }
|
|
|
- );
|
|
|
- unsub();
|
|
|
+ // Get on-chain data related to given account.
|
|
|
+ // For now it's just account balances
|
|
|
+ async getAccountSummary(accountAddresses: string): Promise<AccountSummary> {
|
|
|
+ const balances: DerivedBalances = (await this.getAccountsBalancesInfo([accountAddresses]))[0]
|
|
|
+ // TODO: Some more information can be fetched here in the future
|
|
|
|
|
|
- if (!results.length || results.length !== queries.length) {
|
|
|
- throw new CLIError('API querying issue', { exit: ExitCodes.ApiError });
|
|
|
- }
|
|
|
+ return { balances }
|
|
|
+ }
|
|
|
|
|
|
- return results;
|
|
|
+ async getCouncilInfo(): Promise<CouncilInfoObj> {
|
|
|
+ const queries: { [P in keyof CouncilInfoObj]: QueryableStorageMultiArg<'promise'> } = {
|
|
|
+ activeCouncil: this._api.query.council.activeCouncil,
|
|
|
+ termEndsAt: this._api.query.council.termEndsAt,
|
|
|
+ autoStart: this._api.query.councilElection.autoStart,
|
|
|
+ newTermDuration: this._api.query.councilElection.newTermDuration,
|
|
|
+ candidacyLimit: this._api.query.councilElection.candidacyLimit,
|
|
|
+ councilSize: this._api.query.councilElection.councilSize,
|
|
|
+ minCouncilStake: this._api.query.councilElection.minCouncilStake,
|
|
|
+ minVotingStake: this._api.query.councilElection.minVotingStake,
|
|
|
+ announcingPeriod: this._api.query.councilElection.announcingPeriod,
|
|
|
+ votingPeriod: this._api.query.councilElection.votingPeriod,
|
|
|
+ revealingPeriod: this._api.query.councilElection.revealingPeriod,
|
|
|
+ round: this._api.query.councilElection.round,
|
|
|
+ stage: this._api.query.councilElection.stage,
|
|
|
}
|
|
|
+ const results: CouncilInfoTuple = (await this.queryMultiOnce(Object.values(queries))) as CouncilInfoTuple
|
|
|
|
|
|
- async getAccountsBalancesInfo(accountAddresses: string[]): Promise<DerivedBalances[]> {
|
|
|
- let accountsBalances: DerivedBalances[] = await this._api.derive.balances.votingBalances(accountAddresses);
|
|
|
+ return createCouncilInfoObj(...results)
|
|
|
+ }
|
|
|
|
|
|
- return accountsBalances;
|
|
|
- }
|
|
|
+ // 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 transfer = this._api.tx.balances.transfer(recipientAddr, amount)
|
|
|
+ const signature = account.sign(transfer.toU8a())
|
|
|
+ const transactionByteSize: BN = new BN(transfer.encodedLength + signature.length)
|
|
|
|
|
|
- // Get on-chain data related to given account.
|
|
|
- // For now it's just account balances
|
|
|
- async getAccountSummary(accountAddresses: string): Promise<AccountSummary> {
|
|
|
- const balances: DerivedBalances = (await this.getAccountsBalancesInfo([accountAddresses]))[0];
|
|
|
- // TODO: Some more information can be fetched here in the future
|
|
|
+ const fees: DerivedFees = await this._api.derive.balances.fees()
|
|
|
|
|
|
- return { balances };
|
|
|
- }
|
|
|
+ const estimatedFee = fees.transactionBaseFee.add(fees.transactionByteFee.mul(transactionByteSize))
|
|
|
|
|
|
- async getCouncilInfo(): Promise<CouncilInfoObj> {
|
|
|
- const queries: { [P in keyof CouncilInfoObj]: QueryableStorageMultiArg<"promise"> } = {
|
|
|
- activeCouncil: this._api.query.council.activeCouncil,
|
|
|
- termEndsAt: this._api.query.council.termEndsAt,
|
|
|
- autoStart: this._api.query.councilElection.autoStart,
|
|
|
- newTermDuration: this._api.query.councilElection.newTermDuration,
|
|
|
- candidacyLimit: this._api.query.councilElection.candidacyLimit,
|
|
|
- councilSize: this._api.query.councilElection.councilSize,
|
|
|
- minCouncilStake: this._api.query.councilElection.minCouncilStake,
|
|
|
- minVotingStake: this._api.query.councilElection.minVotingStake,
|
|
|
- announcingPeriod: this._api.query.councilElection.announcingPeriod,
|
|
|
- votingPeriod: this._api.query.councilElection.votingPeriod,
|
|
|
- revealingPeriod: this._api.query.councilElection.revealingPeriod,
|
|
|
- round: this._api.query.councilElection.round,
|
|
|
- stage: this._api.query.councilElection.stage
|
|
|
- }
|
|
|
- const results: CouncilInfoTuple = <CouncilInfoTuple>await this.queryMultiOnce(Object.values(queries));
|
|
|
-
|
|
|
- return createCouncilInfoObj(...results);
|
|
|
- }
|
|
|
+ return estimatedFee
|
|
|
+ }
|
|
|
|
|
|
- // 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 transfer = this._api.tx.balances.transfer(recipientAddr, amount);
|
|
|
- const signature = account.sign(transfer.toU8a());
|
|
|
- const transactionByteSize: BN = new BN(transfer.encodedLength + signature.length);
|
|
|
+ async transfer(account: KeyringPair, recipientAddr: string, amount: BN): Promise<Hash> {
|
|
|
+ const txHash = await this._api.tx.balances.transfer(recipientAddr, amount).signAndSend(account)
|
|
|
+ return txHash
|
|
|
+ }
|
|
|
|
|
|
- const fees: DerivedFees = await this._api.derive.balances.fees();
|
|
|
+ // Working groups
|
|
|
+ // TODO: This is a lot of repeated logic from "/pioneer/joy-roles/src/transport.substrate.ts"
|
|
|
+ // (although simplified a little bit)
|
|
|
+ // Hopefully this will be refactored to "joystream-js" soon
|
|
|
+ protected singleLinkageResult<T extends Codec>(result: LinkageResult) {
|
|
|
+ return result[0] as T
|
|
|
+ }
|
|
|
|
|
|
- const estimatedFee = fees.transactionBaseFee.add(fees.transactionByteFee.mul(transactionByteSize));
|
|
|
+ protected multiLinkageResult<K extends Codec, V extends Codec>(result: LinkageResult): [Vec<K>, Vec<V>] {
|
|
|
+ return [result[0] as Vec<K>, result[1] as Vec<V>]
|
|
|
+ }
|
|
|
|
|
|
- return estimatedFee;
|
|
|
- }
|
|
|
+ protected async blockHash(height: number): Promise<string> {
|
|
|
+ const blockHash = await this._api.rpc.chain.getBlockHash(height)
|
|
|
|
|
|
- async transfer(account: KeyringPair, recipientAddr: string, amount: BN): Promise<Hash> {
|
|
|
- const txHash = await this._api.tx.balances
|
|
|
- .transfer(recipientAddr, amount)
|
|
|
- .signAndSend(account);
|
|
|
- return txHash;
|
|
|
- }
|
|
|
+ return blockHash.toString()
|
|
|
+ }
|
|
|
|
|
|
- // Working groups
|
|
|
- // TODO: This is a lot of repeated logic from "/pioneer/joy-roles/src/transport.substrate.ts"
|
|
|
- // (although simplified a little bit)
|
|
|
- // Hopefully this will be refactored to "joystream-js" soon
|
|
|
- protected singleLinkageResult<T extends Codec>(result: LinkageResult) {
|
|
|
- return result[0] as T;
|
|
|
- }
|
|
|
+ protected async blockTimestamp(height: number): Promise<Date> {
|
|
|
+ const blockTime = (await this._api.query.timestamp.now.at(await this.blockHash(height))) as Moment
|
|
|
|
|
|
- protected multiLinkageResult<K extends Codec, V extends Codec>(result: LinkageResult): [Vec<K>, Vec<V>] {
|
|
|
- return [result[0] as Vec<K>, result[1] as Vec<V>];
|
|
|
- }
|
|
|
+ return new Date(blockTime.toNumber())
|
|
|
+ }
|
|
|
|
|
|
- protected async blockHash(height: number): Promise<string> {
|
|
|
- const blockHash = await this._api.rpc.chain.getBlockHash(height);
|
|
|
+ protected workingGroupApiQuery(group: WorkingGroups) {
|
|
|
+ const module = apiModuleByGroup[group]
|
|
|
+ return this._api.query[module]
|
|
|
+ }
|
|
|
|
|
|
- return blockHash.toString();
|
|
|
- }
|
|
|
+ protected async memberProfileById(memberId: MemberId): Promise<Profile | null> {
|
|
|
+ const profile = (await this._api.query.members.memberProfile(memberId)) as Option<Profile>
|
|
|
|
|
|
- protected async blockTimestamp(height: number): Promise<Date> {
|
|
|
- const blockTime = (await this._api.query.timestamp.now.at(await this.blockHash(height))) as Moment;
|
|
|
+ return profile.unwrapOr(null)
|
|
|
+ }
|
|
|
|
|
|
- return new Date(blockTime.toNumber());
|
|
|
- }
|
|
|
+ async groupLead(group: WorkingGroups): Promise<GroupMember | null> {
|
|
|
+ const optLeadId = (await this.workingGroupApiQuery(group).currentLead()) as Option<WorkerId>
|
|
|
|
|
|
- protected workingGroupApiQuery(group: WorkingGroups) {
|
|
|
- const module = apiModuleByGroup[group];
|
|
|
- return this._api.query[module];
|
|
|
+ if (!optLeadId.isSome) {
|
|
|
+ return null
|
|
|
}
|
|
|
|
|
|
- protected async memberProfileById(memberId: MemberId): Promise<Profile | null> {
|
|
|
- const profile = await this._api.query.members.memberProfile(memberId) as Option<Profile>;
|
|
|
+ const leadWorkerId = optLeadId.unwrap()
|
|
|
+ const leadWorker = await this.workerByWorkerId(group, leadWorkerId.toNumber())
|
|
|
|
|
|
- return profile.unwrapOr(null);
|
|
|
- }
|
|
|
+ return await this.parseGroupMember(leadWorkerId, leadWorker)
|
|
|
+ }
|
|
|
|
|
|
- async groupLead(group: WorkingGroups): Promise<GroupMember | null> {
|
|
|
- const optLeadId = (await this.workingGroupApiQuery(group).currentLead()) as Option<WorkerId>;
|
|
|
+ protected async stakeValue(stakeId: StakeId): Promise<Balance> {
|
|
|
+ const stake = this.singleLinkageResult<Stake>((await this._api.query.stake.stakes(stakeId)) as LinkageResult)
|
|
|
+ return stake.value
|
|
|
+ }
|
|
|
|
|
|
- if (!optLeadId.isSome) {
|
|
|
- return null;
|
|
|
- }
|
|
|
+ protected async workerStake(stakeProfile: RoleStakeProfile): Promise<Balance> {
|
|
|
+ return this.stakeValue(stakeProfile.stake_id)
|
|
|
+ }
|
|
|
|
|
|
- const leadWorkerId = optLeadId.unwrap();
|
|
|
- const leadWorker = await this.workerByWorkerId(group, leadWorkerId.toNumber());
|
|
|
+ protected async workerReward(relationshipId: RewardRelationshipId): Promise<Reward> {
|
|
|
+ const rewardRelationship = this.singleLinkageResult<RewardRelationship>(
|
|
|
+ (await this._api.query.recurringRewards.rewardRelationships(relationshipId)) as LinkageResult
|
|
|
+ )
|
|
|
|
|
|
- return await this.parseGroupMember(leadWorkerId, leadWorker);
|
|
|
+ return {
|
|
|
+ totalRecieved: rewardRelationship.total_reward_received,
|
|
|
+ value: rewardRelationship.amount_per_payout,
|
|
|
+ interval: rewardRelationship.payout_interval.unwrapOr(undefined)?.toNumber(),
|
|
|
+ nextPaymentBlock: rewardRelationship.next_payment_at_block.unwrapOr(new BN(0)).toNumber(),
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- protected async stakeValue(stakeId: StakeId): Promise<Balance> {
|
|
|
- const stake = this.singleLinkageResult<Stake>(
|
|
|
- await this._api.query.stake.stakes(stakeId) as LinkageResult
|
|
|
- );
|
|
|
- return stake.value;
|
|
|
- }
|
|
|
+ protected async parseGroupMember(id: WorkerId, worker: Worker): Promise<GroupMember> {
|
|
|
+ const roleAccount = worker.role_account_id
|
|
|
+ const memberId = worker.member_id
|
|
|
|
|
|
- protected async workerStake (stakeProfile: RoleStakeProfile): Promise<Balance> {
|
|
|
- return this.stakeValue(stakeProfile.stake_id);
|
|
|
- }
|
|
|
+ const profile = await this.memberProfileById(memberId)
|
|
|
|
|
|
- protected async workerReward(relationshipId: RewardRelationshipId): Promise<Reward> {
|
|
|
- const rewardRelationship = this.singleLinkageResult<RewardRelationship>(
|
|
|
- await this._api.query.recurringRewards.rewardRelationships(relationshipId) as LinkageResult
|
|
|
- );
|
|
|
-
|
|
|
- return {
|
|
|
- totalRecieved: rewardRelationship.total_reward_received,
|
|
|
- value: rewardRelationship.amount_per_payout,
|
|
|
- interval: rewardRelationship.payout_interval.unwrapOr(undefined)?.toNumber(),
|
|
|
- nextPaymentBlock: rewardRelationship.next_payment_at_block.unwrapOr(new BN(0)).toNumber()
|
|
|
- };
|
|
|
+ if (!profile) {
|
|
|
+ throw new Error(`Group member profile not found! (member id: ${memberId.toNumber()})`)
|
|
|
}
|
|
|
|
|
|
- protected async parseGroupMember(
|
|
|
- id: WorkerId,
|
|
|
- worker: Worker
|
|
|
- ): Promise<GroupMember> {
|
|
|
- const roleAccount = worker.role_account_id;
|
|
|
- const memberId = worker.member_id;
|
|
|
-
|
|
|
- const profile = await this.memberProfileById(memberId);
|
|
|
-
|
|
|
- if (!profile) {
|
|
|
- throw new Error(`Group member profile not found! (member id: ${memberId.toNumber()})`);
|
|
|
- }
|
|
|
-
|
|
|
- let stake: Balance | undefined;
|
|
|
- if (worker.role_stake_profile && worker.role_stake_profile.isSome) {
|
|
|
- stake = await this.workerStake(worker.role_stake_profile.unwrap());
|
|
|
- }
|
|
|
-
|
|
|
- let reward: Reward | undefined;
|
|
|
- if (worker.reward_relationship && worker.reward_relationship.isSome) {
|
|
|
- reward = await this.workerReward(worker.reward_relationship.unwrap());
|
|
|
- }
|
|
|
-
|
|
|
- return ({
|
|
|
- workerId: id,
|
|
|
- roleAccount,
|
|
|
- memberId,
|
|
|
- profile,
|
|
|
- stake,
|
|
|
- reward
|
|
|
- });
|
|
|
+ let stake: Balance | undefined
|
|
|
+ if (worker.role_stake_profile && worker.role_stake_profile.isSome) {
|
|
|
+ stake = await this.workerStake(worker.role_stake_profile.unwrap())
|
|
|
}
|
|
|
|
|
|
- async workerByWorkerId(group: WorkingGroups, workerId: number): Promise<Worker> {
|
|
|
- const nextId = (await this.workingGroupApiQuery(group).nextWorkerId()) as WorkerId;
|
|
|
-
|
|
|
- // This is chain specfic, but if next id is still 0, it means no workers have been added yet
|
|
|
- if (workerId < 0 || workerId >= nextId.toNumber()) {
|
|
|
- throw new CLIError('Invalid worker id!');
|
|
|
- }
|
|
|
-
|
|
|
- const worker = this.singleLinkageResult<Worker>(
|
|
|
- (await this.workingGroupApiQuery(group).workerById(workerId)) as LinkageResult
|
|
|
- );
|
|
|
-
|
|
|
- if (!worker.is_active) {
|
|
|
- throw new CLIError('This worker is not active anymore');
|
|
|
- }
|
|
|
-
|
|
|
- return worker;
|
|
|
+ let reward: Reward | undefined
|
|
|
+ if (worker.reward_relationship && worker.reward_relationship.isSome) {
|
|
|
+ reward = await this.workerReward(worker.reward_relationship.unwrap())
|
|
|
}
|
|
|
|
|
|
- async groupMember(group: WorkingGroups, workerId: number) {
|
|
|
- const worker = await this.workerByWorkerId(group, workerId);
|
|
|
- return await this.parseGroupMember(new WorkerId(workerId), worker);
|
|
|
+ return {
|
|
|
+ workerId: id,
|
|
|
+ roleAccount,
|
|
|
+ memberId,
|
|
|
+ profile,
|
|
|
+ stake,
|
|
|
+ reward,
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- async groupMembers(group: WorkingGroups): Promise<GroupMember[]> {
|
|
|
- const nextId = (await this.workingGroupApiQuery(group).nextWorkerId()) as WorkerId;
|
|
|
+ async workerByWorkerId(group: WorkingGroups, workerId: number): Promise<Worker> {
|
|
|
+ const nextId = (await this.workingGroupApiQuery(group).nextWorkerId()) as WorkerId
|
|
|
|
|
|
- // This is chain specfic, but if next id is still 0, it means no workers have been added yet
|
|
|
- if (nextId.eq(0)) {
|
|
|
- return [];
|
|
|
- }
|
|
|
-
|
|
|
- const [workerIds, workers] = this.multiLinkageResult<WorkerId, Worker>(
|
|
|
- (await this.workingGroupApiQuery(group).workerById()) as LinkageResult
|
|
|
- );
|
|
|
+ // This is chain specfic, but if next id is still 0, it means no workers have been added yet
|
|
|
+ if (workerId < 0 || workerId >= nextId.toNumber()) {
|
|
|
+ throw new CLIError('Invalid worker id!')
|
|
|
+ }
|
|
|
|
|
|
- let groupMembers: GroupMember[] = [];
|
|
|
- for (let [index, worker] of Object.entries(workers.toArray())) {
|
|
|
- const workerId = workerIds[parseInt(index)];
|
|
|
- if (worker.is_active) {
|
|
|
- groupMembers.push(await this.parseGroupMember(workerId, worker));
|
|
|
- }
|
|
|
- }
|
|
|
+ const worker = this.singleLinkageResult<Worker>(
|
|
|
+ (await this.workingGroupApiQuery(group).workerById(workerId)) as LinkageResult
|
|
|
+ )
|
|
|
|
|
|
- return groupMembers.reverse();
|
|
|
+ if (!worker.is_active) {
|
|
|
+ throw new CLIError('This worker is not active anymore')
|
|
|
}
|
|
|
|
|
|
- async openingsByGroup(group: WorkingGroups): Promise<GroupOpening[]> {
|
|
|
- const openings: GroupOpening[] = [];
|
|
|
- const nextId = (await this.workingGroupApiQuery(group).nextOpeningId()) as OpeningId;
|
|
|
+ return worker
|
|
|
+ }
|
|
|
|
|
|
- // This is chain specfic, but if next id is still 0, it means no openings have been added yet
|
|
|
- if (!nextId.eq(0)) {
|
|
|
- const highestId = nextId.toNumber() - 1;
|
|
|
- for (let i = highestId; i >= 0; i--) {
|
|
|
- openings.push(await this.groupOpening(group, i));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return openings;
|
|
|
- }
|
|
|
+ async groupMember(group: WorkingGroups, workerId: number) {
|
|
|
+ const worker = await this.workerByWorkerId(group, workerId)
|
|
|
+ return await this.parseGroupMember(new WorkerId(workerId), worker)
|
|
|
+ }
|
|
|
|
|
|
- protected async hiringOpeningById(id: number | OpeningId): Promise<Opening> {
|
|
|
- const result = await this._api.query.hiring.openingById(id) as LinkageResult;
|
|
|
- return this.singleLinkageResult<Opening>(result);
|
|
|
- }
|
|
|
+ async groupMembers(group: WorkingGroups): Promise<GroupMember[]> {
|
|
|
+ const nextId = (await this.workingGroupApiQuery(group).nextWorkerId()) as WorkerId
|
|
|
|
|
|
- protected async hiringApplicationById(id: number | ApplicationId): Promise<Application> {
|
|
|
- const result = await this._api.query.hiring.applicationById(id) as LinkageResult;
|
|
|
- return this.singleLinkageResult<Application>(result);
|
|
|
+ // This is chain specfic, but if next id is still 0, it means no workers have been added yet
|
|
|
+ if (nextId.eq(0)) {
|
|
|
+ return []
|
|
|
}
|
|
|
|
|
|
- async wgApplicationById(group: WorkingGroups, wgApplicationId: number): Promise<WGApplication> {
|
|
|
- const nextAppId = await this.workingGroupApiQuery(group).nextApplicationId() as ApplicationId;
|
|
|
-
|
|
|
- if (wgApplicationId < 0 || wgApplicationId >= nextAppId.toNumber()) {
|
|
|
- throw new CLIError('Invalid working group application ID!');
|
|
|
- }
|
|
|
+ const [workerIds, workers] = this.multiLinkageResult<WorkerId, Worker>(
|
|
|
+ (await this.workingGroupApiQuery(group).workerById()) as LinkageResult
|
|
|
+ )
|
|
|
|
|
|
- return this.singleLinkageResult<WGApplication>(
|
|
|
- await this.workingGroupApiQuery(group).applicationById(wgApplicationId) as LinkageResult
|
|
|
- );
|
|
|
+ const groupMembers: GroupMember[] = []
|
|
|
+ for (const [index, worker] of Object.entries(workers.toArray())) {
|
|
|
+ const workerId = workerIds[parseInt(index)]
|
|
|
+ if (worker.is_active) {
|
|
|
+ groupMembers.push(await this.parseGroupMember(workerId, worker))
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- protected async parseApplication(wgApplicationId: number, wgApplication: WGApplication): Promise<GroupApplication> {
|
|
|
- const appId = wgApplication.application_id;
|
|
|
- const application = await this.hiringApplicationById(appId);
|
|
|
-
|
|
|
- const { active_role_staking_id: roleStakingId, active_application_staking_id: appStakingId } = application;
|
|
|
-
|
|
|
- return {
|
|
|
- wgApplicationId,
|
|
|
- applicationId: appId.toNumber(),
|
|
|
- wgOpeningId: wgApplication.opening_id.toNumber(),
|
|
|
- member: await this.memberProfileById(wgApplication.member_id),
|
|
|
- roleAccout: wgApplication.role_account_id,
|
|
|
- stakes: {
|
|
|
- application: appStakingId.isSome ? (await this.stakeValue(appStakingId.unwrap())).toNumber() : 0,
|
|
|
- role: roleStakingId.isSome ? (await this.stakeValue(roleStakingId.unwrap())).toNumber() : 0
|
|
|
- },
|
|
|
- humanReadableText: application.human_readable_text.toString(),
|
|
|
- stage: application.stage.type as ApplicationStageKeys
|
|
|
- };
|
|
|
- }
|
|
|
+ return groupMembers.reverse()
|
|
|
+ }
|
|
|
|
|
|
- async groupApplication(group: WorkingGroups, wgApplicationId: number): Promise<GroupApplication> {
|
|
|
- const wgApplication = await this.wgApplicationById(group, wgApplicationId);
|
|
|
- return await this.parseApplication(wgApplicationId, wgApplication);
|
|
|
- }
|
|
|
+ async openingsByGroup(group: WorkingGroups): Promise<GroupOpening[]> {
|
|
|
+ const openings: GroupOpening[] = []
|
|
|
+ const nextId = (await this.workingGroupApiQuery(group).nextOpeningId()) as OpeningId
|
|
|
|
|
|
- protected async groupOpeningApplications(group: WorkingGroups, wgOpeningId: number): Promise<GroupApplication[]> {
|
|
|
- const applications: GroupApplication[] = [];
|
|
|
+ // This is chain specfic, but if next id is still 0, it means no openings have been added yet
|
|
|
+ if (!nextId.eq(0)) {
|
|
|
+ const highestId = nextId.toNumber() - 1
|
|
|
+ for (let i = highestId; i >= 0; i--) {
|
|
|
+ openings.push(await this.groupOpening(group, i))
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- const nextAppId = await this.workingGroupApiQuery(group).nextApplicationId() as ApplicationId;
|
|
|
- for (let i = 0; i < nextAppId.toNumber(); i++) {
|
|
|
- const wgApplication = await this.wgApplicationById(group, i);
|
|
|
- if (wgApplication.opening_id.toNumber() !== wgOpeningId) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- applications.push(await this.parseApplication(i, wgApplication));
|
|
|
- }
|
|
|
+ return openings
|
|
|
+ }
|
|
|
|
|
|
+ protected async hiringOpeningById(id: number | OpeningId): Promise<Opening> {
|
|
|
+ const result = (await this._api.query.hiring.openingById(id)) as LinkageResult
|
|
|
+ return this.singleLinkageResult<Opening>(result)
|
|
|
+ }
|
|
|
|
|
|
- return applications;
|
|
|
- }
|
|
|
+ protected async hiringApplicationById(id: number | ApplicationId): Promise<Application> {
|
|
|
+ const result = (await this._api.query.hiring.applicationById(id)) as LinkageResult
|
|
|
+ return this.singleLinkageResult<Application>(result)
|
|
|
+ }
|
|
|
|
|
|
- async groupOpening(group: WorkingGroups, wgOpeningId: number): Promise<GroupOpening> {
|
|
|
- const nextId = ((await this.workingGroupApiQuery(group).nextOpeningId()) as OpeningId).toNumber();
|
|
|
-
|
|
|
- if (wgOpeningId < 0 || wgOpeningId >= nextId) {
|
|
|
- throw new CLIError('Invalid working group opening ID!');
|
|
|
- }
|
|
|
-
|
|
|
- const groupOpening = this.singleLinkageResult<WGOpening>(
|
|
|
- await this.workingGroupApiQuery(group).openingById(wgOpeningId) as LinkageResult
|
|
|
- );
|
|
|
-
|
|
|
- const openingId = groupOpening.hiring_opening_id.toNumber();
|
|
|
- const opening = await this.hiringOpeningById(openingId);
|
|
|
- const applications = await this.groupOpeningApplications(group, wgOpeningId);
|
|
|
- const stage = await this.parseOpeningStage(opening.stage);
|
|
|
- const type = groupOpening.opening_type;
|
|
|
- const stakes = {
|
|
|
- application: opening.application_staking_policy.unwrapOr(undefined),
|
|
|
- role: opening.role_staking_policy.unwrapOr(undefined)
|
|
|
- }
|
|
|
-
|
|
|
- return ({
|
|
|
- wgOpeningId,
|
|
|
- openingId,
|
|
|
- opening,
|
|
|
- stage,
|
|
|
- stakes,
|
|
|
- applications,
|
|
|
- type
|
|
|
- });
|
|
|
- }
|
|
|
+ async wgApplicationById(group: WorkingGroups, wgApplicationId: number): Promise<WGApplication> {
|
|
|
+ const nextAppId = (await this.workingGroupApiQuery(group).nextApplicationId()) as ApplicationId
|
|
|
|
|
|
- async parseOpeningStage(stage: OpeningStage): Promise<GroupOpeningStage> {
|
|
|
- let
|
|
|
- status: OpeningStatus | undefined,
|
|
|
- stageBlock: number | undefined,
|
|
|
- stageDate: Date | undefined;
|
|
|
-
|
|
|
- if (stage.isOfType('WaitingToBegin')) {
|
|
|
- const stageData = stage.asType('WaitingToBegin');
|
|
|
- const currentBlockNumber = (await this._api.derive.chain.bestNumber()).toNumber();
|
|
|
- const expectedBlockTime = (this._api.consts.babe.expectedBlockTime as Moment).toNumber();
|
|
|
- status = OpeningStatus.WaitingToBegin;
|
|
|
- stageBlock = stageData.begins_at_block.toNumber();
|
|
|
- stageDate = new Date(Date.now() + (stageBlock - currentBlockNumber) * expectedBlockTime);
|
|
|
- }
|
|
|
-
|
|
|
- if (stage.isOfType('Active')) {
|
|
|
- const stageData = stage.asType('Active');
|
|
|
- const substage = stageData.stage;
|
|
|
- if (substage.isOfType('AcceptingApplications')) {
|
|
|
- status = OpeningStatus.AcceptingApplications;
|
|
|
- stageBlock = substage.asType('AcceptingApplications').started_accepting_applicants_at_block.toNumber();
|
|
|
- }
|
|
|
- if (substage.isOfType('ReviewPeriod')) {
|
|
|
- status = OpeningStatus.InReview;
|
|
|
- stageBlock = substage.asType('ReviewPeriod').started_review_period_at_block.toNumber();
|
|
|
- }
|
|
|
- if (substage.isOfType('Deactivated')) {
|
|
|
- status = substage.asType('Deactivated').cause.isOfType('Filled')
|
|
|
- ? OpeningStatus.Complete
|
|
|
- : OpeningStatus.Cancelled;
|
|
|
- stageBlock = substage.asType('Deactivated').deactivated_at_block.toNumber();
|
|
|
- }
|
|
|
- if (stageBlock) {
|
|
|
- stageDate = new Date(await this.blockTimestamp(stageBlock));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return {
|
|
|
- status: status || OpeningStatus.Unknown,
|
|
|
- block: stageBlock,
|
|
|
- date: stageDate
|
|
|
- };
|
|
|
+ if (wgApplicationId < 0 || wgApplicationId >= nextAppId.toNumber()) {
|
|
|
+ throw new CLIError('Invalid working group application ID!')
|
|
|
}
|
|
|
|
|
|
- async getMemberIdsByControllerAccount(address: string): Promise<MemberId[]> {
|
|
|
- const ids = await this._api.query.members.memberIdsByControllerAccountId(address) as Vec<MemberId>;
|
|
|
- return ids.toArray();
|
|
|
- }
|
|
|
+ return this.singleLinkageResult<WGApplication>(
|
|
|
+ (await this.workingGroupApiQuery(group).applicationById(wgApplicationId)) as LinkageResult
|
|
|
+ )
|
|
|
+ }
|
|
|
|
|
|
- async workerExitRationaleConstraint(group: WorkingGroups): Promise<InputValidationLengthConstraint> {
|
|
|
- return await this.workingGroupApiQuery(group).workerExitRationaleText() as InputValidationLengthConstraint;
|
|
|
+ protected async parseApplication(wgApplicationId: number, wgApplication: WGApplication): Promise<GroupApplication> {
|
|
|
+ const appId = wgApplication.application_id
|
|
|
+ const application = await this.hiringApplicationById(appId)
|
|
|
+
|
|
|
+ const { active_role_staking_id: roleStakingId, active_application_staking_id: appStakingId } = application
|
|
|
+
|
|
|
+ return {
|
|
|
+ wgApplicationId,
|
|
|
+ applicationId: appId.toNumber(),
|
|
|
+ wgOpeningId: wgApplication.opening_id.toNumber(),
|
|
|
+ member: await this.memberProfileById(wgApplication.member_id),
|
|
|
+ roleAccout: wgApplication.role_account_id,
|
|
|
+ stakes: {
|
|
|
+ application: appStakingId.isSome ? (await this.stakeValue(appStakingId.unwrap())).toNumber() : 0,
|
|
|
+ role: roleStakingId.isSome ? (await this.stakeValue(roleStakingId.unwrap())).toNumber() : 0,
|
|
|
+ },
|
|
|
+ humanReadableText: application.human_readable_text.toString(),
|
|
|
+ stage: application.stage.type as ApplicationStageKeys,
|
|
|
}
|
|
|
+ }
|
|
|
+
|
|
|
+ async groupApplication(group: WorkingGroups, wgApplicationId: number): Promise<GroupApplication> {
|
|
|
+ const wgApplication = await this.wgApplicationById(group, wgApplicationId)
|
|
|
+ return await this.parseApplication(wgApplicationId, wgApplication)
|
|
|
+ }
|
|
|
+
|
|
|
+ protected async groupOpeningApplications(group: WorkingGroups, wgOpeningId: number): Promise<GroupApplication[]> {
|
|
|
+ const applications: GroupApplication[] = []
|
|
|
+
|
|
|
+ const nextAppId = (await this.workingGroupApiQuery(group).nextApplicationId()) as ApplicationId
|
|
|
+ for (let i = 0; i < nextAppId.toNumber(); i++) {
|
|
|
+ const wgApplication = await this.wgApplicationById(group, i)
|
|
|
+ if (wgApplication.opening_id.toNumber() !== wgOpeningId) {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ applications.push(await this.parseApplication(i, wgApplication))
|
|
|
+ }
|
|
|
+
|
|
|
+ return applications
|
|
|
+ }
|
|
|
+
|
|
|
+ async groupOpening(group: WorkingGroups, wgOpeningId: number): Promise<GroupOpening> {
|
|
|
+ const nextId = ((await this.workingGroupApiQuery(group).nextOpeningId()) as OpeningId).toNumber()
|
|
|
+
|
|
|
+ if (wgOpeningId < 0 || wgOpeningId >= nextId) {
|
|
|
+ throw new CLIError('Invalid working group opening ID!')
|
|
|
+ }
|
|
|
+
|
|
|
+ const groupOpening = this.singleLinkageResult<WGOpening>(
|
|
|
+ (await this.workingGroupApiQuery(group).openingById(wgOpeningId)) as LinkageResult
|
|
|
+ )
|
|
|
+
|
|
|
+ const openingId = groupOpening.hiring_opening_id.toNumber()
|
|
|
+ const opening = await this.hiringOpeningById(openingId)
|
|
|
+ const applications = await this.groupOpeningApplications(group, wgOpeningId)
|
|
|
+ const stage = await this.parseOpeningStage(opening.stage)
|
|
|
+ const type = groupOpening.opening_type
|
|
|
+ const stakes = {
|
|
|
+ application: opening.application_staking_policy.unwrapOr(undefined),
|
|
|
+ role: opening.role_staking_policy.unwrapOr(undefined),
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ wgOpeningId,
|
|
|
+ openingId,
|
|
|
+ opening,
|
|
|
+ stage,
|
|
|
+ stakes,
|
|
|
+ applications,
|
|
|
+ type,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async parseOpeningStage(stage: OpeningStage): Promise<GroupOpeningStage> {
|
|
|
+ let status: OpeningStatus | undefined, stageBlock: number | undefined, stageDate: Date | undefined
|
|
|
+
|
|
|
+ if (stage.isOfType('WaitingToBegin')) {
|
|
|
+ const stageData = stage.asType('WaitingToBegin')
|
|
|
+ const currentBlockNumber = (await this._api.derive.chain.bestNumber()).toNumber()
|
|
|
+ const expectedBlockTime = (this._api.consts.babe.expectedBlockTime as Moment).toNumber()
|
|
|
+ status = OpeningStatus.WaitingToBegin
|
|
|
+ stageBlock = stageData.begins_at_block.toNumber()
|
|
|
+ stageDate = new Date(Date.now() + (stageBlock - currentBlockNumber) * expectedBlockTime)
|
|
|
+ }
|
|
|
+
|
|
|
+ if (stage.isOfType('Active')) {
|
|
|
+ const stageData = stage.asType('Active')
|
|
|
+ const substage = stageData.stage
|
|
|
+ if (substage.isOfType('AcceptingApplications')) {
|
|
|
+ status = OpeningStatus.AcceptingApplications
|
|
|
+ stageBlock = substage.asType('AcceptingApplications').started_accepting_applicants_at_block.toNumber()
|
|
|
+ }
|
|
|
+ if (substage.isOfType('ReviewPeriod')) {
|
|
|
+ status = OpeningStatus.InReview
|
|
|
+ stageBlock = substage.asType('ReviewPeriod').started_review_period_at_block.toNumber()
|
|
|
+ }
|
|
|
+ if (substage.isOfType('Deactivated')) {
|
|
|
+ status = substage.asType('Deactivated').cause.isOfType('Filled')
|
|
|
+ ? OpeningStatus.Complete
|
|
|
+ : OpeningStatus.Cancelled
|
|
|
+ stageBlock = substage.asType('Deactivated').deactivated_at_block.toNumber()
|
|
|
+ }
|
|
|
+ if (stageBlock) {
|
|
|
+ stageDate = new Date(await this.blockTimestamp(stageBlock))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ status: status || OpeningStatus.Unknown,
|
|
|
+ block: stageBlock,
|
|
|
+ date: stageDate,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async getMemberIdsByControllerAccount(address: string): Promise<MemberId[]> {
|
|
|
+ const ids = (await this._api.query.members.memberIdsByControllerAccountId(address)) as Vec<MemberId>
|
|
|
+ return ids.toArray()
|
|
|
+ }
|
|
|
+
|
|
|
+ async workerExitRationaleConstraint(group: WorkingGroups): Promise<InputValidationLengthConstraint> {
|
|
|
+ return (await this.workingGroupApiQuery(group).workerExitRationaleText()) as InputValidationLengthConstraint
|
|
|
+ }
|
|
|
}
|