git.delta.rocks / unique-network / refs/commits / 04e6d946dd25

difftreelog

tests: move scheduler to dev, move governance to separate file

Andy Smith2023-09-14parent: #383d45a.patch.diff
in: master

7 files changed

modifiedtests/src/governance/util.tsdiffbeforeafterboth
--- a/tests/src/governance/util.ts
+++ b/tests/src/governance/util.ts
@@ -2,6 +2,7 @@
 import {xxhashAsHex} from '@polkadot/util-crypto';
 import {usingPlaygrounds, expect} from '../util';
 import {UniqueHelper} from '../util/playgrounds/unique';
+import {DevUniqueHelper} from '../util/playgrounds/unique.dev';
 
 export const democracyLaunchPeriod = 35;
 export const democracyVotingPeriod = 35;
@@ -203,7 +204,7 @@
   });
 }
 
-export async function voteUnanimouslyInFellowship(helper: UniqueHelper, fellows: IKeyringPair[][], minRank: number, referendumIndex: number) {
+export async function voteUnanimouslyInFellowship(helper: DevUniqueHelper, fellows: IKeyringPair[][], minRank: number, referendumIndex: number) {
   for(let rank = minRank; rank < fellowshipRankLimit; rank++) {
     for(const member of fellows[rank]) {
       await helper.fellowship.collective.vote(member, referendumIndex, true);
modifiedtests/src/util/playgrounds/types.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/types.ts
+++ b/tests/src/util/playgrounds/types.ts
@@ -216,13 +216,6 @@
   },
 }
 
-export interface IForeignAssetMetadata {
-  name?: number | Uint8Array,
-  symbol?: string,
-  decimals?: number,
-  minimalBalance?: bigint,
-}
-
 export interface DemocracySplitAccount {
   aye: bigint,
   nay: bigint,
modifiedtests/src/util/playgrounds/types.xcm.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/types.xcm.ts
+++ b/tests/src/util/playgrounds/types.xcm.ts
@@ -26,4 +26,11 @@
     aye: boolean,
     conviction: number,
   },
+}
+
+export interface IForeignAssetMetadata {
+  name?: number | Uint8Array,
+  symbol?: string,
+  decimals?: number,
+  minimalBalance?: bigint,
 }
\ No newline at end of file
modifiedtests/src/util/playgrounds/unique.dev.tsdiffbeforeafterboth
before · tests/src/util/playgrounds/unique.dev.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// SPDX-License-Identifier: Apache-2.034import {stringToU8a} from '@polkadot/util';5import {blake2AsHex, encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto';6import {UniqueHelper, ChainHelperBase, ChainHelperBaseConstructor} from './unique';7import {ApiPromise, Keyring, WsProvider} from '@polkadot/api';8import * as defs from '../../interfaces/definitions';9import {IKeyringPair} from '@polkadot/types/types';10import {EventRecord} from '@polkadot/types/interfaces';11import {ICrossAccountId, IPovInfo, ITransactionResult, TSigner} from './types';12import {FrameSystemEventRecord, XcmV2TraitsError} from '@polkadot/types/lookup';13import {SignerOptions, VoidFn} from '@polkadot/api/types';14import {Pallets} from '..';15import {spawnSync} from 'child_process';16import {AcalaHelper, AstarHelper, MoonbeamHelper, PolkadexHelper, RelayHelper, WestmintHelper} from './unique.xcm';1718export class SilentLogger {19  log(_msg: any, _level: any): void { }20  level = {21    ERROR: 'ERROR' as const,22    WARNING: 'WARNING' as const,23    INFO: 'INFO' as const,24  };25}2627export class SilentConsole {28  // TODO: Remove, this is temporary: Filter unneeded API output29  // (Jaco promised it will be removed in the next version)30  consoleErr: any;31  consoleLog: any;32  consoleWarn: any;3334  constructor() {35    this.consoleErr = console.error;36    this.consoleLog = console.log;37    this.consoleWarn = console.warn;38  }3940  enable() {41    const outFn = (printer: any) => (...args: any[]) => {42      for(const arg of args) {43        if(typeof arg !== 'string')44          continue;45        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'];46        const needToSkip = skippedWarnings.reduce((a,  b) => a || arg.includes(b), false);47        if(needToSkip || arg === 'Normal connection closure')48          return;49      }50      printer(...args);51    };5253    console.error = outFn(this.consoleErr.bind(console));54    console.log = outFn(this.consoleLog.bind(console));55    console.warn = outFn(this.consoleWarn.bind(console));56  }5758  disable() {59    console.error = this.consoleErr;60    console.log = this.consoleLog;61    console.warn = this.consoleWarn;62  }63}6465export interface IEventHelper {66  section(): string;6768  method(): string;6970  wrapEvent(data: any[]): any;71}7273// eslint-disable-next-line @typescript-eslint/naming-convention74function EventHelper(section: string, method: string, wrapEvent: (data: any[]) => any) {75  const helperClass = class implements IEventHelper {76    wrapEvent: (data: any[]) => any;77    _section: string;78    _method: string;7980    constructor() {81      this.wrapEvent = wrapEvent;82      this._section = section;83      this._method = method;84    }8586    section(): string {87      return this._section;88    }8990    method(): string {91      return this._method;92    }9394    filter(txres: ITransactionResult) {95      return txres.result.events.filter(e => e.event.section === section && e.event.method === method)96        .map(e => this.wrapEvent(e.event.data));97    }9899    find(txres: ITransactionResult) {100      const e = txres.result.events.find(e => e.event.section === section && e.event.method === method);101      return e ? this.wrapEvent(e.event.data) : null;102    }103104    expect(txres: ITransactionResult) {105      const e = this.find(txres);106      if(e) {107        return e;108      } else {109        throw Error(`Expected event ${section}.${method}`);110      }111    }112  };113114  return helperClass;115}116117function eventJsonData<T = any>(data: any[], index: number) {118  return data[index].toJSON() as T;119}120121function eventHumanData(data: any[], index: number) {122  return data[index].toHuman();123}124125function eventData<T = any>(data: any[], index: number) {126  return data[index] as T;127}128129// eslint-disable-next-line @typescript-eslint/naming-convention130function EventSection(section: string) {131  return class Section {132    static section = section;133134    static Method(name: string, wrapEvent: (data: any[]) => any = () => {}) {135      const helperClass = EventHelper(Section.section, name, wrapEvent);136      return new helperClass();137    }138  };139}140141function schedulerSection(schedulerInstance: string) {142  return class extends EventSection(schedulerInstance) {143    static Dispatched = this.Method('Dispatched', data => ({144      task: eventJsonData(data, 0),145      id: eventHumanData(data, 1),146      result: data[2],147    }));148149    static PriorityChanged = this.Method('PriorityChanged', data => ({150      task: eventJsonData(data, 0),151      priority: eventJsonData(data, 1),152    }));153  };154}155156export class Event {157  static Democracy = class extends EventSection('democracy') {158    static Proposed = this.Method('Proposed', data => ({159      proposalIndex: eventJsonData<number>(data, 0),160    }));161162    static ExternalTabled = this.Method('ExternalTabled');163164    static Started = this.Method('Started', data => ({165      referendumIndex: eventJsonData<number>(data, 0),166      threshold: eventHumanData(data, 1),167    }));168169    static Voted = this.Method('Voted', data => ({170      voter: eventJsonData(data, 0),171      referendumIndex: eventJsonData<number>(data, 1),172      vote: eventJsonData(data, 2),173    }));174175    static Passed = this.Method('Passed', data => ({176      referendumIndex: eventJsonData<number>(data, 0),177    }));178179    static ProposalCanceled = this.Method('ProposalCanceled', data => ({180      propIndex: eventJsonData<number>(data, 0),181    }));182183    static Cancelled = this.Method('Cancelled', data => ({184      propIndex: eventJsonData<number>(data, 0),185    }));186187    static Vetoed = this.Method('Vetoed', data => ({188      who: eventHumanData(data, 0),189      proposalHash: eventHumanData(data, 1),190      until: eventJsonData<number>(data, 1),191    }));192  };193194  static Council = class extends EventSection('council') {195    static Proposed = this.Method('Proposed', data => ({196      account: eventHumanData(data, 0),197      proposalIndex: eventJsonData<number>(data, 1),198      proposalHash: eventHumanData(data, 2),199      threshold: eventJsonData<number>(data, 3),200    }));201    static Closed = this.Method('Closed', data => ({202      proposalHash: eventHumanData(data, 0),203      yes: eventJsonData<number>(data, 1),204      no: eventJsonData<number>(data, 2),205    }));206    static Executed = this.Method('Executed', data => ({207      proposalHash: eventHumanData(data, 0),208    }));209  };210211  static TechnicalCommittee = class extends EventSection('technicalCommittee') {212    static Proposed = this.Method('Proposed', data => ({213      account: eventHumanData(data, 0),214      proposalIndex: eventJsonData<number>(data, 1),215      proposalHash: eventHumanData(data, 2),216      threshold: eventJsonData<number>(data, 3),217    }));218    static Closed = this.Method('Closed', data => ({219      proposalHash: eventHumanData(data, 0),220      yes: eventJsonData<number>(data, 1),221      no: eventJsonData<number>(data, 2),222    }));223    static Approved = this.Method('Approved', data => ({224      proposalHash: eventHumanData(data, 0),225    }));226    static Executed = this.Method('Executed', data => ({227      proposalHash: eventHumanData(data, 0),228      result: eventHumanData(data, 1),229    }));230  };231232  static FellowshipReferenda = class extends EventSection('fellowshipReferenda') {233    static Submitted = this.Method('Submitted', data => ({234      referendumIndex: eventJsonData<number>(data, 0),235      trackId: eventJsonData<number>(data, 1),236      proposal: eventJsonData(data, 2),237    }));238239    static Cancelled = this.Method('Cancelled', data => ({240      index: eventJsonData<number>(data, 0),241      tally: eventJsonData(data, 1),242    }));243  };244245  static UniqueScheduler = schedulerSection('uniqueScheduler');246  static Scheduler = schedulerSection('scheduler');247248  static XcmpQueue = class extends EventSection('xcmpQueue') {249    static XcmpMessageSent = this.Method('XcmpMessageSent', data => ({250      messageHash: eventJsonData(data, 0),251    }));252253    static Success = this.Method('Success', data => ({254      messageHash: eventJsonData(data, 0),255    }));256257    static Fail = this.Method('Fail', data => ({258      messageHash: eventJsonData(data, 0),259      outcome: eventData<XcmV2TraitsError>(data, 1),260    }));261  };262}263264// eslint-disable-next-line @typescript-eslint/naming-convention265export function SudoHelper<T extends ChainHelperBaseConstructor>(Base: T) {266  return class extends Base {267    constructor(...args: any[]) {268      super(...args);269    }270271    async executeExtrinsic(272      sender: IKeyringPair,273      extrinsic: string,274      params: any[],275      expectSuccess?: boolean,276      options: Partial<SignerOptions> | null = null,277    ): Promise<ITransactionResult> {278      const call = this.constructApiCall(extrinsic, params);279      const result = await super.executeExtrinsic(280        sender,281        'api.tx.sudo.sudo',282        [call],283        expectSuccess,284        options,285      );286287      if(result.status === 'Fail') return result;288289      const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult;290      if(data.isErr) {291        if(data.asErr.isModule) {292          const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule;293          const metaError = super.getApi()?.registry.findMetaError(error);294          throw new Error(`${metaError.section}.${metaError.name}`);295        } else if(data.asErr.isToken) {296          throw new Error(`Token: ${data.asErr.asToken}`);297        }298        // May be [object Object] in case of unhandled non-unit enum299        throw new Error(`Misc: ${data.asErr.toHuman()}`);300      }301      return result;302    }303    async executeExtrinsicUncheckedWeight(304      sender: IKeyringPair,305      extrinsic: string,306      params: any[],307      expectSuccess?: boolean,308      options: Partial<SignerOptions> | null = null,309    ): Promise<ITransactionResult> {310      const call = this.constructApiCall(extrinsic, params);311      const result = await super.executeExtrinsic(312        sender,313        'api.tx.sudo.sudoUncheckedWeight',314        [call, {refTime: 0, proofSize: 0}],315        expectSuccess,316        options,317      );318319      if(result.status === 'Fail') return result;320321      const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult;322      if(data.isErr) {323        if(data.asErr.isModule) {324          const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule;325          const metaError = super.getApi()?.registry.findMetaError(error);326          throw new Error(`${metaError.section}.${metaError.name}`);327        } else if(data.asErr.isToken) {328          throw new Error(`Token: ${data.asErr.asToken}`);329        }330        // May be [object Object] in case of unhandled non-unit enum331        throw new Error(`Misc: ${data.asErr.toHuman()}`);332      }333      return result;334    }335  };336}337338export class DevUniqueHelper extends UniqueHelper {339  /**340   * Arrange methods for tests341   */342  arrange: ArrangeGroup;343  wait: WaitGroup;344  admin: AdminGroup;345  session: SessionGroup;346  testUtils: TestUtilGroup;347348  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {349    options.helperBase = options.helperBase ?? DevUniqueHelper;350351    super(logger, options);352    this.arrange = new ArrangeGroup(this);353    this.wait = new WaitGroup(this);354    this.admin = new AdminGroup(this);355    this.testUtils = new TestUtilGroup(this);356    this.session = new SessionGroup(this);357  }358359  async connect(wsEndpoint: string, _listeners?: any): Promise<void> {360    if(!wsEndpoint) throw new Error('wsEndpoint was not set');361    const wsProvider = new WsProvider(wsEndpoint);362    this.api = new ApiPromise({363      provider: wsProvider,364      signedExtensions: {365        ContractHelpers: {366          extrinsic: {},367          payload: {},368        },369        CheckMaintenance: {370          extrinsic: {},371          payload: {},372        },373        DisableIdentityCalls: {374          extrinsic: {},375          payload: {},376        },377        FakeTransactionFinalizer: {378          extrinsic: {},379          payload: {},380        },381      },382      rpc: {383        unique: defs.unique.rpc,384        appPromotion: defs.appPromotion.rpc,385        povinfo: defs.povinfo.rpc,386        eth: {387          feeHistory: {388            description: 'Dummy',389            params: [],390            type: 'u8',391          },392          maxPriorityFeePerGas: {393            description: 'Dummy',394            params: [],395            type: 'u8',396          },397        },398      },399    });400    await this.api.isReadyOrError;401    this.network = await UniqueHelper.detectNetwork(this.api);402    this.wsEndpoint = wsEndpoint;403  }404  getSudo<T extends UniqueHelper>() {405    // eslint-disable-next-line @typescript-eslint/naming-convention406    const SudoHelperType = SudoHelper(this.helperBase);407    return this.clone(SudoHelperType) as T;408  }409}410411export class DevRelayHelper extends RelayHelper {412  wait: WaitGroup;413414  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {415    options.helperBase = options.helperBase ?? DevRelayHelper;416417    super(logger, options);418    this.wait = new WaitGroup(this);419  }420}421422export class DevWestmintHelper extends WestmintHelper {423  wait: WaitGroup;424425  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {426    options.helperBase = options.helperBase ?? DevWestmintHelper;427428    super(logger, options);429    this.wait = new WaitGroup(this);430  }431}432433export class DevStatemineHelper extends DevWestmintHelper {}434435export class DevStatemintHelper extends DevWestmintHelper {}436437export class DevMoonbeamHelper extends MoonbeamHelper {438  account: MoonbeamAccountGroup;439  wait: WaitGroup;440  fastDemocracy: MoonbeamFastDemocracyGroup;441442  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {443    options.helperBase = options.helperBase ?? DevMoonbeamHelper;444    options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';445446    super(logger, options);447    this.account = new MoonbeamAccountGroup(this);448    this.wait = new WaitGroup(this);449    this.fastDemocracy = new MoonbeamFastDemocracyGroup(this);450  }451}452453export class DevMoonriverHelper extends DevMoonbeamHelper {454  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {455    options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';456    super(logger, options);457  }458}459460export class DevAstarHelper extends AstarHelper {461  wait: WaitGroup;462463  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {464    options.helperBase = options.helperBase ?? DevAstarHelper;465466    super(logger, options);467    this.wait = new WaitGroup(this);468  }469470  getSudo<T extends UniqueHelper>() {471    // eslint-disable-next-line @typescript-eslint/naming-convention472    const SudoHelperType = SudoHelper(this.helperBase);473    return this.clone(SudoHelperType) as T;474  }475}476477export class DevShidenHelper extends AstarHelper {478  wait: WaitGroup;479480  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {481    options.helperBase = options.helperBase ?? DevShidenHelper;482483    super(logger, options);484    this.wait = new WaitGroup(this);485  }486}487488export class DevAcalaHelper extends AcalaHelper {489  wait: WaitGroup;490491  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {492    options.helperBase = options.helperBase ?? DevAcalaHelper;493494    super(logger, options);495    this.wait = new WaitGroup(this);496  }497  getSudo<T extends UniqueHelper>() {498    // eslint-disable-next-line @typescript-eslint/naming-convention499    const SudoHelperType = SudoHelper(this.helperBase);500    return this.clone(SudoHelperType) as T;501  }502}503504export class DevPolkadexHelper extends PolkadexHelper {505  wait: WaitGroup;506  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {507    options.helperBase = options.helperBase ?? PolkadexHelper;508509    super(logger, options);510    this.wait = new WaitGroup(this);511  }512513  getSudo<T extends PolkadexHelper>() {514    // eslint-disable-next-line @typescript-eslint/naming-convention515    const SudoHelperType = SudoHelper(this.helperBase);516    return this.clone(SudoHelperType) as T;517  }518}519520export class DevKaruraHelper extends DevAcalaHelper {}521522export class ArrangeGroup {523  helper: DevUniqueHelper;524525  scheduledIdSlider = 0;526527  constructor(helper: DevUniqueHelper) {528    this.helper = helper;529  }530531  /**532   * Generates accounts with the specified UNQ token balance533   * @param balances balances for generated accounts. Each balance will be multiplied by the token nominal.534   * @param donor donor account for balances535   * @returns array of newly created accounts536   * @example const [acc1, acc2, acc3] = await createAccounts([0n, 10n, 20n], donor);537   */538  createAccounts = async (balances: bigint[], donor: IKeyringPair): Promise<IKeyringPair[]> => {539    let nonce = await this.helper.chain.getNonce(donor.address);540    const wait = new WaitGroup(this.helper);541    const ss58Format = this.helper.chain.getChainProperties().ss58Format;542    const tokenNominal = this.helper.balance.getOneTokenNominal();543    const transactions = [];544    const accounts: IKeyringPair[] = [];545    for(const balance of balances) {546      const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);547      accounts.push(recipient);548      if(balance !== 0n) {549        const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, balance * tokenNominal]);550        transactions.push(this.helper.signTransaction(donor, tx, {nonce, era: 0}, 'account generation'));551        nonce++;552      }553    }554555    await Promise.all(transactions).catch(_e => {});556557    //#region TODO remove this region, when nonce problem will be solved558    const checkBalances = async () => {559      let isSuccess = true;560      for(let i = 0; i < balances.length; i++) {561        const balance = await this.helper.balance.getSubstrate(accounts[i].address);562        if(balance !== balances[i] * tokenNominal) {563          isSuccess = false;564          break;565        }566      }567      return isSuccess;568    };569570    let accountsCreated = false;571    const maxBlocksChecked = await this.helper.arrange.isDevNode() ? 50 : 5;572    // checkBalances retry up to 5-50 blocks573    for(let index = 0; index < maxBlocksChecked; index++) {574      accountsCreated = await checkBalances();575      if(accountsCreated) break;576      await wait.newBlocks(1);577    }578579    if(!accountsCreated) throw Error('Accounts generation failed');580    //#endregion581582    return accounts;583  };584585  // TODO combine this method and createAccounts into one586  createCrowd = async (accountsToCreate: number, withBalance: bigint, donor: IKeyringPair): Promise<IKeyringPair[]> => {587    const createAsManyAsCan = async () => {588      let transactions: any = [];589      const accounts: IKeyringPair[] = [];590      let nonce = await this.helper.chain.getNonce(donor.address);591      const tokenNominal = this.helper.balance.getOneTokenNominal();592      const ss58Format = this.helper.chain.getChainProperties().ss58Format;593      for(let i = 0; i < accountsToCreate; i++) {594        if(i === 500) { // if there are too many accounts to create595          await Promise.allSettled(transactions); // wait while first 500 (should be 100 for devnode) tx will be settled596          transactions = []; //597          nonce = await this.helper.chain.getNonce(donor.address); // update nonce598        }599        const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);600        accounts.push(recipient);601        if(withBalance !== 0n) {602          const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, withBalance * tokenNominal]);603          transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));604          nonce++;605        }606      }607608      const fullfilledAccounts = [];609      await Promise.allSettled(transactions);610      for(const account of accounts) {611        const accountBalance = await this.helper.balance.getSubstrate(account.address);612        if(accountBalance === withBalance * tokenNominal) {613          fullfilledAccounts.push(account);614        }615      }616      return fullfilledAccounts;617    };618619620    const crowd: IKeyringPair[] = [];621    // do up to 5 retries622    for(let index = 0; index < 5 && accountsToCreate !== 0; index++) {623      const asManyAsCan = await createAsManyAsCan();624      crowd.push(...asManyAsCan);625      accountsToCreate -= asManyAsCan.length;626    }627628    if(accountsToCreate !== 0) throw Error(`Crowd generation failed: ${accountsToCreate} accounts left`);629630    return crowd;631  };632633  /**634   * Generates one account with zero balance635   * @returns the newly generated account636   * @example const account = await helper.arrange.createEmptyAccount();637   */638  createEmptyAccount = (): IKeyringPair => {639    const ss58Format = this.helper.chain.getChainProperties().ss58Format;640    return this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);641  };642643  isDevNode = async () => {644    let blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();645    if(blockNumber == 0) {646      await this.helper.wait.newBlocks(1);647      blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();648    }649    const block2 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])]);650    const block1 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber - 1])]);651    const findCreationDate = (block: any) => {652      const humanBlock = block.toHuman();653      let date;654      humanBlock.block.extrinsics.forEach((ext: any) => {655        if(ext.method.section === 'timestamp') {656          date = Number(ext.method.args.now.replaceAll(',', ''));657        }658      });659      return date;660    };661    const block1date = await findCreationDate(block1);662    const block2date = await findCreationDate(block2);663    if(block2date! - block1date! < 9000) return true;664  };665666  async calculcateFee(payer: ICrossAccountId, promise: () => Promise<any>): Promise<bigint> {667    const address = 'Substrate' in payer ? payer.Substrate : this.helper.address.ethToSubstrate(payer.Ethereum);668    let balance = await this.helper.balance.getSubstrate(address);669670    await promise();671672    balance -= await this.helper.balance.getSubstrate(address);673674    return balance;675  }676677  async calculatePoVInfo(txs: any[]): Promise<IPovInfo> {678    const rawPovInfo = await this.helper.callRpc('api.rpc.povinfo.estimateExtrinsicPoV', [txs]);679680    const kvJson: {[key: string]: string} = {};681682    for(const kv of rawPovInfo.keyValues) {683      kvJson[kv.key.toHex()] = kv.value.toHex();684    }685686    const kvStr = JSON.stringify(kvJson);687688    const chainql = spawnSync(689      'chainql',690      [691        `--tla-code=data=${kvStr}`,692        '-e', `function(data) cql.dump(cql.chain("${this.helper.getEndpoint()}").latest._meta, data, {omit_empty:true})`,693      ],694    );695696    if(!chainql.stdout) {697      throw Error('unable to get an output from the `chainql`');698    }699700    return {701      proofSize: rawPovInfo.proofSize.toNumber(),702      compactProofSize: rawPovInfo.compactProofSize.toNumber(),703      compressedProofSize: rawPovInfo.compressedProofSize.toNumber(),704      results: rawPovInfo.results,705      kv: JSON.parse(chainql.stdout.toString()),706    };707  }708709  calculatePalletAddress(palletId: any) {710    const address = stringToU8a(('modl' + palletId).padEnd(32, '\0'));711    return encodeAddress(address, this.helper.chain.getChainProperties().ss58Format);712  }713714  makeScheduledIds(num: number): string[] {715    function makeId(slider: number) {716      const scheduledIdSize = 64;717      const hexId = slider.toString(16);718      const prefixSize = scheduledIdSize - hexId.length;719720      const scheduledId = '0x' + '0'.repeat(prefixSize) + hexId;721722      return scheduledId;723    }724725    const ids = [];726    for(let i = 0; i < num; i++) {727      ids.push(makeId(this.scheduledIdSlider));728      this.scheduledIdSlider += 1;729    }730731    return ids;732  }733734  makeScheduledId(): string {735    return (this.makeScheduledIds(1))[0];736  }737738  async captureEvents(eventSection: string, eventMethod: string): Promise<EventCapture> {739    const capture = new EventCapture(this.helper, eventSection, eventMethod);740    await capture.startCapture();741742    return capture;743  }744745  makeXcmProgramWithdrawDeposit(beneficiary: Uint8Array, id: any, amount: bigint) {746    return {747      V2: [748        {749          WithdrawAsset: [750            {751              id,752              fun: {753                Fungible: amount,754              },755            },756          ],757        },758        {759          BuyExecution: {760            fees: {761              id,762              fun: {763                Fungible: amount,764              },765            },766            weightLimit: 'Unlimited',767          },768        },769        {770          DepositAsset: {771            assets: {772              Wild: 'All',773            },774            maxAssets: 1,775            beneficiary: {776              parents: 0,777              interior: {778                X1: {779                  AccountId32: {780                    network: 'Any',781                    id: beneficiary,782                  },783                },784              },785            },786          },787        },788      ],789    };790  }791792  makeXcmProgramReserveAssetDeposited(beneficiary: Uint8Array, id: any, amount: bigint) {793    return {794      V2: [795        {796          ReserveAssetDeposited: [797            {798              id,799              fun: {800                Fungible: amount,801              },802            },803          ],804        },805        {806          BuyExecution: {807            fees: {808              id,809              fun: {810                Fungible: amount,811              },812            },813            weightLimit: 'Unlimited',814          },815        },816        {817          DepositAsset: {818            assets: {819              Wild: 'All',820            },821            maxAssets: 1,822            beneficiary: {823              parents: 0,824              interior: {825                X1: {826                  AccountId32: {827                    network: 'Any',828                    id: beneficiary,829                  },830                },831              },832            },833          },834        },835      ],836    };837  }838}839840class MoonbeamAccountGroup {841  helper: MoonbeamHelper;842843  keyring: Keyring;844  _alithAccount: IKeyringPair;845  _baltatharAccount: IKeyringPair;846  _dorothyAccount: IKeyringPair;847848  constructor(helper: MoonbeamHelper) {849    this.helper = helper;850851    this.keyring = new Keyring({type: 'ethereum'});852    const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';853    const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';854    const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';855856    this._alithAccount = this.keyring.addFromUri(alithPrivateKey, undefined, 'ethereum');857    this._baltatharAccount = this.keyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');858    this._dorothyAccount = this.keyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');859  }860861  alithAccount() {862    return this._alithAccount;863  }864865  baltatharAccount() {866    return this._baltatharAccount;867  }868869  dorothyAccount() {870    return this._dorothyAccount;871  }872873  create() {874    return this.keyring.addFromUri(mnemonicGenerate());875  }876}877878class MoonbeamFastDemocracyGroup {879  helper: DevMoonbeamHelper;880881  constructor(helper: DevMoonbeamHelper) {882    this.helper = helper;883  }884885  async executeProposal(proposalDesciption: string, encodedProposal: string) {886    const proposalHash = blake2AsHex(encodedProposal);887888    const alithAccount = this.helper.account.alithAccount();889    const baltatharAccount = this.helper.account.baltatharAccount();890    const dorothyAccount = this.helper.account.dorothyAccount();891892    const councilVotingThreshold = 2;893    const technicalCommitteeThreshold = 2;894    const fastTrackVotingPeriod = 3;895    const fastTrackDelayPeriod = 0;896897    console.log(`[democracy] executing '${proposalDesciption}' proposal`);898899    // >>> Propose external motion through council >>>900    console.log('\t* Propose external motion through council.......');901    const externalMotion = this.helper.democracy.externalProposeMajority({Inline: encodedProposal});902    const encodedMotion = externalMotion?.method.toHex() || '';903    const motionHash = blake2AsHex(encodedMotion);904    console.log('\t* Motion hash is %s', motionHash);905906    await this.helper.collective.council.propose(907      baltatharAccount,908      councilVotingThreshold,909      externalMotion,910      externalMotion.encodedLength,911    );912913    const councilProposalIdx = await this.helper.collective.council.proposalCount() - 1;914    await this.helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);915    await this.helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);916917    await this.helper.collective.council.close(918      dorothyAccount,919      motionHash,920      councilProposalIdx,921      {922        refTime: 1_000_000_000,923        proofSize: 1_000_000,924      },925      externalMotion.encodedLength,926    );927    console.log('\t* Propose external motion through council.......DONE');928    // <<< Propose external motion through council <<<929930    // >>> Fast track proposal through technical committee >>>931    console.log('\t* Fast track proposal through technical committee.......');932    const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod);933    const encodedFastTrack = fastTrack?.method.toHex() || '';934    const fastTrackHash = blake2AsHex(encodedFastTrack);935    console.log('\t* FastTrack hash is %s', fastTrackHash);936937    await this.helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);938939    const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1;940    await this.helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);941    await this.helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);942943    await this.helper.collective.techCommittee.close(944      baltatharAccount,945      fastTrackHash,946      techProposalIdx,947      {948        refTime: 1_000_000_000,949        proofSize: 1_000_000,950      },951      fastTrack.encodedLength,952    );953    console.log('\t* Fast track proposal through technical committee.......DONE');954    // <<< Fast track proposal through technical committee <<<955956    const democracyStarted = await this.helper.wait.expectEvent(3, Event.Democracy.Started);957    const referendumIndex = democracyStarted.referendumIndex;958959    // >>> Referendum voting >>>960    console.log(`\t* Referendum #${referendumIndex} voting.......`);961    await this.helper.democracy.referendumVote(dorothyAccount, referendumIndex, {962      balance: 10_000_000_000_000_000_000n,963      vote: {aye: true, conviction: 1},964    });965    console.log(`\t* Referendum #${referendumIndex} voting.......DONE`);966    // <<< Referendum voting <<<967968    // Wait the proposal to pass969    await this.helper.wait.expectEvent(3, Event.Democracy.Passed, event => event.referendumIndex == referendumIndex);970971    await this.helper.wait.newBlocks(1);972973    console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`);974  }975}976977class WaitGroup {978  helper: ChainHelperBase;979980  constructor(helper: ChainHelperBase) {981    this.helper = helper;982  }983984  sleep(milliseconds: number) {985    return new Promise((resolve) => setTimeout(resolve, milliseconds));986  }987988  private async waitWithTimeout(promise: Promise<any>, timeout: number) {989    let isBlock = false;990    promise.then(() => isBlock = true).catch(() => isBlock = true);991    let totalTime = 0;992    const step = 100;993    while(!isBlock) {994      await this.sleep(step);995      totalTime += step;996      if(totalTime >= timeout) throw Error('Blocks production failed');997    }998    return promise;999  }10001001  /**1002   * Launch some async operation, or throw an error after some time. Note that it will still continue executing after the timeout.1003   * @param promise async operation to race against the timeout1004   * @param timeoutMS time after which to time out1005   * @param timeoutError error message to throw1006   * @returns promise of the same type the operation had1007   */1008  withTimeout<T>(1009    promise: Promise<T>,1010    timeoutMS = 30000,1011    timeoutError = 'The operation has timed out!',1012  ): Promise<T> {1013    const timeout = new Promise<never>((_, reject) => {1014      setTimeout(() => {1015        reject(new Error(timeoutError));1016      }, timeoutMS);1017    });10181019    return Promise.race<T>([promise, timeout]).catch(e => {throw new Error(e);});1020  }10211022  /**1023   * Wait for specified number of blocks1024   * @param blocksCount number of blocks to wait1025   * @returns1026   */1027  async newBlocks(blocksCount = 1, timeout?: number): Promise<void> {1028    timeout = timeout ?? blocksCount * 60_000;1029    // eslint-disable-next-line no-async-promise-executor1030    const promise = new Promise<void>(async (resolve) => {1031      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(() => {1032        if(blocksCount > 0) {1033          blocksCount--;1034        } else {1035          unsubscribe();1036          resolve();1037        }1038      });1039    });1040    await this.waitWithTimeout(promise, timeout);1041    return promise;1042  }10431044  /**1045   * Wait for the specified number of sessions to pass.1046   * Only applicable if the Session pallet is turned on.1047   * @param sessionCount number of sessions to wait1048   * @param blockTimeout time in ms until panicking that the chain has stopped producing blocks1049   * @returns1050   */1051  async newSessions(sessionCount = 1, blockTimeout = 60000): Promise<void> {1052    console.log(`Waiting for ${sessionCount} new session${sessionCount > 1 ? 's' : ''}.`1053      + ' This might take a while -- check SessionPeriod in pallet_session::Config for session time.');10541055    const expectedSessionIndex = await (this.helper as DevUniqueHelper).session.getIndex() + sessionCount;1056    let currentSessionIndex = -1;10571058    while(currentSessionIndex < expectedSessionIndex) {1059      // eslint-disable-next-line no-async-promise-executor1060      currentSessionIndex = await this.withTimeout(new Promise(async (resolve) => {1061        await this.newBlocks(1);1062        const res = await (this.helper as DevUniqueHelper).session.getIndex();1063        resolve(res);1064      }), blockTimeout, 'The chain has stopped producing blocks!');1065    }1066  }10671068  async forParachainBlockNumber(blockNumber: bigint | number, timeout?: number) {1069    timeout = timeout ?? 30 * 60 * 1000;1070    // eslint-disable-next-line no-async-promise-executor1071    const promise = new Promise<void>(async (resolve) => {1072      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {1073        if(data.number.toNumber() >= blockNumber) {1074          unsubscribe();1075          resolve();1076        }1077      });1078    });1079    await this.waitWithTimeout(promise, timeout);1080    return promise;1081  }10821083  async forRelayBlockNumber(blockNumber: bigint | number, timeout?: number) {1084    timeout = timeout ?? 30 * 60 * 1000;1085    // eslint-disable-next-line no-async-promise-executor1086    const promise = new Promise<void>(async (resolve) => {1087      const unsubscribe = await this.helper.getApi().query.parachainSystem.validationData((data: any) => {1088        if(data.value.relayParentNumber.toNumber() >= blockNumber) {1089          // @ts-ignore1090          unsubscribe();1091          resolve();1092        }1093      });1094    });1095    await this.waitWithTimeout(promise, timeout);1096    return promise;1097  }10981099  noScheduledTasks() {1100    const api = this.helper.getApi();11011102    // eslint-disable-next-line no-async-promise-executor1103    const promise = new Promise<void>(async resolve => {1104      const unsubscribe = await api.rpc.chain.subscribeNewHeads(async () => {1105        const areThereScheduledTasks = await api.query.scheduler.lookup.entries();11061107        if(areThereScheduledTasks.length == 0) {1108          unsubscribe();1109          resolve();1110        }1111      });1112    });11131114    return promise;1115  }11161117  parachainBlockMultiplesOf(val: bigint) {1118    // eslint-disable-next-line no-async-promise-executor1119    const promise = new Promise<void>(async resolve => {1120      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {1121        if(data.number.toBigInt() % val == 0n) {1122          console.log(`from waiter: ${data.number.toBigInt()}`);1123          unsubscribe();1124          resolve();1125        }1126      });1127    });1128    return promise;1129  }11301131  event<T extends IEventHelper>(1132    maxBlocksToWait: number,1133    eventHelper: T,1134    filter: (_: any) => boolean = () => true,1135  ): any {1136    // eslint-disable-next-line no-async-promise-executor1137    const promise = new Promise<T | null>(async (resolve) => {1138      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => {1139        const blockNumber = header.number.toHuman();1140        const blockHash = header.hash;1141        const eventIdStr = `${eventHelper.section()}.${eventHelper.method()}`;1142        const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;11431144        this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);11451146        const apiAt = await this.helper.getApi().at(blockHash);1147        const eventRecords = (await apiAt.query.system.events()) as any;11481149        const neededEvent = eventRecords.toArray()1150          .filter((r: FrameSystemEventRecord) => r.event.section == eventHelper.section() && r.event.method == eventHelper.method())1151          .map((r: FrameSystemEventRecord) => eventHelper.wrapEvent(r.event.data))1152          .find(filter);11531154        if(neededEvent) {1155          unsubscribe();1156          resolve(neededEvent);1157        } else if(maxBlocksToWait > 0) {1158          maxBlocksToWait--;1159        } else {1160          this.helper.logger.log(`Eligible event \`${eventIdStr}\` is NOT found`);1161          unsubscribe();1162          resolve(null);1163        }1164      });1165    });1166    return promise;1167  }11681169  async expectEvent<T extends IEventHelper>(1170    maxBlocksToWait: number,1171    eventHelper: T,1172    filter: (e: any) => boolean = () => true,1173  ) {1174    const e = await this.event(maxBlocksToWait, eventHelper, filter);1175    if(e == null) {1176      throw Error(`The event '${eventHelper.section()}.${eventHelper.method()}' is expected`);1177    } else {1178      return e;1179    }1180  }1181}11821183class SessionGroup {1184  helper: ChainHelperBase;11851186  constructor(helper: ChainHelperBase) {1187    this.helper = helper;1188  }11891190  //todo:collator documentation1191  async getIndex(): Promise<number> {1192    return (await this.helper.callRpc('api.query.session.currentIndex', [])).toNumber();1193  }11941195  newSessions(sessionCount = 1, blockTimeout = 24000): Promise<void> {1196    return (this.helper as DevUniqueHelper).wait.newSessions(sessionCount, blockTimeout);1197  }11981199  setOwnKeys(signer: TSigner, key: string) {1200    return this.helper.executeExtrinsic(1201      signer,1202      'api.tx.session.setKeys',1203      [key, '0x0'],1204      true,1205    );1206  }12071208  setOwnKeysFromAddress(signer: TSigner) {1209    return this.setOwnKeys(signer, '0x' + Buffer.from(signer.addressRaw).toString('hex'));1210  }1211}12121213class TestUtilGroup {1214  helper: DevUniqueHelper;12151216  constructor(helper: DevUniqueHelper) {1217    this.helper = helper;1218  }12191220  async enable() {1221    if(this.helper.fetchMissingPalletNames([Pallets.TestUtils]).length != 0) {1222      return;1223    }12241225    const signer = this.helper.util.fromSeed('//Alice');1226    await this.helper.getSudo<DevUniqueHelper>().executeExtrinsic(signer, 'api.tx.testUtils.enable', [], true);1227  }12281229  async setTestValue(signer: TSigner, testVal: number) {1230    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValue', [testVal], true);1231  }12321233  async incTestValue(signer: TSigner) {1234    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.incTestValue', [], true);1235  }12361237  async setTestValueAndRollback(signer: TSigner, testVal: number) {1238    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValueAndRollback', [testVal], true);1239  }12401241  async testValue(blockIdx?: number) {1242    const api = blockIdx1243      ? await this.helper.getApi().at(await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockIdx]))1244      : this.helper.getApi();12451246    return (await api.query.testUtils.testValue()).toJSON();1247  }12481249  async justTakeFee(signer: TSigner) {1250    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.justTakeFee', [], true);1251  }12521253  async selfCancelingInc(signer: TSigner, scheduledId: string, maxTestVal: number) {1254    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.selfCancelingInc', [scheduledId, maxTestVal], true);1255  }1256}12571258class EventCapture {1259  helper: DevUniqueHelper;1260  eventSection: string;1261  eventMethod: string;1262  events: EventRecord[] = [];1263  unsubscribe: VoidFn | null = null;12641265  constructor(1266    helper: DevUniqueHelper,1267    eventSection: string,1268    eventMethod: string,1269  ) {1270    this.helper = helper;1271    this.eventSection = eventSection;1272    this.eventMethod = eventMethod;1273  }12741275  async startCapture() {1276    this.stopCapture();1277    this.unsubscribe = (await this.helper.getApi().query.system.events((eventRecords: FrameSystemEventRecord[]) => {1278      const newEvents = eventRecords.filter(r => r.event.section == this.eventSection && r.event.method == this.eventMethod);12791280      this.events.push(...newEvents);1281    })) as any;1282  }12831284  stopCapture() {1285    if(this.unsubscribe !== null) {1286      this.unsubscribe();1287    }1288  }12891290  extractCapturedEvents() {1291    return this.events;1292  }1293}12941295class AdminGroup {1296  helper: UniqueHelper;12971298  constructor(helper: UniqueHelper) {1299    this.helper = helper;1300  }13011302  async payoutStakers(signer: IKeyringPair, stakersToPayout: number):  Promise<{staker: string, stake: bigint, payout: bigint}[]> {1303    const payoutResult = await this.helper.executeExtrinsic(signer, 'api.tx.appPromotion.payoutStakers', [stakersToPayout], true);1304    return payoutResult.result.events.filter(e => e.event.method === 'StakingRecalculation').map(e => ({1305      staker: e.event.data[0].toString(),1306      stake: e.event.data[1].toBigInt(),1307      payout: e.event.data[2].toBigInt(),1308    }));1309  }1310}
addedtests/src/util/playgrounds/unique.governance.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/util/playgrounds/unique.governance.ts
@@ -0,0 +1,531 @@
+import {blake2AsHex} from '@polkadot/util-crypto';
+import {PalletDemocracyConviction} from '@polkadot/types/lookup';
+import {IPhasicEvent, TSigner} from './types';
+import {HelperGroup, UniqueHelper} from './unique';
+
+export class CollectiveGroup extends HelperGroup<UniqueHelper> {
+  /**
+   * Pallet name to make an API call to. Examples: 'council', 'technicalCommittee'
+   */
+  private collective: string;
+
+  constructor(helper: UniqueHelper, collective: string) {
+    super(helper);
+    this.collective = collective;
+  }
+
+  /**
+   * Check the result of a proposal execution for the success of the underlying proposed extrinsic.
+   * @param events events of the proposal execution
+   * @returns proposal hash
+   */
+  private checkExecutedEvent(events: IPhasicEvent[]): string {
+    const executionEvents = events.filter(x =>
+      x.event.section === this.collective && (x.event.method === 'Executed' || x.event.method === 'MemberExecuted'));
+
+    if(executionEvents.length != 1) {
+      if(events.filter(x => x.event.section === this.collective && x.event.method === 'Disapproved').length > 0)
+        throw new Error(`Disapproved by ${this.collective}`);
+      else
+        throw new Error(`Expected one 'Executed' or 'MemberExecuted' event for ${this.collective}`);
+    }
+
+    const result = (executionEvents[0].event.data as any).result;
+
+    if(result.isErr) {
+      if(result.asErr.isModule) {
+        const error = result.asErr.asModule;
+        const metaError = this.helper.getApi()?.registry.findMetaError(error);
+        throw new Error(`Proposal execution failed with ${metaError.section}.${metaError.name}`);
+      } else {
+        throw new Error('Proposal execution failed with ' + result.asErr.toHuman());
+      }
+    }
+
+    return (executionEvents[0].event.data as any).proposalHash;
+  }
+
+  /**
+   * Returns an array of members' addresses.
+   */
+  async getMembers() {
+    return (await this.helper.callRpc(`api.query.${this.collective}.members`, [])).toHuman();
+  }
+
+  /**
+   * Returns the optional address of the prime member of the collective.
+   */
+  async getPrimeMember() {
+    return (await this.helper.callRpc(`api.query.${this.collective}.prime`, [])).toHuman();
+  }
+
+  /**
+   * Returns an array of proposal hashes that are currently active for this collective.
+   */
+  async getProposals() {
+    return (await this.helper.callRpc(`api.query.${this.collective}.proposals`, [])).toHuman();
+  }
+
+  /**
+   * Returns the call originally encoded under the specified hash.
+   * @param hash h256-encoded proposal
+   * @returns the optional call that the proposal hash stands for.
+   */
+  async getProposalCallOf(hash: string) {
+    return (await this.helper.callRpc(`api.query.${this.collective}.proposalOf`, [hash])).toHuman();
+  }
+
+  /**
+   * Returns the total number of proposals so far.
+   */
+  async getTotalProposalsCount() {
+    return (await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, [])).toNumber();
+  }
+
+  /**
+   * Creates a new proposal up for voting. If the threshold is set to 1, the proposal will be executed immediately.
+   * @param signer keyring of the proposer
+   * @param proposal constructed call to be executed if the proposal is successful
+   * @param voteThreshold minimal number of votes for the proposal to be verified and executed
+   * @param lengthBound byte length of the encoded call
+   * @returns promise of extrinsic execution and its result
+   */
+  async propose(signer: TSigner, proposal: any, voteThreshold: number, lengthBound = 10000) {
+    return await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [voteThreshold, proposal, lengthBound]);
+  }
+
+  /**
+   * Casts a vote to either approve or reject a proposal.
+   * @param signer keyring of the voter
+   * @param proposalHash hash of the proposal to be voted for
+   * @param proposalIndex absolute index of the proposal used for absolutely nothing but throwing pointless errors
+   * @param approve aye or nay
+   * @returns promise of extrinsic execution and its result
+   */
+  vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) {
+    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve]);
+  }
+
+  /**
+   * Executes a call immediately as a member of the collective. Needed for the Member origin.
+   * @param signer keyring of the executor member
+   * @param proposal constructed call to be executed by the member
+   * @param lengthBound byte length of the encoded call
+   * @returns promise of extrinsic execution
+   */
+  async execute(signer: TSigner, proposal: any, lengthBound = 10000) {
+    const result = await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.execute`, [proposal, lengthBound]);
+    this.checkExecutedEvent(result.result.events);
+    return result;
+  }
+
+  /**
+   * Attempt to close and execute a proposal. Note that there must already be enough votes to meet the threshold set when proposing.
+   * @param signer keyring of the executor. Can be absolutely anyone.
+   * @param proposalHash hash of the proposal to close
+   * @param proposalIndex index of the proposal generated on its creation
+   * @param weightBound weight of the proposed call. Can be obtained by calling `paymentInfo()` on the call.
+   * @param lengthBound byte length of the encoded call
+   * @returns promise of extrinsic execution and its result
+   */
+  async close(
+    signer: TSigner,
+    proposalHash: string,
+    proposalIndex: number,
+    weightBound: [number, number] | any = [20_000_000_000, 1000_000],
+    lengthBound = 10_000,
+  ) {
+    const result = await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [
+      proposalHash,
+      proposalIndex,
+      weightBound,
+      lengthBound,
+    ]);
+    this.checkExecutedEvent(result.result.events);
+    return result;
+  }
+
+  /**
+   * Shut down a proposal, regardless of its current state.
+   * @param signer keyring of the disapprover. Must be root
+   * @param proposalHash hash of the proposal to close
+   * @returns promise of extrinsic execution and its result
+   */
+  disapproveProposal(signer: TSigner, proposalHash: string) {
+    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.disapproveProposal`, [proposalHash]);
+  }
+}
+
+export class CollectiveMembershipGroup extends HelperGroup<UniqueHelper> {
+  /**
+   * Pallet name to make an API call to. Examples: 'councilMembership', 'technicalCommitteeMembership'
+   */
+  private membership: string;
+
+  constructor(helper: UniqueHelper, membership: string) {
+    super(helper);
+    this.membership = membership;
+  }
+
+  /**
+   * Returns an array of members' addresses according to the membership pallet's perception.
+   * Note that it does not recognize the original pallet's members set with `setMembers()`.
+   */
+  async getMembers() {
+    return (await this.helper.callRpc(`api.query.${this.membership}.members`, [])).toHuman();
+  }
+
+  /**
+   * Returns the optional address of the prime member of the collective.
+   */
+  async getPrimeMember() {
+    return (await this.helper.callRpc(`api.query.${this.membership}.prime`, [])).toHuman();
+  }
+
+  /**
+   * Add a member to the collective.
+   * @param signer keyring of the setter. Must be root
+   * @param member address of the member to add
+   * @returns promise of extrinsic execution and its result
+   */
+  addMember(signer: TSigner, member: string) {
+    return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.addMember`, [member]);
+  }
+
+  addMemberCall(member: string) {
+    return this.helper.constructApiCall(`api.tx.${this.membership}.addMember`, [member]);
+  }
+
+  /**
+   * Remove a member from the collective.
+   * @param signer keyring of the setter. Must be root
+   * @param member address of the member to remove
+   * @returns promise of extrinsic execution and its result
+   */
+  removeMember(signer: TSigner, member: string) {
+    return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.removeMember`, [member]);
+  }
+
+  removeMemberCall(member: string) {
+    return this.helper.constructApiCall(`api.tx.${this.membership}.removeMember`, [member]);
+  }
+
+  /**
+   * Set members of the collective to the given list of addresses.
+   * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion)
+   * @param members addresses of the members to set
+   * @returns promise of extrinsic execution and its result
+   */
+  resetMembers(signer: TSigner, members: string[]) {
+    return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.resetMembers`, [members]);
+  }
+
+  /**
+   * Set the collective's prime member to the given address.
+   * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion)
+   * @param prime address of the prime member of the collective
+   * @returns promise of extrinsic execution and its result
+   */
+  setPrime(signer: TSigner, prime: string) {
+    return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.setPrime`, [prime]);
+  }
+
+  setPrimeCall(member: string) {
+    return this.helper.constructApiCall(`api.tx.${this.membership}.setPrime`, [member]);
+  }
+
+  /**
+   * Remove the collective's prime member.
+   * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion)
+   * @returns promise of extrinsic execution and its result
+   */
+  clearPrime(signer: TSigner) {
+    return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.clearPrime`, []);
+  }
+
+  clearPrimeCall() {
+    return this.helper.constructApiCall(`api.tx.${this.membership}.clearPrime`, []);
+  }
+}
+
+export class RankedCollectiveGroup extends HelperGroup<UniqueHelper> {
+  /**
+   * Pallet name to make an API call to. Examples: 'FellowshipCollective'
+   */
+  private collective: string;
+
+  constructor(helper: UniqueHelper, collective: string) {
+    super(helper);
+    this.collective = collective;
+  }
+
+  addMember(signer: TSigner, newMember: string) {
+    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.addMember`, [newMember]);
+  }
+
+  addMemberCall(newMember: string) {
+    return this.helper.constructApiCall(`api.tx.${this.collective}.addMember`, [newMember]);
+  }
+
+  removeMember(signer: TSigner, member: string, minRank: number) {
+    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.removeMember`, [member, minRank]);
+  }
+
+  removeMemberCall(newMember: string, minRank: number) {
+    return this.helper.constructApiCall(`api.tx.${this.collective}.removeMember`, [newMember, minRank]);
+  }
+
+  promote(signer: TSigner, member: string) {
+    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.promoteMember`, [member]);
+  }
+
+  promoteCall(member: string) {
+    return this.helper.constructApiCall(`api.tx.${this.collective}.promoteMember`, [member]);
+  }
+
+  demote(signer: TSigner, member: string) {
+    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.demoteMember`, [member]);
+  }
+
+  demoteCall(newMember: string) {
+    return this.helper.constructApiCall(`api.tx.${this.collective}.demoteMember`, [newMember]);
+  }
+
+  vote(signer: TSigner, pollIndex: number, aye: boolean) {
+    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [pollIndex, aye]);
+  }
+
+  async getMembers() {
+    return (await this.helper.getApi().query.fellowshipCollective.members.keys())
+      .map((key) => key.args[0].toString());
+  }
+
+  async getMemberRank(member: string) {
+    return (await this.helper.callRpc('api.query.fellowshipCollective.members', [member])).toJSON().rank;
+  }
+}
+
+export class ReferendaGroup extends HelperGroup<UniqueHelper> {
+  /**
+   * Pallet name to make an API call to. Examples: 'FellowshipReferenda'
+   */
+  private referenda: string;
+
+  constructor(helper: UniqueHelper, referenda: string) {
+    super(helper);
+    this.referenda = referenda;
+  }
+
+  submit(
+    signer: TSigner,
+    proposalOrigin: string,
+    proposal: any,
+    enactmentMoment: any,
+  ) {
+    return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.submit`, [
+      {Origins: proposalOrigin},
+      proposal,
+      enactmentMoment,
+    ]);
+  }
+
+  placeDecisionDeposit(signer: TSigner, referendumIndex: number) {
+    return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.placeDecisionDeposit`, [referendumIndex]);
+  }
+
+  cancel(signer: TSigner, referendumIndex: number) {
+    return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.cancel`, [referendumIndex]);
+  }
+
+  cancelCall(referendumIndex: number) {
+    return this.helper.constructApiCall(`api.tx.${this.referenda}.cancel`, [referendumIndex]);
+  }
+
+  async referendumInfo(referendumIndex: number) {
+    return (await this.helper.callRpc(`api.query.${this.referenda}.referendumInfoFor`, [referendumIndex])).toJSON();
+  }
+
+  async enactmentEventId(referendumIndex: number) {
+    const api = await this.helper.getApi();
+
+    const bytes = api.createType('([u8;8], Text, u32)', ['assembly', 'enactment', referendumIndex]).toU8a();
+    return blake2AsHex(bytes, 256);
+  }
+}
+
+export interface IFellowshipGroup {
+  collective: RankedCollectiveGroup;
+  referenda: ReferendaGroup;
+}
+
+export interface ICollectiveGroup {
+  collective: CollectiveGroup;
+  membership: CollectiveMembershipGroup;
+}
+
+export class DemocracyGroup extends HelperGroup<UniqueHelper> {
+  // todo displace proposal into types?
+  propose(signer: TSigner, call: any, deposit: bigint) {
+    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.propose', [{Inline: call.method.toHex()}, deposit]);
+  }
+
+  proposeWithPreimage(signer: TSigner, preimage: string, deposit: bigint) {
+    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.propose', [{Legacy: preimage}, deposit]);
+  }
+
+  proposeCall(call: any, deposit: bigint) {
+    return this.helper.constructApiCall('api.tx.democracy.propose', [{Inline: call.method.toHex()}, deposit]);
+  }
+
+  second(signer: TSigner, proposalIndex: number) {
+    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.second', [proposalIndex]);
+  }
+
+  externalPropose(signer: TSigner, proposalCall: any) {
+    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalPropose', [{Inline: proposalCall.method.toHex()}]);
+  }
+
+  externalProposeMajority(signer: TSigner, proposalCall: any) {
+    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeMajority', [{Inline: proposalCall.method.toHex()}]);
+  }
+
+  externalProposeDefault(signer: TSigner, proposalCall: any) {
+    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeDefault', [{Inline: proposalCall.method.toHex()}]);
+  }
+
+  externalProposeDefaultWithPreimage(signer: TSigner, preimage: string) {
+    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeDefault', [{Legacy: preimage}]);
+  }
+
+  externalProposeCall(proposalCall: any) {
+    return this.helper.constructApiCall('api.tx.democracy.externalPropose', [{Inline: proposalCall.method.toHex()}]);
+  }
+
+  externalProposeMajorityCall(proposalCall: any) {
+    return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [{Inline: proposalCall.method.toHex()}]);
+  }
+
+  externalProposeDefaultCall(proposalCall: any) {
+    return this.helper.constructApiCall('api.tx.democracy.externalProposeDefault', [{Inline: proposalCall.method.toHex()}]);
+  }
+
+  externalProposeDefaultWithPreimageCall(preimage: string) {
+    return this.helper.constructApiCall('api.tx.democracy.externalProposeDefault', [{Legacy: preimage}]);
+  }
+
+  // ... and blacklist external proposal hash.
+  vetoExternal(signer: TSigner, proposalHash: string) {
+    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vetoExternal', [proposalHash]);
+  }
+
+  vetoExternalCall(proposalHash: string) {
+    return this.helper.constructApiCall('api.tx.democracy.vetoExternal', [proposalHash]);
+  }
+
+  blacklist(signer: TSigner, proposalHash: string, referendumIndex: number | null = null) {
+    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.blacklist', [proposalHash, referendumIndex]);
+  }
+
+  blacklistCall(proposalHash: string, referendumIndex: number | null = null) {
+    return this.helper.constructApiCall('api.tx.democracy.blacklist', [proposalHash, referendumIndex]);
+  }
+
+  // proposal. CancelProposalOrigin (root or all techcom)
+  cancelProposal(signer: TSigner, proposalIndex: number) {
+    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.cancelProposal', [proposalIndex]);
+  }
+
+  cancelProposalCall(proposalIndex: number) {
+    return this.helper.constructApiCall('api.tx.democracy.cancelProposal', [proposalIndex]);
+  }
+
+  clearPublicProposals(signer: TSigner) {
+    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.clearPublicProposals', []);
+  }
+
+  fastTrack(signer: TSigner, proposalHash: string, votingPeriod: number, delayPeriod: number) {
+    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);
+  }
+
+  fastTrackCall(proposalHash: string, votingPeriod: number, delayPeriod: number) {
+    return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);
+  }
+
+  // referendum. CancellationOrigin (TechCom member)
+  emergencyCancel(signer: TSigner, referendumIndex: number) {
+    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.emergencyCancel', [referendumIndex]);
+  }
+
+  emergencyCancelCall(referendumIndex: number) {
+    return this.helper.constructApiCall('api.tx.democracy.emergencyCancel', [referendumIndex]);
+  }
+
+  vote(signer: TSigner, referendumIndex: number, vote: any) {
+    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, vote]);
+  }
+
+  removeVote(signer: TSigner, referendumIndex: number, targetAccount?: string) {
+    if(targetAccount) {
+      return this.helper.executeExtrinsic(signer, 'api.tx.democracy.removeOtherVote', [targetAccount, referendumIndex]);
+    } else {
+      return this.helper.executeExtrinsic(signer, 'api.tx.democracy.removeVote', [referendumIndex]);
+    }
+  }
+
+  unlock(signer: TSigner, targetAccount: string) {
+    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.unlock', [targetAccount]);
+  }
+
+  delegate(signer: TSigner, toAccount: string, conviction: PalletDemocracyConviction, balance: bigint) {
+    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.delegate', [toAccount, conviction, balance]);
+  }
+
+  undelegate(signer: TSigner) {
+    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.undelegate', []);
+  }
+
+  async referendumInfo(referendumIndex: number) {
+    return (await this.helper.callRpc('api.query.democracy.referendumInfoOf', [referendumIndex])).toJSON();
+  }
+
+  async publicProposals() {
+    return (await this.helper.callRpc('api.query.democracy.publicProps', [])).toJSON();
+  }
+
+  async findPublicProposal(proposalIndex: number) {
+    const proposalInfo = (await this.publicProposals()).find((proposalInfo: any[]) => proposalInfo[0] == proposalIndex);
+
+    return proposalInfo ? proposalInfo[1] : null;
+  }
+
+  async expectPublicProposal(proposalIndex: number) {
+    const proposal = await this.findPublicProposal(proposalIndex);
+
+    if(proposal) {
+      return proposal;
+    } else {
+      throw Error(`Proposal #${proposalIndex} is expected to exist`);
+    }
+  }
+
+  async getExternalProposal() {
+    return (await this.helper.callRpc('api.query.democracy.nextExternal', []));
+  }
+
+  async expectExternalProposal() {
+    const proposal = await this.getExternalProposal();
+
+    if(proposal) {
+      return proposal;
+    } else {
+      throw Error('An external proposal is expected to exist');
+    }
+  }
+
+  /* setMetadata? */
+
+  /* todo?
+  referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) {
+    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true);
+  }*/
+}
modifiedtests/src/util/playgrounds/unique.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/unique.ts
+++ b/tests/src/util/playgrounds/unique.ts
@@ -39,13 +39,11 @@
   TSigner,
   TSubstrateAccount,
   TNetworks,
-  IForeignAssetMetadata,
   IEthCrossAccountId,
-  IPhasicEvent,
 } from './types';
 import {RuntimeDispatchInfo} from '@polkadot/types/interfaces';
 import type {Vec} from '@polkadot/types-codec';
-import {FrameSystemEventRecord, PalletDemocracyConviction} from '@polkadot/types/lookup';
+import {FrameSystemEventRecord} from '@polkadot/types/lookup';
 
 export class CrossAccountId {
   Substrate!: TSubstrateAccount;
@@ -2757,6 +2755,7 @@
   }
 }
 
+
 class StakingGroup extends HelperGroup<UniqueHelper> {
   /**
    * Stake tokens for App Promotion
@@ -2860,648 +2859,10 @@
       block: block.toBigInt(),
       amount: amount.toBigInt(),
     }));
-    return result;
-  }
-}
-
-class SchedulerGroup extends HelperGroup<UniqueHelper> {
-  constructor(helper: UniqueHelper) {
-    super(helper);
-  }
-
-  cancelScheduled(signer: TSigner, scheduledId: string) {
-    return this.helper.executeExtrinsic(
-      signer,
-      'api.tx.scheduler.cancelNamed',
-      [scheduledId],
-      true,
-    );
-  }
-
-  changePriority(signer: TSigner, scheduledId: string, priority: number) {
-    return this.helper.executeExtrinsic(
-      signer,
-      'api.tx.scheduler.changeNamedPriority',
-      [scheduledId, priority],
-      true,
-    );
-  }
-
-  scheduleAt<T extends UniqueHelper>(
-    executionBlockNumber: number,
-    options: ISchedulerOptions = {},
-  ) {
-    return this.schedule<T>('schedule', executionBlockNumber, options);
-  }
-
-  scheduleAfter<T extends UniqueHelper>(
-    blocksBeforeExecution: number,
-    options: ISchedulerOptions = {},
-  ) {
-    return this.schedule<T>('scheduleAfter', blocksBeforeExecution, options);
-  }
-
-  schedule<T extends UniqueHelper>(
-    scheduleFn: 'schedule' | 'scheduleAfter',
-    blocksNum: number,
-    options: ISchedulerOptions = {},
-  ) {
-    // eslint-disable-next-line @typescript-eslint/naming-convention
-    const ScheduledHelperType = ScheduledUniqueHelper(this.helper.helperBase);
-    return this.helper.clone(ScheduledHelperType, {
-      scheduleFn,
-      blocksNum,
-      options,
-    }) as T;
-  }
-}
-
-class CollatorSelectionGroup extends HelperGroup<UniqueHelper> {
-  //todo:collator documentation
-  addInvulnerable(signer: TSigner, address: string) {
-    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.addInvulnerable', [address]);
-  }
-
-  removeInvulnerable(signer: TSigner, address: string) {
-    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.removeInvulnerable', [address]);
-  }
-
-  async getInvulnerables(): Promise<string[]> {
-    return (await this.helper.callRpc('api.query.collatorSelection.invulnerables')).map((x: any) => x.toHuman());
-  }
-
-  /** and also total max invulnerables */
-  maxCollators(): number {
-    return (this.helper.getApi().consts.configuration.defaultCollatorSelectionMaxCollators.toJSON() as number);
-  }
-
-  async getDesiredCollators(): Promise<number> {
-    return (await this.helper.callRpc('api.query.configuration.collatorSelectionDesiredCollatorsOverride')).toNumber();
-  }
-
-  setLicenseBond(signer: TSigner, amount: bigint) {
-    return this.helper.executeExtrinsic(signer, 'api.tx.configuration.setCollatorSelectionLicenseBond', [amount]);
-  }
-
-  async getLicenseBond(): Promise<bigint> {
-    return (await this.helper.callRpc('api.query.configuration.collatorSelectionLicenseBondOverride')).toBigInt();
-  }
-
-  obtainLicense(signer: TSigner) {
-    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.getLicense', []);
-  }
-
-  releaseLicense(signer: TSigner) {
-    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.releaseLicense', []);
-  }
-
-  forceReleaseLicense(signer: TSigner, released: string) {
-    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.forceReleaseLicense', [released]);
-  }
-
-  async hasLicense(address: string): Promise<bigint> {
-    return (await this.helper.callRpc('api.query.collatorSelection.licenseDepositOf', [address])).toBigInt();
-  }
-
-  onboard(signer: TSigner) {
-    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.onboard', []);
-  }
-
-  offboard(signer: TSigner) {
-    return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.offboard', []);
-  }
-
-  async getCandidates(): Promise<string[]> {
-    return (await this.helper.callRpc('api.query.collatorSelection.candidates')).map((x: any) => x.toHuman());
-  }
-}
-
-class CollectiveGroup extends HelperGroup<UniqueHelper> {
-  /**
-   * Pallet name to make an API call to. Examples: 'council', 'technicalCommittee'
-   */
-  private collective: string;
-
-  constructor(helper: UniqueHelper, collective: string) {
-    super(helper);
-    this.collective = collective;
-  }
-
-  /**
-   * Check the result of a proposal execution for the success of the underlying proposed extrinsic.
-   * @param events events of the proposal execution
-   * @returns proposal hash
-   */
-  private checkExecutedEvent(events: IPhasicEvent[]): string {
-    const executionEvents = events.filter(x =>
-      x.event.section === this.collective && (x.event.method === 'Executed' || x.event.method === 'MemberExecuted'));
-
-    if(executionEvents.length != 1) {
-      if(events.filter(x => x.event.section === this.collective && x.event.method === 'Disapproved').length > 0)
-        throw new Error(`Disapproved by ${this.collective}`);
-      else
-        throw new Error(`Expected one 'Executed' or 'MemberExecuted' event for ${this.collective}`);
-    }
-
-    const result = (executionEvents[0].event.data as any).result;
-
-    if(result.isErr) {
-      if(result.asErr.isModule) {
-        const error = result.asErr.asModule;
-        const metaError = this.helper.getApi()?.registry.findMetaError(error);
-        throw new Error(`Proposal execution failed with ${metaError.section}.${metaError.name}`);
-      } else {
-        throw new Error('Proposal execution failed with ' + result.asErr.toHuman());
-      }
-    }
-
-    return (executionEvents[0].event.data as any).proposalHash;
-  }
-
-  /**
-   * Returns an array of members' addresses.
-   */
-  async getMembers() {
-    return (await this.helper.callRpc(`api.query.${this.collective}.members`, [])).toHuman();
-  }
-
-  /**
-   * Returns the optional address of the prime member of the collective.
-   */
-  async getPrimeMember() {
-    return (await this.helper.callRpc(`api.query.${this.collective}.prime`, [])).toHuman();
-  }
-
-  /**
-   * Returns an array of proposal hashes that are currently active for this collective.
-   */
-  async getProposals() {
-    return (await this.helper.callRpc(`api.query.${this.collective}.proposals`, [])).toHuman();
-  }
-
-  /**
-   * Returns the call originally encoded under the specified hash.
-   * @param hash h256-encoded proposal
-   * @returns the optional call that the proposal hash stands for.
-   */
-  async getProposalCallOf(hash: string) {
-    return (await this.helper.callRpc(`api.query.${this.collective}.proposalOf`, [hash])).toHuman();
-  }
-
-  /**
-   * Returns the total number of proposals so far.
-   */
-  async getTotalProposalsCount() {
-    return (await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, [])).toNumber();
-  }
-
-  /**
-   * Creates a new proposal up for voting. If the threshold is set to 1, the proposal will be executed immediately.
-   * @param signer keyring of the proposer
-   * @param proposal constructed call to be executed if the proposal is successful
-   * @param voteThreshold minimal number of votes for the proposal to be verified and executed
-   * @param lengthBound byte length of the encoded call
-   * @returns promise of extrinsic execution and its result
-   */
-  async propose(signer: TSigner, proposal: any, voteThreshold: number, lengthBound = 10000) {
-    return await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [voteThreshold, proposal, lengthBound]);
-  }
-
-  /**
-   * Casts a vote to either approve or reject a proposal.
-   * @param signer keyring of the voter
-   * @param proposalHash hash of the proposal to be voted for
-   * @param proposalIndex absolute index of the proposal used for absolutely nothing but throwing pointless errors
-   * @param approve aye or nay
-   * @returns promise of extrinsic execution and its result
-   */
-  vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) {
-    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve]);
-  }
-
-  /**
-   * Executes a call immediately as a member of the collective. Needed for the Member origin.
-   * @param signer keyring of the executor member
-   * @param proposal constructed call to be executed by the member
-   * @param lengthBound byte length of the encoded call
-   * @returns promise of extrinsic execution
-   */
-  async execute(signer: TSigner, proposal: any, lengthBound = 10000) {
-    const result = await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.execute`, [proposal, lengthBound]);
-    this.checkExecutedEvent(result.result.events);
-    return result;
-  }
-
-  /**
-   * Attempt to close and execute a proposal. Note that there must already be enough votes to meet the threshold set when proposing.
-   * @param signer keyring of the executor. Can be absolutely anyone.
-   * @param proposalHash hash of the proposal to close
-   * @param proposalIndex index of the proposal generated on its creation
-   * @param weightBound weight of the proposed call. Can be obtained by calling `paymentInfo()` on the call.
-   * @param lengthBound byte length of the encoded call
-   * @returns promise of extrinsic execution and its result
-   */
-  async close(
-    signer: TSigner,
-    proposalHash: string,
-    proposalIndex: number,
-    weightBound: [number, number] | any = [20_000_000_000, 1000_000],
-    lengthBound = 10_000,
-  ) {
-    const result = await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [
-      proposalHash,
-      proposalIndex,
-      weightBound,
-      lengthBound,
-    ]);
-    this.checkExecutedEvent(result.result.events);
     return result;
-  }
-
-  /**
-   * Shut down a proposal, regardless of its current state.
-   * @param signer keyring of the disapprover. Must be root
-   * @param proposalHash hash of the proposal to close
-   * @returns promise of extrinsic execution and its result
-   */
-  disapproveProposal(signer: TSigner, proposalHash: string) {
-    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.disapproveProposal`, [proposalHash]);
-  }
-}
-
-class CollectiveMembershipGroup extends HelperGroup<UniqueHelper> {
-  /**
-   * Pallet name to make an API call to. Examples: 'councilMembership', 'technicalCommitteeMembership'
-   */
-  private membership: string;
-
-  constructor(helper: UniqueHelper, membership: string) {
-    super(helper);
-    this.membership = membership;
-  }
-
-  /**
-   * Returns an array of members' addresses according to the membership pallet's perception.
-   * Note that it does not recognize the original pallet's members set with `setMembers()`.
-   */
-  async getMembers() {
-    return (await this.helper.callRpc(`api.query.${this.membership}.members`, [])).toHuman();
-  }
-
-  /**
-   * Returns the optional address of the prime member of the collective.
-   */
-  async getPrimeMember() {
-    return (await this.helper.callRpc(`api.query.${this.membership}.prime`, [])).toHuman();
-  }
-
-  /**
-   * Add a member to the collective.
-   * @param signer keyring of the setter. Must be root
-   * @param member address of the member to add
-   * @returns promise of extrinsic execution and its result
-   */
-  addMember(signer: TSigner, member: string) {
-    return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.addMember`, [member]);
-  }
-
-  addMemberCall(member: string) {
-    return this.helper.constructApiCall(`api.tx.${this.membership}.addMember`, [member]);
-  }
-
-  /**
-   * Remove a member from the collective.
-   * @param signer keyring of the setter. Must be root
-   * @param member address of the member to remove
-   * @returns promise of extrinsic execution and its result
-   */
-  removeMember(signer: TSigner, member: string) {
-    return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.removeMember`, [member]);
-  }
-
-  removeMemberCall(member: string) {
-    return this.helper.constructApiCall(`api.tx.${this.membership}.removeMember`, [member]);
-  }
-
-  /**
-   * Set members of the collective to the given list of addresses.
-   * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion)
-   * @param members addresses of the members to set
-   * @returns promise of extrinsic execution and its result
-   */
-  resetMembers(signer: TSigner, members: string[]) {
-    return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.resetMembers`, [members]);
-  }
-
-  /**
-   * Set the collective's prime member to the given address.
-   * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion)
-   * @param prime address of the prime member of the collective
-   * @returns promise of extrinsic execution and its result
-   */
-  setPrime(signer: TSigner, prime: string) {
-    return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.setPrime`, [prime]);
-  }
-
-  setPrimeCall(member: string) {
-    return this.helper.constructApiCall(`api.tx.${this.membership}.setPrime`, [member]);
-  }
-
-  /**
-   * Remove the collective's prime member.
-   * @param signer keyring of the setter. Must be root (for the direct call, bypassing a public motion)
-   * @returns promise of extrinsic execution and its result
-   */
-  clearPrime(signer: TSigner) {
-    return this.helper.executeExtrinsic(signer, `api.tx.${this.membership}.clearPrime`, []);
-  }
-
-  clearPrimeCall() {
-    return this.helper.constructApiCall(`api.tx.${this.membership}.clearPrime`, []);
-  }
-}
-
-class RankedCollectiveGroup extends HelperGroup<UniqueHelper> {
-  /**
-   * Pallet name to make an API call to. Examples: 'FellowshipCollective'
-   */
-  private collective: string;
-
-  constructor(helper: UniqueHelper, collective: string) {
-    super(helper);
-    this.collective = collective;
-  }
-
-  addMember(signer: TSigner, newMember: string) {
-    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.addMember`, [newMember]);
   }
-
-  addMemberCall(newMember: string) {
-    return this.helper.constructApiCall(`api.tx.${this.collective}.addMember`, [newMember]);
-  }
-
-  removeMember(signer: TSigner, member: string, minRank: number) {
-    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.removeMember`, [member, minRank]);
-  }
-
-  removeMemberCall(newMember: string, minRank: number) {
-    return this.helper.constructApiCall(`api.tx.${this.collective}.removeMember`, [newMember, minRank]);
-  }
-
-  promote(signer: TSigner, member: string) {
-    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.promoteMember`, [member]);
-  }
-
-  promoteCall(member: string) {
-    return this.helper.constructApiCall(`api.tx.${this.collective}.promoteMember`, [member]);
-  }
-
-  demote(signer: TSigner, member: string) {
-    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.demoteMember`, [member]);
-  }
-
-  demoteCall(newMember: string) {
-    return this.helper.constructApiCall(`api.tx.${this.collective}.demoteMember`, [newMember]);
-  }
-
-  vote(signer: TSigner, pollIndex: number, aye: boolean) {
-    return this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [pollIndex, aye]);
-  }
-
-  async getMembers() {
-    return (await this.helper.getApi().query.fellowshipCollective.members.keys())
-      .map((key) => key.args[0].toString());
-  }
-
-  async getMemberRank(member: string) {
-    return (await this.helper.callRpc('api.query.fellowshipCollective.members', [member])).toJSON().rank;
-  }
-}
-
-class ReferendaGroup extends HelperGroup<UniqueHelper> {
-  /**
-   * Pallet name to make an API call to. Examples: 'FellowshipReferenda'
-   */
-  private referenda: string;
-
-  constructor(helper: UniqueHelper, referenda: string) {
-    super(helper);
-    this.referenda = referenda;
-  }
-
-  submit(
-    signer: TSigner,
-    proposalOrigin: string,
-    proposal: any,
-    enactmentMoment: any,
-  ) {
-    return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.submit`, [
-      {Origins: proposalOrigin},
-      proposal,
-      enactmentMoment,
-    ]);
-  }
-
-  placeDecisionDeposit(signer: TSigner, referendumIndex: number) {
-    return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.placeDecisionDeposit`, [referendumIndex]);
-  }
-
-  cancel(signer: TSigner, referendumIndex: number) {
-    return this.helper.executeExtrinsic(signer, `api.tx.${this.referenda}.cancel`, [referendumIndex]);
-  }
-
-  cancelCall(referendumIndex: number) {
-    return this.helper.constructApiCall(`api.tx.${this.referenda}.cancel`, [referendumIndex]);
-  }
-
-  async referendumInfo(referendumIndex: number) {
-    return (await this.helper.callRpc(`api.query.${this.referenda}.referendumInfoFor`, [referendumIndex])).toJSON();
-  }
-
-  async enactmentEventId(referendumIndex: number) {
-    const api = await this.helper.getApi();
-
-    const bytes = api.createType('([u8;8], Text, u32)', ['assembly', 'enactment', referendumIndex]).toU8a();
-    return blake2AsHex(bytes, 256);
-  }
-}
-
-export interface IFellowshipGroup {
-  collective: RankedCollectiveGroup;
-  referenda: ReferendaGroup;
 }
-
-export interface ICollectiveGroup {
-  collective: CollectiveGroup;
-  membership: CollectiveMembershipGroup;
-}
-
-class DemocracyGroup extends HelperGroup<UniqueHelper> {
-  // todo displace proposal into types?
-  propose(signer: TSigner, call: any, deposit: bigint) {
-    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.propose', [{Inline: call.method.toHex()}, deposit]);
-  }
-
-  proposeWithPreimage(signer: TSigner, preimage: string, deposit: bigint) {
-    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.propose', [{Legacy: preimage}, deposit]);
-  }
-
-  proposeCall(call: any, deposit: bigint) {
-    return this.helper.constructApiCall('api.tx.democracy.propose', [{Inline: call.method.toHex()}, deposit]);
-  }
-
-  second(signer: TSigner, proposalIndex: number) {
-    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.second', [proposalIndex]);
-  }
-
-  externalPropose(signer: TSigner, proposalCall: any) {
-    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalPropose', [{Inline: proposalCall.method.toHex()}]);
-  }
-
-  externalProposeMajority(signer: TSigner, proposalCall: any) {
-    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeMajority', [{Inline: proposalCall.method.toHex()}]);
-  }
-
-  externalProposeDefault(signer: TSigner, proposalCall: any) {
-    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeDefault', [{Inline: proposalCall.method.toHex()}]);
-  }
-
-  externalProposeDefaultWithPreimage(signer: TSigner, preimage: string) {
-    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.externalProposeDefault', [{Legacy: preimage}]);
-  }
-
-  externalProposeCall(proposalCall: any) {
-    return this.helper.constructApiCall('api.tx.democracy.externalPropose', [{Inline: proposalCall.method.toHex()}]);
-  }
-
-  externalProposeMajorityCall(proposalCall: any) {
-    return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [{Inline: proposalCall.method.toHex()}]);
-  }
-
-  externalProposeDefaultCall(proposalCall: any) {
-    return this.helper.constructApiCall('api.tx.democracy.externalProposeDefault', [{Inline: proposalCall.method.toHex()}]);
-  }
-
-  externalProposeDefaultWithPreimageCall(preimage: string) {
-    return this.helper.constructApiCall('api.tx.democracy.externalProposeDefault', [{Legacy: preimage}]);
-  }
-
-  // ... and blacklist external proposal hash.
-  vetoExternal(signer: TSigner, proposalHash: string) {
-    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vetoExternal', [proposalHash]);
-  }
-
-  vetoExternalCall(proposalHash: string) {
-    return this.helper.constructApiCall('api.tx.democracy.vetoExternal', [proposalHash]);
-  }
-
-  blacklist(signer: TSigner, proposalHash: string, referendumIndex: number | null = null) {
-    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.blacklist', [proposalHash, referendumIndex]);
-  }
-
-  blacklistCall(proposalHash: string, referendumIndex: number | null = null) {
-    return this.helper.constructApiCall('api.tx.democracy.blacklist', [proposalHash, referendumIndex]);
-  }
-
-  // proposal. CancelProposalOrigin (root or all techcom)
-  cancelProposal(signer: TSigner, proposalIndex: number) {
-    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.cancelProposal', [proposalIndex]);
-  }
-
-  cancelProposalCall(proposalIndex: number) {
-    return this.helper.constructApiCall('api.tx.democracy.cancelProposal', [proposalIndex]);
-  }
-
-  clearPublicProposals(signer: TSigner) {
-    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.clearPublicProposals', []);
-  }
-
-  fastTrack(signer: TSigner, proposalHash: string, votingPeriod: number, delayPeriod: number) {
-    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);
-  }
-
-  fastTrackCall(proposalHash: string, votingPeriod: number, delayPeriod: number) {
-    return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);
-  }
-
-  // referendum. CancellationOrigin (TechCom member)
-  emergencyCancel(signer: TSigner, referendumIndex: number) {
-    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.emergencyCancel', [referendumIndex]);
-  }
-
-  emergencyCancelCall(referendumIndex: number) {
-    return this.helper.constructApiCall('api.tx.democracy.emergencyCancel', [referendumIndex]);
-  }
-
-  vote(signer: TSigner, referendumIndex: number, vote: any) {
-    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, vote]);
-  }
-
-  removeVote(signer: TSigner, referendumIndex: number, targetAccount?: string) {
-    if(targetAccount) {
-      return this.helper.executeExtrinsic(signer, 'api.tx.democracy.removeOtherVote', [targetAccount, referendumIndex]);
-    } else {
-      return this.helper.executeExtrinsic(signer, 'api.tx.democracy.removeVote', [referendumIndex]);
-    }
-  }
-
-  unlock(signer: TSigner, targetAccount: string) {
-    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.unlock', [targetAccount]);
-  }
-
-  delegate(signer: TSigner, toAccount: string, conviction: PalletDemocracyConviction, balance: bigint) {
-    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.delegate', [toAccount, conviction, balance]);
-  }
-
-  undelegate(signer: TSigner) {
-    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.undelegate', []);
-  }
-
-  async referendumInfo(referendumIndex: number) {
-    return (await this.helper.callRpc('api.query.democracy.referendumInfoOf', [referendumIndex])).toJSON();
-  }
-
-  async publicProposals() {
-    return (await this.helper.callRpc('api.query.democracy.publicProps', [])).toJSON();
-  }
-
-  async findPublicProposal(proposalIndex: number) {
-    const proposalInfo = (await this.publicProposals()).find((proposalInfo: any[]) => proposalInfo[0] == proposalIndex);
-
-    return proposalInfo ? proposalInfo[1] : null;
-  }
-
-  async expectPublicProposal(proposalIndex: number) {
-    const proposal = await this.findPublicProposal(proposalIndex);
-
-    if(proposal) {
-      return proposal;
-    } else {
-      throw Error(`Proposal #${proposalIndex} is expected to exist`);
-    }
-  }
-
-  async getExternalProposal() {
-    return (await this.helper.callRpc('api.query.democracy.nextExternal', []));
-  }
-
-  async expectExternalProposal() {
-    const proposal = await this.getExternalProposal();
-
-    if(proposal) {
-      return proposal;
-    } else {
-      throw Error('An external proposal is expected to exist');
-    }
-  }
-
-  /* setMetadata? */
 
-  /* todo?
-  referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) {
-    return this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true);
-  }*/
-}
 
 class PreimageGroup extends HelperGroup<UniqueHelper> {
   async getPreimageInfo(h256: string) {
@@ -3569,171 +2930,9 @@
    */
   unrequestPreimage(signer: TSigner, h256: string) {
     return this.helper.executeExtrinsic(signer, 'api.tx.preimage.unrequestPreimage', [h256]);
-  }
-}
-
-class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {
-  async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) {
-    await this.helper.executeExtrinsic(
-      signer,
-      'api.tx.foreignAssets.registerForeignAsset',
-      [ownerAddress, location, metadata],
-      true,
-    );
-  }
-
-  async update(signer: TSigner, foreignAssetId: number, location: any, metadata: IForeignAssetMetadata) {
-    await this.helper.executeExtrinsic(
-      signer,
-      'api.tx.foreignAssets.updateForeignAsset',
-      [foreignAssetId, location, metadata],
-      true,
-    );
-  }
-}
-
-export class XcmGroup<T extends ChainHelperBase> extends HelperGroup<T> {
-  palletName: string;
-
-  constructor(helper: T, palletName: string) {
-    super(helper);
-
-    this.palletName = palletName;
-  }
-
-  async limitedReserveTransferAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number, weightLimit: any) {
-    await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.limitedReserveTransferAssets`, [destination, beneficiary, assets, feeAssetItem, weightLimit], true);
-  }
-
-  async setSafeXcmVersion(signer: TSigner, version: number) {
-    await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.forceDefaultXcmVersion`, [version], true);
-  }
-
-  async teleportAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number) {
-    await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.teleportAssets`, [destination, beneficiary, assets, feeAssetItem], true);
-  }
-
-  async teleportNativeAsset(signer: TSigner, destinationParaId: number, targetAccount: Uint8Array, amount: bigint, xcmVersion = 3) {
-    const destinationContent = {
-      parents: 0,
-      interior: {
-        X1: {
-          Parachain: destinationParaId,
-        },
-      },
-    };
-
-    const beneficiaryContent = {
-      parents: 0,
-      interior: {
-        X1: {
-          AccountId32: {
-            network: 'Any',
-            id: targetAccount,
-          },
-        },
-      },
-    };
-
-    const assetsContent = [
-      {
-        id: {
-          Concrete: {
-            parents: 0,
-            interior: 'Here',
-          },
-        },
-        fun: {
-          Fungible: amount,
-        },
-      },
-    ];
-
-    let destination;
-    let beneficiary;
-    let assets;
-
-    if(xcmVersion == 2) {
-      destination = {V1: destinationContent};
-      beneficiary = {V1: beneficiaryContent};
-      assets = {V1: assetsContent};
-
-    } else if(xcmVersion == 3) {
-      destination = {V2: destinationContent};
-      beneficiary = {V2: beneficiaryContent};
-      assets = {V2: assetsContent};
-
-    } else {
-      throw Error('Unknown XCM version: ' + xcmVersion);
-    }
-
-    const feeAssetItem = 0;
-
-    await this.teleportAssets(signer, destination, beneficiary, assets, feeAssetItem);
-  }
-
-  async send(signer: IKeyringPair, destination: any, message: any) {
-    await this.helper.executeExtrinsic(
-      signer,
-      `api.tx.${this.palletName}.send`,
-      [
-        destination,
-        message,
-      ],
-      true,
-    );
   }
 }
 
-export class XTokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {
-  async transfer(signer: TSigner, currencyId: any, amount: bigint, destination: any, destWeight: any) {
-    await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transfer', [currencyId, amount, destination, destWeight], true);
-  }
-
-  async transferMultiasset(signer: TSigner, asset: any, destination: any, destWeight: any) {
-    await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMultiasset', [asset, destination, destWeight], true);
-  }
-
-  async transferMulticurrencies(signer: TSigner, currencies: any[], feeItem: number, destLocation: any, destWeight: any) {
-    await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMulticurrencies', [currencies, feeItem, destLocation, destWeight], true);
-  }
-}
-
-
-
-export class TokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {
-  async accounts(address: string, currencyId: any) {
-    const {free} = (await this.helper.callRpc('api.query.tokens.accounts', [address, currencyId])).toJSON() as any;
-    return BigInt(free);
-  }
-}
-
-export class AssetsGroup<T extends ChainHelperBase> extends HelperGroup<T> {
-  async create(signer: TSigner, assetId: number, admin: string, minimalBalance: bigint) {
-    await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true);
-  }
-
-  async setMetadata(signer: TSigner, assetId: number, name: string, symbol: string, decimals: number) {
-    await this.helper.executeExtrinsic(signer, 'api.tx.assets.setMetadata', [assetId, name, symbol, decimals], true);
-  }
-
-  async mint(signer: TSigner, assetId: number, beneficiary: string, amount: bigint) {
-    await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);
-  }
-
-  async account(assetId: string | number, address: string) {
-    const accountAsset = (
-      await this.helper.callRpc('api.query.assets.account', [assetId, address])
-    ).toJSON()! as any;
-
-    if(accountAsset !== null) {
-      return BigInt(accountAsset['balance']);
-    } else {
-      return null;
-    }
-  }
-}
-
 class UtilityGroup<T extends ChainHelperBase> extends HelperGroup<T> {
   async batch(signer: TSigner, txs: any[]) {
     return await this.helper.executeExtrinsic(signer, 'api.tx.utility.batch', [txs]);
@@ -3747,9 +2946,7 @@
     return this.helper.constructApiCall('api.tx.utility.batchAll', [txs]);
   }
 }
-
 
-
 export type ChainHelperBaseConstructor = new (...args: any[]) => ChainHelperBase;
 export type UniqueHelperConstructor = new (...args: any[]) => UniqueHelper;
 
@@ -3760,17 +2957,7 @@
   rft: RFTGroup;
   ft: FTGroup;
   staking: StakingGroup;
-  scheduler: SchedulerGroup;
-  collatorSelection: CollatorSelectionGroup;
-  council: ICollectiveGroup;
-  technicalCommittee: ICollectiveGroup;
-  fellowship: IFellowshipGroup;
-  democracy: DemocracyGroup;
   preimage: PreimageGroup;
-  foreignAssets: ForeignAssetsGroup;
-  xcm: XcmGroup<UniqueHelper>;
-  xTokens: XTokensGroup<UniqueHelper>;
-  tokens: TokensGroup<UniqueHelper>;
   utility: UtilityGroup<UniqueHelper>;
 
   constructor(logger?: ILogger, options: { [key: string]: any } = {}) {
@@ -3782,88 +2969,9 @@
     this.rft = new RFTGroup(this);
     this.ft = new FTGroup(this);
     this.staking = new StakingGroup(this);
-    this.scheduler = new SchedulerGroup(this);
-    this.collatorSelection = new CollatorSelectionGroup(this);
-    this.council = {
-      collective: new CollectiveGroup(this, 'council'),
-      membership: new CollectiveMembershipGroup(this, 'councilMembership'),
-    };
-    this.technicalCommittee = {
-      collective: new CollectiveGroup(this, 'technicalCommittee'),
-      membership: new CollectiveMembershipGroup(this, 'technicalCommitteeMembership'),
-    };
-    this.fellowship = {
-      collective: new RankedCollectiveGroup(this, 'fellowshipCollective'),
-      referenda: new ReferendaGroup(this, 'fellowshipReferenda'),
-    };
-    this.democracy = new DemocracyGroup(this);
     this.preimage = new PreimageGroup(this);
-    this.foreignAssets = new ForeignAssetsGroup(this);
-    this.xcm = new XcmGroup(this, 'polkadotXcm');
-    this.xTokens = new XTokensGroup(this);
-    this.tokens = new TokensGroup(this);
     this.utility = new UtilityGroup(this);
   }
-}
-
-// eslint-disable-next-line @typescript-eslint/naming-convention
-function ScheduledUniqueHelper<T extends UniqueHelperConstructor>(Base: T) {
-  return class extends Base {
-    scheduleFn: 'schedule' | 'scheduleAfter';
-    blocksNum: number;
-    options: ISchedulerOptions;
-
-    constructor(...args: any[]) {
-      const logger = args[0] as ILogger;
-      const options = args[1] as {
-        scheduleFn: 'schedule' | 'scheduleAfter',
-        blocksNum: number,
-        options: ISchedulerOptions
-      };
-
-      super(logger);
-
-      this.scheduleFn = options.scheduleFn;
-      this.blocksNum = options.blocksNum;
-      this.options = options.options;
-    }
-
-    executeExtrinsic(sender: IKeyringPair, scheduledExtrinsic: string, scheduledParams: any[], expectSuccess?: boolean): Promise<ITransactionResult> {
-      const scheduledTx = this.constructApiCall(scheduledExtrinsic, scheduledParams);
-
-      const mandatorySchedArgs = [
-        this.blocksNum,
-        this.options.periodic ? [this.options.periodic.period, this.options.periodic.repetitions] : null,
-        this.options.priority ?? null,
-        scheduledTx,
-      ];
-
-      let schedArgs;
-      let scheduleFn;
-
-      if(this.options.scheduledId) {
-        schedArgs = [this.options.scheduledId!, ...mandatorySchedArgs];
-
-        if(this.scheduleFn == 'schedule') {
-          scheduleFn = 'scheduleNamed';
-        } else if(this.scheduleFn == 'scheduleAfter') {
-          scheduleFn = 'scheduleNamedAfter';
-        }
-      } else {
-        schedArgs = mandatorySchedArgs;
-        scheduleFn = this.scheduleFn;
-      }
-
-      const extrinsic = 'api.tx.scheduler.' + scheduleFn;
-
-      return super.executeExtrinsic(
-        sender,
-        extrinsic as any,
-        schedArgs,
-        expectSuccess,
-      );
-    }
-  };
 }
 
 export class UniqueBaseCollection {
@@ -3973,22 +3081,6 @@
 
   async burn(signer: TSigner) {
     return await this.helper.collection.burn(signer, this.collectionId);
-  }
-
-  scheduleAt<T extends UniqueHelper>(
-    executionBlockNumber: number,
-    options: ISchedulerOptions = {},
-  ) {
-    const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);
-    return new UniqueBaseCollection(this.collectionId, scheduledHelper);
-  }
-
-  scheduleAfter<T extends UniqueHelper>(
-    blocksBeforeExecution: number,
-    options: ISchedulerOptions = {},
-  ) {
-    const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);
-    return new UniqueBaseCollection(this.collectionId, scheduledHelper);
   }
 }
 
@@ -4083,22 +3175,6 @@
   async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {
     return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);
   }
-
-  scheduleAt<T extends UniqueHelper>(
-    executionBlockNumber: number,
-    options: ISchedulerOptions = {},
-  ) {
-    const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);
-    return new UniqueNFTCollection(this.collectionId, scheduledHelper);
-  }
-
-  scheduleAfter<T extends UniqueHelper>(
-    blocksBeforeExecution: number,
-    options: ISchedulerOptions = {},
-  ) {
-    const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);
-    return new UniqueNFTCollection(this.collectionId, scheduledHelper);
-  }
 }
 
 export class UniqueRFTCollection extends UniqueBaseCollection {
@@ -4204,22 +3280,6 @@
   async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {
     return await this.helper.rft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);
   }
-
-  scheduleAt<T extends UniqueHelper>(
-    executionBlockNumber: number,
-    options: ISchedulerOptions = {},
-  ) {
-    const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);
-    return new UniqueRFTCollection(this.collectionId, scheduledHelper);
-  }
-
-  scheduleAfter<T extends UniqueHelper>(
-    blocksBeforeExecution: number,
-    options: ISchedulerOptions = {},
-  ) {
-    const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);
-    return new UniqueRFTCollection(this.collectionId, scheduledHelper);
-  }
 }
 
 export class UniqueFTCollection extends UniqueBaseCollection {
@@ -4265,22 +3325,6 @@
 
   async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount = 1n) {
     return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount);
-  }
-
-  scheduleAt<T extends UniqueHelper>(
-    executionBlockNumber: number,
-    options: ISchedulerOptions = {},
-  ) {
-    const scheduledHelper = this.helper.scheduler.scheduleAt<T>(executionBlockNumber, options);
-    return new UniqueFTCollection(this.collectionId, scheduledHelper);
-  }
-
-  scheduleAfter<T extends UniqueHelper>(
-    blocksBeforeExecution: number,
-    options: ISchedulerOptions = {},
-  ) {
-    const scheduledHelper = this.helper.scheduler.scheduleAfter<T>(blocksBeforeExecution, options);
-    return new UniqueFTCollection(this.collectionId, scheduledHelper);
   }
 }
 
@@ -4321,22 +3365,6 @@
 
   nestingAccount() {
     return this.collection.helper.util.getTokenAccount(this);
-  }
-
-  scheduleAt<T extends UniqueHelper>(
-    executionBlockNumber: number,
-    options: ISchedulerOptions = {},
-  ) {
-    const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);
-    return new UniqueBaseToken(this.tokenId, scheduledCollection);
-  }
-
-  scheduleAfter<T extends UniqueHelper>(
-    blocksBeforeExecution: number,
-    options: ISchedulerOptions = {},
-  ) {
-    const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);
-    return new UniqueBaseToken(this.tokenId, scheduledCollection);
   }
 }
 
@@ -4395,22 +3423,6 @@
   async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId) {
     return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj);
   }
-
-  scheduleAt<T extends UniqueHelper>(
-    executionBlockNumber: number,
-    options: ISchedulerOptions = {},
-  ) {
-    const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);
-    return new UniqueNFToken(this.tokenId, scheduledCollection);
-  }
-
-  scheduleAfter<T extends UniqueHelper>(
-    blocksBeforeExecution: number,
-    options: ISchedulerOptions = {},
-  ) {
-    const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);
-    return new UniqueNFToken(this.tokenId, scheduledCollection);
-  }
 }
 
 export class UniqueRFToken extends UniqueBaseToken {
@@ -4479,21 +3491,5 @@
 
   async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount = 1n) {
     return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj, amount);
-  }
-
-  scheduleAt<T extends UniqueHelper>(
-    executionBlockNumber: number,
-    options: ISchedulerOptions = {},
-  ) {
-    const scheduledCollection = this.collection.scheduleAt<T>(executionBlockNumber, options);
-    return new UniqueRFToken(this.tokenId, scheduledCollection);
-  }
-
-  scheduleAfter<T extends UniqueHelper>(
-    blocksBeforeExecution: number,
-    options: ISchedulerOptions = {},
-  ) {
-    const scheduledCollection = this.collection.scheduleAfter<T>(blocksBeforeExecution, options);
-    return new UniqueRFToken(this.tokenId, scheduledCollection);
   }
 }
modifiedtests/src/util/playgrounds/unique.xcm.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/unique.xcm.ts
+++ b/tests/src/util/playgrounds/unique.xcm.ts
@@ -1,9 +1,9 @@
-
 import {ApiPromise, WsProvider} from '@polkadot/api';
-import {AssetsGroup, ChainHelperBase, EthereumBalanceGroup, HelperGroup, SubstrateBalanceGroup, TokensGroup, UniqueHelper, XTokensGroup, XcmGroup} from './unique';
-import {ILogger,  TSigner} from './types';
-import {SudoHelper} from './unique.dev';
-import {AcalaAssetMetadata, DemocracyStandardAccountVote, MoonbeamAssetInfo} from './types.xcm';
+import {IKeyringPair} from '@polkadot/types/types';
+import {ChainHelperBase, EthereumBalanceGroup, HelperGroup, SubstrateBalanceGroup, UniqueHelper} from './unique';
+import {ILogger, TSigner, TSubstrateAccount} from './types';
+import {AcalaAssetMetadata, DemocracyStandardAccountVote, IForeignAssetMetadata, MoonbeamAssetInfo} from './types.xcm';
+
 
 export class XcmChainHelper extends ChainHelperBase {
   async connect(wsEndpoint: string, _listeners?: any): Promise<void> {
@@ -102,6 +102,169 @@
     await this.helper.executeExtrinsic(signer, 'api.tx.xcmHelper.whitelistToken', [assetId], true);
   }
 }
+
+export class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {
+  async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) {
+    await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.foreignAssets.registerForeignAsset',
+      [ownerAddress, location, metadata],
+      true,
+    );
+  }
+
+  async update(signer: TSigner, foreignAssetId: number, location: any, metadata: IForeignAssetMetadata) {
+    await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.foreignAssets.updateForeignAsset',
+      [foreignAssetId, location, metadata],
+      true,
+    );
+  }
+}
+
+export class XcmGroup<T extends ChainHelperBase> extends HelperGroup<T> {
+  palletName: string;
+
+  constructor(helper: T, palletName: string) {
+    super(helper);
+
+    this.palletName = palletName;
+  }
+
+  async limitedReserveTransferAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number, weightLimit: any) {
+    await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.limitedReserveTransferAssets`, [destination, beneficiary, assets, feeAssetItem, weightLimit], true);
+  }
+
+  async setSafeXcmVersion(signer: TSigner, version: number) {
+    await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.forceDefaultXcmVersion`, [version], true);
+  }
+
+  async teleportAssets(signer: TSigner, destination: any, beneficiary: any, assets: any, feeAssetItem: number) {
+    await this.helper.executeExtrinsic(signer, `api.tx.${this.palletName}.teleportAssets`, [destination, beneficiary, assets, feeAssetItem], true);
+  }
+
+  async teleportNativeAsset(signer: TSigner, destinationParaId: number, targetAccount: Uint8Array, amount: bigint, xcmVersion = 3) {
+    const destinationContent = {
+      parents: 0,
+      interior: {
+        X1: {
+          Parachain: destinationParaId,
+        },
+      },
+    };
+
+    const beneficiaryContent = {
+      parents: 0,
+      interior: {
+        X1: {
+          AccountId32: {
+            network: 'Any',
+            id: targetAccount,
+          },
+        },
+      },
+    };
+
+    const assetsContent = [
+      {
+        id: {
+          Concrete: {
+            parents: 0,
+            interior: 'Here',
+          },
+        },
+        fun: {
+          Fungible: amount,
+        },
+      },
+    ];
+
+    let destination;
+    let beneficiary;
+    let assets;
+
+    if(xcmVersion == 2) {
+      destination = {V1: destinationContent};
+      beneficiary = {V1: beneficiaryContent};
+      assets = {V1: assetsContent};
+
+    } else if(xcmVersion == 3) {
+      destination = {V2: destinationContent};
+      beneficiary = {V2: beneficiaryContent};
+      assets = {V2: assetsContent};
+
+    } else {
+      throw Error('Unknown XCM version: ' + xcmVersion);
+    }
+
+    const feeAssetItem = 0;
+
+    await this.teleportAssets(signer, destination, beneficiary, assets, feeAssetItem);
+  }
+
+  async send(signer: IKeyringPair, destination: any, message: any) {
+    await this.helper.executeExtrinsic(
+      signer,
+      `api.tx.${this.palletName}.send`,
+      [
+        destination,
+        message,
+      ],
+      true,
+    );
+  }
+}
+
+export class XTokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {
+  async transfer(signer: TSigner, currencyId: any, amount: bigint, destination: any, destWeight: any) {
+    await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transfer', [currencyId, amount, destination, destWeight], true);
+  }
+
+  async transferMultiasset(signer: TSigner, asset: any, destination: any, destWeight: any) {
+    await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMultiasset', [asset, destination, destWeight], true);
+  }
+
+  async transferMulticurrencies(signer: TSigner, currencies: any[], feeItem: number, destLocation: any, destWeight: any) {
+    await this.helper.executeExtrinsic(signer, 'api.tx.xTokens.transferMulticurrencies', [currencies, feeItem, destLocation, destWeight], true);
+  }
+}
+
+
+
+export class TokensGroup<T extends ChainHelperBase> extends HelperGroup<T> {
+  async accounts(address: string, currencyId: any) {
+    const {free} = (await this.helper.callRpc('api.query.tokens.accounts', [address, currencyId])).toJSON() as any;
+    return BigInt(free);
+  }
+}
+
+export class AssetsGroup<T extends ChainHelperBase> extends HelperGroup<T> {
+  async create(signer: TSigner, assetId: number, admin: string, minimalBalance: bigint) {
+    await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true);
+  }
+
+  async setMetadata(signer: TSigner, assetId: number, name: string, symbol: string, decimals: number) {
+    await this.helper.executeExtrinsic(signer, 'api.tx.assets.setMetadata', [assetId, name, symbol, decimals], true);
+  }
+
+  async mint(signer: TSigner, assetId: number, beneficiary: string, amount: bigint) {
+    await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);
+  }
+
+  async account(assetId: string | number, address: string) {
+    const accountAsset = (
+      await this.helper.callRpc('api.query.assets.account', [assetId, address])
+    ).toJSON()! as any;
+
+    if(accountAsset !== null) {
+      return BigInt(accountAsset['balance']);
+    } else {
+      return null;
+    }
+  }
+}
+
 export class RelayHelper extends XcmChainHelper {
   balance: SubstrateBalanceGroup<RelayHelper>;
   xcm: XcmGroup<RelayHelper>;