apiWrapper.ts 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107
  1. import { ApiPromise, WsProvider } from '@polkadot/api';
  2. import { Option, Vec, Bytes, u32 } from '@polkadot/types';
  3. import { Codec } from '@polkadot/types/types';
  4. import { KeyringPair } from '@polkadot/keyring/types';
  5. import { UserInfo, PaidMembershipTerms, MemberId } from '@nicaea/types/members';
  6. import { Mint, MintId } from '@nicaea/types/mint';
  7. import { Lead, LeadId } from '@nicaea/types/content-working-group';
  8. import { Application, WorkerId, Worker, ApplicationIdToWorkerIdMap, Opening } from '@nicaea/types/working-group';
  9. import { Application as HiringApplication } from '@nicaea/types/hiring';
  10. import { RoleParameters } from '@nicaea/types/roles';
  11. import { Seat } from '@nicaea/types/lib/council';
  12. import { Balance, EventRecord, AccountId, BlockNumber, BalanceOf } from '@polkadot/types/interfaces';
  13. import BN from 'bn.js';
  14. import { SubmittableExtrinsic } from '@polkadot/api/types';
  15. import { Sender } from './sender';
  16. import { Utils } from './utils';
  17. import { Stake, StakedState } from '@nicaea/types/stake';
  18. import { RewardRelationship } from '@nicaea/types/recurring-rewards';
  19. import { Opening as HiringOpening, ApplicationId } from '@nicaea/types/hiring';
  20. import { WorkingGroupOpening } from '../dto/workingGroupOpening';
  21. export enum WorkingGroups {
  22. storageWorkingGroup = 'storageWorkingGroup',
  23. }
  24. export class ApiWrapper {
  25. private readonly api: ApiPromise;
  26. private readonly sender: Sender;
  27. public static async create(provider: WsProvider): Promise<ApiWrapper> {
  28. const api = await ApiPromise.create({ provider });
  29. return new ApiWrapper(api);
  30. }
  31. constructor(api: ApiPromise) {
  32. this.api = api;
  33. this.sender = new Sender(api);
  34. }
  35. public close() {
  36. this.api.disconnect();
  37. }
  38. public async buyMembership(
  39. account: KeyringPair,
  40. paidTermsId: number,
  41. name: string,
  42. expectFailure = false
  43. ): Promise<void> {
  44. return this.sender.signAndSend(
  45. this.api.tx.members.buyMembership(paidTermsId, new UserInfo({ handle: name, avatar_uri: '', about: '' })),
  46. account,
  47. expectFailure
  48. );
  49. }
  50. public getMemberIds(address: string): Promise<MemberId[]> {
  51. return this.api.query.members.memberIdsByControllerAccountId<Vec<MemberId>>(address);
  52. }
  53. public getBalance(address: string): Promise<Balance> {
  54. return this.api.query.balances.freeBalance<Balance>(address);
  55. }
  56. public async transferBalance(from: KeyringPair, to: string, amount: BN): Promise<void> {
  57. return this.sender.signAndSend(this.api.tx.balances.transfer(to, amount), from);
  58. }
  59. public getPaidMembershipTerms(paidTermsId: number): Promise<Option<PaidMembershipTerms>> {
  60. return this.api.query.members.paidMembershipTermsById<Option<PaidMembershipTerms>>(paidTermsId);
  61. }
  62. public getMembershipFee(paidTermsId: number): Promise<BN> {
  63. return this.getPaidMembershipTerms(paidTermsId).then(terms => terms.unwrap().fee.toBn());
  64. }
  65. public async transferBalanceToAccounts(from: KeyringPair, to: KeyringPair[], amount: BN): Promise<void[]> {
  66. return Promise.all(
  67. to.map(async keyPair => {
  68. await this.transferBalance(from, keyPair.address, amount);
  69. })
  70. );
  71. }
  72. private getBaseTxFee(): BN {
  73. return this.api.createType('BalanceOf', this.api.consts.transactionPayment.transactionBaseFee).toBn();
  74. }
  75. private estimateTxFee(tx: SubmittableExtrinsic<'promise'>): BN {
  76. const baseFee: BN = this.getBaseTxFee();
  77. const byteFee: BN = this.api.createType('BalanceOf', this.api.consts.transactionPayment.transactionByteFee).toBn();
  78. return Utils.calcTxLength(tx).mul(byteFee).add(baseFee);
  79. }
  80. public estimateBuyMembershipFee(account: KeyringPair, paidTermsId: number, name: string): BN {
  81. return this.estimateTxFee(
  82. this.api.tx.members.buyMembership(paidTermsId, new UserInfo({ handle: name, avatar_uri: '', about: '' }))
  83. );
  84. }
  85. public estimateApplyForCouncilFee(amount: BN): BN {
  86. return this.estimateTxFee(this.api.tx.councilElection.apply(amount));
  87. }
  88. public estimateVoteForCouncilFee(nominee: string, salt: string, stake: BN): BN {
  89. const hashedVote: string = Utils.hashVote(nominee, salt);
  90. return this.estimateTxFee(this.api.tx.councilElection.vote(hashedVote, stake));
  91. }
  92. public estimateRevealVoteFee(nominee: string, salt: string): BN {
  93. const hashedVote: string = Utils.hashVote(nominee, salt);
  94. return this.estimateTxFee(this.api.tx.councilElection.reveal(hashedVote, nominee, salt));
  95. }
  96. public estimateProposeRuntimeUpgradeFee(stake: BN, name: string, description: string, runtime: Bytes | string): BN {
  97. return this.estimateTxFee(
  98. this.api.tx.proposalsCodex.createRuntimeUpgradeProposal(stake, name, description, stake, runtime)
  99. );
  100. }
  101. public estimateProposeTextFee(stake: BN, name: string, description: string, text: string): BN {
  102. return this.estimateTxFee(this.api.tx.proposalsCodex.createTextProposal(stake, name, description, stake, text));
  103. }
  104. public estimateProposeSpendingFee(
  105. title: string,
  106. description: string,
  107. stake: BN,
  108. balance: BN,
  109. destination: string
  110. ): BN {
  111. return this.estimateTxFee(
  112. this.api.tx.proposalsCodex.createSpendingProposal(stake, title, description, stake, balance, destination)
  113. );
  114. }
  115. public estimateProposeWorkingGroupMintCapacityFee(title: string, description: string, stake: BN, balance: BN): BN {
  116. return this.estimateTxFee(
  117. this.api.tx.proposalsCodex.createSetContentWorkingGroupMintCapacityProposal(
  118. stake,
  119. title,
  120. description,
  121. stake,
  122. balance
  123. )
  124. );
  125. }
  126. public estimateProposeValidatorCountFee(title: string, description: string, stake: BN): BN {
  127. return this.estimateTxFee(
  128. this.api.tx.proposalsCodex.createSetValidatorCountProposal(stake, title, description, stake, stake)
  129. );
  130. }
  131. public estimateProposeLeadFee(title: string, description: string, stake: BN, address: string): BN {
  132. return this.estimateTxFee(
  133. this.api.tx.proposalsCodex.createSetLeadProposal(stake, title, description, stake, { stake, address })
  134. );
  135. }
  136. public estimateProposeEvictStorageProviderFee(title: string, description: string, stake: BN, address: string): BN {
  137. return this.estimateTxFee(
  138. this.api.tx.proposalsCodex.createEvictStorageProviderProposal(stake, title, description, stake, address)
  139. );
  140. }
  141. public estimateProposeStorageRoleParametersFee(
  142. title: string,
  143. description: string,
  144. stake: BN,
  145. minStake: BN,
  146. minActors: BN,
  147. maxActors: BN,
  148. reward: BN,
  149. rewardPeriod: BN,
  150. bondingPeriod: BN,
  151. unbondingPeriod: BN,
  152. minServicePeriod: BN,
  153. startupGracePeriod: BN,
  154. entryRequestFee: BN
  155. ): BN {
  156. return this.estimateTxFee(
  157. this.api.tx.proposalsCodex.createSetStorageRoleParametersProposal(stake, title, description, stake, [
  158. minStake,
  159. minActors,
  160. maxActors,
  161. reward,
  162. rewardPeriod,
  163. bondingPeriod,
  164. unbondingPeriod,
  165. minServicePeriod,
  166. startupGracePeriod,
  167. entryRequestFee,
  168. ])
  169. );
  170. }
  171. public estimateProposeElectionParametersFee(
  172. title: string,
  173. description: string,
  174. stake: BN,
  175. announcingPeriod: BN,
  176. votingPeriod: BN,
  177. revealingPeriod: BN,
  178. councilSize: BN,
  179. candidacyLimit: BN,
  180. newTermDuration: BN,
  181. minCouncilStake: BN,
  182. minVotingStake: BN
  183. ): BN {
  184. return this.estimateTxFee(
  185. this.api.tx.proposalsCodex.createSetElectionParametersProposal(stake, title, description, stake, [
  186. announcingPeriod,
  187. votingPeriod,
  188. revealingPeriod,
  189. councilSize,
  190. candidacyLimit,
  191. newTermDuration,
  192. minCouncilStake,
  193. minVotingStake,
  194. ])
  195. );
  196. }
  197. public estimateVoteForProposalFee(): BN {
  198. return this.estimateTxFee(this.api.tx.proposalsEngine.vote(0, 0, 'Approve'));
  199. }
  200. public estimateAddOpeningFee(opening: WorkingGroupOpening, module: WorkingGroups): BN {
  201. return this.estimateTxFee(
  202. this.api.tx[module].addOpening(
  203. opening.getActivateAt(),
  204. opening.getCommitment(),
  205. opening.getText(),
  206. opening.getOpeningType()
  207. )
  208. );
  209. }
  210. public estimateAcceptApplicationsFee(module: WorkingGroups): BN {
  211. return this.estimateTxFee(this.api.tx[module].acceptApplications(0));
  212. }
  213. public estimateApplyOnOpeningFee(account: KeyringPair, module: WorkingGroups): BN {
  214. return this.estimateTxFee(
  215. this.api.tx[module].applyOnOpening(
  216. 0,
  217. 0,
  218. account.address,
  219. 0,
  220. 0,
  221. 'Some testing text used for estimation purposes which is longer than text expected during the test'
  222. )
  223. );
  224. }
  225. public estimateBeginApplicantReviewFee(module: WorkingGroups): BN {
  226. return this.estimateTxFee(this.api.tx[module].beginApplicantReview(0));
  227. }
  228. public estimateFillOpeningFee(module: WorkingGroups): BN {
  229. return this.estimateTxFee(
  230. this.api.tx[module].fillOpening(0, [0], {
  231. amount_per_payout: 0,
  232. next_payment_at_block: 0,
  233. payout_interval: 0,
  234. })
  235. );
  236. }
  237. public estimateIncreaseStakeFee(module: WorkingGroups): BN {
  238. return this.estimateTxFee(this.api.tx[module].increaseStake(0, 0));
  239. }
  240. public estimateDecreaseStakeFee(module: WorkingGroups): BN {
  241. return this.estimateTxFee(this.api.tx[module].decreaseStake(0, 0));
  242. }
  243. public estimateUpdateRoleAccountFee(address: string, module: WorkingGroups): BN {
  244. return this.estimateTxFee(this.api.tx[module].updateRoleAccount(0, address));
  245. }
  246. public estimateUpdateRewardAccountFee(address: string, module: WorkingGroups): BN {
  247. return this.estimateTxFee(this.api.tx[module].updateRewardAccount(0, address));
  248. }
  249. public estimateLeaveRoleFee(module: WorkingGroups): BN {
  250. return this.estimateTxFee(this.api.tx[module].leaveRole(0, 'Long justification text'));
  251. }
  252. public estimateWithdrawApplicationFee(module: WorkingGroups): BN {
  253. return this.estimateTxFee(this.api.tx[module].withdrawApplication(0));
  254. }
  255. public estimateTerminateApplicationFee(module: WorkingGroups): BN {
  256. return this.estimateTxFee(this.api.tx[module].terminateApplication(0));
  257. }
  258. public estimateSlashStakeFee(module: WorkingGroups): BN {
  259. return this.estimateTxFee(this.api.tx[module].slashStake(0, 0));
  260. }
  261. public estimateTerminateRoleFee(module: WorkingGroups): BN {
  262. return this.estimateTxFee(
  263. this.api.tx[module].terminateRole(
  264. 0,
  265. 'Long justification text explaining why the worker role will be terminated',
  266. false
  267. )
  268. );
  269. }
  270. private applyForCouncilElection(account: KeyringPair, amount: BN): Promise<void> {
  271. return this.sender.signAndSend(this.api.tx.councilElection.apply(amount), account, false);
  272. }
  273. public batchApplyForCouncilElection(accounts: KeyringPair[], amount: BN): Promise<void[]> {
  274. return Promise.all(
  275. accounts.map(async keyPair => {
  276. await this.applyForCouncilElection(keyPair, amount);
  277. })
  278. );
  279. }
  280. public async getCouncilElectionStake(address: string): Promise<BN> {
  281. // TODO alter then `applicantStake` type will be introduced
  282. return this.api.query.councilElection.applicantStakes(address).then(stake => {
  283. const parsed = JSON.parse(stake.toString());
  284. return new BN(parsed.new);
  285. });
  286. }
  287. private voteForCouncilMember(account: KeyringPair, nominee: string, salt: string, stake: BN): Promise<void> {
  288. const hashedVote: string = Utils.hashVote(nominee, salt);
  289. return this.sender.signAndSend(this.api.tx.councilElection.vote(hashedVote, stake), account, false);
  290. }
  291. public batchVoteForCouncilMember(
  292. accounts: KeyringPair[],
  293. nominees: KeyringPair[],
  294. salt: string[],
  295. stake: BN
  296. ): Promise<void[]> {
  297. return Promise.all(
  298. accounts.map(async (keyPair, index) => {
  299. await this.voteForCouncilMember(keyPair, nominees[index].address, salt[index], stake);
  300. })
  301. );
  302. }
  303. private revealVote(account: KeyringPair, commitment: string, nominee: string, salt: string): Promise<void> {
  304. return this.sender.signAndSend(this.api.tx.councilElection.reveal(commitment, nominee, salt), account, false);
  305. }
  306. public batchRevealVote(accounts: KeyringPair[], nominees: KeyringPair[], salt: string[]): Promise<void[]> {
  307. return Promise.all(
  308. accounts.map(async (keyPair, index) => {
  309. const commitment = Utils.hashVote(nominees[index].address, salt[index]);
  310. await this.revealVote(keyPair, commitment, nominees[index].address, salt[index]);
  311. })
  312. );
  313. }
  314. // TODO consider using configurable genesis instead
  315. public sudoStartAnnouncingPerion(sudo: KeyringPair, endsAtBlock: BN): Promise<void> {
  316. return this.sender.signAndSend(
  317. this.api.tx.sudo.sudo(this.api.tx.councilElection.setStageAnnouncing(endsAtBlock)),
  318. sudo,
  319. false
  320. );
  321. }
  322. public sudoStartVotingPerion(sudo: KeyringPair, endsAtBlock: BN): Promise<void> {
  323. return this.sender.signAndSend(
  324. this.api.tx.sudo.sudo(this.api.tx.councilElection.setStageVoting(endsAtBlock)),
  325. sudo,
  326. false
  327. );
  328. }
  329. public sudoStartRevealingPerion(sudo: KeyringPair, endsAtBlock: BN): Promise<void> {
  330. return this.sender.signAndSend(
  331. this.api.tx.sudo.sudo(this.api.tx.councilElection.setStageRevealing(endsAtBlock)),
  332. sudo,
  333. false
  334. );
  335. }
  336. public sudoSetCouncilMintCapacity(sudo: KeyringPair, capacity: BN): Promise<void> {
  337. return this.sender.signAndSend(
  338. this.api.tx.sudo.sudo(this.api.tx.council.setCouncilMintCapacity(capacity)),
  339. sudo,
  340. false
  341. );
  342. }
  343. public sudoSetWorkingGroupMintCapacity(sudo: KeyringPair, capacity: BN, module: WorkingGroups): Promise<void> {
  344. return this.sender.signAndSend(this.api.tx.sudo.sudo(this.api.tx[module].setMintCapacity(capacity)), sudo, false);
  345. }
  346. public getBestBlock(): Promise<BN> {
  347. return this.api.derive.chain.bestNumber();
  348. }
  349. public getCouncil(): Promise<Seat[]> {
  350. return this.api.query.council.activeCouncil<Vec<Codec>>().then(seats => {
  351. return (seats as unknown) as Seat[];
  352. });
  353. }
  354. public getRuntime(): Promise<Bytes> {
  355. return this.api.query.substrate.code<Bytes>();
  356. }
  357. public async proposeRuntime(
  358. account: KeyringPair,
  359. stake: BN,
  360. name: string,
  361. description: string,
  362. runtime: Bytes | string
  363. ): Promise<void> {
  364. const memberId: BN = (await this.getMemberIds(account.address))[0].toBn();
  365. return this.sender.signAndSend(
  366. this.api.tx.proposalsCodex.createRuntimeUpgradeProposal(memberId, name, description, stake, runtime),
  367. account,
  368. false
  369. );
  370. }
  371. public async proposeText(
  372. account: KeyringPair,
  373. stake: BN,
  374. name: string,
  375. description: string,
  376. text: string
  377. ): Promise<void> {
  378. const memberId: BN = (await this.getMemberIds(account.address))[0].toBn();
  379. return this.sender.signAndSend(
  380. this.api.tx.proposalsCodex.createTextProposal(memberId, name, description, stake, text),
  381. account,
  382. false
  383. );
  384. }
  385. public async proposeSpending(
  386. account: KeyringPair,
  387. title: string,
  388. description: string,
  389. stake: BN,
  390. balance: BN,
  391. destination: string
  392. ): Promise<void> {
  393. const memberId: BN = (await this.getMemberIds(account.address))[0].toBn();
  394. return this.sender.signAndSend(
  395. this.api.tx.proposalsCodex.createSpendingProposal(memberId, title, description, stake, balance, destination),
  396. account,
  397. false
  398. );
  399. }
  400. public async proposeWorkingGroupMintCapacity(
  401. account: KeyringPair,
  402. title: string,
  403. description: string,
  404. stake: BN,
  405. balance: BN
  406. ): Promise<void> {
  407. const memberId: BN = (await this.getMemberIds(account.address))[0].toBn();
  408. return this.sender.signAndSend(
  409. this.api.tx.proposalsCodex.createSetContentWorkingGroupMintCapacityProposal(
  410. memberId,
  411. title,
  412. description,
  413. stake,
  414. balance
  415. ),
  416. account,
  417. false
  418. );
  419. }
  420. public async proposeValidatorCount(
  421. account: KeyringPair,
  422. title: string,
  423. description: string,
  424. stake: BN,
  425. validatorCount: BN
  426. ): Promise<void> {
  427. const memberId: BN = (await this.getMemberIds(account.address))[0].toBn();
  428. return this.sender.signAndSend(
  429. this.api.tx.proposalsCodex.createSetValidatorCountProposal(memberId, title, description, stake, validatorCount),
  430. account,
  431. false
  432. );
  433. }
  434. public async proposeLead(
  435. account: KeyringPair,
  436. title: string,
  437. description: string,
  438. stake: BN,
  439. leadAccount: KeyringPair
  440. ): Promise<void> {
  441. const memberId: BN = (await this.getMemberIds(account.address))[0].toBn();
  442. const leadMemberId: BN = (await this.getMemberIds(leadAccount.address))[0].toBn();
  443. const addressString: string = leadAccount.address;
  444. return this.sender.signAndSend(
  445. this.api.tx.proposalsCodex.createSetLeadProposal(memberId, title, description, stake, [
  446. leadMemberId,
  447. addressString,
  448. ]),
  449. account,
  450. false
  451. );
  452. }
  453. public async proposeEvictStorageProvider(
  454. account: KeyringPair,
  455. title: string,
  456. description: string,
  457. stake: BN,
  458. storageProvider: string
  459. ): Promise<void> {
  460. const memberId: BN = (await this.getMemberIds(account.address))[0].toBn();
  461. return this.sender.signAndSend(
  462. this.api.tx.proposalsCodex.createEvictStorageProviderProposal(
  463. memberId,
  464. title,
  465. description,
  466. stake,
  467. storageProvider
  468. ),
  469. account,
  470. false
  471. );
  472. }
  473. public async proposeStorageRoleParameters(
  474. account: KeyringPair,
  475. title: string,
  476. description: string,
  477. stake: BN,
  478. minStake: BN,
  479. minActors: BN,
  480. maxActors: BN,
  481. reward: BN,
  482. rewardPeriod: BN,
  483. bondingPeriod: BN,
  484. unbondingPeriod: BN,
  485. minServicePeriod: BN,
  486. startupGracePeriod: BN,
  487. entryRequestFee: BN
  488. ): Promise<void> {
  489. const memberId: BN = (await this.getMemberIds(account.address))[0].toBn();
  490. return this.sender.signAndSend(
  491. this.api.tx.proposalsCodex.createSetStorageRoleParametersProposal(memberId, title, description, stake, [
  492. minStake,
  493. minActors,
  494. maxActors,
  495. reward,
  496. rewardPeriod,
  497. bondingPeriod,
  498. unbondingPeriod,
  499. minServicePeriod,
  500. startupGracePeriod,
  501. entryRequestFee,
  502. ]),
  503. account,
  504. false
  505. );
  506. }
  507. public async proposeElectionParameters(
  508. account: KeyringPair,
  509. title: string,
  510. description: string,
  511. stake: BN,
  512. announcingPeriod: BN,
  513. votingPeriod: BN,
  514. revealingPeriod: BN,
  515. councilSize: BN,
  516. candidacyLimit: BN,
  517. newTermDuration: BN,
  518. minCouncilStake: BN,
  519. minVotingStake: BN
  520. ): Promise<void> {
  521. const memberId: BN = (await this.getMemberIds(account.address))[0].toBn();
  522. return this.sender.signAndSend(
  523. this.api.tx.proposalsCodex.createSetElectionParametersProposal(memberId, title, description, stake, [
  524. announcingPeriod,
  525. votingPeriod,
  526. revealingPeriod,
  527. councilSize,
  528. candidacyLimit,
  529. newTermDuration,
  530. minCouncilStake,
  531. minVotingStake,
  532. ]),
  533. account,
  534. false
  535. );
  536. }
  537. public approveProposal(account: KeyringPair, memberId: BN, proposal: BN): Promise<void> {
  538. return this.sender.signAndSend(this.api.tx.proposalsEngine.vote(memberId, proposal, 'Approve'), account, false);
  539. }
  540. public batchApproveProposal(council: KeyringPair[], proposal: BN): Promise<void[]> {
  541. return Promise.all(
  542. council.map(async keyPair => {
  543. const memberId: BN = (await this.getMemberIds(keyPair.address))[0].toBn();
  544. await this.approveProposal(keyPair, memberId, proposal);
  545. })
  546. );
  547. }
  548. public getBlockDuration(): BN {
  549. return this.api.createType('Moment', this.api.consts.babe.expectedBlockTime).toBn();
  550. }
  551. public expectProposalCreated(): Promise<BN> {
  552. return new Promise(async resolve => {
  553. await this.api.query.system.events<Vec<EventRecord>>(events => {
  554. events.forEach(record => {
  555. if (record.event.method && record.event.method.toString() === 'ProposalCreated') {
  556. resolve(new BN(record.event.data[1].toString()));
  557. }
  558. });
  559. });
  560. });
  561. }
  562. public expectRuntimeUpgraded(): Promise<void> {
  563. return new Promise(async resolve => {
  564. await this.api.query.system.events<Vec<EventRecord>>(events => {
  565. events.forEach(record => {
  566. if (record.event.method.toString() === 'RuntimeUpdated') {
  567. resolve();
  568. }
  569. });
  570. });
  571. });
  572. }
  573. public expectProposalFinalized(): Promise<void> {
  574. return new Promise(async resolve => {
  575. await this.api.query.system.events<Vec<EventRecord>>(events => {
  576. events.forEach(record => {
  577. if (
  578. record.event.method &&
  579. record.event.method.toString() === 'ProposalStatusUpdated' &&
  580. record.event.data[1].toString().includes('Executed')
  581. ) {
  582. resolve();
  583. }
  584. });
  585. });
  586. });
  587. }
  588. public expectOpeningFilled(): Promise<ApplicationIdToWorkerIdMap> {
  589. return new Promise(async resolve => {
  590. await this.api.query.system.events<Vec<EventRecord>>(events => {
  591. events.forEach(record => {
  592. if (record.event.method && record.event.method.toString() === 'OpeningFilled') {
  593. resolve((record.event.data[1] as unknown) as ApplicationIdToWorkerIdMap);
  594. }
  595. });
  596. });
  597. });
  598. }
  599. public expectOpeningAdded(): Promise<BN> {
  600. return new Promise(async resolve => {
  601. await this.api.query.system.events<Vec<EventRecord>>(events => {
  602. events.forEach(record => {
  603. if (record.event.method && record.event.method.toString() === 'OpeningAdded') {
  604. resolve((record.event.data as unknown) as BN);
  605. }
  606. });
  607. });
  608. });
  609. }
  610. public expectApplicationReviewBegan(): Promise<void> {
  611. return new Promise(async resolve => {
  612. await this.api.query.system.events<Vec<EventRecord>>(events => {
  613. events.forEach(record => {
  614. if (record.event.method && record.event.method.toString() === 'BeganApplicationReview') {
  615. resolve();
  616. }
  617. });
  618. });
  619. });
  620. }
  621. public getTotalIssuance(): Promise<BN> {
  622. return this.api.query.balances.totalIssuance<Balance>();
  623. }
  624. public async getRequiredProposalStake(numerator: number, denominator: number): Promise<BN> {
  625. const issuance: number = await (await this.getTotalIssuance()).toNumber();
  626. const stake = (issuance * numerator) / denominator;
  627. return new BN(stake.toFixed(0));
  628. }
  629. public getProposalCount(): Promise<BN> {
  630. return this.api.query.proposalsEngine.proposalCount<u32>();
  631. }
  632. public async getWorkingGroupMintCapacity(): Promise<BN> {
  633. const mintId: MintId = await this.api.query.contentWorkingGroup.mint<MintId>();
  634. const mintCodec = await this.api.query.minting.mints<Codec[]>(mintId);
  635. const mint: Mint = (mintCodec[0] as unknown) as Mint;
  636. return mint.getField<Balance>('capacity');
  637. }
  638. public getValidatorCount(): Promise<BN> {
  639. return this.api.query.staking.validatorCount<u32>();
  640. }
  641. public async getCurrentLeadAddress(): Promise<string> {
  642. const leadId: Option<LeadId> = await this.api.query.contentWorkingGroup.currentLeadId<Option<LeadId>>();
  643. const leadCodec = await this.api.query.contentWorkingGroup.leadById<Codec[]>(leadId.unwrap());
  644. const lead = (leadCodec[0] as unknown) as Lead;
  645. return lead.role_account.toString();
  646. }
  647. public async createStorageProvider(account: KeyringPair): Promise<void> {
  648. const memberId: BN = (await this.getMemberIds(account.address))[0].toBn();
  649. await this.sender.signAndSend(this.api.tx.actors.roleEntryRequest('StorageProvider', memberId), account, false);
  650. await this.sender.signAndSend(this.api.tx.actors.stake('StorageProvider', account.address), account, false);
  651. return;
  652. }
  653. public async isStorageProvider(address: string): Promise<boolean> {
  654. const storageProviders: Vec<AccountId> = await this.api.query.actors.accountIdsByRole<Vec<AccountId>>(
  655. 'StorageProvider'
  656. );
  657. const accountWorkers: BN = await this.getWorkerIdByRoleAccount(address, WorkingGroups.storageWorkingGroup);
  658. return accountWorkers !== undefined;
  659. }
  660. public async addOpening(
  661. leader: KeyringPair,
  662. opening: WorkingGroupOpening,
  663. module: WorkingGroups,
  664. expectFailure: boolean
  665. ): Promise<void> {
  666. await this.sender.signAndSend(this.createAddOpeningTransaction(opening, module), leader, expectFailure);
  667. }
  668. public async sudoAddOpening(sudo: KeyringPair, opening: WorkingGroupOpening, module: WorkingGroups): Promise<void> {
  669. await this.sender.signAndSend(
  670. this.api.tx.sudo.sudo(this.createAddOpeningTransaction(opening, module)),
  671. sudo,
  672. false
  673. );
  674. }
  675. private createAddOpeningTransaction(
  676. opening: WorkingGroupOpening,
  677. module: WorkingGroups
  678. ): SubmittableExtrinsic<'promise'> {
  679. return this.api.tx[module].addOpening(
  680. opening.getActivateAt(),
  681. opening.getCommitment(),
  682. opening.getText(),
  683. opening.getOpeningType()
  684. );
  685. }
  686. public async acceptApplications(leader: KeyringPair, openingId: BN, module: WorkingGroups): Promise<void> {
  687. return this.sender.signAndSend(this.api.tx[module].acceptApplications(openingId), leader, false);
  688. }
  689. public async beginApplicantReview(leader: KeyringPair, openingId: BN, module: WorkingGroups): Promise<void> {
  690. return this.sender.signAndSend(this.api.tx[module].beginApplicantReview(openingId), leader, false);
  691. }
  692. public async sudoBeginApplicantReview(sudo: KeyringPair, openingId: BN, module: WorkingGroups): Promise<void> {
  693. return this.sender.signAndSend(
  694. this.api.tx.sudo.sudo(this.api.tx[module].beginApplicantReview(openingId)),
  695. sudo,
  696. false
  697. );
  698. }
  699. public async applyOnOpening(
  700. account: KeyringPair,
  701. roleAccountAddress: string,
  702. openingId: BN,
  703. roleStake: BN,
  704. applicantStake: BN,
  705. text: string,
  706. expectFailure: boolean,
  707. module: WorkingGroups
  708. ): Promise<void> {
  709. const memberId: BN = (await this.getMemberIds(account.address))[0];
  710. return this.sender.signAndSend(
  711. this.api.tx[module].applyOnOpening(memberId, openingId, roleAccountAddress, roleStake, applicantStake, text),
  712. account,
  713. expectFailure
  714. );
  715. }
  716. public async batchApplyOnOpening(
  717. accounts: KeyringPair[],
  718. openingId: BN,
  719. roleStake: BN,
  720. applicantStake: BN,
  721. text: string,
  722. module: WorkingGroups,
  723. expectFailure: boolean
  724. ): Promise<void[]> {
  725. return Promise.all(
  726. accounts.map(async keyPair => {
  727. await this.applyOnOpening(
  728. keyPair,
  729. keyPair.address,
  730. openingId,
  731. roleStake,
  732. applicantStake,
  733. text,
  734. expectFailure,
  735. module
  736. );
  737. })
  738. );
  739. }
  740. public async fillOpening(
  741. leader: KeyringPair,
  742. openingId: BN,
  743. applicationId: BN[],
  744. amountPerPayout: BN,
  745. nextPaymentBlock: BN,
  746. payoutInterval: BN,
  747. module: WorkingGroups
  748. ): Promise<void> {
  749. return this.sender.signAndSend(
  750. this.api.tx[module].fillOpening(openingId, applicationId, {
  751. amount_per_payout: amountPerPayout,
  752. next_payment_at_block: nextPaymentBlock,
  753. payout_interval: payoutInterval,
  754. }),
  755. leader,
  756. false
  757. );
  758. }
  759. public async sudoFillOpening(
  760. sudo: KeyringPair,
  761. openingId: BN,
  762. applicationId: BN[],
  763. amountPerPayout: BN,
  764. nextPaymentBlock: BN,
  765. payoutInterval: BN,
  766. module: WorkingGroups
  767. ): Promise<void> {
  768. return this.sender.signAndSend(
  769. this.api.tx.sudo.sudo(
  770. this.api.tx[module].fillOpening(openingId, applicationId, {
  771. amount_per_payout: amountPerPayout,
  772. next_payment_at_block: nextPaymentBlock,
  773. payout_interval: payoutInterval,
  774. })
  775. ),
  776. sudo,
  777. false
  778. );
  779. }
  780. public async increaseStake(worker: KeyringPair, workerId: BN, stake: BN, module: WorkingGroups): Promise<void> {
  781. return this.sender.signAndSend(this.api.tx[module].increaseStake(workerId, stake), worker, false);
  782. }
  783. public async decreaseStake(
  784. leader: KeyringPair,
  785. workerId: BN,
  786. stake: BN,
  787. module: WorkingGroups,
  788. expectFailure: boolean
  789. ): Promise<void> {
  790. return this.sender.signAndSend(this.api.tx[module].decreaseStake(workerId, stake), leader, expectFailure);
  791. }
  792. public async slashStake(
  793. leader: KeyringPair,
  794. workerId: BN,
  795. stake: BN,
  796. module: WorkingGroups,
  797. expectFailure: boolean
  798. ): Promise<void> {
  799. return this.sender.signAndSend(this.api.tx[module].slashStake(workerId, stake), leader, expectFailure);
  800. }
  801. public async updateRoleAccount(
  802. worker: KeyringPair,
  803. workerId: BN,
  804. newRoleAccount: string,
  805. module: WorkingGroups
  806. ): Promise<void> {
  807. return this.sender.signAndSend(this.api.tx[module].updateRoleAccount(workerId, newRoleAccount), worker, false);
  808. }
  809. public async updateRewardAccount(
  810. worker: KeyringPair,
  811. workerId: BN,
  812. newRewardAccount: string,
  813. module: WorkingGroups
  814. ): Promise<void> {
  815. return this.sender.signAndSend(this.api.tx[module].updateRewardAccount(workerId, newRewardAccount), worker, false);
  816. }
  817. public async withdrawApplication(account: KeyringPair, workerId: BN, module: WorkingGroups): Promise<void> {
  818. return this.sender.signAndSend(this.api.tx[module].withdrawApplication(workerId), account, false);
  819. }
  820. public async batchWithdrawApplication(accounts: KeyringPair[], module: WorkingGroups): Promise<void[]> {
  821. return Promise.all(
  822. accounts.map(async keyPair => {
  823. const applicationIds: BN[] = await this.getApplicationsIdsByRoleAccount(keyPair.address, module);
  824. await this.withdrawApplication(keyPair, applicationIds[0], module);
  825. })
  826. );
  827. }
  828. public async terminateApplication(leader: KeyringPair, applicationId: BN, module: WorkingGroups): Promise<void> {
  829. return this.sender.signAndSend(this.api.tx[module].terminateApplication(applicationId), leader, false);
  830. }
  831. public async batchTerminateApplication(
  832. leader: KeyringPair,
  833. roleAccounts: KeyringPair[],
  834. module: WorkingGroups
  835. ): Promise<void[]> {
  836. return Promise.all(
  837. roleAccounts.map(async keyPair => {
  838. const applicationIds: BN[] = await this.getActiveApplicationsIdsByRoleAccount(keyPair.address, module);
  839. await this.terminateApplication(leader, applicationIds[0], module);
  840. })
  841. );
  842. }
  843. public async terminateRole(
  844. leader: KeyringPair,
  845. applicationId: BN,
  846. text: string,
  847. module: WorkingGroups,
  848. expectFailure: boolean
  849. ): Promise<void> {
  850. return this.sender.signAndSend(
  851. this.api.tx[module].terminateRole(applicationId, text, false),
  852. leader,
  853. expectFailure
  854. );
  855. }
  856. public async leaveRole(
  857. account: KeyringPair,
  858. text: string,
  859. expectFailure: boolean,
  860. module: WorkingGroups
  861. ): Promise<void> {
  862. const workerId: BN = await this.getWorkerIdByRoleAccount(account.address, module);
  863. return this.sender.signAndSend(this.api.tx[module].leaveRole(workerId, text), account, expectFailure);
  864. }
  865. public async batchLeaveRole(
  866. roleAccounts: KeyringPair[],
  867. text: string,
  868. expectFailure: boolean,
  869. module: WorkingGroups
  870. ): Promise<void[]> {
  871. return Promise.all(
  872. roleAccounts.map(async keyPair => {
  873. await this.leaveRole(keyPair, text, expectFailure, module);
  874. })
  875. );
  876. }
  877. public async getStorageRoleParameters(): Promise<RoleParameters> {
  878. return (await this.api.query.actors.parameters<Option<RoleParameters>>('StorageProvider')).unwrap();
  879. }
  880. public async getAnnouncingPeriod(): Promise<BN> {
  881. return this.api.query.councilElection.announcingPeriod<BlockNumber>();
  882. }
  883. public async getVotingPeriod(): Promise<BN> {
  884. return this.api.query.councilElection.votingPeriod<BlockNumber>();
  885. }
  886. public async getRevealingPeriod(): Promise<BN> {
  887. return this.api.query.councilElection.revealingPeriod<BlockNumber>();
  888. }
  889. public async getCouncilSize(): Promise<BN> {
  890. return this.api.query.councilElection.councilSize<u32>();
  891. }
  892. public async getCandidacyLimit(): Promise<BN> {
  893. return this.api.query.councilElection.candidacyLimit<u32>();
  894. }
  895. public async getNewTermDuration(): Promise<BN> {
  896. return this.api.query.councilElection.newTermDuration<BlockNumber>();
  897. }
  898. public async getMinCouncilStake(): Promise<BN> {
  899. return this.api.query.councilElection.minCouncilStake<BalanceOf>();
  900. }
  901. public async getMinVotingStake(): Promise<BN> {
  902. return this.api.query.councilElection.minVotingStake<BalanceOf>();
  903. }
  904. public async getNextOpeningId(module: WorkingGroups): Promise<BN> {
  905. return this.api.query[module].nextOpeningId<u32>();
  906. }
  907. public async getNextApplicationId(module: WorkingGroups): Promise<BN> {
  908. return this.api.query[module].nextApplicationId<u32>();
  909. }
  910. public async getOpening(id: BN, module: WorkingGroups): Promise<Opening> {
  911. return ((await this.api.query[module].openingById<Codec[]>(id))[0] as unknown) as Opening;
  912. }
  913. public async getHiringOpening(id: BN): Promise<HiringOpening> {
  914. return ((await this.api.query.hiring.openingById<Codec[]>(id))[0] as unknown) as HiringOpening;
  915. }
  916. public async getWorkers(module: WorkingGroups): Promise<Worker[]> {
  917. return ((await this.api.query[module].workerById<Codec[]>())[1] as unknown) as Worker[];
  918. }
  919. public async getWorkerById(id: BN, module: WorkingGroups): Promise<Worker> {
  920. return ((await this.api.query[module].workerById<Codec[]>(id))[0] as unknown) as Worker;
  921. }
  922. public async getWorkerIdByRoleAccount(address: string, module: WorkingGroups): Promise<BN> {
  923. const workersAndIds = await this.api.query[module].workerById<Codec[]>();
  924. const workers: Worker[] = (workersAndIds[1] as unknown) as Worker[];
  925. const ids: WorkerId[] = (workersAndIds[0] as unknown) as WorkerId[];
  926. const index: number = workers.findIndex(worker => worker.role_account_id.toString() === address);
  927. return ids[index];
  928. }
  929. public async getApplicationsIdsByRoleAccount(address: string, module: WorkingGroups): Promise<BN[]> {
  930. const applicationsAndIds = await this.api.query[module].applicationById<Codec[]>();
  931. const applications: Application[] = (applicationsAndIds[1] as unknown) as Application[];
  932. const ids: ApplicationId[] = (applicationsAndIds[0] as unknown) as ApplicationId[];
  933. return applications
  934. .map((application, index) => (application.role_account_id.toString() === address ? ids[index] : undefined))
  935. .filter(id => id !== undefined) as BN[];
  936. }
  937. public async getHiringApplicationById(id: BN): Promise<HiringApplication> {
  938. return ((await this.api.query.hiring.applicationById<Codec[]>(id))[0] as unknown) as HiringApplication;
  939. }
  940. public async getApplicationById(id: BN, module: WorkingGroups): Promise<Application> {
  941. return ((await this.api.query[module].applicationById<Codec[]>(id))[0] as unknown) as Application;
  942. }
  943. public async getActiveApplicationsIdsByRoleAccount(address: string, module: WorkingGroups): Promise<BN[]> {
  944. const applicationsAndIds = await this.api.query[module].applicationById<Codec[]>();
  945. const applications: Application[] = (applicationsAndIds[1] as unknown) as Application[];
  946. const ids: ApplicationId[] = (applicationsAndIds[0] as unknown) as ApplicationId[];
  947. return (
  948. await Promise.all(
  949. applications.map(async (application, index) => {
  950. if (
  951. application.role_account_id.toString() === address &&
  952. (await this.getHiringApplicationById(application.application_id)).stage.type === 'Active'
  953. ) {
  954. return ids[index];
  955. } else {
  956. return undefined;
  957. }
  958. })
  959. )
  960. ).filter(index => index !== undefined) as BN[];
  961. }
  962. public async getStake(id: BN): Promise<Stake> {
  963. return ((await this.api.query.stake.stakes<Codec[]>(id))[0] as unknown) as Stake;
  964. }
  965. public async getWorkerStakeAmount(workerId: BN, module: WorkingGroups): Promise<BN> {
  966. let stakeId: BN = (await this.getWorkerById(workerId, module)).role_stake_profile.unwrap().stake_id;
  967. return (((await this.getStake(stakeId)).staking_status.value as unknown) as StakedState).staked_amount;
  968. }
  969. public async getRewardRelationship(id: BN): Promise<RewardRelationship> {
  970. return ((
  971. await this.api.query.recurringRewards.rewardRelationships<Codec[]>(id)
  972. )[0] as unknown) as RewardRelationship;
  973. }
  974. public async getWorkerRewardAccount(workerId: BN, module: WorkingGroups): Promise<string> {
  975. let rewardRelationshipId: BN = (await this.getWorkerById(workerId, module)).reward_relationship.unwrap();
  976. return (await this.getRewardRelationship(rewardRelationshipId)).getField('account').toString();
  977. }
  978. }