git.delta.rocks / unique-network / refs/commits / 4a84b64c6fc6

difftreelog

source

js-packages/playgrounds/unique.dev.ts49.8 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// SPDX-License-Identifier: Apache-2.034import '@unique/opal-types/augment-api.js';5import '@unique/opal-types/augment-types.js';6import '@unique/opal-types/types-lookup.js';78import {stringToU8a} from '@polkadot/util';9import {blake2AsHex, encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto';10import type {ChainHelperBaseConstructor, UniqueHelperConstructor} from './unique.js';11import {UniqueHelper, ChainHelperBase, HelperGroup} from './unique.js';12import {ApiPromise, Keyring, WsProvider} from '@polkadot/api';13import * as defs from '@unique/opal-types/definitions.js';14import type {IKeyringPair} from '@polkadot/types/types';15import type {EventRecord} from '@polkadot/types/interfaces';16import type {ICrossAccountId, ILogger, IPovInfo, ISchedulerOptions, ITransactionResult, TSigner} from './types.js';17import type {FrameSystemEventRecord, StagingXcmV2TraitsError, StagingXcmV3TraitsOutcome} from '@polkadot/types/lookup';18import type {SignerOptions, VoidFn} from '@polkadot/api/types';19import {spawnSync} from 'child_process';20import {AcalaHelper, AstarHelper, MoonbeamHelper, PolkadexHelper, RelayHelper, WestmintHelper, ForeignAssetsGroup, XcmGroup, XTokensGroup, TokensGroup} from './unique.xcm.js';21import {CollectiveGroup, CollectiveMembershipGroup, DemocracyGroup, RankedCollectiveGroup, ReferendaGroup} from './unique.governance.js';22import type {ICollectiveGroup, IFellowshipGroup} from './unique.governance.js';2324export class SilentLogger {25  log(_msg: any, _level: any): void { }26  level = {27    ERROR: 'ERROR' as const,28    WARNING: 'WARNING' as const,29    INFO: 'INFO' as const,30  };31}3233export class SilentConsole {34  // TODO: Remove, this is temporary: Filter unneeded API output35  // (Jaco promised it will be removed in the next version)36  consoleErr: any;37  consoleLog: any;38  consoleWarn: any;3940  constructor() {41    this.consoleErr = console.error;42    this.consoleLog = console.log;43    this.consoleWarn = console.warn;44  }4546  enable() {47    const outFn = (printer: any) => (...args: any[]) => {48      for(const arg of args) {49        if(typeof arg !== 'string')50          continue;51        const skippedWarnings = ['1000:: Normal connection closure', 'Not decorating unknown runtime apis:', 'RPC methods not decorated:', 'Not decorating runtime apis', 'Bad input data provided to validate_transaction', 'account balance too low', '1006:: Abnormal Closure'];52        const needToSkip = skippedWarnings.reduce((a,  b) => a || arg.includes(b), false);53        if(needToSkip || arg === 'Normal connection closure')54          return;55      }56      printer(...args);57    };5859    console.error = outFn(this.consoleErr.bind(console));60    console.log = outFn(this.consoleLog.bind(console));61    console.warn = outFn(this.consoleWarn.bind(console));62  }6364  disable() {65    console.error = this.consoleErr;66    console.log = this.consoleLog;67    console.warn = this.consoleWarn;68  }69}7071export interface IEventHelper {72  section(): string;7374  method(): string;7576  wrapEvent(data: any[]): any;77}7879// eslint-disable-next-line @typescript-eslint/naming-convention80function EventHelper(section: string, method: string, wrapEvent: (data: any[]) => any) {81  const helperClass = class implements IEventHelper {82    wrapEvent: (data: any[]) => any;83    _section: string;84    _method: string;8586    constructor() {87      this.wrapEvent = wrapEvent;88      this._section = section;89      this._method = method;90    }9192    section(): string {93      return this._section;94    }9596    method(): string {97      return this._method;98    }99100    filter(txres: ITransactionResult) {101      return txres.result.events.filter(e => e.event.section === section && e.event.method === method)102        .map(e => this.wrapEvent(e.event.data));103    }104105    find(txres: ITransactionResult) {106      const e = txres.result.events.find(e => e.event.section === section && e.event.method === method);107      return e ? this.wrapEvent(e.event.data) : null;108    }109110    expect(txres: ITransactionResult) {111      const e = this.find(txres);112      if(e) {113        return e;114      } else {115        throw Error(`Expected event ${section}.${method}`);116      }117    }118  };119120  return helperClass;121}122123function eventJsonData<T = any>(data: any[], index: number) {124  return data[index].toJSON() as T;125}126127function eventHumanData(data: any[], index: number) {128  return data[index].toHuman();129}130131function eventData<T = any>(data: any[], index: number) {132  return data[index] as T;133}134135// eslint-disable-next-line @typescript-eslint/naming-convention136function EventSection(section: string) {137  return class Section {138    static section = section;139140    static Method(name: string, wrapEvent: (data: any[]) => any = () => {}) {141      const helperClass = EventHelper(Section.section, name, wrapEvent);142      return new helperClass();143    }144  };145}146147function schedulerSection(schedulerInstance: string) {148  return class extends EventSection(schedulerInstance) {149    static Dispatched = this.Method('Dispatched', data => ({150      task: eventJsonData(data, 0),151      id: eventHumanData(data, 1),152      result: data[2],153    }));154155    static PriorityChanged = this.Method('PriorityChanged', data => ({156      task: eventJsonData(data, 0),157      priority: eventJsonData(data, 1),158    }));159  };160}161162export class Event {163  static Democracy = class extends EventSection('democracy') {164    static Proposed = this.Method('Proposed', data => ({165      proposalIndex: eventJsonData<number>(data, 0),166    }));167168    static ExternalTabled = this.Method('ExternalTabled');169170    static Started = this.Method('Started', data => ({171      referendumIndex: eventJsonData<number>(data, 0),172      threshold: eventHumanData(data, 1),173    }));174175    static Voted = this.Method('Voted', data => ({176      voter: eventJsonData(data, 0),177      referendumIndex: eventJsonData<number>(data, 1),178      vote: eventJsonData(data, 2),179    }));180181    static Passed = this.Method('Passed', data => ({182      referendumIndex: eventJsonData<number>(data, 0),183    }));184185    static ProposalCanceled = this.Method('ProposalCanceled', data => ({186      propIndex: eventJsonData<number>(data, 0),187    }));188189    static Cancelled = this.Method('Cancelled', data => ({190      propIndex: eventJsonData<number>(data, 0),191    }));192193    static Vetoed = this.Method('Vetoed', data => ({194      who: eventHumanData(data, 0),195      proposalHash: eventHumanData(data, 1),196      until: eventJsonData<number>(data, 1),197    }));198  };199200  static Council = class extends EventSection('council') {201    static Proposed = this.Method('Proposed', data => ({202      account: eventHumanData(data, 0),203      proposalIndex: eventJsonData<number>(data, 1),204      proposalHash: eventHumanData(data, 2),205      threshold: eventJsonData<number>(data, 3),206    }));207    static Closed = this.Method('Closed', data => ({208      proposalHash: eventHumanData(data, 0),209      yes: eventJsonData<number>(data, 1),210      no: eventJsonData<number>(data, 2),211    }));212    static Executed = this.Method('Executed', data => ({213      proposalHash: eventHumanData(data, 0),214    }));215  };216217  static TechnicalCommittee = class extends EventSection('technicalCommittee') {218    static Proposed = this.Method('Proposed', data => ({219      account: eventHumanData(data, 0),220      proposalIndex: eventJsonData<number>(data, 1),221      proposalHash: eventHumanData(data, 2),222      threshold: eventJsonData<number>(data, 3),223    }));224    static Closed = this.Method('Closed', data => ({225      proposalHash: eventHumanData(data, 0),226      yes: eventJsonData<number>(data, 1),227      no: eventJsonData<number>(data, 2),228    }));229    static Approved = this.Method('Approved', data => ({230      proposalHash: eventHumanData(data, 0),231    }));232    static Executed = this.Method('Executed', data => ({233      proposalHash: eventHumanData(data, 0),234      result: eventHumanData(data, 1),235    }));236  };237238  static FellowshipReferenda = class extends EventSection('fellowshipReferenda') {239    static Submitted = this.Method('Submitted', data => ({240      referendumIndex: eventJsonData<number>(data, 0),241      trackId: eventJsonData<number>(data, 1),242      proposal: eventJsonData(data, 2),243    }));244245    static Cancelled = this.Method('Cancelled', data => ({246      index: eventJsonData<number>(data, 0),247      tally: eventJsonData(data, 1),248    }));249  };250251  static UniqueScheduler = schedulerSection('uniqueScheduler');252  static Scheduler = schedulerSection('scheduler');253254  static XcmpQueue = class extends EventSection('xcmpQueue') {255    static XcmpMessageSent = this.Method('XcmpMessageSent', data => ({256      messageHash: eventJsonData(data, 0),257    }));258259    static Success = this.Method('Success', data => ({260      messageHash: eventJsonData(data, 0),261    }));262263    static Fail = this.Method('Fail', data => ({264      messageHash: eventJsonData(data, 0),265      outcome: eventData<StagingXcmV2TraitsError>(data, 2),266    }));267  };268269  static DmpQueue = class extends EventSection('dmpQueue') {270    static ExecutedDownward = this.Method('ExecutedDownward', data => ({271      outcome: eventData<StagingXcmV3TraitsOutcome>(data, 2),272    }));273  };274}275276// eslint-disable-next-line @typescript-eslint/naming-convention277export function SudoHelper<T extends ChainHelperBaseConstructor>(Base: T) {278  return class extends Base {279    constructor(...args: any[]) {280      super(...args);281    }282283    override async executeExtrinsic(284      sender: IKeyringPair,285      extrinsic: string,286      params: any[],287      expectSuccess?: boolean,288      options: Partial<SignerOptions> | null = null,289    ): Promise<ITransactionResult> {290      const call = this.constructApiCall(extrinsic, params);291      const result = await super.executeExtrinsic(292        sender,293        'api.tx.sudo.sudo',294        [call],295        expectSuccess,296        options,297      );298299      if(result.status === 'Fail') return result;300301      const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult;302      if(data.isErr) {303        if(data.asErr.isModule) {304          const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule;305          const metaError = super.getApi()?.registry.findMetaError(error);306          throw new Error(`${metaError.section}.${metaError.name}`);307        } else if(data.asErr.isToken) {308          throw new Error(`Token: ${data.asErr.asToken}`);309        }310        // May be [object Object] in case of unhandled non-unit enum311        throw new Error(`Misc: ${data.asErr.toHuman()}`);312      }313      return result;314    }315    override async executeExtrinsicUncheckedWeight(316      sender: IKeyringPair,317      extrinsic: string,318      params: any[],319      expectSuccess?: boolean,320      options: Partial<SignerOptions> | null = null,321    ): Promise<ITransactionResult> {322      const call = this.constructApiCall(extrinsic, params);323      const result = await super.executeExtrinsic(324        sender,325        'api.tx.sudo.sudoUncheckedWeight',326        [call, {refTime: 0, proofSize: 0}],327        expectSuccess,328        options,329      );330331      if(result.status === 'Fail') return result;332333      const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult;334      if(data.isErr) {335        if(data.asErr.isModule) {336          const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule;337          const metaError = super.getApi()?.registry.findMetaError(error);338          throw new Error(`${metaError.section}.${metaError.name}`);339        } else if(data.asErr.isToken) {340          throw new Error(`Token: ${data.asErr.asToken}`);341        }342        // May be [object Object] in case of unhandled non-unit enum343        throw new Error(`Misc: ${data.asErr.toHuman()}`);344      }345      return result;346    }347  };348}349350class SchedulerGroup extends HelperGroup<UniqueHelper> {351  constructor(helper: UniqueHelper) {352    super(helper);353  }354355  cancelScheduled(signer: TSigner, scheduledId: string) {356    return this.helper.executeExtrinsic(357      signer,358      'api.tx.scheduler.cancelNamed',359      [scheduledId],360      true,361    );362  }363364  changePriority(signer: TSigner, scheduledId: string, priority: number) {365    return this.helper.executeExtrinsic(366      signer,367      'api.tx.scheduler.changeNamedPriority',368      [scheduledId, priority],369      true,370    );371  }372373  scheduleAt<T extends DevUniqueHelper>(374    executionBlockNumber: number,375    options: ISchedulerOptions = {},376  ) {377    return this.schedule<T>('schedule', executionBlockNumber, options);378  }379380  scheduleAfter<T extends DevUniqueHelper>(381    blocksBeforeExecution: number,382    options: ISchedulerOptions = {},383  ) {384    return this.schedule<T>('scheduleAfter', blocksBeforeExecution, options);385  }386387  schedule<T extends UniqueHelper>(388    scheduleFn: 'schedule' | 'scheduleAfter',389    blocksNum: number,390    options: ISchedulerOptions = {},391  ) {392    // eslint-disable-next-line @typescript-eslint/naming-convention393    const ScheduledHelperType = ScheduledUniqueHelper(this.helper.helperBase);394    return this.helper.clone(ScheduledHelperType, {395      scheduleFn,396      blocksNum,397      options,398    }) as T;399  }400}401402class CollatorSelectionGroup extends HelperGroup<UniqueHelper> {403  //todo:collator documentation404  addInvulnerable(signer: TSigner, address: string) {405    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.addInvulnerable', [address]);406  }407408  removeInvulnerable(signer: TSigner, address: string) {409    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.removeInvulnerable', [address]);410  }411412  async getInvulnerables(): Promise<string[]> {413    return (await this.helper.callRpc('api.query.collatorSelection.invulnerables')).map((x: any) => x.toHuman());414  }415416  /** and also total max invulnerables */417  maxCollators(): number {418    return (this.helper.getApi().consts.configuration.defaultCollatorSelectionMaxCollators.toJSON() as number);419  }420421  async getDesiredCollators(): Promise<number> {422    return (await this.helper.callRpc('api.query.configuration.collatorSelectionDesiredCollatorsOverride')).toNumber();423  }424425  setLicenseBond(signer: TSigner, amount: bigint) {426    return this.helper.executeExtrinsic(signer, 'api.tx.configuration.setCollatorSelectionLicenseBond', [amount]);427  }428429  async getLicenseBond(): Promise<bigint> {430    return (await this.helper.callRpc('api.query.configuration.collatorSelectionLicenseBondOverride')).toBigInt();431  }432433  obtainLicense(signer: TSigner) {434    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.getLicense', []);435  }436437  releaseLicense(signer: TSigner) {438    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.releaseLicense', []);439  }440441  forceReleaseLicense(signer: TSigner, released: string) {442    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.forceReleaseLicense', [released]);443  }444445  async hasLicense(address: string): Promise<bigint> {446    return (await this.helper.callRpc('api.query.collatorSelection.licenseDepositOf', [address])).toBigInt();447  }448449  onboard(signer: TSigner) {450    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.onboard', []);451  }452453  offboard(signer: TSigner) {454    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.offboard', []);455  }456457  async getCandidates(): Promise<string[]> {458    return (await this.helper.callRpc('api.query.collatorSelection.candidates')).map((x: any) => x.toHuman());459  }460}461462export class DevUniqueHelper extends UniqueHelper {463  /**464   * Arrange methods for tests465   */466  arrange: ArrangeGroup;467  wait: WaitGroup;468  admin: AdminGroup;469  session: SessionGroup;470  testUtils: TestUtilGroup;471  foreignAssets: ForeignAssetsGroup;472  xcm: XcmGroup<DevUniqueHelper>;473  xTokens: XTokensGroup<DevUniqueHelper>;474  tokens: TokensGroup<DevUniqueHelper>;475  scheduler: SchedulerGroup;476  collatorSelection: CollatorSelectionGroup;477  council: ICollectiveGroup;478  technicalCommittee: ICollectiveGroup;479  fellowship: IFellowshipGroup;480  democracy: DemocracyGroup;481482  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {483    options.helperBase = options.helperBase ?? DevUniqueHelper;484485    super(logger, options);486    this.arrange = new ArrangeGroup(this);487    this.wait = new WaitGroup(this);488    this.admin = new AdminGroup(this);489    this.testUtils = new TestUtilGroup(this);490    this.session = new SessionGroup(this);491    this.foreignAssets = new ForeignAssetsGroup(this);492    this.xcm = new XcmGroup(this, 'polkadotXcm');493    this.xTokens = new XTokensGroup(this);494    this.tokens = new TokensGroup(this);495    this.scheduler = new SchedulerGroup(this);496    this.collatorSelection = new CollatorSelectionGroup(this);497    this.council = {498      collective: new CollectiveGroup(this, 'council'),499      membership: new CollectiveMembershipGroup(this, 'councilMembership'),500    };501    this.technicalCommittee = {502      collective: new CollectiveGroup(this, 'technicalCommittee'),503      membership: new CollectiveMembershipGroup(this, 'technicalCommitteeMembership'),504    };505    this.fellowship = {506      collective: new RankedCollectiveGroup(this, 'fellowshipCollective'),507      referenda: new ReferendaGroup(this, 'fellowshipReferenda'),508    };509    this.democracy = new DemocracyGroup(this);510  }511512  override async connect(wsEndpoint: string, _listeners?: any): Promise<void> {513    if(!wsEndpoint) throw new Error('wsEndpoint was not set');514    const wsProvider = new WsProvider(wsEndpoint);515    this.api = new ApiPromise({516      provider: wsProvider,517      signedExtensions: {518        ContractHelpers: {519          extrinsic: {},520          payload: {},521        },522        CheckMaintenance: {523          extrinsic: {},524          payload: {},525        },526        DisableIdentityCalls: {527          extrinsic: {},528          payload: {},529        },530        FakeTransactionFinalizer: {531          extrinsic: {},532          payload: {},533        },534      },535      rpc: {536        unique: defs.unique.rpc,537        appPromotion: defs.appPromotion.rpc,538        povinfo: defs.povinfo.rpc,539        eth: {540          feeHistory: {541            description: 'Dummy',542            params: [],543            type: 'u8',544          },545          maxPriorityFeePerGas: {546            description: 'Dummy',547            params: [],548            type: 'u8',549          },550        },551      },552    });553    await this.api.isReadyOrError;554    this.network = await UniqueHelper.detectNetwork(this.api);555    this.wsEndpoint = wsEndpoint;556  }557  getSudo<T extends DevUniqueHelper>() {558    // eslint-disable-next-line @typescript-eslint/naming-convention559    const SudoHelperType = SudoHelper(this.helperBase);560    return this.clone(SudoHelperType) as T;561  }562}563564export class DevRelayHelper extends RelayHelper {565  wait: WaitGroup;566567  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {568    options.helperBase = options.helperBase ?? DevRelayHelper;569570    super(logger, options);571    this.wait = new WaitGroup(this);572  }573574  getSudo() {575    // eslint-disable-next-line @typescript-eslint/naming-convention576    const SudoHelperType = SudoHelper(this.helperBase);577    return this.clone(SudoHelperType) as DevRelayHelper;578  }579}580581export class DevWestmintHelper extends WestmintHelper {582  wait: WaitGroup;583584  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {585    options.helperBase = options.helperBase ?? DevWestmintHelper;586587    super(logger, options);588    this.wait = new WaitGroup(this);589  }590}591592export class DevStatemineHelper extends DevWestmintHelper {}593594export class DevStatemintHelper extends DevWestmintHelper {}595596export class DevMoonbeamHelper extends MoonbeamHelper {597  account: MoonbeamAccountGroup;598  wait: WaitGroup;599  fastDemocracy: MoonbeamFastDemocracyGroup;600601  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {602    options.helperBase = options.helperBase ?? DevMoonbeamHelper;603    options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';604605    super(logger, options);606    this.account = new MoonbeamAccountGroup(this);607    this.wait = new WaitGroup(this);608    this.fastDemocracy = new MoonbeamFastDemocracyGroup(this);609  }610}611612export class DevMoonriverHelper extends DevMoonbeamHelper {613  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {614    options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';615    super(logger, options);616  }617}618619export class DevAstarHelper extends AstarHelper {620  wait: WaitGroup;621622  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {623    options.helperBase = options.helperBase ?? DevAstarHelper;624625    super(logger, options);626    this.wait = new WaitGroup(this);627  }628629  getSudo<T extends AstarHelper>() {630    // eslint-disable-next-line @typescript-eslint/naming-convention631    const SudoHelperType = SudoHelper(this.helperBase);632    return this.clone(SudoHelperType) as T;633  }634}635636export class DevShidenHelper extends DevAstarHelper { }637638export class DevAcalaHelper extends AcalaHelper {639  wait: WaitGroup;640641  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {642    options.helperBase = options.helperBase ?? DevAcalaHelper;643644    super(logger, options);645    this.wait = new WaitGroup(this);646  }647  getSudo() {648    // eslint-disable-next-line @typescript-eslint/naming-convention649    const SudoHelperType = SudoHelper(this.helperBase);650    return this.clone(SudoHelperType) as DevAcalaHelper;651  }652}653654export class DevPolkadexHelper extends PolkadexHelper {655  wait: WaitGroup;656  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {657    options.helperBase = options.helperBase ?? PolkadexHelper;658659    super(logger, options);660    this.wait = new WaitGroup(this);661  }662663  getSudo() {664    // eslint-disable-next-line @typescript-eslint/naming-convention665    const SudoHelperType = SudoHelper(this.helperBase);666    return this.clone(SudoHelperType) as DevPolkadexHelper;667  }668}669670export class DevKaruraHelper extends DevAcalaHelper {}671672export class ArrangeGroup {673  helper: DevUniqueHelper;674675  scheduledIdSlider = 0;676677  constructor(helper: DevUniqueHelper) {678    this.helper = helper;679  }680681  /**682   * Generates accounts with the specified UNQ token balance683   * @param balances balances for generated accounts. Each balance will be multiplied by the token nominal.684   * @param donor donor account for balances685   * @returns array of newly created accounts686   * @example const [acc1, acc2, acc3] = await createAccounts([0n, 10n, 20n], donor);687   */688  createAccounts = async (balances: bigint[], donor: IKeyringPair): Promise<IKeyringPair[]> => {689    let nonce = await this.helper.chain.getNonce(donor.address);690    const wait = new WaitGroup(this.helper);691    const ss58Format = this.helper.chain.getChainProperties().ss58Format;692    const tokenNominal = this.helper.balance.getOneTokenNominal();693    const transactions = [];694    const accounts: IKeyringPair[] = [];695    for(const balance of balances) {696      const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);697      accounts.push(recipient);698      if(balance !== 0n) {699        const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, balance * tokenNominal]);700        transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));701        nonce++;702      }703    }704705    await Promise.all(transactions).catch(_e => {});706707    //#region TODO remove this region, when nonce problem will be solved708    const checkBalances = async () => {709      let isSuccess = true;710      for(let i = 0; i < balances.length; i++) {711        const balance = await this.helper.balance.getSubstrate(accounts[i].address);712        if(balance !== balances[i] * tokenNominal) {713          isSuccess = false;714          break;715        }716      }717      return isSuccess;718    };719720    let accountsCreated = false;721    const maxBlocksChecked = await this.helper.arrange.isDevNode() ? 50 : 5;722    // checkBalances retry up to 5-50 blocks723    for(let index = 0; index < maxBlocksChecked; index++) {724      accountsCreated = await checkBalances();725      if(accountsCreated) break;726      await wait.newBlocks(1);727    }728729    if(!accountsCreated) throw Error('Accounts generation failed');730    //#endregion731732    return accounts;733  };734735  // TODO combine this method and createAccounts into one736  createCrowd = async (accountsToCreate: number, withBalance: bigint, donor: IKeyringPair): Promise<IKeyringPair[]> => {737    const createAsManyAsCan = async () => {738      let transactions: any = [];739      const accounts: IKeyringPair[] = [];740      let nonce = await this.helper.chain.getNonce(donor.address);741      const tokenNominal = this.helper.balance.getOneTokenNominal();742      const ss58Format = this.helper.chain.getChainProperties().ss58Format;743      for(let i = 0; i < accountsToCreate; i++) {744        if(i === 500) { // if there are too many accounts to create745          await Promise.allSettled(transactions); // wait while first 500 (should be 100 for devnode) tx will be settled746          transactions = []; //747          nonce = await this.helper.chain.getNonce(donor.address); // update nonce748        }749        const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);750        accounts.push(recipient);751        if(withBalance !== 0n) {752          const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, withBalance * tokenNominal]);753          transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));754          nonce++;755        }756      }757758      const fullfilledAccounts = [];759      await Promise.allSettled(transactions);760      for(const account of accounts) {761        const accountBalance = await this.helper.balance.getSubstrate(account.address);762        if(accountBalance === withBalance * tokenNominal) {763          fullfilledAccounts.push(account);764        }765      }766      return fullfilledAccounts;767    };768769770    const crowd: IKeyringPair[] = [];771    // do up to 5 retries772    for(let index = 0; index < 5 && accountsToCreate !== 0; index++) {773      const asManyAsCan = await createAsManyAsCan();774      crowd.push(...asManyAsCan);775      accountsToCreate -= asManyAsCan.length;776    }777778    if(accountsToCreate !== 0) throw Error(`Crowd generation failed: ${accountsToCreate} accounts left`);779780    return crowd;781  };782783  /**784   * Generates one account with zero balance785   * @returns the newly generated account786   * @example const account = await helper.arrange.createEmptyAccount();787   */788  createEmptyAccount = (): IKeyringPair => {789    const ss58Format = this.helper.chain.getChainProperties().ss58Format;790    return this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);791  };792793  isDevNode = async () => {794    let blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();795    if(blockNumber == 0) {796      await this.helper.wait.newBlocks(1);797      blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();798    }799    const block2 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])]);800    const block1 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber - 1])]);801    const findCreationDate = (block: any) => {802      const humanBlock = block.toHuman();803      let date;804      humanBlock.block.extrinsics.forEach((ext: any) => {805        if(ext.method.section === 'timestamp') {806          date = Number(ext.method.args.now.replaceAll(',', ''));807        }808      });809      return date;810    };811    const block1date = await findCreationDate(block1);812    const block2date = await findCreationDate(block2);813    if(block2date! - block1date! < 9000) return true;814    return false;815  };816817  async calculcateFee(payer: ICrossAccountId, promise: () => Promise<any>): Promise<bigint> {818    const address = 'Substrate' in payer ? payer.Substrate : this.helper.address.ethToSubstrate(payer.Ethereum);819    let balance = await this.helper.balance.getSubstrate(address);820821    await promise();822823    balance -= await this.helper.balance.getSubstrate(address);824825    return balance;826  }827828  async calculatePoVInfo(txs: any[]): Promise<IPovInfo> {829    const rawPovInfo = await this.helper.callRpc('api.rpc.povinfo.estimateExtrinsicPoV', [txs]);830831    const kvJson: {[key: string]: string} = {};832833    for(const kv of rawPovInfo.keyValues) {834      kvJson[kv.key.toHex()] = kv.value.toHex();835    }836837    const kvStr = JSON.stringify(kvJson);838839    const chainql = spawnSync(840      'chainql',841      [842        `--tla-code=data=${kvStr}`,843        '-e', `function(data) cql.dump(cql.chain("${this.helper.getEndpoint()}").latest._meta, data, {omit_empty:true})`,844      ],845    );846847    if(!chainql.stdout) {848      throw Error('unable to get an output from the `chainql`');849    }850851    return {852      proofSize: rawPovInfo.proofSize.toNumber(),853      compactProofSize: rawPovInfo.compactProofSize.toNumber(),854      compressedProofSize: rawPovInfo.compressedProofSize.toNumber(),855      results: rawPovInfo.results,856      kv: JSON.parse(chainql.stdout.toString()),857    };858  }859860  calculatePalletAddress(palletId: any) {861    const address = stringToU8a(('modl' + palletId).padEnd(32, '\0'));862    return encodeAddress(address, this.helper.chain.getChainProperties().ss58Format);863  }864865  makeScheduledIds(num: number): string[] {866    function makeId(slider: number) {867      const scheduledIdSize = 64;868      const hexId = slider.toString(16);869      const prefixSize = scheduledIdSize - hexId.length;870871      const scheduledId = '0x' + '0'.repeat(prefixSize) + hexId;872873      return scheduledId;874    }875876    const ids = [];877    for(let i = 0; i < num; i++) {878      ids.push(makeId(this.scheduledIdSlider));879      this.scheduledIdSlider += 1;880    }881882    return ids;883  }884885  makeScheduledId(): string {886    return (this.makeScheduledIds(1))[0];887  }888889  async captureEvents(eventSection: string, eventMethod: string): Promise<EventCapture> {890    const capture = new EventCapture(this.helper, eventSection, eventMethod);891    await capture.startCapture();892893    return capture;894  }895896  makeXcmProgramWithdrawDeposit(beneficiary: Uint8Array, id: any, amount: bigint) {897    return {898      V2: [899        {900          WithdrawAsset: [901            {902              id,903              fun: {904                Fungible: amount,905              },906            },907          ],908        },909        {910          BuyExecution: {911            fees: {912              id,913              fun: {914                Fungible: amount,915              },916            },917            weightLimit: 'Unlimited',918          },919        },920        {921          DepositAsset: {922            assets: {923              Wild: 'All',924            },925            maxAssets: 1,926            beneficiary: {927              parents: 0,928              interior: {929                X1: {930                  AccountId32: {931                    network: 'Any',932                    id: beneficiary,933                  },934                },935              },936            },937          },938        },939      ],940    };941  }942943  makeXcmProgramReserveAssetDeposited(beneficiary: Uint8Array, id: any, amount: bigint) {944    return {945      V2: [946        {947          ReserveAssetDeposited: [948            {949              id,950              fun: {951                Fungible: amount,952              },953            },954          ],955        },956        {957          BuyExecution: {958            fees: {959              id,960              fun: {961                Fungible: amount,962              },963            },964            weightLimit: 'Unlimited',965          },966        },967        {968          DepositAsset: {969            assets: {970              Wild: 'All',971            },972            maxAssets: 1,973            beneficiary: {974              parents: 0,975              interior: {976                X1: {977                  AccountId32: {978                    network: 'Any',979                    id: beneficiary,980                  },981                },982              },983            },984          },985        },986      ],987    };988  }989990  makeUnpaidSudoTransactProgram(info: {weightMultiplier: number, call: string}) {991    return {992      V3: [993        {994          UnpaidExecution: {995            weightLimit: 'Unlimited',996            checkOrigin: null,997          },998        },999        {1000          Transact: {1001            originKind: 'Superuser',1002            requireWeightAtMost: {1003              refTime: info.weightMultiplier * 200000000,1004              proofSize: info.weightMultiplier * 3000,1005            },1006            call: {1007              encoded: info.call,1008            },1009          },1010        },1011      ],1012    };1013  }1014}10151016class MoonbeamAccountGroup {1017  helper: MoonbeamHelper;10181019  keyring: Keyring;1020  _alithAccount: IKeyringPair;1021  _baltatharAccount: IKeyringPair;1022  _dorothyAccount: IKeyringPair;10231024  constructor(helper: MoonbeamHelper) {1025    this.helper = helper;10261027    this.keyring = new Keyring({type: 'ethereum'});1028    const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';1029    const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';1030    const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';10311032    this._alithAccount = this.keyring.addFromUri(alithPrivateKey, undefined, 'ethereum');1033    this._baltatharAccount = this.keyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');1034    this._dorothyAccount = this.keyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');1035  }10361037  alithAccount() {1038    return this._alithAccount;1039  }10401041  baltatharAccount() {1042    return this._baltatharAccount;1043  }10441045  dorothyAccount() {1046    return this._dorothyAccount;1047  }10481049  create() {1050    return this.keyring.addFromUri(mnemonicGenerate());1051  }1052}10531054class MoonbeamFastDemocracyGroup {1055  helper: DevMoonbeamHelper;10561057  constructor(helper: DevMoonbeamHelper) {1058    this.helper = helper;1059  }10601061  async executeProposal(proposalDesciption: string, encodedProposal: string) {1062    const proposalHash = blake2AsHex(encodedProposal);10631064    const alithAccount = this.helper.account.alithAccount();1065    const baltatharAccount = this.helper.account.baltatharAccount();1066    const dorothyAccount = this.helper.account.dorothyAccount();10671068    const councilVotingThreshold = 2;1069    const technicalCommitteeThreshold = 2;1070    const fastTrackVotingPeriod = 3;1071    const fastTrackDelayPeriod = 0;10721073    console.log(`[democracy] executing '${proposalDesciption}' proposal`);10741075    // >>> Propose external motion through council >>>1076    console.log('\t* Propose external motion through council.......');1077    const externalMotion = this.helper.democracy.externalProposeMajority({Inline: encodedProposal});1078    const encodedMotion = externalMotion?.method.toHex() || '';1079    const motionHash = blake2AsHex(encodedMotion);1080    console.log('\t* Motion hash is %s', motionHash);10811082    await this.helper.collective.council.propose(1083      baltatharAccount,1084      councilVotingThreshold,1085      externalMotion,1086      externalMotion.encodedLength,1087    );10881089    const councilProposalIdx = await this.helper.collective.council.proposalCount() - 1;1090    await this.helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);1091    await this.helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);10921093    await this.helper.collective.council.close(1094      dorothyAccount,1095      motionHash,1096      councilProposalIdx,1097      {1098        refTime: 1_000_000_000,1099        proofSize: 1_000_000,1100      },1101      externalMotion.encodedLength,1102    );1103    console.log('\t* Propose external motion through council.......DONE');1104    // <<< Propose external motion through council <<<11051106    // >>> Fast track proposal through technical committee >>>1107    console.log('\t* Fast track proposal through technical committee.......');1108    const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod);1109    const encodedFastTrack = fastTrack?.method.toHex() || '';1110    const fastTrackHash = blake2AsHex(encodedFastTrack);1111    console.log('\t* FastTrack hash is %s', fastTrackHash);11121113    await this.helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);11141115    const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1;1116    await this.helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);1117    await this.helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);11181119    await this.helper.collective.techCommittee.close(1120      baltatharAccount,1121      fastTrackHash,1122      techProposalIdx,1123      {1124        refTime: 1_000_000_000,1125        proofSize: 1_000_000,1126      },1127      fastTrack.encodedLength,1128    );1129    console.log('\t* Fast track proposal through technical committee.......DONE');1130    // <<< Fast track proposal through technical committee <<<11311132    const democracyStarted = await this.helper.wait.expectEvent(3, Event.Democracy.Started);1133    const referendumIndex = democracyStarted.referendumIndex;11341135    // >>> Referendum voting >>>1136    console.log(`\t* Referendum #${referendumIndex} voting.......`);1137    await this.helper.democracy.referendumVote(dorothyAccount, referendumIndex, {1138      balance: 10_000_000_000_000_000_000n,1139      vote: {aye: true, conviction: 1},1140    });1141    console.log(`\t* Referendum #${referendumIndex} voting.......DONE`);1142    // <<< Referendum voting <<<11431144    // Wait the proposal to pass1145    await this.helper.wait.expectEvent(3, Event.Democracy.Passed, event => event.referendumIndex == referendumIndex);11461147    await this.helper.wait.newBlocks(1);11481149    console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`);1150  }1151}11521153class WaitGroup {1154  helper: ChainHelperBase;11551156  constructor(helper: ChainHelperBase) {1157    this.helper = helper;1158  }11591160  sleep(milliseconds: number) {1161    return new Promise((resolve) => setTimeout(resolve, milliseconds));1162  }11631164  private async waitWithTimeout(promise: Promise<any>, timeout: number) {1165    let isBlock = false;1166    promise.then(() => isBlock = true).catch(() => isBlock = true);1167    let totalTime = 0;1168    const step = 100;1169    while(!isBlock) {1170      await this.sleep(step);1171      totalTime += step;1172      if(totalTime >= timeout) throw Error('Blocks production failed');1173    }1174    return promise;1175  }11761177  /**1178   * Launch some async operation, or throw an error after some time. Note that it will still continue executing after the timeout.1179   * @param promise async operation to race against the timeout1180   * @param timeoutMS time after which to time out1181   * @param timeoutError error message to throw1182   * @returns promise of the same type the operation had1183   */1184  withTimeout<T>(1185    promise: Promise<T>,1186    timeoutMS = 30000,1187    timeoutError = 'The operation has timed out!',1188  ): Promise<T> {1189    const timeout = new Promise<never>((_, reject) => {1190      setTimeout(() => {1191        reject(new Error(timeoutError));1192      }, timeoutMS);1193    });11941195    return Promise.race<T>([promise, timeout]).catch(e => {throw new Error(e);});1196  }11971198  /**1199   * Wait for specified number of blocks1200   * @param blocksCount number of blocks to wait1201   * @returns1202   */1203  async newBlocks(blocksCount = 1, timeout?: number): Promise<void> {1204    timeout = timeout ?? blocksCount * 60_000;1205    // eslint-disable-next-line no-async-promise-executor1206    const promise = new Promise<void>(async (resolve) => {1207      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(() => {1208        if(blocksCount > 0) {1209          blocksCount--;1210        } else {1211          unsubscribe();1212          resolve();1213        }1214      });1215    });1216    await this.waitWithTimeout(promise, timeout);1217    return promise;1218  }12191220  /**1221   * Wait for the specified number of sessions to pass.1222   * Only applicable if the Session pallet is turned on.1223   * @param sessionCount number of sessions to wait1224   * @param blockTimeout time in ms until panicking that the chain has stopped producing blocks1225   * @returns1226   */1227  async newSessions(sessionCount = 1, blockTimeout = 60000): Promise<void> {1228    console.log(`Waiting for ${sessionCount} new session${sessionCount > 1 ? 's' : ''}.`1229      + ' This might take a while -- check SessionPeriod in pallet_session::Config for session time.');12301231    const expectedSessionIndex = await (this.helper as DevUniqueHelper).session.getIndex() + sessionCount;1232    let currentSessionIndex = -1;12331234    while(currentSessionIndex < expectedSessionIndex) {1235      // eslint-disable-next-line no-async-promise-executor1236      currentSessionIndex = await this.withTimeout(new Promise(async (resolve) => {1237        await this.newBlocks(1);1238        const res = await (this.helper as DevUniqueHelper).session.getIndex();1239        resolve(res);1240      }), blockTimeout, 'The chain has stopped producing blocks!');1241    }1242  }12431244  async forParachainBlockNumber(blockNumber: bigint | number, timeout?: number) {1245    timeout = timeout ?? 30 * 60 * 1000;1246    // eslint-disable-next-line no-async-promise-executor1247    const promise = new Promise<void>(async (resolve) => {1248      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {1249        if(data.number.toNumber() >= blockNumber) {1250          unsubscribe();1251          resolve();1252        }1253      });1254    });1255    await this.waitWithTimeout(promise, timeout);1256    return promise;1257  }12581259  async forRelayBlockNumber(blockNumber: bigint | number, timeout?: number) {1260    timeout = timeout ?? 30 * 60 * 1000;1261    // eslint-disable-next-line no-async-promise-executor1262    const promise = new Promise<void>(async (resolve) => {1263      const unsubscribe = await this.helper.getApi().query.parachainSystem.validationData((data: any) => {1264        if(data.value.relayParentNumber.toNumber() >= blockNumber) {1265          // @ts-ignore1266          unsubscribe();1267          resolve();1268        }1269      });1270    });1271    await this.waitWithTimeout(promise, timeout);1272    return promise;1273  }12741275  noScheduledTasks() {1276    const api = this.helper.getApi();12771278    // eslint-disable-next-line no-async-promise-executor1279    const promise = new Promise<void>(async resolve => {1280      const unsubscribe = await api.rpc.chain.subscribeNewHeads(async () => {1281        const areThereScheduledTasks = await api.query.scheduler.lookup.entries();12821283        if(areThereScheduledTasks.length == 0) {1284          unsubscribe();1285          resolve();1286        }1287      });1288    });12891290    return promise;1291  }12921293  parachainBlockMultiplesOf(val: bigint) {1294    // eslint-disable-next-line no-async-promise-executor1295    const promise = new Promise<void>(async resolve => {1296      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {1297        if(data.number.toBigInt() % val == 0n) {1298          console.log(`from waiter: ${data.number.toBigInt()}`);1299          unsubscribe();1300          resolve();1301        }1302      });1303    });1304    return promise;1305  }13061307  event<T extends IEventHelper>(1308    maxBlocksToWait: number,1309    eventHelper: T,1310    filter: (_: any) => boolean = () => true,1311  ): any {1312    // eslint-disable-next-line no-async-promise-executor1313    const promise = new Promise<T | null>(async (resolve) => {1314      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => {1315        const blockNumber = header.number.toJSON();1316        const blockHash = header.hash;1317        const eventIdStr = `${eventHelper.section()}.${eventHelper.method()}`;1318        const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;13191320        this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);13211322        const apiAt = await this.helper.getApi().at(blockHash);1323        const eventRecords = (await apiAt.query.system.events()) as any;13241325        const neededEvent = eventRecords.toArray()1326          .filter((r: FrameSystemEventRecord) => r.event.section == eventHelper.section() && r.event.method == eventHelper.method())1327          .map((r: FrameSystemEventRecord) => eventHelper.wrapEvent(r.event.data))1328          .find(filter);13291330        if(neededEvent) {1331          unsubscribe();1332          resolve(neededEvent);1333        } else if(maxBlocksToWait > 0) {1334          maxBlocksToWait--;1335        } else {1336          this.helper.logger.log(`Eligible event \`${eventIdStr}\` is NOT found.1337          The wait lasted until block ${blockNumber} inclusive`);1338          unsubscribe();1339          resolve(null);1340        }1341      });1342    });1343    return promise;1344  }13451346  async expectEvent<T extends IEventHelper>(1347    maxBlocksToWait: number,1348    eventHelper: T,1349    filter: (e: any) => boolean = () => true,1350  ) {1351    const e = await this.event(maxBlocksToWait, eventHelper, filter);1352    if(e == null) {1353      throw Error(`The event '${eventHelper.section()}.${eventHelper.method()}' is expected`);1354    } else {1355      return e;1356    }1357  }1358}13591360class SessionGroup {1361  helper: ChainHelperBase;13621363  constructor(helper: ChainHelperBase) {1364    this.helper = helper;1365  }13661367  //todo:collator documentation1368  async getIndex(): Promise<number> {1369    return (await this.helper.callRpc('api.query.session.currentIndex', [])).toNumber();1370  }13711372  newSessions(sessionCount = 1, blockTimeout = 24000): Promise<void> {1373    return (this.helper as DevUniqueHelper).wait.newSessions(sessionCount, blockTimeout);1374  }13751376  setOwnKeys(signer: TSigner, key: string) {1377    return this.helper.executeExtrinsic(1378      signer,1379      'api.tx.session.setKeys',1380      [key, '0x0'],1381      true,1382    );1383  }13841385  setOwnKeysFromAddress(signer: TSigner) {1386    return this.setOwnKeys(signer, '0x' + Buffer.from(signer.addressRaw).toString('hex'));1387  }1388}13891390class TestUtilGroup {1391  helper: DevUniqueHelper;13921393  constructor(helper: DevUniqueHelper) {1394    this.helper = helper;1395  }13961397  async enable(testUtilsPalletName: string) {1398    if(this.helper.fetchMissingPalletNames([testUtilsPalletName]).length != 0) {1399      return;1400    }14011402    const signer = this.helper.util.fromSeed('//Alice');1403    await this.helper.getSudo<DevUniqueHelper>().executeExtrinsic(signer, 'api.tx.testUtils.enable', [], true);1404  }14051406  async setTestValue(signer: TSigner, testVal: number) {1407    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValue', [testVal], true);1408  }14091410  async incTestValue(signer: TSigner) {1411    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.incTestValue', [], true);1412  }14131414  async setTestValueAndRollback(signer: TSigner, testVal: number) {1415    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValueAndRollback', [testVal], true);1416  }14171418  async testValue(blockIdx?: number) {1419    const api = blockIdx1420      ? await this.helper.getApi().at(await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockIdx]))1421      : this.helper.getApi();14221423    return (await api.query.testUtils.testValue()).toJSON();1424  }14251426  async justTakeFee(signer: TSigner) {1427    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.justTakeFee', [], true);1428  }14291430  async selfCancelingInc(signer: TSigner, scheduledId: string, maxTestVal: number) {1431    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.selfCancelingInc', [scheduledId, maxTestVal], true);1432  }1433}14341435class EventCapture {1436  helper: DevUniqueHelper;1437  eventSection: string;1438  eventMethod: string;1439  events: EventRecord[] = [];1440  unsubscribe: VoidFn | null = null;14411442  constructor(1443    helper: DevUniqueHelper,1444    eventSection: string,1445    eventMethod: string,1446  ) {1447    this.helper = helper;1448    this.eventSection = eventSection;1449    this.eventMethod = eventMethod;1450  }14511452  async startCapture() {1453    this.stopCapture();1454    this.unsubscribe = (await this.helper.getApi().query.system.events((eventRecords: FrameSystemEventRecord[]) => {1455      const newEvents = eventRecords.filter(r => r.event.section == this.eventSection && r.event.method == this.eventMethod);14561457      this.events.push(...newEvents);1458    })) as any;1459  }14601461  stopCapture() {1462    if(this.unsubscribe !== null) {1463      this.unsubscribe();1464    }1465  }14661467  extractCapturedEvents() {1468    return this.events;1469  }1470}14711472class AdminGroup {1473  helper: UniqueHelper;14741475  constructor(helper: UniqueHelper) {1476    this.helper = helper;1477  }14781479  async payoutStakers(signer: IKeyringPair, stakersToPayout: number):  Promise<{staker: string, stake: bigint, payout: bigint}[]> {1480    const payoutResult = await this.helper.executeExtrinsic(signer, 'api.tx.appPromotion.payoutStakers', [stakersToPayout], true);1481    return payoutResult.result.events.filter(e => e.event.method === 'StakingRecalculation').map(e => ({1482      staker: e.event.data[0].toString(),1483      stake: e.event.data[1].toBigInt(),1484      payout: e.event.data[2].toBigInt(),1485    }));1486  }1487}14881489// eslint-disable-next-line @typescript-eslint/naming-convention1490function ScheduledUniqueHelper<T extends UniqueHelperConstructor>(Base: T) {1491  return class extends Base {1492    scheduleFn: 'schedule' | 'scheduleAfter';1493    blocksNum: number;1494    options: ISchedulerOptions;14951496    constructor(...args: any[]) {1497      const logger = args[0] as ILogger;1498      const options = args[1] as {1499        scheduleFn: 'schedule' | 'scheduleAfter',1500        blocksNum: number,1501        options: ISchedulerOptions1502      };15031504      super(logger);15051506      this.scheduleFn = options.scheduleFn;1507      this.blocksNum = options.blocksNum;1508      this.options = options.options;1509    }15101511    override executeExtrinsic(sender: IKeyringPair, scheduledExtrinsic: string, scheduledParams: any[], expectSuccess?: boolean): Promise<ITransactionResult> {1512      const scheduledTx = this.constructApiCall(scheduledExtrinsic, scheduledParams);15131514      const mandatorySchedArgs = [1515        this.blocksNum,1516        this.options.periodic ? [this.options.periodic.period, this.options.periodic.repetitions] : null,1517        this.options.priority ?? null,1518        scheduledTx,1519      ];15201521      let schedArgs;1522      let scheduleFn;15231524      if(this.options.scheduledId) {1525        schedArgs = [this.options.scheduledId!, ...mandatorySchedArgs];15261527        if(this.scheduleFn == 'schedule') {1528          scheduleFn = 'scheduleNamed';1529        } else if(this.scheduleFn == 'scheduleAfter') {1530          scheduleFn = 'scheduleNamedAfter';1531        }1532      } else {1533        schedArgs = mandatorySchedArgs;1534        scheduleFn = this.scheduleFn;1535      }15361537      const extrinsic = 'api.tx.scheduler.' + scheduleFn;15381539      return super.executeExtrinsic(1540        sender,1541        extrinsic as any,1542        schedArgs,1543        expectSuccess,1544      );1545    }1546  };1547}