Api.ts 74 KB

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