Api.ts 66 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806
  1. import { ApiPromise, WsProvider, Keyring } from '@polkadot/api'
  2. import { Bytes, Option, u32, Vec, StorageKey } from '@polkadot/types'
  3. import { Codec, ISubmittableResult } from '@polkadot/types/types'
  4. import { KeyringPair } from '@polkadot/keyring/types'
  5. import { MemberId, PaidMembershipTerms, PaidTermId } from '@joystream/types/members'
  6. import { Mint, MintId } from '@joystream/types/mint'
  7. import {
  8. Application,
  9. ApplicationIdToWorkerIdMap,
  10. Worker,
  11. WorkerId,
  12. OpeningPolicyCommitment,
  13. Opening as WorkingGroupOpening,
  14. } from '@joystream/types/working-group'
  15. import { ElectionStake, Seat } from '@joystream/types/council'
  16. import { AccountInfo, Balance, BalanceOf, BlockNumber, Event, EventRecord } from '@polkadot/types/interfaces'
  17. import BN from 'bn.js'
  18. import { SubmittableExtrinsic } from '@polkadot/api/types'
  19. import { Sender, LogLevel } from './sender'
  20. import { Utils } from './utils'
  21. import { Stake, StakedState, StakeId } from '@joystream/types/stake'
  22. import { RewardRelationship, RewardRelationshipId } from '@joystream/types/recurring-rewards'
  23. import { types } from '@joystream/types'
  24. import {
  25. ActivateOpeningAt,
  26. Application as HiringApplication,
  27. ApplicationId,
  28. Opening as HiringOpening,
  29. OpeningId,
  30. } from '@joystream/types/hiring'
  31. import { FillOpeningParameters, ProposalId } from '@joystream/types/proposals'
  32. import { v4 as uuid } from 'uuid'
  33. import { ChannelEntity } from '@joystream/cd-schemas/types/entities/ChannelEntity'
  34. import { VideoEntity } from '@joystream/cd-schemas/types/entities/VideoEntity'
  35. import { initializeContentDir, InputParser } from '@joystream/cd-schemas'
  36. import { OperationType } from '@joystream/types/content-directory'
  37. import { ContentId, DataObject } from '@joystream/types/media'
  38. import Debugger from 'debug'
  39. export enum WorkingGroups {
  40. StorageWorkingGroup = 'storageWorkingGroup',
  41. ContentDirectoryWorkingGroup = 'contentDirectoryWorkingGroup',
  42. }
  43. export class ApiFactory {
  44. private readonly api: ApiPromise
  45. private readonly keyring: Keyring
  46. // source of funds for all new accounts
  47. private readonly treasuryAccount: string
  48. public static async create(
  49. provider: WsProvider,
  50. treasuryAccountUri: string,
  51. sudoAccountUri: string
  52. ): Promise<ApiFactory> {
  53. const debug = Debugger('api-factory')
  54. let connectAttempts = 0
  55. while (true) {
  56. connectAttempts++
  57. debug(`Connecting to chain, attempt ${connectAttempts}..`)
  58. try {
  59. const api = await ApiPromise.create({ provider, types })
  60. // Wait for api to be connected and ready
  61. await api.isReady
  62. // If a node was just started up it might take a few seconds to start producing blocks
  63. // Give it a few seconds to be ready.
  64. await Utils.wait(5000)
  65. return new ApiFactory(api, treasuryAccountUri, sudoAccountUri)
  66. } catch (err) {
  67. if (connectAttempts === 3) {
  68. throw new Error('Unable to connect to chain')
  69. }
  70. }
  71. await Utils.wait(5000)
  72. }
  73. }
  74. constructor(api: ApiPromise, treasuryAccountUri: string, sudoAccountUri: string) {
  75. this.api = api
  76. this.keyring = new Keyring({ type: 'sr25519' })
  77. this.treasuryAccount = this.keyring.addFromUri(treasuryAccountUri).address
  78. this.keyring.addFromUri(sudoAccountUri)
  79. }
  80. public getApi(label: string): Api {
  81. return new Api(this.api, this.treasuryAccount, this.keyring, label)
  82. }
  83. public close(): void {
  84. this.api.disconnect()
  85. }
  86. }
  87. export class Api {
  88. private readonly api: ApiPromise
  89. private readonly sender: Sender
  90. private readonly keyring: Keyring
  91. // source of funds for all new accounts
  92. private readonly treasuryAccount: string
  93. constructor(api: ApiPromise, treasuryAccount: string, keyring: Keyring, label: string) {
  94. this.api = api
  95. this.keyring = keyring
  96. this.treasuryAccount = treasuryAccount
  97. this.sender = new Sender(api, keyring, label)
  98. }
  99. public enableDebugTxLogs(): void {
  100. this.sender.setLogLevel(LogLevel.Debug)
  101. }
  102. public enableVerboseTxLogs(): void {
  103. this.sender.setLogLevel(LogLevel.Verbose)
  104. }
  105. public createKeyPairs(n: number): KeyringPair[] {
  106. const nKeyPairs: KeyringPair[] = []
  107. for (let i = 0; i < n; i++) {
  108. nKeyPairs.push(this.keyring.addFromUri(i + uuid().substring(0, 8)))
  109. }
  110. return nKeyPairs
  111. }
  112. // Well known WorkingGroup enum defined in runtime
  113. public getWorkingGroupString(workingGroup: WorkingGroups): string {
  114. switch (workingGroup) {
  115. case WorkingGroups.StorageWorkingGroup:
  116. return 'Storage'
  117. case WorkingGroups.ContentDirectoryWorkingGroup:
  118. return 'Content'
  119. default:
  120. throw new Error(`Invalid working group string representation: ${workingGroup}`)
  121. }
  122. }
  123. public async makeSudoCall(tx: SubmittableExtrinsic<'promise'>): Promise<ISubmittableResult> {
  124. const sudo = await this.api.query.sudo.key()
  125. return this.sender.signAndSend(this.api.tx.sudo.sudo(tx), sudo)
  126. }
  127. public createPaidTermId(value: BN): PaidTermId {
  128. return this.api.createType('PaidTermId', value)
  129. }
  130. public async buyMembership(account: string, paidTermsId: PaidTermId, name: string): Promise<ISubmittableResult> {
  131. return this.sender.signAndSend(
  132. this.api.tx.members.buyMembership(paidTermsId, /* Handle: */ name, /* Avatar uri: */ '', /* About: */ ''),
  133. account
  134. )
  135. }
  136. public getMemberIds(address: string): Promise<MemberId[]> {
  137. return this.api.query.members.memberIdsByControllerAccountId<Vec<MemberId>>(address)
  138. }
  139. public async getBalance(address: string): Promise<Balance> {
  140. const accountData: AccountInfo = await this.api.query.system.account<AccountInfo>(address)
  141. return accountData.data.free
  142. }
  143. public async transferBalance(from: string, to: string, amount: BN): Promise<ISubmittableResult> {
  144. return this.sender.signAndSend(this.api.tx.balances.transfer(to, amount), from)
  145. }
  146. public async treasuryTransferBalance(to: string, amount: BN): Promise<ISubmittableResult> {
  147. return this.transferBalance(this.treasuryAccount, to, amount)
  148. }
  149. public treasuryTransferBalanceToAccounts(to: string[], amount: BN): Promise<ISubmittableResult[]> {
  150. return Promise.all(to.map((account) => this.transferBalance(this.treasuryAccount, account, amount)))
  151. }
  152. public getPaidMembershipTerms(paidTermsId: PaidTermId): Promise<PaidMembershipTerms> {
  153. return this.api.query.members.paidMembershipTermsById<PaidMembershipTerms>(paidTermsId)
  154. }
  155. public async getMembershipFee(paidTermsId: PaidTermId): Promise<BN> {
  156. const terms: PaidMembershipTerms = await this.getPaidMembershipTerms(paidTermsId)
  157. return terms.fee
  158. }
  159. // This method does not take into account weights and the runtime weight to fees computation!
  160. private estimateTxFee(tx: SubmittableExtrinsic<'promise'>): BN {
  161. const byteFee: BN = this.api.createType('BalanceOf', this.api.consts.transactionPayment.transactionByteFee)
  162. return Utils.calcTxLength(tx).mul(byteFee)
  163. }
  164. public estimateBuyMembershipFee(account: string, paidTermsId: PaidTermId, name: string): BN {
  165. return this.estimateTxFee(
  166. this.api.tx.members.buyMembership(paidTermsId, /* Handle: */ name, /* Avatar uri: */ '', /* About: */ '')
  167. )
  168. }
  169. public estimateApplyForCouncilFee(amount: BN): BN {
  170. return this.estimateTxFee(this.api.tx.councilElection.apply(amount))
  171. }
  172. public estimateVoteForCouncilFee(nominee: string, salt: string, stake: BN): BN {
  173. const hashedVote: string = Utils.hashVote(nominee, salt)
  174. return this.estimateTxFee(this.api.tx.councilElection.vote(hashedVote, stake))
  175. }
  176. public estimateRevealVoteFee(nominee: string, salt: string): BN {
  177. const hashedVote: string = Utils.hashVote(nominee, salt)
  178. return this.estimateTxFee(this.api.tx.councilElection.reveal(hashedVote, nominee, salt))
  179. }
  180. public estimateProposeRuntimeUpgradeFee(stake: BN, name: string, description: string, runtime: Bytes | string): BN {
  181. return this.estimateTxFee(
  182. this.api.tx.proposalsCodex.createRuntimeUpgradeProposal(stake, name, description, stake, runtime)
  183. )
  184. }
  185. public estimateProposeTextFee(stake: BN, name: string, description: string, text: string): BN {
  186. return this.estimateTxFee(this.api.tx.proposalsCodex.createTextProposal(stake, name, description, stake, text))
  187. }
  188. public estimateProposeSpendingFee(
  189. title: string,
  190. description: string,
  191. stake: BN,
  192. balance: BN,
  193. destination: string
  194. ): BN {
  195. return this.estimateTxFee(
  196. this.api.tx.proposalsCodex.createSpendingProposal(stake, title, description, stake, balance, destination)
  197. )
  198. }
  199. public estimateProposeValidatorCountFee(title: string, description: string, stake: BN): BN {
  200. return this.estimateTxFee(
  201. this.api.tx.proposalsCodex.createSetValidatorCountProposal(stake, title, description, stake, stake)
  202. )
  203. }
  204. public estimateProposeElectionParametersFee(
  205. title: string,
  206. description: string,
  207. stake: BN,
  208. announcingPeriod: BN,
  209. votingPeriod: BN,
  210. revealingPeriod: BN,
  211. councilSize: BN,
  212. candidacyLimit: BN,
  213. newTermDuration: BN,
  214. minCouncilStake: BN,
  215. minVotingStake: BN
  216. ): BN {
  217. return this.estimateTxFee(
  218. this.api.tx.proposalsCodex.createSetElectionParametersProposal(stake, title, description, stake, {
  219. announcing_period: announcingPeriod,
  220. voting_period: votingPeriod,
  221. revealing_period: revealingPeriod,
  222. council_size: councilSize,
  223. candidacy_limit: candidacyLimit,
  224. new_term_duration: newTermDuration,
  225. min_council_stake: minCouncilStake,
  226. min_voting_stake: minVotingStake,
  227. })
  228. )
  229. }
  230. public estimateVoteForProposalFee(): BN {
  231. return this.estimateTxFee(
  232. this.api.tx.proposalsEngine.vote(
  233. this.api.createType('MemberId', 0),
  234. this.api.createType('ProposalId', 0),
  235. 'Approve'
  236. )
  237. )
  238. }
  239. public estimateAddOpeningFee(module: WorkingGroups): BN {
  240. const commitment: OpeningPolicyCommitment = this.api.createType('OpeningPolicyCommitment', {
  241. application_rationing_policy: this.api.createType('Option<ApplicationRationingPolicy>', {
  242. max_active_applicants: new BN(32) as u32,
  243. }),
  244. max_review_period_length: new BN(32) as u32,
  245. application_staking_policy: this.api.createType('Option<StakingPolicy>', {
  246. amount: new BN(1),
  247. amount_mode: 'AtLeast',
  248. crowded_out_unstaking_period_length: new BN(1),
  249. review_period_expired_unstaking_period_length: new BN(1),
  250. }),
  251. role_staking_policy: this.api.createType('Option<StakingPolicy>', {
  252. amount: new BN(1),
  253. amount_mode: 'AtLeast',
  254. crowded_out_unstaking_period_length: new BN(1),
  255. review_period_expired_unstaking_period_length: new BN(1),
  256. }),
  257. role_slashing_terms: this.api.createType('SlashingTerms', {
  258. Slashable: {
  259. max_count: new BN(0),
  260. max_percent_pts_per_time: new BN(0),
  261. },
  262. }),
  263. fill_opening_successful_applicant_application_stake_unstaking_period: this.api.createType(
  264. 'Option<BlockNumber>',
  265. new BN(1)
  266. ),
  267. fill_opening_failed_applicant_application_stake_unstaking_period: this.api.createType(
  268. 'Option<BlockNumber>',
  269. new BN(1)
  270. ),
  271. fill_opening_failed_applicant_role_stake_unstaking_period: this.api.createType('Option<BlockNumber>', new BN(1)),
  272. terminate_application_stake_unstaking_period: this.api.createType('Option<BlockNumber>', new BN(1)),
  273. terminate_role_stake_unstaking_period: this.api.createType('Option<BlockNumber>', new BN(1)),
  274. exit_role_application_stake_unstaking_period: this.api.createType('Option<BlockNumber>', new BN(1)),
  275. exit_role_stake_unstaking_period: this.api.createType('Option<BlockNumber>', new BN(1)),
  276. })
  277. return this.estimateTxFee(
  278. this.api.tx[module].addOpening('CurrentBlock', commitment, 'Human readable text', 'Worker')
  279. )
  280. }
  281. public estimateAcceptApplicationsFee(module: WorkingGroups): BN {
  282. return this.estimateTxFee(
  283. (this.api.tx[module].acceptApplications(this.api.createType('OpeningId', 0)) as unknown) as SubmittableExtrinsic<
  284. 'promise'
  285. >
  286. )
  287. }
  288. public estimateApplyOnOpeningFee(account: string, module: WorkingGroups): BN {
  289. return this.estimateTxFee(
  290. this.api.tx[module].applyOnOpening(
  291. this.api.createType('MemberId', 0),
  292. this.api.createType('OpeningId', 0),
  293. account,
  294. null,
  295. null,
  296. 'Some testing text used for estimation purposes which is longer than text expected during the test'
  297. )
  298. )
  299. }
  300. public estimateBeginApplicantReviewFee(module: WorkingGroups): BN {
  301. return this.estimateTxFee(this.api.tx[module].beginApplicantReview(0))
  302. }
  303. public estimateFillOpeningFee(module: WorkingGroups): BN {
  304. return this.estimateTxFee(
  305. this.api.tx[module].fillOpening(0, this.api.createType('ApplicationIdSet', [0]), {
  306. 'amount_per_payout': 0,
  307. 'next_payment_at_block': 0,
  308. 'payout_interval': 0,
  309. })
  310. )
  311. }
  312. public estimateIncreaseStakeFee(module: WorkingGroups): BN {
  313. return this.estimateTxFee(
  314. (this.api.tx[module].increaseStake(this.api.createType('WorkerId', 0), 0) as unknown) as SubmittableExtrinsic<
  315. 'promise'
  316. >
  317. )
  318. }
  319. public estimateDecreaseStakeFee(module: WorkingGroups): BN {
  320. return this.estimateTxFee(
  321. (this.api.tx[module].decreaseStake(this.api.createType('WorkerId', 0), 0) as unknown) as SubmittableExtrinsic<
  322. 'promise'
  323. >
  324. )
  325. }
  326. public estimateUpdateRoleAccountFee(address: string, module: WorkingGroups): BN {
  327. return this.estimateTxFee(this.api.tx[module].updateRoleAccount(this.api.createType('WorkerId', 0), address))
  328. }
  329. public estimateUpdateRewardAccountFee(address: string, module: WorkingGroups): BN {
  330. return this.estimateTxFee(this.api.tx[module].updateRewardAccount(this.api.createType('WorkerId', 0), address))
  331. }
  332. public estimateLeaveRoleFee(module: WorkingGroups): BN {
  333. return this.estimateTxFee(
  334. this.api.tx[module].leaveRole(this.api.createType('WorkerId', 0), 'Long justification text')
  335. )
  336. }
  337. public estimateWithdrawApplicationFee(module: WorkingGroups): BN {
  338. return this.estimateTxFee(this.api.tx[module].withdrawApplication(this.api.createType('ApplicationId', 0)))
  339. }
  340. public estimateTerminateApplicationFee(module: WorkingGroups): BN {
  341. return this.estimateTxFee(this.api.tx[module].terminateApplication(this.api.createType('ApplicationId', 0)))
  342. }
  343. public estimateSlashStakeFee(module: WorkingGroups): BN {
  344. return this.estimateTxFee(
  345. (this.api.tx[module].slashStake(this.api.createType('WorkerId', 0), 0) as unknown) as SubmittableExtrinsic<
  346. 'promise'
  347. >
  348. )
  349. }
  350. public estimateTerminateRoleFee(module: WorkingGroups): BN {
  351. return this.estimateTxFee(
  352. this.api.tx[module].terminateRole(
  353. this.api.createType('WorkerId', 0),
  354. 'Long justification text explaining why the worker role will be terminated',
  355. false
  356. )
  357. )
  358. }
  359. public estimateProposeCreateWorkingGroupLeaderOpeningFee(): BN {
  360. const commitment: OpeningPolicyCommitment = this.api.createType('OpeningPolicyCommitment', {
  361. application_rationing_policy: this.api.createType('Option<ApplicationRationingPolicy>', {
  362. max_active_applicants: new BN(32) as u32,
  363. }),
  364. max_review_period_length: new BN(32) as u32,
  365. application_staking_policy: this.api.createType('Option<StakingPolicy>', {
  366. amount: new BN(1),
  367. amount_mode: 'AtLeast',
  368. crowded_out_unstaking_period_length: new BN(1),
  369. review_period_expired_unstaking_period_length: new BN(1),
  370. }),
  371. role_staking_policy: this.api.createType('Option<StakingPolicy>', {
  372. amount: new BN(1),
  373. amount_mode: 'AtLeast',
  374. crowded_out_unstaking_period_length: new BN(1),
  375. review_period_expired_unstaking_period_length: new BN(1),
  376. }),
  377. role_slashing_terms: this.api.createType('SlashingTerms', {
  378. Slashable: {
  379. max_count: new BN(0),
  380. max_percent_pts_per_time: new BN(0),
  381. },
  382. }),
  383. fill_opening_successful_applicant_application_stake_unstaking_period: this.api.createType(
  384. 'Option<BlockNumber>',
  385. new BN(1)
  386. ),
  387. fill_opening_failed_applicant_application_stake_unstaking_period: this.api.createType(
  388. 'Option<BlockNumber>',
  389. new BN(1)
  390. ),
  391. fill_opening_failed_applicant_role_stake_unstaking_period: this.api.createType('Option<BlockNumber>', new BN(1)),
  392. terminate_application_stake_unstaking_period: this.api.createType('Option<BlockNumber>', new BN(1)),
  393. terminate_role_stake_unstaking_period: this.api.createType('Option<BlockNumber>', new BN(1)),
  394. exit_role_application_stake_unstaking_period: this.api.createType('Option<BlockNumber>', new BN(1)),
  395. exit_role_stake_unstaking_period: this.api.createType('Option<BlockNumber>', new BN(1)),
  396. })
  397. return this.estimateTxFee(
  398. this.api.tx.proposalsCodex.createAddWorkingGroupLeaderOpeningProposal(
  399. 0,
  400. 'some long title for the purpose of testing',
  401. 'some long description for the purpose of testing',
  402. null,
  403. {
  404. activate_at: 'CurrentBlock',
  405. commitment: commitment,
  406. human_readable_text: 'Opening readable text',
  407. working_group: 'Storage',
  408. }
  409. )
  410. )
  411. }
  412. public estimateProposeBeginWorkingGroupLeaderApplicationReviewFee(): BN {
  413. return this.estimateTxFee(
  414. this.api.tx.proposalsCodex.createBeginReviewWorkingGroupLeaderApplicationsProposal(
  415. this.api.createType('MemberId', 0),
  416. 'Some testing text used for estimation purposes which is longer than text expected during the test',
  417. 'Some testing text used for estimation purposes which is longer than text expected during the test',
  418. null,
  419. this.api.createType('OpeningId', 0),
  420. 'Storage'
  421. )
  422. )
  423. }
  424. public estimateProposeFillLeaderOpeningFee(): BN {
  425. const fillOpeningParameters: FillOpeningParameters = this.api.createType('FillOpeningParameters', {
  426. opening_id: this.api.createType('OpeningId', 0),
  427. successful_application_id: this.api.createType('ApplicationId', 0),
  428. reward_policy: this.api.createType('Option<RewardPolicy>', {
  429. amount_per_payout: new BN(1) as Balance,
  430. next_payment_at_block: new BN(99999) as BlockNumber,
  431. payout_interval: this.api.createType('Option<u32>', new BN(99999)),
  432. }),
  433. working_group: this.api.createType('WorkingGroup', 'Storage'),
  434. })
  435. return this.estimateTxFee(
  436. this.api.tx.proposalsCodex.createFillWorkingGroupLeaderOpeningProposal(
  437. this.api.createType('MemberId', 0),
  438. 'Some testing text used for estimation purposes which is longer than text expected during the test',
  439. 'Some testing text used for estimation purposes which is longer than text expected during the test',
  440. null,
  441. fillOpeningParameters
  442. )
  443. )
  444. }
  445. public estimateProposeTerminateLeaderRoleFee(): BN {
  446. return this.estimateTxFee(
  447. this.api.tx.proposalsCodex.createTerminateWorkingGroupLeaderRoleProposal(
  448. this.api.createType('MemberId', 0),
  449. 'Some testing text used for estimation purposes which is longer than text expected during the test',
  450. 'Some testing text used for estimation purposes which is longer than text expected during the test',
  451. null,
  452. {
  453. 'worker_id': this.api.createType('WorkerId', 0),
  454. 'rationale': 'Exceptionaly long and extraordinary descriptive rationale',
  455. 'slash': true,
  456. 'working_group': 'Storage',
  457. }
  458. )
  459. )
  460. }
  461. public estimateProposeLeaderRewardFee(): BN {
  462. return this.estimateTxFee(
  463. this.api.tx.proposalsCodex.createSetWorkingGroupLeaderRewardProposal(
  464. this.api.createType('MemberId', 0),
  465. 'Some testing text used for estimation purposes which is longer than text expected during the test',
  466. 'Some testing text used for estimation purposes which is longer than text expected during the test',
  467. null,
  468. this.api.createType('WorkerId', 0),
  469. 0,
  470. 'Storage'
  471. )
  472. )
  473. }
  474. public estimateProposeDecreaseLeaderStakeFee(): BN {
  475. return this.estimateTxFee(
  476. this.api.tx.proposalsCodex.createDecreaseWorkingGroupLeaderStakeProposal(
  477. this.api.createType('MemberId', 0),
  478. 'Some testing text used for estimation purposes which is longer than text expected during the test',
  479. 'Some testing text used for estimation purposes which is longer than text expected during the test',
  480. null,
  481. this.api.createType('WorkerId', 0),
  482. 0,
  483. 'Storage'
  484. )
  485. )
  486. }
  487. public estimateProposeSlashLeaderStakeFee(): BN {
  488. return this.estimateTxFee(
  489. this.api.tx.proposalsCodex.createSlashWorkingGroupLeaderStakeProposal(
  490. this.api.createType('MemberId', 0),
  491. 'Some testing text used for estimation purposes which is longer than text expected during the test',
  492. 'Some testing text used for estimation purposes which is longer than text expected during the test',
  493. null,
  494. this.api.createType('WorkerId', 0),
  495. 0,
  496. 'Storage'
  497. )
  498. )
  499. }
  500. public estimateProposeWorkingGroupMintCapacityFee(): BN {
  501. return this.estimateTxFee(
  502. this.api.tx.proposalsCodex.createSetWorkingGroupMintCapacityProposal(
  503. this.api.createType('MemberId', 0),
  504. 'Some testing text used for estimation purposes which is longer than text expected during the test',
  505. 'Some testing text used for estimation purposes which is longer than text expected during the test',
  506. null,
  507. 0,
  508. 'Storage'
  509. )
  510. )
  511. }
  512. private applyForCouncilElection(account: string, amount: BN): Promise<ISubmittableResult> {
  513. return this.sender.signAndSend(this.api.tx.councilElection.apply(amount), account)
  514. }
  515. public batchApplyForCouncilElection(accounts: string[], amount: BN): Promise<ISubmittableResult[]> {
  516. return Promise.all(accounts.map(async (account) => this.applyForCouncilElection(account, amount)))
  517. }
  518. public async getCouncilElectionStake(address: string): Promise<BN> {
  519. return (((await this.api.query.councilElection.applicantStakes(address)) as unknown) as ElectionStake).new
  520. }
  521. private voteForCouncilMember(account: string, nominee: string, salt: string, stake: BN): Promise<ISubmittableResult> {
  522. const hashedVote: string = Utils.hashVote(nominee, salt)
  523. return this.sender.signAndSend(this.api.tx.councilElection.vote(hashedVote, stake), account)
  524. }
  525. public batchVoteForCouncilMember(
  526. accounts: string[],
  527. nominees: string[],
  528. salt: string[],
  529. stake: BN
  530. ): Promise<ISubmittableResult[]> {
  531. return Promise.all(
  532. accounts.map(async (account, index) => this.voteForCouncilMember(account, nominees[index], salt[index], stake))
  533. )
  534. }
  535. private revealVote(account: string, commitment: string, nominee: string, salt: string): Promise<ISubmittableResult> {
  536. return this.sender.signAndSend(this.api.tx.councilElection.reveal(commitment, nominee, salt), account)
  537. }
  538. public batchRevealVote(accounts: string[], nominees: string[], salt: string[]): Promise<ISubmittableResult[]> {
  539. return Promise.all(
  540. accounts.map(async (account, index) => {
  541. const commitment = Utils.hashVote(nominees[index], salt[index])
  542. return this.revealVote(account, commitment, nominees[index], salt[index])
  543. })
  544. )
  545. }
  546. public sudoStartAnnouncingPeriod(endsAtBlock: BN): Promise<ISubmittableResult> {
  547. return this.makeSudoCall(this.api.tx.councilElection.setStageAnnouncing(endsAtBlock))
  548. }
  549. public sudoStartVotingPeriod(endsAtBlock: BN): Promise<ISubmittableResult> {
  550. return this.makeSudoCall(this.api.tx.councilElection.setStageVoting(endsAtBlock))
  551. }
  552. public sudoStartRevealingPeriod(endsAtBlock: BN): Promise<ISubmittableResult> {
  553. return this.makeSudoCall(this.api.tx.councilElection.setStageRevealing(endsAtBlock))
  554. }
  555. public sudoSetCouncilMintCapacity(capacity: BN): Promise<ISubmittableResult> {
  556. return this.makeSudoCall(this.api.tx.council.setCouncilMintCapacity(capacity))
  557. }
  558. public getBestBlock(): Promise<BN> {
  559. return this.api.derive.chain.bestNumber()
  560. }
  561. public getCouncil(): Promise<Seat[]> {
  562. return this.api.query.council.activeCouncil<Vec<Codec>>().then((seats) => {
  563. return (seats as unknown) as Seat[]
  564. })
  565. }
  566. public async getCouncilAccounts(): Promise<string[]> {
  567. const council = await this.getCouncil()
  568. return council.map((seat) => seat.member.toString())
  569. }
  570. public async proposeRuntime(
  571. account: string,
  572. stake: BN,
  573. name: string,
  574. description: string,
  575. runtime: Bytes | string
  576. ): Promise<ISubmittableResult> {
  577. const memberId: MemberId = (await this.getMemberIds(account))[0]
  578. return this.sender.signAndSend(
  579. this.api.tx.proposalsCodex.createRuntimeUpgradeProposal(memberId, name, description, stake, runtime),
  580. account
  581. )
  582. }
  583. public async proposeText(
  584. account: string,
  585. stake: BN,
  586. name: string,
  587. description: string,
  588. text: string
  589. ): Promise<ISubmittableResult> {
  590. const memberId: MemberId = (await this.getMemberIds(account))[0]
  591. return this.sender.signAndSend(
  592. this.api.tx.proposalsCodex.createTextProposal(memberId, name, description, stake, text),
  593. account
  594. )
  595. }
  596. public async proposeSpending(
  597. account: string,
  598. title: string,
  599. description: string,
  600. stake: BN,
  601. balance: BN,
  602. destination: string
  603. ): Promise<ISubmittableResult> {
  604. const memberId: MemberId = (await this.getMemberIds(account))[0]
  605. return this.sender.signAndSend(
  606. this.api.tx.proposalsCodex.createSpendingProposal(memberId, title, description, stake, balance, destination),
  607. account
  608. )
  609. }
  610. public async proposeValidatorCount(
  611. account: string,
  612. title: string,
  613. description: string,
  614. stake: BN,
  615. validatorCount: BN
  616. ): Promise<ISubmittableResult> {
  617. const memberId: MemberId = (await this.getMemberIds(account))[0]
  618. return this.sender.signAndSend(
  619. this.api.tx.proposalsCodex.createSetValidatorCountProposal(memberId, title, description, stake, validatorCount),
  620. account
  621. )
  622. }
  623. public async proposeElectionParameters(
  624. account: string,
  625. title: string,
  626. description: string,
  627. stake: BN,
  628. announcingPeriod: BN,
  629. votingPeriod: BN,
  630. revealingPeriod: BN,
  631. councilSize: BN,
  632. candidacyLimit: BN,
  633. newTermDuration: BN,
  634. minCouncilStake: BN,
  635. minVotingStake: BN
  636. ): Promise<ISubmittableResult> {
  637. const memberId: MemberId = (await this.getMemberIds(account))[0]
  638. return this.sender.signAndSend(
  639. this.api.tx.proposalsCodex.createSetElectionParametersProposal(memberId, title, description, stake, {
  640. announcing_period: announcingPeriod,
  641. voting_period: votingPeriod,
  642. revealing_period: revealingPeriod,
  643. council_size: councilSize,
  644. candidacy_limit: candidacyLimit,
  645. new_term_duration: newTermDuration,
  646. min_council_stake: minCouncilStake,
  647. min_voting_stake: minVotingStake,
  648. }),
  649. account
  650. )
  651. }
  652. public async proposeBeginWorkingGroupLeaderApplicationReview(
  653. account: string,
  654. title: string,
  655. description: string,
  656. stake: BN,
  657. openingId: OpeningId,
  658. workingGroup: string
  659. ): Promise<ISubmittableResult> {
  660. const memberId: MemberId = (await this.getMemberIds(account))[0]
  661. return this.sender.signAndSend(
  662. this.api.tx.proposalsCodex.createBeginReviewWorkingGroupLeaderApplicationsProposal(
  663. memberId,
  664. title,
  665. description,
  666. stake,
  667. openingId,
  668. this.api.createType('WorkingGroup', workingGroup)
  669. ),
  670. account
  671. )
  672. }
  673. public approveProposal(account: string, memberId: MemberId, proposal: ProposalId): Promise<ISubmittableResult> {
  674. return this.sender.signAndSend(this.api.tx.proposalsEngine.vote(memberId, proposal, 'Approve'), account)
  675. }
  676. public async batchApproveProposal(proposal: ProposalId): Promise<ISubmittableResult[]> {
  677. const councilAccounts = await this.getCouncilAccounts()
  678. return Promise.all(
  679. councilAccounts.map(async (account) => {
  680. const memberId: MemberId = (await this.getMemberIds(account))[0]
  681. return this.approveProposal(account, memberId, proposal)
  682. })
  683. )
  684. }
  685. public getBlockDuration(): BN {
  686. return this.api.createType('Moment', this.api.consts.babe.expectedBlockTime)
  687. }
  688. public durationInMsFromBlocks(durationInBlocks: number): number {
  689. return this.getBlockDuration().muln(durationInBlocks).toNumber()
  690. }
  691. public findEventRecord(events: EventRecord[], section: string, method: string): EventRecord | undefined {
  692. return events.find((record) => record.event.section === section && record.event.method === method)
  693. }
  694. public findMemberRegisteredEvent(events: EventRecord[]): MemberId | undefined {
  695. const record = this.findEventRecord(events, 'members', 'MemberRegistered')
  696. if (record) {
  697. return record.event.data[0] as MemberId
  698. }
  699. }
  700. public findProposalCreatedEvent(events: EventRecord[]): ProposalId | undefined {
  701. const record = this.findEventRecord(events, 'proposalsEngine', 'ProposalCreated')
  702. if (record) {
  703. return record.event.data[1] as ProposalId
  704. }
  705. }
  706. public findOpeningAddedEvent(events: EventRecord[], workingGroup: WorkingGroups): OpeningId | undefined {
  707. const record = this.findEventRecord(events, workingGroup, 'OpeningAdded')
  708. if (record) {
  709. return record.event.data[0] as OpeningId
  710. }
  711. }
  712. public findLeaderSetEvent(events: EventRecord[], workingGroup: WorkingGroups): WorkerId | undefined {
  713. const record = this.findEventRecord(events, workingGroup, 'LeaderSet')
  714. if (record) {
  715. return (record.event.data as unknown) as WorkerId
  716. }
  717. }
  718. public findBeganApplicationReviewEvent(
  719. events: EventRecord[],
  720. workingGroup: WorkingGroups
  721. ): ApplicationId | undefined {
  722. const record = this.findEventRecord(events, workingGroup, 'BeganApplicationReview')
  723. if (record) {
  724. return (record.event.data as unknown) as ApplicationId
  725. }
  726. }
  727. public findTerminatedLeaderEvent(events: EventRecord[], workingGroup: WorkingGroups): EventRecord | undefined {
  728. return this.findEventRecord(events, workingGroup, 'TerminatedLeader')
  729. }
  730. public findWorkerRewardAmountUpdatedEvent(
  731. events: EventRecord[],
  732. workingGroup: WorkingGroups,
  733. workerId: WorkerId
  734. ): WorkerId | undefined {
  735. const record = this.findEventRecord(events, workingGroup, 'WorkerRewardAmountUpdated')
  736. if (record) {
  737. const id = (record.event.data[0] as unknown) as WorkerId
  738. if (id.eq(workerId)) {
  739. return workerId
  740. }
  741. }
  742. }
  743. public findStakeDecreasedEvent(events: EventRecord[], workingGroup: WorkingGroups): EventRecord | undefined {
  744. return this.findEventRecord(events, workingGroup, 'StakeDecreased')
  745. }
  746. public findStakeSlashedEvent(events: EventRecord[], workingGroup: WorkingGroups): EventRecord | undefined {
  747. return this.findEventRecord(events, workingGroup, 'StakeSlashed')
  748. }
  749. public findMintCapacityChangedEvent(events: EventRecord[], workingGroup: WorkingGroups): BN | undefined {
  750. const record = this.findEventRecord(events, workingGroup, 'MintCapacityChanged')
  751. if (record) {
  752. return (record.event.data[1] as unknown) as BN
  753. }
  754. }
  755. // Resolves to true when proposal finalized and executed successfully
  756. // Resolved to false when proposal finalized and execution fails
  757. public waitForProposalToFinalize(id: ProposalId): Promise<[boolean, EventRecord[]]> {
  758. return new Promise(async (resolve) => {
  759. const unsubscribe = await this.api.query.system.events<Vec<EventRecord>>((events) => {
  760. events.forEach((record) => {
  761. if (
  762. record.event.method &&
  763. record.event.method.toString() === 'ProposalStatusUpdated' &&
  764. record.event.data[0].eq(id) &&
  765. record.event.data[1].toString().includes('Executed')
  766. ) {
  767. unsubscribe()
  768. resolve([true, events])
  769. } else if (
  770. record.event.method &&
  771. record.event.method.toString() === 'ProposalStatusUpdated' &&
  772. record.event.data[0].eq(id) &&
  773. record.event.data[1].toString().includes('ExecutionFailed')
  774. ) {
  775. unsubscribe()
  776. resolve([false, events])
  777. }
  778. })
  779. })
  780. })
  781. }
  782. public findOpeningFilledEvent(
  783. events: EventRecord[],
  784. workingGroup: WorkingGroups
  785. ): ApplicationIdToWorkerIdMap | undefined {
  786. const record = this.findEventRecord(events, workingGroup, 'OpeningFilled')
  787. if (record) {
  788. return (record.event.data[1] as unknown) as ApplicationIdToWorkerIdMap
  789. }
  790. }
  791. // Looks for the first occurance of an expected event, and resolves.
  792. // Use this when the event we are expecting is not particular to a specific extrinsic
  793. public waitForSystemEvent(eventName: string): Promise<Event> {
  794. return new Promise(async (resolve) => {
  795. const unsubscribe = await this.api.query.system.events<Vec<EventRecord>>((events) => {
  796. events.forEach((record) => {
  797. if (record.event.method && record.event.method.toString() === eventName) {
  798. unsubscribe()
  799. resolve(record.event)
  800. }
  801. })
  802. })
  803. })
  804. }
  805. public findApplicationReviewBeganEvent(
  806. events: EventRecord[],
  807. workingGroup: WorkingGroups
  808. ): ApplicationId | undefined {
  809. const record = this.findEventRecord(events, workingGroup, 'BeganApplicationReview')
  810. if (record) {
  811. return (record.event.data as unknown) as ApplicationId
  812. }
  813. }
  814. public async getWorkingGroupMintCapacity(module: WorkingGroups): Promise<BN> {
  815. const mintId: MintId = await this.api.query[module].mint<MintId>()
  816. const mint: Mint = await this.api.query.minting.mints<Mint>(mintId)
  817. return mint.capacity
  818. }
  819. public getValidatorCount(): Promise<BN> {
  820. return this.api.query.staking.validatorCount<u32>()
  821. }
  822. public async addOpening(
  823. lead: string,
  824. openingParameters: {
  825. activationDelay: BN
  826. maxActiveApplicants: BN
  827. maxReviewPeriodLength: BN
  828. applicationStakingPolicyAmount: BN
  829. applicationCrowdedOutUnstakingPeriodLength: BN
  830. applicationReviewPeriodExpiredUnstakingPeriodLength: BN
  831. roleStakingPolicyAmount: BN
  832. roleCrowdedOutUnstakingPeriodLength: BN
  833. roleReviewPeriodExpiredUnstakingPeriodLength: BN
  834. slashableMaxCount: BN
  835. slashableMaxPercentPtsPerTime: BN
  836. fillOpeningSuccessfulApplicantApplicationStakeUnstakingPeriod: BN
  837. fillOpeningFailedApplicantApplicationStakeUnstakingPeriod: BN
  838. fillOpeningFailedApplicantRoleStakeUnstakingPeriod: BN
  839. terminateApplicationStakeUnstakingPeriod: BN
  840. terminateRoleStakeUnstakingPeriod: BN
  841. exitRoleApplicationStakeUnstakingPeriod: BN
  842. exitRoleStakeUnstakingPeriod: BN
  843. text: string
  844. type: string
  845. },
  846. module: WorkingGroups
  847. ): Promise<ISubmittableResult> {
  848. const activateAt: ActivateOpeningAt = this.api.createType(
  849. 'ActivateOpeningAt',
  850. openingParameters.activationDelay.eqn(0)
  851. ? 'CurrentBlock'
  852. : { ExactBlock: (await this.getBestBlock()).add(openingParameters.activationDelay) }
  853. )
  854. const commitment: OpeningPolicyCommitment = this.api.createType('OpeningPolicyCommitment', {
  855. application_rationing_policy: this.api.createType('Option<ApplicationRationingPolicy>', {
  856. max_active_applicants: openingParameters.maxActiveApplicants as u32,
  857. }),
  858. max_review_period_length: openingParameters.maxReviewPeriodLength as u32,
  859. application_staking_policy: this.api.createType('Option<StakingPolicy>', {
  860. amount: openingParameters.applicationStakingPolicyAmount,
  861. amount_mode: 'AtLeast',
  862. crowded_out_unstaking_period_length: openingParameters.applicationCrowdedOutUnstakingPeriodLength,
  863. review_period_expired_unstaking_period_length:
  864. openingParameters.applicationReviewPeriodExpiredUnstakingPeriodLength,
  865. }),
  866. role_staking_policy: this.api.createType('Option<StakingPolicy>', {
  867. amount: openingParameters.roleStakingPolicyAmount,
  868. amount_mode: 'AtLeast',
  869. crowded_out_unstaking_period_length: openingParameters.roleCrowdedOutUnstakingPeriodLength,
  870. review_period_expired_unstaking_period_length: openingParameters.roleReviewPeriodExpiredUnstakingPeriodLength,
  871. }),
  872. role_slashing_terms: this.api.createType('SlashingTerms', {
  873. Slashable: {
  874. max_count: openingParameters.slashableMaxCount,
  875. max_percent_pts_per_time: openingParameters.slashableMaxPercentPtsPerTime,
  876. },
  877. }),
  878. fill_opening_successful_applicant_application_stake_unstaking_period: this.api.createType(
  879. 'Option<BlockNumber>',
  880. openingParameters.fillOpeningSuccessfulApplicantApplicationStakeUnstakingPeriod
  881. ),
  882. fill_opening_failed_applicant_application_stake_unstaking_period: this.api.createType(
  883. 'Option<BlockNumber>',
  884. openingParameters.fillOpeningFailedApplicantApplicationStakeUnstakingPeriod
  885. ),
  886. fill_opening_failed_applicant_role_stake_unstaking_period: this.api.createType(
  887. 'Option<BlockNumber>',
  888. openingParameters.fillOpeningFailedApplicantRoleStakeUnstakingPeriod
  889. ),
  890. terminate_application_stake_unstaking_period: this.api.createType(
  891. 'Option<BlockNumber>',
  892. openingParameters.terminateApplicationStakeUnstakingPeriod
  893. ),
  894. terminate_role_stake_unstaking_period: this.api.createType(
  895. 'Option<BlockNumber>',
  896. openingParameters.terminateRoleStakeUnstakingPeriod
  897. ),
  898. exit_role_application_stake_unstaking_period: this.api.createType(
  899. 'Option<BlockNumber>',
  900. openingParameters.exitRoleApplicationStakeUnstakingPeriod
  901. ),
  902. exit_role_stake_unstaking_period: this.api.createType(
  903. 'Option<BlockNumber>',
  904. openingParameters.exitRoleStakeUnstakingPeriod
  905. ),
  906. })
  907. return this.sender.signAndSend(
  908. this.createAddOpeningTransaction(activateAt, commitment, openingParameters.text, openingParameters.type, module),
  909. lead
  910. )
  911. }
  912. public async sudoAddOpening(
  913. openingParameters: {
  914. activationDelay: BN
  915. maxActiveApplicants: BN
  916. maxReviewPeriodLength: BN
  917. applicationStakingPolicyAmount: BN
  918. applicationCrowdedOutUnstakingPeriodLength: BN
  919. applicationReviewPeriodExpiredUnstakingPeriodLength: BN
  920. roleStakingPolicyAmount: BN
  921. roleCrowdedOutUnstakingPeriodLength: BN
  922. roleReviewPeriodExpiredUnstakingPeriodLength: BN
  923. slashableMaxCount: BN
  924. slashableMaxPercentPtsPerTime: BN
  925. fillOpeningSuccessfulApplicantApplicationStakeUnstakingPeriod: BN
  926. fillOpeningFailedApplicantApplicationStakeUnstakingPeriod: BN
  927. fillOpeningFailedApplicantRoleStakeUnstakingPeriod: BN
  928. terminateApplicationStakeUnstakingPeriod: BN
  929. terminateRoleStakeUnstakingPeriod: BN
  930. exitRoleApplicationStakeUnstakingPeriod: BN
  931. exitRoleStakeUnstakingPeriod: BN
  932. text: string
  933. type: string
  934. },
  935. module: WorkingGroups
  936. ): Promise<ISubmittableResult> {
  937. const activateAt: ActivateOpeningAt = this.api.createType(
  938. 'ActivateOpeningAt',
  939. openingParameters.activationDelay.eqn(0)
  940. ? 'CurrentBlock'
  941. : { ExactBlock: (await this.getBestBlock()).add(openingParameters.activationDelay) }
  942. )
  943. const commitment: OpeningPolicyCommitment = this.api.createType('OpeningPolicyCommitment', {
  944. application_rationing_policy: this.api.createType('Option<ApplicationRationingPolicy>', {
  945. max_active_applicants: openingParameters.maxActiveApplicants as u32,
  946. }),
  947. max_review_period_length: openingParameters.maxReviewPeriodLength as u32,
  948. application_staking_policy: this.api.createType('Option<StakingPolicy>', {
  949. amount: openingParameters.applicationStakingPolicyAmount,
  950. amount_mode: 'AtLeast',
  951. crowded_out_unstaking_period_length: openingParameters.applicationCrowdedOutUnstakingPeriodLength,
  952. review_period_expired_unstaking_period_length:
  953. openingParameters.applicationReviewPeriodExpiredUnstakingPeriodLength,
  954. }),
  955. role_staking_policy: this.api.createType('Option<StakingPolicy>', {
  956. amount: openingParameters.roleStakingPolicyAmount,
  957. amount_mode: 'AtLeast',
  958. crowded_out_unstaking_period_length: openingParameters.roleCrowdedOutUnstakingPeriodLength,
  959. review_period_expired_unstaking_period_length: openingParameters.roleReviewPeriodExpiredUnstakingPeriodLength,
  960. }),
  961. role_slashing_terms: this.api.createType('SlashingTerms', {
  962. Slashable: {
  963. max_count: openingParameters.slashableMaxCount,
  964. max_percent_pts_per_time: openingParameters.slashableMaxPercentPtsPerTime,
  965. },
  966. }),
  967. fill_opening_successful_applicant_application_stake_unstaking_period: this.api.createType(
  968. 'Option<BlockNumber>',
  969. openingParameters.fillOpeningSuccessfulApplicantApplicationStakeUnstakingPeriod
  970. ),
  971. fill_opening_failed_applicant_application_stake_unstaking_period: this.api.createType(
  972. 'Option<BlockNumber>',
  973. openingParameters.fillOpeningFailedApplicantApplicationStakeUnstakingPeriod
  974. ),
  975. fill_opening_failed_applicant_role_stake_unstaking_period: this.api.createType(
  976. 'Option<BlockNumber>',
  977. openingParameters.fillOpeningFailedApplicantRoleStakeUnstakingPeriod
  978. ),
  979. terminate_application_stake_unstaking_period: this.api.createType(
  980. 'Option<BlockNumber>',
  981. openingParameters.terminateApplicationStakeUnstakingPeriod
  982. ),
  983. terminate_role_stake_unstaking_period: this.api.createType(
  984. 'Option<BlockNumber>',
  985. openingParameters.terminateRoleStakeUnstakingPeriod
  986. ),
  987. exit_role_application_stake_unstaking_period: this.api.createType(
  988. 'Option<BlockNumber>',
  989. openingParameters.exitRoleApplicationStakeUnstakingPeriod
  990. ),
  991. exit_role_stake_unstaking_period: this.api.createType(
  992. 'Option<BlockNumber>',
  993. openingParameters.exitRoleStakeUnstakingPeriod
  994. ),
  995. })
  996. return this.makeSudoCall(
  997. this.createAddOpeningTransaction(activateAt, commitment, openingParameters.text, openingParameters.type, module)
  998. )
  999. }
  1000. public async proposeCreateWorkingGroupLeaderOpening(leaderOpening: {
  1001. account: string
  1002. title: string
  1003. description: string
  1004. proposalStake: BN
  1005. actiavteAt: string
  1006. maxActiveApplicants: BN
  1007. maxReviewPeriodLength: BN
  1008. applicationStakingPolicyAmount: BN
  1009. applicationCrowdedOutUnstakingPeriodLength: BN
  1010. applicationReviewPeriodExpiredUnstakingPeriodLength: BN
  1011. roleStakingPolicyAmount: BN
  1012. roleCrowdedOutUnstakingPeriodLength: BN
  1013. roleReviewPeriodExpiredUnstakingPeriodLength: BN
  1014. slashableMaxCount: BN
  1015. slashableMaxPercentPtsPerTime: BN
  1016. fillOpeningSuccessfulApplicantApplicationStakeUnstakingPeriod: BN
  1017. fillOpeningFailedApplicantApplicationStakeUnstakingPeriod: BN
  1018. fillOpeningFailedApplicantRoleStakeUnstakingPeriod: BN
  1019. terminateApplicationStakeUnstakingPeriod: BN
  1020. terminateRoleStakeUnstakingPeriod: BN
  1021. exitRoleApplicationStakeUnstakingPeriod: BN
  1022. exitRoleStakeUnstakingPeriod: BN
  1023. text: string
  1024. workingGroup: string
  1025. }): Promise<ISubmittableResult> {
  1026. const commitment: OpeningPolicyCommitment = this.api.createType('OpeningPolicyCommitment', {
  1027. application_rationing_policy: this.api.createType('Option<ApplicationRationingPolicy>', {
  1028. max_active_applicants: leaderOpening.maxActiveApplicants as u32,
  1029. }),
  1030. max_review_period_length: leaderOpening.maxReviewPeriodLength as u32,
  1031. application_staking_policy: this.api.createType('Option<StakingPolicy>', {
  1032. amount: leaderOpening.applicationStakingPolicyAmount,
  1033. amount_mode: 'AtLeast',
  1034. crowded_out_unstaking_period_length: leaderOpening.applicationCrowdedOutUnstakingPeriodLength,
  1035. review_period_expired_unstaking_period_length:
  1036. leaderOpening.applicationReviewPeriodExpiredUnstakingPeriodLength,
  1037. }),
  1038. role_staking_policy: this.api.createType('Option<StakingPolicy>', {
  1039. amount: leaderOpening.roleStakingPolicyAmount,
  1040. amount_mode: 'AtLeast',
  1041. crowded_out_unstaking_period_length: leaderOpening.roleCrowdedOutUnstakingPeriodLength,
  1042. review_period_expired_unstaking_period_length: leaderOpening.roleReviewPeriodExpiredUnstakingPeriodLength,
  1043. }),
  1044. role_slashing_terms: this.api.createType('SlashingTerms', {
  1045. Slashable: {
  1046. max_count: leaderOpening.slashableMaxCount,
  1047. max_percent_pts_per_time: leaderOpening.slashableMaxPercentPtsPerTime,
  1048. },
  1049. }),
  1050. fill_opening_successful_applicant_application_stake_unstaking_period: this.api.createType(
  1051. 'Option<BlockNumber>',
  1052. leaderOpening.fillOpeningSuccessfulApplicantApplicationStakeUnstakingPeriod
  1053. ),
  1054. fill_opening_failed_applicant_application_stake_unstaking_period: this.api.createType(
  1055. 'Option<BlockNumber>',
  1056. leaderOpening.fillOpeningFailedApplicantApplicationStakeUnstakingPeriod
  1057. ),
  1058. fill_opening_failed_applicant_role_stake_unstaking_period: this.api.createType(
  1059. 'Option<BlockNumber>',
  1060. leaderOpening.fillOpeningFailedApplicantRoleStakeUnstakingPeriod
  1061. ),
  1062. terminate_application_stake_unstaking_period: this.api.createType(
  1063. 'Option<BlockNumber>',
  1064. leaderOpening.terminateApplicationStakeUnstakingPeriod
  1065. ),
  1066. terminate_role_stake_unstaking_period: this.api.createType(
  1067. 'Option<BlockNumber>',
  1068. leaderOpening.terminateRoleStakeUnstakingPeriod
  1069. ),
  1070. exit_role_application_stake_unstaking_period: this.api.createType(
  1071. 'Option<BlockNumber>',
  1072. leaderOpening.exitRoleApplicationStakeUnstakingPeriod
  1073. ),
  1074. exit_role_stake_unstaking_period: this.api.createType(
  1075. 'Option<BlockNumber>',
  1076. leaderOpening.exitRoleStakeUnstakingPeriod
  1077. ),
  1078. })
  1079. const memberId: MemberId = (await this.getMemberIds(leaderOpening.account))[0]
  1080. return this.sender.signAndSend(
  1081. this.api.tx.proposalsCodex.createAddWorkingGroupLeaderOpeningProposal(
  1082. memberId,
  1083. leaderOpening.title,
  1084. leaderOpening.description,
  1085. leaderOpening.proposalStake,
  1086. {
  1087. activate_at: leaderOpening.actiavteAt,
  1088. commitment: commitment,
  1089. human_readable_text: leaderOpening.text,
  1090. working_group: leaderOpening.workingGroup,
  1091. }
  1092. ),
  1093. leaderOpening.account
  1094. )
  1095. }
  1096. public async proposeFillLeaderOpening(fillOpening: {
  1097. account: string
  1098. title: string
  1099. description: string
  1100. proposalStake: BN
  1101. openingId: OpeningId
  1102. successfulApplicationId: ApplicationId
  1103. amountPerPayout: BN
  1104. nextPaymentAtBlock: BN
  1105. payoutInterval: BN
  1106. workingGroup: string
  1107. }): Promise<ISubmittableResult> {
  1108. const memberId: MemberId = (await this.getMemberIds(fillOpening.account))[0]
  1109. const fillOpeningParameters: FillOpeningParameters = this.api.createType('FillOpeningParameters', {
  1110. opening_id: fillOpening.openingId,
  1111. successful_application_id: fillOpening.successfulApplicationId,
  1112. reward_policy: this.api.createType('Option<RewardPolicy>', {
  1113. amount_per_payout: fillOpening.amountPerPayout as Balance,
  1114. next_payment_at_block: fillOpening.nextPaymentAtBlock as BlockNumber,
  1115. payout_interval: this.api.createType('Option<u32>', fillOpening.payoutInterval),
  1116. }),
  1117. working_group: this.api.createType('WorkingGroup', fillOpening.workingGroup),
  1118. })
  1119. return this.sender.signAndSend(
  1120. this.api.tx.proposalsCodex.createFillWorkingGroupLeaderOpeningProposal(
  1121. memberId,
  1122. fillOpening.title,
  1123. fillOpening.description,
  1124. fillOpening.proposalStake,
  1125. fillOpeningParameters
  1126. ),
  1127. fillOpening.account
  1128. )
  1129. }
  1130. public async proposeTerminateLeaderRole(
  1131. account: string,
  1132. title: string,
  1133. description: string,
  1134. proposalStake: BN,
  1135. leadWorkerId: WorkerId,
  1136. rationale: string,
  1137. slash: boolean,
  1138. workingGroup: string
  1139. ): Promise<ISubmittableResult> {
  1140. const memberId: MemberId = (await this.getMemberIds(account))[0]
  1141. return this.sender.signAndSend(
  1142. this.api.tx.proposalsCodex.createTerminateWorkingGroupLeaderRoleProposal(
  1143. memberId,
  1144. title,
  1145. description,
  1146. proposalStake,
  1147. {
  1148. 'worker_id': leadWorkerId,
  1149. rationale,
  1150. slash,
  1151. 'working_group': workingGroup,
  1152. }
  1153. ),
  1154. account
  1155. )
  1156. }
  1157. public async proposeLeaderReward(
  1158. account: string,
  1159. title: string,
  1160. description: string,
  1161. proposalStake: BN,
  1162. workerId: WorkerId,
  1163. rewardAmount: BN,
  1164. workingGroup: string
  1165. ): Promise<ISubmittableResult> {
  1166. const memberId: MemberId = (await this.getMemberIds(account))[0]
  1167. return this.sender.signAndSend(
  1168. this.api.tx.proposalsCodex.createSetWorkingGroupLeaderRewardProposal(
  1169. memberId,
  1170. title,
  1171. description,
  1172. proposalStake,
  1173. workerId,
  1174. rewardAmount,
  1175. this.api.createType('WorkingGroup', workingGroup)
  1176. ),
  1177. account
  1178. )
  1179. }
  1180. public async proposeDecreaseLeaderStake(
  1181. account: string,
  1182. title: string,
  1183. description: string,
  1184. proposalStake: BN,
  1185. workerId: WorkerId,
  1186. rewardAmount: BN,
  1187. workingGroup: string
  1188. ): Promise<ISubmittableResult> {
  1189. const memberId: MemberId = (await this.getMemberIds(account))[0]
  1190. return this.sender.signAndSend(
  1191. this.api.tx.proposalsCodex.createDecreaseWorkingGroupLeaderStakeProposal(
  1192. memberId,
  1193. title,
  1194. description,
  1195. proposalStake,
  1196. workerId,
  1197. rewardAmount,
  1198. this.api.createType('WorkingGroup', workingGroup)
  1199. ),
  1200. account
  1201. )
  1202. }
  1203. public async proposeSlashLeaderStake(
  1204. account: string,
  1205. title: string,
  1206. description: string,
  1207. proposalStake: BN,
  1208. workerId: WorkerId,
  1209. rewardAmount: BN,
  1210. workingGroup: string
  1211. ): Promise<ISubmittableResult> {
  1212. const memberId: MemberId = (await this.getMemberIds(account))[0]
  1213. return this.sender.signAndSend(
  1214. this.api.tx.proposalsCodex.createSlashWorkingGroupLeaderStakeProposal(
  1215. memberId,
  1216. title,
  1217. description,
  1218. proposalStake,
  1219. workerId,
  1220. rewardAmount,
  1221. this.api.createType('WorkingGroup', workingGroup)
  1222. ),
  1223. account
  1224. )
  1225. }
  1226. public async proposeWorkingGroupMintCapacity(
  1227. account: string,
  1228. title: string,
  1229. description: string,
  1230. proposalStake: BN,
  1231. mintCapacity: BN,
  1232. workingGroup: string
  1233. ): Promise<ISubmittableResult> {
  1234. const memberId: MemberId = (await this.getMemberIds(account))[0]
  1235. return this.sender.signAndSend(
  1236. this.api.tx.proposalsCodex.createSetWorkingGroupMintCapacityProposal(
  1237. memberId,
  1238. title,
  1239. description,
  1240. proposalStake,
  1241. mintCapacity,
  1242. this.api.createType('WorkingGroup', workingGroup)
  1243. ),
  1244. account
  1245. )
  1246. }
  1247. private createAddOpeningTransaction(
  1248. actiavteAt: ActivateOpeningAt,
  1249. commitment: OpeningPolicyCommitment,
  1250. text: string,
  1251. type: string,
  1252. module: WorkingGroups
  1253. ): SubmittableExtrinsic<'promise'> {
  1254. return this.api.tx[module].addOpening(actiavteAt, commitment, text, this.api.createType('OpeningType', type))
  1255. }
  1256. public async acceptApplications(
  1257. leader: string,
  1258. openingId: OpeningId,
  1259. module: WorkingGroups
  1260. ): Promise<ISubmittableResult> {
  1261. return this.sender.signAndSend(this.api.tx[module].acceptApplications(openingId), leader)
  1262. }
  1263. public async beginApplicantReview(
  1264. leader: string,
  1265. openingId: OpeningId,
  1266. module: WorkingGroups
  1267. ): Promise<ISubmittableResult> {
  1268. return this.sender.signAndSend(this.api.tx[module].beginApplicantReview(openingId), leader)
  1269. }
  1270. public async sudoBeginApplicantReview(openingId: OpeningId, module: WorkingGroups): Promise<ISubmittableResult> {
  1271. return this.makeSudoCall(this.api.tx[module].beginApplicantReview(openingId))
  1272. }
  1273. public async applyOnOpening(
  1274. account: string,
  1275. roleAccountAddress: string,
  1276. openingId: OpeningId,
  1277. roleStake: BN,
  1278. applicantStake: BN,
  1279. text: string,
  1280. module: WorkingGroups
  1281. ): Promise<ISubmittableResult> {
  1282. const memberId: MemberId = (await this.getMemberIds(account))[0]
  1283. return this.sender.signAndSend(
  1284. this.api.tx[module].applyOnOpening(memberId, openingId, roleAccountAddress, roleStake, applicantStake, text),
  1285. account
  1286. )
  1287. }
  1288. public async batchApplyOnOpening(
  1289. accounts: string[],
  1290. openingId: OpeningId,
  1291. roleStake: BN,
  1292. applicantStake: BN,
  1293. text: string,
  1294. module: WorkingGroups
  1295. ): Promise<ISubmittableResult[]> {
  1296. return Promise.all(
  1297. accounts.map(async (account) =>
  1298. this.applyOnOpening(account, account, openingId, roleStake, applicantStake, text, module)
  1299. )
  1300. )
  1301. }
  1302. public async fillOpening(
  1303. leader: string,
  1304. openingId: OpeningId,
  1305. applicationIds: ApplicationId[],
  1306. amountPerPayout: BN,
  1307. nextPaymentBlock: BN,
  1308. payoutInterval: BN,
  1309. module: WorkingGroups
  1310. ): Promise<ISubmittableResult> {
  1311. return this.sender.signAndSend(
  1312. this.api.tx[module].fillOpening(openingId, this.api.createType('ApplicationIdSet', applicationIds), {
  1313. amount_per_payout: amountPerPayout,
  1314. next_payment_at_block: nextPaymentBlock,
  1315. payout_interval: payoutInterval,
  1316. }),
  1317. leader
  1318. )
  1319. }
  1320. public async sudoFillOpening(
  1321. openingId: OpeningId,
  1322. applicationIds: ApplicationId[],
  1323. amountPerPayout: BN,
  1324. nextPaymentBlock: BN,
  1325. payoutInterval: BN,
  1326. module: WorkingGroups
  1327. ): Promise<ISubmittableResult> {
  1328. return this.makeSudoCall(
  1329. this.api.tx[module].fillOpening(openingId, this.api.createType('ApplicationIdSet', applicationIds), {
  1330. 'amount_per_payout': amountPerPayout,
  1331. 'next_payment_at_block': nextPaymentBlock,
  1332. 'payout_interval': payoutInterval,
  1333. })
  1334. )
  1335. }
  1336. public async increaseStake(
  1337. worker: string,
  1338. workerId: WorkerId,
  1339. stake: BN,
  1340. module: WorkingGroups
  1341. ): Promise<ISubmittableResult> {
  1342. return this.sender.signAndSend(this.api.tx[module].increaseStake(workerId, stake), worker)
  1343. }
  1344. public async decreaseStake(
  1345. leader: string,
  1346. workerId: WorkerId,
  1347. stake: BN,
  1348. module: WorkingGroups
  1349. ): Promise<ISubmittableResult> {
  1350. return this.sender.signAndSend(this.api.tx[module].decreaseStake(workerId, stake), leader)
  1351. }
  1352. public async slashStake(
  1353. leader: string,
  1354. workerId: WorkerId,
  1355. stake: BN,
  1356. module: WorkingGroups
  1357. ): Promise<ISubmittableResult> {
  1358. return this.sender.signAndSend(this.api.tx[module].slashStake(workerId, stake), leader)
  1359. }
  1360. public async updateRoleAccount(
  1361. worker: string,
  1362. workerId: WorkerId,
  1363. newRoleAccount: string,
  1364. module: WorkingGroups
  1365. ): Promise<ISubmittableResult> {
  1366. return this.sender.signAndSend(this.api.tx[module].updateRoleAccount(workerId, newRoleAccount), worker)
  1367. }
  1368. public async updateRewardAccount(
  1369. worker: string,
  1370. workerId: WorkerId,
  1371. newRewardAccount: string,
  1372. module: WorkingGroups
  1373. ): Promise<ISubmittableResult> {
  1374. return this.sender.signAndSend(this.api.tx[module].updateRewardAccount(workerId, newRewardAccount), worker)
  1375. }
  1376. public async withdrawApplication(
  1377. account: string,
  1378. applicationId: ApplicationId,
  1379. module: WorkingGroups
  1380. ): Promise<ISubmittableResult> {
  1381. return this.sender.signAndSend(this.api.tx[module].withdrawApplication(applicationId), account)
  1382. }
  1383. public async batchWithdrawActiveApplications(
  1384. applicationIds: ApplicationId[],
  1385. module: WorkingGroups
  1386. ): Promise<ISubmittableResult[]> {
  1387. const entries: [StorageKey, Application][] = await this.api.query[module].applicationById.entries<Application>()
  1388. return Promise.all(
  1389. entries
  1390. .filter(([idKey]) => {
  1391. return applicationIds.includes(idKey.args[0] as ApplicationId)
  1392. })
  1393. .map(([idKey, application]) => ({
  1394. id: idKey.args[0] as ApplicationId,
  1395. account: application.role_account_id.toString(),
  1396. }))
  1397. .map(({ id, account }) => this.withdrawApplication(account, id, module))
  1398. )
  1399. }
  1400. public async terminateApplication(
  1401. leader: string,
  1402. applicationId: ApplicationId,
  1403. module: WorkingGroups
  1404. ): Promise<ISubmittableResult> {
  1405. return this.sender.signAndSend(this.api.tx[module].terminateApplication(applicationId), leader)
  1406. }
  1407. public async batchTerminateApplication(
  1408. leader: string,
  1409. applicationIds: ApplicationId[],
  1410. module: WorkingGroups
  1411. ): Promise<ISubmittableResult[]> {
  1412. return Promise.all(applicationIds.map((id) => this.terminateApplication(leader, id, module)))
  1413. }
  1414. public async terminateRole(
  1415. leader: string,
  1416. workerId: WorkerId,
  1417. text: string,
  1418. module: WorkingGroups
  1419. ): Promise<ISubmittableResult> {
  1420. return this.sender.signAndSend(this.api.tx[module].terminateRole(workerId, text, false), leader)
  1421. }
  1422. public async leaveRole(
  1423. account: string,
  1424. workerId: WorkerId,
  1425. text: string,
  1426. module: WorkingGroups
  1427. ): Promise<ISubmittableResult> {
  1428. return this.sender.signAndSend(this.api.tx[module].leaveRole(workerId, text), account)
  1429. }
  1430. public async batchLeaveRole(
  1431. workerIds: WorkerId[],
  1432. text: string,
  1433. module: WorkingGroups
  1434. ): Promise<ISubmittableResult[]> {
  1435. return Promise.all(
  1436. workerIds.map(async (workerId) => {
  1437. // get role_account of worker
  1438. const worker = await this.getWorkerById(workerId, module)
  1439. return this.leaveRole(worker.role_account_id.toString(), workerId, text, module)
  1440. })
  1441. )
  1442. }
  1443. public async getAnnouncingPeriod(): Promise<BN> {
  1444. return this.api.query.councilElection.announcingPeriod<BlockNumber>()
  1445. }
  1446. public async getVotingPeriod(): Promise<BN> {
  1447. return this.api.query.councilElection.votingPeriod<BlockNumber>()
  1448. }
  1449. public async getRevealingPeriod(): Promise<BN> {
  1450. return this.api.query.councilElection.revealingPeriod<BlockNumber>()
  1451. }
  1452. public async getCouncilSize(): Promise<BN> {
  1453. return this.api.query.councilElection.councilSize<u32>()
  1454. }
  1455. public async getCandidacyLimit(): Promise<BN> {
  1456. return this.api.query.councilElection.candidacyLimit<u32>()
  1457. }
  1458. public async getNewTermDuration(): Promise<BN> {
  1459. return this.api.query.councilElection.newTermDuration<BlockNumber>()
  1460. }
  1461. public async getMinCouncilStake(): Promise<BN> {
  1462. return this.api.query.councilElection.minCouncilStake<BalanceOf>()
  1463. }
  1464. public async getMinVotingStake(): Promise<BN> {
  1465. return this.api.query.councilElection.minVotingStake<BalanceOf>()
  1466. }
  1467. public async getHiringOpening(id: OpeningId): Promise<HiringOpening> {
  1468. return await this.api.query.hiring.openingById<HiringOpening>(id)
  1469. }
  1470. public async getWorkingGroupOpening(id: OpeningId, group: WorkingGroups): Promise<WorkingGroupOpening> {
  1471. return await this.api.query[group].openingById<WorkingGroupOpening>(id)
  1472. }
  1473. public async getWorkers(module: WorkingGroups): Promise<Worker[]> {
  1474. return (await this.api.query[module].workerById.entries<Worker>()).map((workerWithId) => workerWithId[1])
  1475. }
  1476. public async getWorkerById(id: WorkerId, module: WorkingGroups): Promise<Worker> {
  1477. return await this.api.query[module].workerById<Worker>(id)
  1478. }
  1479. public async isWorker(workerId: WorkerId, module: WorkingGroups): Promise<boolean> {
  1480. const workersAndIds: [StorageKey, Worker][] = await this.api.query[module].workerById.entries<Worker>()
  1481. const index: number = workersAndIds.findIndex((workersAndId) => workersAndId[0].args[0].eq(workerId))
  1482. return index !== -1
  1483. }
  1484. public async getApplicationsIdsByRoleAccount(address: string, module: WorkingGroups): Promise<ApplicationId[]> {
  1485. const applicationsAndIds: [StorageKey, Application][] = await this.api.query[module].applicationById.entries<
  1486. Application
  1487. >()
  1488. return applicationsAndIds
  1489. .map((applicationWithId) => {
  1490. const application: Application = applicationWithId[1]
  1491. return application.role_account_id.toString() === address
  1492. ? (applicationWithId[0].args[0] as ApplicationId)
  1493. : undefined
  1494. })
  1495. .filter((id) => id !== undefined) as ApplicationId[]
  1496. }
  1497. public async getHiringApplicationById(id: ApplicationId): Promise<HiringApplication> {
  1498. return this.api.query.hiring.applicationById<HiringApplication>(id)
  1499. }
  1500. public async getApplicationById(id: ApplicationId, module: WorkingGroups): Promise<Application> {
  1501. return this.api.query[module].applicationById<Application>(id)
  1502. }
  1503. public async getApplicantRoleAccounts(filterActiveIds: ApplicationId[], module: WorkingGroups): Promise<string[]> {
  1504. const entries: [StorageKey, Application][] = await this.api.query[module].applicationById.entries<Application>()
  1505. const applications = entries
  1506. .filter(([idKey]) => {
  1507. return filterActiveIds.includes(idKey.args[0] as ApplicationId)
  1508. })
  1509. .map(([, application]) => application)
  1510. return (
  1511. await Promise.all(
  1512. applications.map(async (application) => {
  1513. const active = (await this.getHiringApplicationById(application.application_id)).stage.type === 'Active'
  1514. return active ? application.role_account_id.toString() : ''
  1515. })
  1516. )
  1517. ).filter((addr) => addr !== '')
  1518. }
  1519. public async getWorkerRoleAccounts(workerIds: WorkerId[], module: WorkingGroups): Promise<string[]> {
  1520. const entries: [StorageKey, Worker][] = await this.api.query[module].workerById.entries<Worker>()
  1521. return entries
  1522. .filter(([idKey]) => {
  1523. return workerIds.includes(idKey.args[0] as WorkerId)
  1524. })
  1525. .map(([, worker]) => worker.role_account_id.toString())
  1526. }
  1527. public async getStake(id: StakeId): Promise<Stake> {
  1528. return this.api.query.stake.stakes<Stake>(id)
  1529. }
  1530. public async getWorkerStakeAmount(workerId: WorkerId, module: WorkingGroups): Promise<BN> {
  1531. const stakeId: StakeId = (await this.getWorkerById(workerId, module)).role_stake_profile.unwrap().stake_id
  1532. return (((await this.getStake(stakeId)).staking_status.value as unknown) as StakedState).staked_amount
  1533. }
  1534. public async getRewardRelationship(id: RewardRelationshipId): Promise<RewardRelationship> {
  1535. return this.api.query.recurringRewards.rewardRelationships<RewardRelationship>(id)
  1536. }
  1537. public async getWorkerRewardRelationship(workerId: WorkerId, module: WorkingGroups): Promise<RewardRelationship> {
  1538. const rewardRelationshipId: RewardRelationshipId = (
  1539. await this.getWorkerById(workerId, module)
  1540. ).reward_relationship.unwrap()
  1541. return this.getRewardRelationship(rewardRelationshipId)
  1542. }
  1543. public async getWorkerRewardAccount(workerId: WorkerId, module: WorkingGroups): Promise<string> {
  1544. const rewardRelationshipId: RewardRelationshipId = (
  1545. await this.getWorkerById(workerId, module)
  1546. ).reward_relationship.unwrap()
  1547. return (await this.getRewardRelationship(rewardRelationshipId)).getField('account').toString()
  1548. }
  1549. public async getLeadWorkerId(module: WorkingGroups): Promise<WorkerId | undefined> {
  1550. return (await this.api.query[module].currentLead<Option<WorkerId>>()).unwrapOr(undefined)
  1551. }
  1552. public async getGroupLead(module: WorkingGroups): Promise<Worker | undefined> {
  1553. const leadId = await this.getLeadWorkerId(module)
  1554. return leadId ? this.getWorkerById(leadId, module) : undefined
  1555. }
  1556. public async getActiveWorkersCount(module: WorkingGroups): Promise<BN> {
  1557. return this.api.query[module].activeWorkerCount<u32>()
  1558. }
  1559. public getMaxWorkersCount(module: WorkingGroups): BN {
  1560. return this.api.createType('u32', this.api.consts[module].maxWorkerNumberLimit)
  1561. }
  1562. async sendContentDirectoryTransaction(operations: OperationType[]): Promise<ISubmittableResult> {
  1563. const transaction = this.api.tx.contentDirectory.transaction(
  1564. { Lead: null }, // We use member with id 0 as actor (in this case we assume this is Alice)
  1565. operations // We provide parsed operations as second argument
  1566. )
  1567. const lead = (await this.getGroupLead(WorkingGroups.ContentDirectoryWorkingGroup)) as Worker
  1568. return this.sender.signAndSend(transaction, lead.role_account_id)
  1569. }
  1570. public async createChannelEntity(channel: ChannelEntity): Promise<ISubmittableResult> {
  1571. // Create the parser with known entity schemas (the ones in content-directory-schemas/inputs)
  1572. const parser = InputParser.createWithKnownSchemas(
  1573. this.api,
  1574. // The second argument is an array of entity batches, following standard entity batch syntax ({ className, entries }):
  1575. [
  1576. {
  1577. className: 'Channel',
  1578. entries: [channel], // We could specify multiple entries here, but in this case we only need one
  1579. },
  1580. ]
  1581. )
  1582. // We parse the input into CreateEntity and AddSchemaSupportToEntity operations
  1583. const operations = await parser.getEntityBatchOperations()
  1584. return this.sendContentDirectoryTransaction(operations)
  1585. }
  1586. public async createVideoEntity(video: VideoEntity): Promise<ISubmittableResult> {
  1587. // Create the parser with known entity schemas (the ones in content-directory-schemas/inputs)
  1588. const parser = InputParser.createWithKnownSchemas(
  1589. this.api,
  1590. // The second argument is an array of entity batches, following standard entity batch syntax ({ className, entries }):
  1591. [
  1592. {
  1593. className: 'Video',
  1594. entries: [video], // We could specify multiple entries here, but in this case we only need one
  1595. },
  1596. ]
  1597. )
  1598. // We parse the input into CreateEntity and AddSchemaSupportToEntity operations
  1599. const operations = await parser.getEntityBatchOperations()
  1600. return this.sendContentDirectoryTransaction(operations)
  1601. }
  1602. public async updateChannelEntity(
  1603. channelUpdateInput: Record<string, any>,
  1604. uniquePropValue: Record<string, any>
  1605. ): Promise<ISubmittableResult> {
  1606. // Create the parser with known entity schemas (the ones in content-directory-schemas/inputs)
  1607. const parser = InputParser.createWithKnownSchemas(this.api)
  1608. // We can reuse InputParser's `findEntityIdByUniqueQuery` method to find entityId of the channel we
  1609. // created in ./createChannel.ts example (normally we would probably use some other way to do it, ie.: query node)
  1610. const CHANNEL_ID = await parser.findEntityIdByUniqueQuery(uniquePropValue, 'Channel') // Use getEntityUpdateOperations to parse the update input
  1611. const updateOperations = await parser.getEntityUpdateOperations(
  1612. channelUpdateInput,
  1613. 'Channel', // Class name
  1614. CHANNEL_ID // Id of the entity we want to update
  1615. )
  1616. return this.sendContentDirectoryTransaction(updateOperations)
  1617. }
  1618. async getDataByContentId(contentId: ContentId): Promise<DataObject | null> {
  1619. const dataObject = await this.api.query.dataDirectory.DataByContentId<Option<DataObject>>(contentId)
  1620. return dataObject.unwrapOr(null)
  1621. }
  1622. public async initializeContentDirectory(): Promise<void> {
  1623. const lead = await this.getGroupLead(WorkingGroups.ContentDirectoryWorkingGroup)
  1624. if (!lead) {
  1625. throw new Error('No Lead is set for storage wokring group')
  1626. }
  1627. const leadKeyPair = this.keyring.getPair(lead.role_account_id.toString())
  1628. return initializeContentDir(this.api, leadKeyPair)
  1629. }
  1630. }