import { ApiPromise, WsProvider } from '@polkadot/api'; import { Option, Vec, Bytes, u32 } from '@polkadot/types'; import { Codec } from '@polkadot/types/types'; import { KeyringPair } from '@polkadot/keyring/types'; import { UserInfo, PaidMembershipTerms, MemberId } from '@nicaea/types/members'; import { Mint, MintId } from '@nicaea/types/mint'; import { Lead, LeadId } from '@nicaea/types/content-working-group'; import { Application, WorkerId, Worker, ApplicationIdToWorkerIdMap, Opening } from '@nicaea/types/working-group'; import { Application as HiringApplication } from '@nicaea/types/hiring'; import { RoleParameters } from '@nicaea/types/roles'; import { Seat } from '@nicaea/types/lib/council'; import { Balance, EventRecord, AccountId, BlockNumber, BalanceOf } from '@polkadot/types/interfaces'; import BN from 'bn.js'; import { SubmittableExtrinsic } from '@polkadot/api/types'; import { Sender } from './sender'; import { Utils } from './utils'; import { Stake, StakedState } from '@nicaea/types/stake'; import { RewardRelationship } from '@nicaea/types/recurring-rewards'; import { Opening as HiringOpening, ApplicationId } from '@nicaea/types/hiring'; import { WorkingGroupOpening } from '../dto/workingGroupOpening'; export enum WorkingGroups { storageWorkingGroup = 'storageWorkingGroup', } export class ApiWrapper { private readonly api: ApiPromise; private readonly sender: Sender; public static async create(provider: WsProvider): Promise { const api = await ApiPromise.create({ provider }); return new ApiWrapper(api); } constructor(api: ApiPromise) { this.api = api; this.sender = new Sender(api); } public close() { this.api.disconnect(); } public async buyMembership( account: KeyringPair, paidTermsId: number, name: string, expectFailure = false ): Promise { return this.sender.signAndSend( this.api.tx.members.buyMembership(paidTermsId, new UserInfo({ handle: name, avatar_uri: '', about: '' })), account, expectFailure ); } public getMemberIds(address: string): Promise { return this.api.query.members.memberIdsByControllerAccountId>(address); } public getBalance(address: string): Promise { return this.api.query.balances.freeBalance(address); } public async transferBalance(from: KeyringPair, to: string, amount: BN): Promise { return this.sender.signAndSend(this.api.tx.balances.transfer(to, amount), from); } public getPaidMembershipTerms(paidTermsId: number): Promise> { return this.api.query.members.paidMembershipTermsById>(paidTermsId); } public getMembershipFee(paidTermsId: number): Promise { return this.getPaidMembershipTerms(paidTermsId).then(terms => terms.unwrap().fee.toBn()); } public async transferBalanceToAccounts(from: KeyringPair, to: KeyringPair[], amount: BN): Promise { return Promise.all( to.map(async keyPair => { await this.transferBalance(from, keyPair.address, amount); }) ); } private getBaseTxFee(): BN { return this.api.createType('BalanceOf', this.api.consts.transactionPayment.transactionBaseFee).toBn(); } private estimateTxFee(tx: SubmittableExtrinsic<'promise'>): BN { const baseFee: BN = this.getBaseTxFee(); const byteFee: BN = this.api.createType('BalanceOf', this.api.consts.transactionPayment.transactionByteFee).toBn(); return Utils.calcTxLength(tx).mul(byteFee).add(baseFee); } public estimateBuyMembershipFee(account: KeyringPair, paidTermsId: number, name: string): BN { return this.estimateTxFee( this.api.tx.members.buyMembership(paidTermsId, new UserInfo({ handle: name, avatar_uri: '', about: '' })) ); } public estimateApplyForCouncilFee(amount: BN): BN { return this.estimateTxFee(this.api.tx.councilElection.apply(amount)); } public estimateVoteForCouncilFee(nominee: string, salt: string, stake: BN): BN { const hashedVote: string = Utils.hashVote(nominee, salt); return this.estimateTxFee(this.api.tx.councilElection.vote(hashedVote, stake)); } public estimateRevealVoteFee(nominee: string, salt: string): BN { const hashedVote: string = Utils.hashVote(nominee, salt); return this.estimateTxFee(this.api.tx.councilElection.reveal(hashedVote, nominee, salt)); } public estimateProposeRuntimeUpgradeFee(stake: BN, name: string, description: string, runtime: Bytes | string): BN { return this.estimateTxFee( this.api.tx.proposalsCodex.createRuntimeUpgradeProposal(stake, name, description, stake, runtime) ); } public estimateProposeTextFee(stake: BN, name: string, description: string, text: string): BN { return this.estimateTxFee(this.api.tx.proposalsCodex.createTextProposal(stake, name, description, stake, text)); } public estimateProposeSpendingFee( title: string, description: string, stake: BN, balance: BN, destination: string ): BN { return this.estimateTxFee( this.api.tx.proposalsCodex.createSpendingProposal(stake, title, description, stake, balance, destination) ); } public estimateProposeWorkingGroupMintCapacityFee(title: string, description: string, stake: BN, balance: BN): BN { return this.estimateTxFee( this.api.tx.proposalsCodex.createSetContentWorkingGroupMintCapacityProposal( stake, title, description, stake, balance ) ); } public estimateProposeValidatorCountFee(title: string, description: string, stake: BN): BN { return this.estimateTxFee( this.api.tx.proposalsCodex.createSetValidatorCountProposal(stake, title, description, stake, stake) ); } public estimateProposeLeadFee(title: string, description: string, stake: BN, address: string): BN { return this.estimateTxFee( this.api.tx.proposalsCodex.createSetLeadProposal(stake, title, description, stake, { stake, address }) ); } public estimateProposeEvictStorageProviderFee(title: string, description: string, stake: BN, address: string): BN { return this.estimateTxFee( this.api.tx.proposalsCodex.createEvictStorageProviderProposal(stake, title, description, stake, address) ); } public estimateProposeStorageRoleParametersFee( title: string, description: string, stake: BN, minStake: BN, minActors: BN, maxActors: BN, reward: BN, rewardPeriod: BN, bondingPeriod: BN, unbondingPeriod: BN, minServicePeriod: BN, startupGracePeriod: BN, entryRequestFee: BN ): BN { return this.estimateTxFee( this.api.tx.proposalsCodex.createSetStorageRoleParametersProposal(stake, title, description, stake, [ minStake, minActors, maxActors, reward, rewardPeriod, bondingPeriod, unbondingPeriod, minServicePeriod, startupGracePeriod, entryRequestFee, ]) ); } public estimateProposeElectionParametersFee( title: string, description: string, stake: BN, announcingPeriod: BN, votingPeriod: BN, revealingPeriod: BN, councilSize: BN, candidacyLimit: BN, newTermDuration: BN, minCouncilStake: BN, minVotingStake: BN ): BN { return this.estimateTxFee( this.api.tx.proposalsCodex.createSetElectionParametersProposal(stake, title, description, stake, [ announcingPeriod, votingPeriod, revealingPeriod, councilSize, candidacyLimit, newTermDuration, minCouncilStake, minVotingStake, ]) ); } public estimateVoteForProposalFee(): BN { return this.estimateTxFee(this.api.tx.proposalsEngine.vote(0, 0, 'Approve')); } public estimateAddOpeningFee(opening: WorkingGroupOpening, module: WorkingGroups): BN { return this.estimateTxFee( this.api.tx[module].addOpening( opening.getActivateAt(), opening.getCommitment(), opening.getText(), opening.getOpeningType() ) ); } public estimateAcceptApplicationsFee(module: WorkingGroups): BN { return this.estimateTxFee(this.api.tx[module].acceptApplications(0)); } public estimateApplyOnOpeningFee(account: KeyringPair, module: WorkingGroups): BN { return this.estimateTxFee( this.api.tx[module].applyOnOpening( 0, 0, account.address, 0, 0, 'Some testing text used for estimation purposes which is longer than text expected during the test' ) ); } public estimateBeginApplicantReviewFee(module: WorkingGroups): BN { return this.estimateTxFee(this.api.tx[module].beginApplicantReview(0)); } public estimateFillOpeningFee(module: WorkingGroups): BN { return this.estimateTxFee( this.api.tx[module].fillOpening(0, [0], { amount_per_payout: 0, next_payment_at_block: 0, payout_interval: 0, }) ); } public estimateIncreaseStakeFee(module: WorkingGroups): BN { return this.estimateTxFee(this.api.tx[module].increaseStake(0, 0)); } public estimateDecreaseStakeFee(module: WorkingGroups): BN { return this.estimateTxFee(this.api.tx[module].decreaseStake(0, 0)); } public estimateUpdateRoleAccountFee(address: string, module: WorkingGroups): BN { return this.estimateTxFee(this.api.tx[module].updateRoleAccount(0, address)); } public estimateUpdateRewardAccountFee(address: string, module: WorkingGroups): BN { return this.estimateTxFee(this.api.tx[module].updateRewardAccount(0, address)); } public estimateLeaveRoleFee(module: WorkingGroups): BN { return this.estimateTxFee(this.api.tx[module].leaveRole(0, 'Long justification text')); } public estimateWithdrawApplicationFee(module: WorkingGroups): BN { return this.estimateTxFee(this.api.tx[module].withdrawApplication(0)); } public estimateTerminateApplicationFee(module: WorkingGroups): BN { return this.estimateTxFee(this.api.tx[module].terminateApplication(0)); } public estimateSlashStakeFee(module: WorkingGroups): BN { return this.estimateTxFee(this.api.tx[module].slashStake(0, 0)); } public estimateTerminateRoleFee(module: WorkingGroups): BN { return this.estimateTxFee( this.api.tx[module].terminateRole( 0, 'Long justification text explaining why the worker role will be terminated', false ) ); } private applyForCouncilElection(account: KeyringPair, amount: BN): Promise { return this.sender.signAndSend(this.api.tx.councilElection.apply(amount), account, false); } public batchApplyForCouncilElection(accounts: KeyringPair[], amount: BN): Promise { return Promise.all( accounts.map(async keyPair => { await this.applyForCouncilElection(keyPair, amount); }) ); } public async getCouncilElectionStake(address: string): Promise { // TODO alter then `applicantStake` type will be introduced return this.api.query.councilElection.applicantStakes(address).then(stake => { const parsed = JSON.parse(stake.toString()); return new BN(parsed.new); }); } private voteForCouncilMember(account: KeyringPair, nominee: string, salt: string, stake: BN): Promise { const hashedVote: string = Utils.hashVote(nominee, salt); return this.sender.signAndSend(this.api.tx.councilElection.vote(hashedVote, stake), account, false); } public batchVoteForCouncilMember( accounts: KeyringPair[], nominees: KeyringPair[], salt: string[], stake: BN ): Promise { return Promise.all( accounts.map(async (keyPair, index) => { await this.voteForCouncilMember(keyPair, nominees[index].address, salt[index], stake); }) ); } private revealVote(account: KeyringPair, commitment: string, nominee: string, salt: string): Promise { return this.sender.signAndSend(this.api.tx.councilElection.reveal(commitment, nominee, salt), account, false); } public batchRevealVote(accounts: KeyringPair[], nominees: KeyringPair[], salt: string[]): Promise { return Promise.all( accounts.map(async (keyPair, index) => { const commitment = Utils.hashVote(nominees[index].address, salt[index]); await this.revealVote(keyPair, commitment, nominees[index].address, salt[index]); }) ); } // TODO consider using configurable genesis instead public sudoStartAnnouncingPerion(sudo: KeyringPair, endsAtBlock: BN): Promise { return this.sender.signAndSend( this.api.tx.sudo.sudo(this.api.tx.councilElection.setStageAnnouncing(endsAtBlock)), sudo, false ); } public sudoStartVotingPerion(sudo: KeyringPair, endsAtBlock: BN): Promise { return this.sender.signAndSend( this.api.tx.sudo.sudo(this.api.tx.councilElection.setStageVoting(endsAtBlock)), sudo, false ); } public sudoStartRevealingPerion(sudo: KeyringPair, endsAtBlock: BN): Promise { return this.sender.signAndSend( this.api.tx.sudo.sudo(this.api.tx.councilElection.setStageRevealing(endsAtBlock)), sudo, false ); } public sudoSetCouncilMintCapacity(sudo: KeyringPair, capacity: BN): Promise { return this.sender.signAndSend( this.api.tx.sudo.sudo(this.api.tx.council.setCouncilMintCapacity(capacity)), sudo, false ); } public sudoSetWorkingGroupMintCapacity(sudo: KeyringPair, capacity: BN, module: WorkingGroups): Promise { return this.sender.signAndSend(this.api.tx.sudo.sudo(this.api.tx[module].setMintCapacity(capacity)), sudo, false); } public getBestBlock(): Promise { return this.api.derive.chain.bestNumber(); } public getCouncil(): Promise { return this.api.query.council.activeCouncil>().then(seats => { return (seats as unknown) as Seat[]; }); } public getRuntime(): Promise { return this.api.query.substrate.code(); } public async proposeRuntime( account: KeyringPair, stake: BN, name: string, description: string, runtime: Bytes | string ): Promise { const memberId: BN = (await this.getMemberIds(account.address))[0].toBn(); return this.sender.signAndSend( this.api.tx.proposalsCodex.createRuntimeUpgradeProposal(memberId, name, description, stake, runtime), account, false ); } public async proposeText( account: KeyringPair, stake: BN, name: string, description: string, text: string ): Promise { const memberId: BN = (await this.getMemberIds(account.address))[0].toBn(); return this.sender.signAndSend( this.api.tx.proposalsCodex.createTextProposal(memberId, name, description, stake, text), account, false ); } public async proposeSpending( account: KeyringPair, title: string, description: string, stake: BN, balance: BN, destination: string ): Promise { const memberId: BN = (await this.getMemberIds(account.address))[0].toBn(); return this.sender.signAndSend( this.api.tx.proposalsCodex.createSpendingProposal(memberId, title, description, stake, balance, destination), account, false ); } public async proposeWorkingGroupMintCapacity( account: KeyringPair, title: string, description: string, stake: BN, balance: BN ): Promise { const memberId: BN = (await this.getMemberIds(account.address))[0].toBn(); return this.sender.signAndSend( this.api.tx.proposalsCodex.createSetContentWorkingGroupMintCapacityProposal( memberId, title, description, stake, balance ), account, false ); } public async proposeValidatorCount( account: KeyringPair, title: string, description: string, stake: BN, validatorCount: BN ): Promise { const memberId: BN = (await this.getMemberIds(account.address))[0].toBn(); return this.sender.signAndSend( this.api.tx.proposalsCodex.createSetValidatorCountProposal(memberId, title, description, stake, validatorCount), account, false ); } public async proposeLead( account: KeyringPair, title: string, description: string, stake: BN, leadAccount: KeyringPair ): Promise { const memberId: BN = (await this.getMemberIds(account.address))[0].toBn(); const leadMemberId: BN = (await this.getMemberIds(leadAccount.address))[0].toBn(); const addressString: string = leadAccount.address; return this.sender.signAndSend( this.api.tx.proposalsCodex.createSetLeadProposal(memberId, title, description, stake, [ leadMemberId, addressString, ]), account, false ); } public async proposeEvictStorageProvider( account: KeyringPair, title: string, description: string, stake: BN, storageProvider: string ): Promise { const memberId: BN = (await this.getMemberIds(account.address))[0].toBn(); return this.sender.signAndSend( this.api.tx.proposalsCodex.createEvictStorageProviderProposal( memberId, title, description, stake, storageProvider ), account, false ); } public async proposeStorageRoleParameters( account: KeyringPair, title: string, description: string, stake: BN, minStake: BN, minActors: BN, maxActors: BN, reward: BN, rewardPeriod: BN, bondingPeriod: BN, unbondingPeriod: BN, minServicePeriod: BN, startupGracePeriod: BN, entryRequestFee: BN ): Promise { const memberId: BN = (await this.getMemberIds(account.address))[0].toBn(); return this.sender.signAndSend( this.api.tx.proposalsCodex.createSetStorageRoleParametersProposal(memberId, title, description, stake, [ minStake, minActors, maxActors, reward, rewardPeriod, bondingPeriod, unbondingPeriod, minServicePeriod, startupGracePeriod, entryRequestFee, ]), account, false ); } public async proposeElectionParameters( account: KeyringPair, title: string, description: string, stake: BN, announcingPeriod: BN, votingPeriod: BN, revealingPeriod: BN, councilSize: BN, candidacyLimit: BN, newTermDuration: BN, minCouncilStake: BN, minVotingStake: BN ): Promise { const memberId: BN = (await this.getMemberIds(account.address))[0].toBn(); return this.sender.signAndSend( this.api.tx.proposalsCodex.createSetElectionParametersProposal(memberId, title, description, stake, [ announcingPeriod, votingPeriod, revealingPeriod, councilSize, candidacyLimit, newTermDuration, minCouncilStake, minVotingStake, ]), account, false ); } public approveProposal(account: KeyringPair, memberId: BN, proposal: BN): Promise { return this.sender.signAndSend(this.api.tx.proposalsEngine.vote(memberId, proposal, 'Approve'), account, false); } public batchApproveProposal(council: KeyringPair[], proposal: BN): Promise { return Promise.all( council.map(async keyPair => { const memberId: BN = (await this.getMemberIds(keyPair.address))[0].toBn(); await this.approveProposal(keyPair, memberId, proposal); }) ); } public getBlockDuration(): BN { return this.api.createType('Moment', this.api.consts.babe.expectedBlockTime).toBn(); } public expectProposalCreated(): Promise { return new Promise(async resolve => { await this.api.query.system.events>(events => { events.forEach(record => { if (record.event.method && record.event.method.toString() === 'ProposalCreated') { resolve(new BN(record.event.data[1].toString())); } }); }); }); } public expectRuntimeUpgraded(): Promise { return new Promise(async resolve => { await this.api.query.system.events>(events => { events.forEach(record => { if (record.event.method.toString() === 'RuntimeUpdated') { resolve(); } }); }); }); } public expectProposalFinalized(): Promise { return new Promise(async resolve => { await this.api.query.system.events>(events => { events.forEach(record => { if ( record.event.method && record.event.method.toString() === 'ProposalStatusUpdated' && record.event.data[1].toString().includes('Executed') ) { resolve(); } }); }); }); } public expectOpeningFilled(): Promise { return new Promise(async resolve => { await this.api.query.system.events>(events => { events.forEach(record => { if (record.event.method && record.event.method.toString() === 'OpeningFilled') { resolve((record.event.data[1] as unknown) as ApplicationIdToWorkerIdMap); } }); }); }); } public expectOpeningAdded(): Promise { return new Promise(async resolve => { await this.api.query.system.events>(events => { events.forEach(record => { if (record.event.method && record.event.method.toString() === 'OpeningAdded') { resolve((record.event.data as unknown) as BN); } }); }); }); } public expectApplicationReviewBegan(): Promise { return new Promise(async resolve => { await this.api.query.system.events>(events => { events.forEach(record => { if (record.event.method && record.event.method.toString() === 'BeganApplicationReview') { resolve(); } }); }); }); } public getTotalIssuance(): Promise { return this.api.query.balances.totalIssuance(); } public async getRequiredProposalStake(numerator: number, denominator: number): Promise { const issuance: number = await (await this.getTotalIssuance()).toNumber(); const stake = (issuance * numerator) / denominator; return new BN(stake.toFixed(0)); } public getProposalCount(): Promise { return this.api.query.proposalsEngine.proposalCount(); } public async getWorkingGroupMintCapacity(): Promise { const mintId: MintId = await this.api.query.contentWorkingGroup.mint(); const mintCodec = await this.api.query.minting.mints(mintId); const mint: Mint = (mintCodec[0] as unknown) as Mint; return mint.getField('capacity'); } public getValidatorCount(): Promise { return this.api.query.staking.validatorCount(); } public async getCurrentLeadAddress(): Promise { const leadId: Option = await this.api.query.contentWorkingGroup.currentLeadId>(); const leadCodec = await this.api.query.contentWorkingGroup.leadById(leadId.unwrap()); const lead = (leadCodec[0] as unknown) as Lead; return lead.role_account.toString(); } public async createStorageProvider(account: KeyringPair): Promise { const memberId: BN = (await this.getMemberIds(account.address))[0].toBn(); await this.sender.signAndSend(this.api.tx.actors.roleEntryRequest('StorageProvider', memberId), account, false); await this.sender.signAndSend(this.api.tx.actors.stake('StorageProvider', account.address), account, false); return; } public async isStorageProvider(address: string): Promise { const storageProviders: Vec = await this.api.query.actors.accountIdsByRole>( 'StorageProvider' ); const accountWorkers: BN = await this.getWorkerIdByRoleAccount(address, WorkingGroups.storageWorkingGroup); return accountWorkers !== undefined; } public async addOpening( leader: KeyringPair, opening: WorkingGroupOpening, module: WorkingGroups, expectFailure: boolean ): Promise { await this.sender.signAndSend(this.createAddOpeningTransaction(opening, module), leader, expectFailure); } public async sudoAddOpening(sudo: KeyringPair, opening: WorkingGroupOpening, module: WorkingGroups): Promise { await this.sender.signAndSend( this.api.tx.sudo.sudo(this.createAddOpeningTransaction(opening, module)), sudo, false ); } private createAddOpeningTransaction( opening: WorkingGroupOpening, module: WorkingGroups ): SubmittableExtrinsic<'promise'> { return this.api.tx[module].addOpening( opening.getActivateAt(), opening.getCommitment(), opening.getText(), opening.getOpeningType() ); } public async acceptApplications(leader: KeyringPair, openingId: BN, module: WorkingGroups): Promise { return this.sender.signAndSend(this.api.tx[module].acceptApplications(openingId), leader, false); } public async beginApplicantReview(leader: KeyringPair, openingId: BN, module: WorkingGroups): Promise { return this.sender.signAndSend(this.api.tx[module].beginApplicantReview(openingId), leader, false); } public async sudoBeginApplicantReview(sudo: KeyringPair, openingId: BN, module: WorkingGroups): Promise { return this.sender.signAndSend( this.api.tx.sudo.sudo(this.api.tx[module].beginApplicantReview(openingId)), sudo, false ); } public async applyOnOpening( account: KeyringPair, roleAccountAddress: string, openingId: BN, roleStake: BN, applicantStake: BN, text: string, expectFailure: boolean, module: WorkingGroups ): Promise { const memberId: BN = (await this.getMemberIds(account.address))[0]; return this.sender.signAndSend( this.api.tx[module].applyOnOpening(memberId, openingId, roleAccountAddress, roleStake, applicantStake, text), account, expectFailure ); } public async batchApplyOnOpening( accounts: KeyringPair[], openingId: BN, roleStake: BN, applicantStake: BN, text: string, module: WorkingGroups, expectFailure: boolean ): Promise { return Promise.all( accounts.map(async keyPair => { await this.applyOnOpening( keyPair, keyPair.address, openingId, roleStake, applicantStake, text, expectFailure, module ); }) ); } public async fillOpening( leader: KeyringPair, openingId: BN, applicationId: BN[], amountPerPayout: BN, nextPaymentBlock: BN, payoutInterval: BN, module: WorkingGroups ): Promise { return this.sender.signAndSend( this.api.tx[module].fillOpening(openingId, applicationId, { amount_per_payout: amountPerPayout, next_payment_at_block: nextPaymentBlock, payout_interval: payoutInterval, }), leader, false ); } public async sudoFillOpening( sudo: KeyringPair, openingId: BN, applicationId: BN[], amountPerPayout: BN, nextPaymentBlock: BN, payoutInterval: BN, module: WorkingGroups ): Promise { return this.sender.signAndSend( this.api.tx.sudo.sudo( this.api.tx[module].fillOpening(openingId, applicationId, { amount_per_payout: amountPerPayout, next_payment_at_block: nextPaymentBlock, payout_interval: payoutInterval, }) ), sudo, false ); } public async increaseStake(worker: KeyringPair, workerId: BN, stake: BN, module: WorkingGroups): Promise { return this.sender.signAndSend(this.api.tx[module].increaseStake(workerId, stake), worker, false); } public async decreaseStake( leader: KeyringPair, workerId: BN, stake: BN, module: WorkingGroups, expectFailure: boolean ): Promise { return this.sender.signAndSend(this.api.tx[module].decreaseStake(workerId, stake), leader, expectFailure); } public async slashStake( leader: KeyringPair, workerId: BN, stake: BN, module: WorkingGroups, expectFailure: boolean ): Promise { return this.sender.signAndSend(this.api.tx[module].slashStake(workerId, stake), leader, expectFailure); } public async updateRoleAccount( worker: KeyringPair, workerId: BN, newRoleAccount: string, module: WorkingGroups ): Promise { return this.sender.signAndSend(this.api.tx[module].updateRoleAccount(workerId, newRoleAccount), worker, false); } public async updateRewardAccount( worker: KeyringPair, workerId: BN, newRewardAccount: string, module: WorkingGroups ): Promise { return this.sender.signAndSend(this.api.tx[module].updateRewardAccount(workerId, newRewardAccount), worker, false); } public async withdrawApplication(account: KeyringPair, workerId: BN, module: WorkingGroups): Promise { return this.sender.signAndSend(this.api.tx[module].withdrawApplication(workerId), account, false); } public async batchWithdrawApplication(accounts: KeyringPair[], module: WorkingGroups): Promise { return Promise.all( accounts.map(async keyPair => { const applicationIds: BN[] = await this.getApplicationsIdsByRoleAccount(keyPair.address, module); await this.withdrawApplication(keyPair, applicationIds[0], module); }) ); } public async terminateApplication(leader: KeyringPair, applicationId: BN, module: WorkingGroups): Promise { return this.sender.signAndSend(this.api.tx[module].terminateApplication(applicationId), leader, false); } public async batchTerminateApplication( leader: KeyringPair, roleAccounts: KeyringPair[], module: WorkingGroups ): Promise { return Promise.all( roleAccounts.map(async keyPair => { const applicationIds: BN[] = await this.getActiveApplicationsIdsByRoleAccount(keyPair.address, module); await this.terminateApplication(leader, applicationIds[0], module); }) ); } public async terminateRole( leader: KeyringPair, applicationId: BN, text: string, module: WorkingGroups, expectFailure: boolean ): Promise { return this.sender.signAndSend( this.api.tx[module].terminateRole(applicationId, text, false), leader, expectFailure ); } public async leaveRole( account: KeyringPair, text: string, expectFailure: boolean, module: WorkingGroups ): Promise { const workerId: BN = await this.getWorkerIdByRoleAccount(account.address, module); return this.sender.signAndSend(this.api.tx[module].leaveRole(workerId, text), account, expectFailure); } public async batchLeaveRole( roleAccounts: KeyringPair[], text: string, expectFailure: boolean, module: WorkingGroups ): Promise { return Promise.all( roleAccounts.map(async keyPair => { await this.leaveRole(keyPair, text, expectFailure, module); }) ); } public async getStorageRoleParameters(): Promise { return (await this.api.query.actors.parameters>('StorageProvider')).unwrap(); } public async getAnnouncingPeriod(): Promise { return this.api.query.councilElection.announcingPeriod(); } public async getVotingPeriod(): Promise { return this.api.query.councilElection.votingPeriod(); } public async getRevealingPeriod(): Promise { return this.api.query.councilElection.revealingPeriod(); } public async getCouncilSize(): Promise { return this.api.query.councilElection.councilSize(); } public async getCandidacyLimit(): Promise { return this.api.query.councilElection.candidacyLimit(); } public async getNewTermDuration(): Promise { return this.api.query.councilElection.newTermDuration(); } public async getMinCouncilStake(): Promise { return this.api.query.councilElection.minCouncilStake(); } public async getMinVotingStake(): Promise { return this.api.query.councilElection.minVotingStake(); } public async getNextOpeningId(module: WorkingGroups): Promise { return this.api.query[module].nextOpeningId(); } public async getNextApplicationId(module: WorkingGroups): Promise { return this.api.query[module].nextApplicationId(); } public async getOpening(id: BN, module: WorkingGroups): Promise { return ((await this.api.query[module].openingById(id))[0] as unknown) as Opening; } public async getHiringOpening(id: BN): Promise { return ((await this.api.query.hiring.openingById(id))[0] as unknown) as HiringOpening; } public async getWorkers(module: WorkingGroups): Promise { return ((await this.api.query[module].workerById())[1] as unknown) as Worker[]; } public async getWorkerById(id: BN, module: WorkingGroups): Promise { return ((await this.api.query[module].workerById(id))[0] as unknown) as Worker; } public async getWorkerIdByRoleAccount(address: string, module: WorkingGroups): Promise { const workersAndIds = await this.api.query[module].workerById(); const workers: Worker[] = (workersAndIds[1] as unknown) as Worker[]; const ids: WorkerId[] = (workersAndIds[0] as unknown) as WorkerId[]; const index: number = workers.findIndex(worker => worker.role_account_id.toString() === address); return ids[index]; } public async getApplicationsIdsByRoleAccount(address: string, module: WorkingGroups): Promise { const applicationsAndIds = await this.api.query[module].applicationById(); const applications: Application[] = (applicationsAndIds[1] as unknown) as Application[]; const ids: ApplicationId[] = (applicationsAndIds[0] as unknown) as ApplicationId[]; return applications .map((application, index) => (application.role_account_id.toString() === address ? ids[index] : undefined)) .filter(id => id !== undefined) as BN[]; } public async getHiringApplicationById(id: BN): Promise { return ((await this.api.query.hiring.applicationById(id))[0] as unknown) as HiringApplication; } public async getApplicationById(id: BN, module: WorkingGroups): Promise { return ((await this.api.query[module].applicationById(id))[0] as unknown) as Application; } public async getActiveApplicationsIdsByRoleAccount(address: string, module: WorkingGroups): Promise { const applicationsAndIds = await this.api.query[module].applicationById(); const applications: Application[] = (applicationsAndIds[1] as unknown) as Application[]; const ids: ApplicationId[] = (applicationsAndIds[0] as unknown) as ApplicationId[]; return ( await Promise.all( applications.map(async (application, index) => { if ( application.role_account_id.toString() === address && (await this.getHiringApplicationById(application.application_id)).stage.type === 'Active' ) { return ids[index]; } else { return undefined; } }) ) ).filter(index => index !== undefined) as BN[]; } public async getStake(id: BN): Promise { return ((await this.api.query.stake.stakes(id))[0] as unknown) as Stake; } public async getWorkerStakeAmount(workerId: BN, module: WorkingGroups): Promise { let stakeId: BN = (await this.getWorkerById(workerId, module)).role_stake_profile.unwrap().stake_id; return (((await this.getStake(stakeId)).staking_status.value as unknown) as StakedState).staked_amount; } public async getRewardRelationship(id: BN): Promise { return (( await this.api.query.recurringRewards.rewardRelationships(id) )[0] as unknown) as RewardRelationship; } public async getWorkerRewardAccount(workerId: BN, module: WorkingGroups): Promise { let rewardRelationshipId: BN = (await this.getWorkerById(workerId, module)).reward_relationship.unwrap(); return (await this.getRewardRelationship(rewardRelationshipId)).getField('account').toString(); } }