git.delta.rocks / unique-network / refs/commits / f32d3e56bf10

difftreelog

Merge pull request #996 from UniqueNetwork/tests/playgrounds-refactor

Yaroslav Bolyukin2023-09-15parents: #1054e65 #477a07d.patch.diff
in: master
refactor(playgorunds): rearranging the code structure

10 files changed

modified.envrcdiffbeforeafterboth
--- a/.envrc
+++ b/.envrc
@@ -30,7 +30,7 @@
     fi
 
     echo -e "${GREEN}Baedeker env updated${RESET}"
-    nginx_id=$(docker compose -f .baedeker/.bdk-env/docker-compose.yml ps --format=json | jq -r '.[] | select(.Service == "nginx") | .ID' -e)
+    nginx_id=$(docker compose -f .baedeker/.bdk-env/docker-compose.yml ps --format=json | jq -s 'flatten' | jq -r '.[] | select(.Service == "nginx") | .ID' -e)
     if ! [ $? -eq 0 ]; then
         echo -e "${RED}Nginx container not found${RESET}"
         exit 0
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/maintenance.seqtest.tsdiffbeforeafterboth
--- a/tests/src/maintenance.seqtest.ts
+++ b/tests/src/maintenance.seqtest.ts
@@ -192,19 +192,22 @@
       const blocksToWait = 6;
 
       // Scheduling works before the maintenance
-      await nftBeforeMM.scheduleAfter(blocksToWait, {scheduledId: scheduledIdBeforeMM})
-        .transfer(bob, {Substrate: superuser.address});
+      await helper.scheduler.scheduleAfter(blocksToWait, {scheduledId: scheduledIdBeforeMM})
+        .nft.transferToken(bob, collection.collectionId, nftBeforeMM.tokenId, {Substrate: superuser.address});
+
 
       await helper.wait.newBlocks(blocksToWait + 1);
       expect(await nftBeforeMM.getOwner()).to.be.deep.equal({Substrate: superuser.address});
 
       // Schedule a transaction that should occur *during* the maintenance
-      await nftDuringMM.scheduleAfter(blocksToWait, {scheduledId: scheduledIdDuringMM})
-        .transfer(bob, {Substrate: superuser.address});
+      await helper.scheduler.scheduleAfter(blocksToWait, {scheduledId: scheduledIdDuringMM})
+        .nft.transferToken(bob, collection.collectionId, nftDuringMM.tokenId, {Substrate: superuser.address});
+
 
       // Schedule a transaction that should occur *after* the maintenance
-      await nftDuringMM.scheduleAfter(blocksToWait * 2, {scheduledId: scheduledIdBunkerThroughMM})
-        .transfer(bob, {Substrate: superuser.address});
+      await helper.scheduler.scheduleAfter(blocksToWait * 2, {scheduledId: scheduledIdBunkerThroughMM})
+        .nft.transferToken(bob, collection.collectionId, nftDuringMM.tokenId, {Substrate: superuser.address});
+
 
       await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.enable', []);
       expect(await maintenanceEnabled(helper.getApi()), 'MM is OFF when it should be ON').to.be.true;
@@ -214,16 +217,16 @@
       expect(await nftDuringMM.getOwner()).to.be.deep.equal({Substrate: bob.address});
 
       // Any attempts to schedule a tx during the MM should be rejected
-      await expect(nftDuringMM.scheduleAfter(blocksToWait, {scheduledId: scheduledIdAttemptDuringMM})
-        .transfer(bob, {Substrate: superuser.address}))
+      await expect(helper.scheduler.scheduleAfter(blocksToWait, {scheduledId: scheduledIdAttemptDuringMM})
+        .nft.transferToken(bob, collection.collectionId, nftDuringMM.tokenId, {Substrate: superuser.address}))
         .to.be.rejectedWith(/Invalid Transaction: Transaction call is not expected/);
 
       await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.disable', []);
       expect(await maintenanceEnabled(helper.getApi()), 'MM is ON when it should be OFF').to.be.false;
 
       // Scheduling works after the maintenance
-      await nftAfterMM.scheduleAfter(blocksToWait, {scheduledId: scheduledIdAfterMM})
-        .transfer(bob, {Substrate: superuser.address});
+      await helper.scheduler.scheduleAfter(blocksToWait, {scheduledId: scheduledIdAfterMM})
+        .nft.transferToken(bob, collection.collectionId, nftAfterMM.tokenId, {Substrate: superuser.address});
 
       await helper.wait.newBlocks(blocksToWait + 1);
 
modifiedtests/src/scheduler.seqtest.tsdiffbeforeafterboth
48 const scheduledId = scheduleKind == 'named' ? helper.arrange.makeScheduledId() : undefined;48 const scheduledId = scheduleKind == 'named' ? helper.arrange.makeScheduledId() : undefined;
49 const blocksBeforeExecution = 4;49 const blocksBeforeExecution = 4;
50
51 await token.scheduleAfter(blocksBeforeExecution, {scheduledId})50 await helper.scheduler.scheduleAfter(blocksBeforeExecution, {scheduledId})
52 .transfer(alice, {Substrate: bob.address});51 .nft.transferToken(alice, collection.collectionId, token.tokenId, {Substrate: bob.address});
53 const executionBlock = await helper.chain.getLatestBlockNumber() + blocksBeforeExecution + 1;52 const executionBlock = await helper.chain.getLatestBlockNumber() + blocksBeforeExecution + 1;
5453
55 expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});54 expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});
103102
104 expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});103 expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address});
105104
106 await token.scheduleAfter(waitForBlocks, {scheduledId})105 await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})
107 .transfer(alice, {Substrate: bob.address});106 .nft.transferToken(alice, collection.collectionId, token.tokenId, {Substrate: bob.address});
108 const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;107 const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;
109108
110 await helper.scheduler.cancelScheduled(alice, scheduledId);109 await helper.scheduler.cancelScheduled(alice, scheduledId);
363 const scheduledId = helper.arrange.makeScheduledId();362 const scheduledId = helper.arrange.makeScheduledId();
364 const waitForBlocks = 4;363 const waitForBlocks = 4;
365364
366 await token.scheduleAfter(waitForBlocks, {scheduledId})365 await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})
367 .transfer(bob, {Substrate: alice.address});366 .nft.transferToken(bob, collection.collectionId, token.tokenId, {Substrate: alice.address});
368 const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;367 const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;
369368
370 await helper.getSudo().scheduler.cancelScheduled(superuser, scheduledId);369 await helper.getSudo().scheduler.cancelScheduled(superuser, scheduledId);
404 const scheduledId = helper.arrange.makeScheduledId();403 const scheduledId = helper.arrange.makeScheduledId();
405 const waitForBlocks = 6;404 const waitForBlocks = 6;
406405
407 await token.scheduleAfter(waitForBlocks, {scheduledId})406 await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})
408 .transfer(bob, {Substrate: alice.address});407 .nft.transferToken(bob, collection.collectionId, token.tokenId, {Substrate: alice.address});
409 const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;408 const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;
410409
411 const priority = 112;410 const priority = 112;
583 const scheduledId = helper.arrange.makeScheduledId();582 const scheduledId = helper.arrange.makeScheduledId();
584 const waitForBlocks = 4;583 const waitForBlocks = 4;
585584
586 await token.scheduleAfter(waitForBlocks, {scheduledId})585 await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})
587 .transfer(alice, {Substrate: bob.address});586 .nft.transferToken(alice, collection.collectionId, token.tokenId, {Substrate: bob.address});
588 const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;587 const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;
589588
590 const scheduled = helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId});589 const scheduled = helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId});
614 const scheduledId = helper.arrange.makeScheduledId();613 const scheduledId = helper.arrange.makeScheduledId();
615 const waitForBlocks = 4;614 const waitForBlocks = 4;
616615
617 await token.scheduleAfter(waitForBlocks, {scheduledId})616 await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})
618 .transfer(alice, {Substrate: bob.address});617 .nft.transferToken(alice, collection.collectionId, token.tokenId, {Substrate: bob.address});
619 const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;618 const executionBlock = await helper.chain.getLatestBlockNumber() + waitForBlocks + 1;
620619
621 await expect(helper.scheduler.cancelScheduled(bob, scheduledId))620 await expect(helper.scheduler.cancelScheduled(bob, scheduledId))
655 const scheduledId = helper.arrange.makeScheduledId();654 const scheduledId = helper.arrange.makeScheduledId();
656 const waitForBlocks = 4;655 const waitForBlocks = 4;
657656
658 await token.scheduleAfter(waitForBlocks, {scheduledId})657 await helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId})
659 .transfer(bob, {Substrate: alice.address});658 .nft.transferToken(bob, collection.collectionId, token.tokenId, {Substrate: alice.address});
660659
661 const priority = 112;660 const priority = 112;
662 await expect(helper.scheduler.changePriority(alice, scheduledId, priority))661 await expect(helper.scheduler.changePriority(alice, scheduledId, priority))
modifiedtests/src/util/playgrounds/types.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/types.ts
+++ b/tests/src/util/playgrounds/types.ts
@@ -216,43 +216,6 @@
   },
 }
 
-export interface IForeignAssetMetadata {
-  name?: number | Uint8Array,
-  symbol?: string,
-  decimals?: number,
-  minimalBalance?: bigint,
-}
-
-export interface MoonbeamAssetInfo {
-  location: any,
-  metadata: {
-    name: string,
-    symbol: string,
-    decimals: number,
-    isFrozen: boolean,
-    minimalBalance: bigint,
-  },
-  existentialDeposit: bigint,
-  isSufficient: boolean,
-  unitsPerSecond: bigint,
-  numAssetsWeightHint: number,
-}
-
-export interface AcalaAssetMetadata {
-  name: string,
-  symbol: string,
-  decimals: number,
-  minimalBalance: bigint,
-}
-
-export interface DemocracyStandardAccountVote {
-  balance: bigint,
-  vote: {
-    aye: boolean,
-    conviction: number,
-  },
-}
-
 export interface DemocracySplitAccount {
   aye: bigint,
   nay: bigint,
addedtests/src/util/playgrounds/types.xcm.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/util/playgrounds/types.xcm.ts
@@ -0,0 +1,36 @@
+export interface AcalaAssetMetadata {
+  name: string,
+  symbol: string,
+  decimals: number,
+  minimalBalance: bigint,
+}
+
+export interface MoonbeamAssetInfo {
+  location: any,
+  metadata: {
+    name: string,
+    symbol: string,
+    decimals: number,
+    isFrozen: boolean,
+    minimalBalance: bigint,
+  },
+  existentialDeposit: bigint,
+  isSufficient: boolean,
+  unitsPerSecond: bigint,
+  numAssetsWeightHint: number,
+}
+
+export interface DemocracyStandardAccountVote {
+  balance: bigint,
+  vote: {
+    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
--- a/tests/src/util/playgrounds/unique.dev.ts
+++ b/tests/src/util/playgrounds/unique.dev.ts
@@ -3,16 +3,18 @@
 
 import {stringToU8a} from '@polkadot/util';
 import {blake2AsHex, encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto';
-import {UniqueHelper, MoonbeamHelper, ChainHelperBase, AcalaHelper, RelayHelper, WestmintHelper, AstarHelper, PolkadexHelper} from './unique';
+import {UniqueHelper, ChainHelperBase, ChainHelperBaseConstructor, HelperGroup, UniqueHelperConstructor} from './unique';
 import {ApiPromise, Keyring, WsProvider} from '@polkadot/api';
 import * as defs from '../../interfaces/definitions';
 import {IKeyringPair} from '@polkadot/types/types';
 import {EventRecord} from '@polkadot/types/interfaces';
-import {ICrossAccountId, IPovInfo, ITransactionResult, TSigner} from './types';
+import {ICrossAccountId, ILogger, IPovInfo, ISchedulerOptions, ITransactionResult, TSigner} from './types';
 import {FrameSystemEventRecord, XcmV2TraitsError} from '@polkadot/types/lookup';
-import {VoidFn} from '@polkadot/api/types';
+import {SignerOptions, VoidFn} from '@polkadot/api/types';
 import {Pallets} from '..';
 import {spawnSync} from 'child_process';
+import {AcalaHelper, AstarHelper, MoonbeamHelper, PolkadexHelper, RelayHelper, WestmintHelper, ForeignAssetsGroup, XcmGroup, XTokensGroup, TokensGroup} from './unique.xcm';
+import {CollectiveGroup, CollectiveMembershipGroup, DemocracyGroup, ICollectiveGroup, IFellowshipGroup, RankedCollectiveGroup, ReferendaGroup} from './unique.governance';
 
 export class SilentLogger {
   log(_msg: any, _level: any): void { }
@@ -260,6 +262,192 @@
   };
 }
 
+// eslint-disable-next-line @typescript-eslint/naming-convention
+export function SudoHelper<T extends ChainHelperBaseConstructor>(Base: T) {
+  return class extends Base {
+    constructor(...args: any[]) {
+      super(...args);
+    }
+
+    async executeExtrinsic(
+      sender: IKeyringPair,
+      extrinsic: string,
+      params: any[],
+      expectSuccess?: boolean,
+      options: Partial<SignerOptions> | null = null,
+    ): Promise<ITransactionResult> {
+      const call = this.constructApiCall(extrinsic, params);
+      const result = await super.executeExtrinsic(
+        sender,
+        'api.tx.sudo.sudo',
+        [call],
+        expectSuccess,
+        options,
+      );
+
+      if(result.status === 'Fail') return result;
+
+      const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult;
+      if(data.isErr) {
+        if(data.asErr.isModule) {
+          const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule;
+          const metaError = super.getApi()?.registry.findMetaError(error);
+          throw new Error(`${metaError.section}.${metaError.name}`);
+        } else if(data.asErr.isToken) {
+          throw new Error(`Token: ${data.asErr.asToken}`);
+        }
+        // May be [object Object] in case of unhandled non-unit enum
+        throw new Error(`Misc: ${data.asErr.toHuman()}`);
+      }
+      return result;
+    }
+    async executeExtrinsicUncheckedWeight(
+      sender: IKeyringPair,
+      extrinsic: string,
+      params: any[],
+      expectSuccess?: boolean,
+      options: Partial<SignerOptions> | null = null,
+    ): Promise<ITransactionResult> {
+      const call = this.constructApiCall(extrinsic, params);
+      const result = await super.executeExtrinsic(
+        sender,
+        'api.tx.sudo.sudoUncheckedWeight',
+        [call, {refTime: 0, proofSize: 0}],
+        expectSuccess,
+        options,
+      );
+
+      if(result.status === 'Fail') return result;
+
+      const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult;
+      if(data.isErr) {
+        if(data.asErr.isModule) {
+          const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule;
+          const metaError = super.getApi()?.registry.findMetaError(error);
+          throw new Error(`${metaError.section}.${metaError.name}`);
+        } else if(data.asErr.isToken) {
+          throw new Error(`Token: ${data.asErr.asToken}`);
+        }
+        // May be [object Object] in case of unhandled non-unit enum
+        throw new Error(`Misc: ${data.asErr.toHuman()}`);
+      }
+      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 DevUniqueHelper>(
+    executionBlockNumber: number,
+    options: ISchedulerOptions = {},
+  ) {
+    return this.schedule<T>('schedule', executionBlockNumber, options);
+  }
+
+  scheduleAfter<T extends DevUniqueHelper>(
+    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());
+  }
+}
+
 export class DevUniqueHelper extends UniqueHelper {
   /**
    * Arrange methods for tests
@@ -269,6 +457,16 @@
   admin: AdminGroup;
   session: SessionGroup;
   testUtils: TestUtilGroup;
+  foreignAssets: ForeignAssetsGroup;
+  xcm: XcmGroup<UniqueHelper>;
+  xTokens: XTokensGroup<UniqueHelper>;
+  tokens: TokensGroup<UniqueHelper>;
+  scheduler: SchedulerGroup;
+  collatorSelection: CollatorSelectionGroup;
+  council: ICollectiveGroup;
+  technicalCommittee: ICollectiveGroup;
+  fellowship: IFellowshipGroup;
+  democracy: DemocracyGroup;
 
   constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
     options.helperBase = options.helperBase ?? DevUniqueHelper;
@@ -279,6 +477,25 @@
     this.admin = new AdminGroup(this);
     this.testUtils = new TestUtilGroup(this);
     this.session = new SessionGroup(this);
+    this.foreignAssets = new ForeignAssetsGroup(this);
+    this.xcm = new XcmGroup(this, 'polkadotXcm');
+    this.xTokens = new XTokensGroup(this);
+    this.tokens = new TokensGroup(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);
   }
 
   async connect(wsEndpoint: string, _listeners?: any): Promise<void> {
@@ -326,6 +543,11 @@
     this.network = await UniqueHelper.detectNetwork(this.api);
     this.wsEndpoint = wsEndpoint;
   }
+  getSudo<T extends DevUniqueHelper>() {
+    // eslint-disable-next-line @typescript-eslint/naming-convention
+    const SudoHelperType = SudoHelper(this.helperBase);
+    return this.clone(SudoHelperType) as T;
+  }
 }
 
 export class DevRelayHelper extends RelayHelper {
@@ -386,19 +608,16 @@
     super(logger, options);
     this.wait = new WaitGroup(this);
   }
-}
-
-export class DevShidenHelper extends AstarHelper {
-  wait: WaitGroup;
 
-  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
-    options.helperBase = options.helperBase ?? DevShidenHelper;
-
-    super(logger, options);
-    this.wait = new WaitGroup(this);
+  getSudo<T extends AstarHelper>() {
+    // eslint-disable-next-line @typescript-eslint/naming-convention
+    const SudoHelperType = SudoHelper(this.helperBase);
+    return this.clone(SudoHelperType) as T;
   }
 }
 
+export class DevShidenHelper extends DevAstarHelper { }
+
 export class DevAcalaHelper extends AcalaHelper {
   wait: WaitGroup;
 
@@ -408,6 +627,11 @@
     super(logger, options);
     this.wait = new WaitGroup(this);
   }
+  getSudo<T extends AcalaHelper>() {
+    // eslint-disable-next-line @typescript-eslint/naming-convention
+    const SudoHelperType = SudoHelper(this.helperBase);
+    return this.clone(SudoHelperType) as T;
+  }
 }
 
 export class DevPolkadexHelper extends PolkadexHelper {
@@ -418,6 +642,12 @@
     super(logger, options);
     this.wait = new WaitGroup(this);
   }
+
+  getSudo<T extends PolkadexHelper>() {
+    // eslint-disable-next-line @typescript-eslint/naming-convention
+    const SudoHelperType = SudoHelper(this.helperBase);
+    return this.clone(SudoHelperType) as T;
+  }
 }
 
 export class DevKaruraHelper extends DevAcalaHelper {}
@@ -1211,3 +1441,63 @@
     }));
   }
 }
+
+// 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,
+      );
+    }
+  };
+}
\ No newline at end of file
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,16 +39,11 @@
   TSigner,
   TSubstrateAccount,
   TNetworks,
-  IForeignAssetMetadata,
-  AcalaAssetMetadata,
-  MoonbeamAssetInfo,
-  DemocracyStandardAccountVote,
   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;
@@ -818,7 +813,7 @@
 }
 
 
-class HelperGroup<T extends ChainHelperBase> {
+export class HelperGroup<T extends ChainHelperBase> {
   helper: T;
 
   constructor(uniqueHelper: T) {
@@ -2373,7 +2368,7 @@
   }
 }
 
-class SubstrateBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {
+export class SubstrateBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {
   /**
  * Get substrate address balance
  * @param address substrate address
@@ -2440,7 +2435,7 @@
   }
 }
 
-class EthereumBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {
+export class EthereumBalanceGroup<T extends ChainHelperBase> extends HelperGroup<T> {
   /**
    * Get ethereum address balance
    * @param address ethereum address
@@ -2760,6 +2755,7 @@
   }
 }
 
+
 class StakingGroup extends HelperGroup<UniqueHelper> {
   /**
    * Stake tokens for App Promotion
@@ -2867,645 +2863,7 @@
   }
 }
 
-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) {
     return (await this.helper.callRpc('api.query.preimage.statusFor', [h256])).toJSON();
@@ -3572,173 +2930,7 @@
    */
   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,
-    );
-  }
-}
-
-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,
-    );
-  }
-}
-
-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);
-  }
-}
-
-class PolkadexXcmHelperGroup<T extends ChainHelperBase> extends HelperGroup<T> {
-  async whitelistToken(signer: TSigner, assetId: any) {
-    await this.helper.executeExtrinsic(signer, 'api.tx.xcmHelper.whitelistToken', [assetId], true);
-  }
-}
-
-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);
-  }
-}
-
-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> {
@@ -3752,90 +2944,9 @@
 
   batchAllCall(txs: any[]) {
     return this.helper.constructApiCall('api.tx.utility.batchAll', [txs]);
-  }
-}
-
-class AcalaAssetRegistryGroup extends HelperGroup<AcalaHelper> {
-  async registerForeignAsset(signer: TSigner, destination: any, metadata: AcalaAssetMetadata) {
-    await this.helper.executeExtrinsic(signer, 'api.tx.assetRegistry.registerForeignAsset', [destination, metadata], true);
   }
 }
 
-class MoonbeamAssetManagerGroup extends HelperGroup<MoonbeamHelper> {
-  makeRegisterForeignAssetProposal(assetInfo: MoonbeamAssetInfo) {
-    const apiPrefix = 'api.tx.assetManager.';
-
-    const registerTx = this.helper.constructApiCall(
-      apiPrefix + 'registerForeignAsset',
-      [assetInfo.location, assetInfo.metadata, assetInfo.existentialDeposit, assetInfo.isSufficient],
-    );
-
-    const setUnitsTx = this.helper.constructApiCall(
-      apiPrefix + 'setAssetUnitsPerSecond',
-      [assetInfo.location, assetInfo.unitsPerSecond, assetInfo.numAssetsWeightHint],
-    );
-
-    const batchCall = this.helper.getApi().tx.utility.batchAll([registerTx, setUnitsTx]);
-    const encodedProposal = batchCall?.method.toHex() || '';
-    return encodedProposal;
-  }
-
-  async assetTypeId(location: any) {
-    return await this.helper.callRpc('api.query.assetManager.assetTypeId', [location]);
-  }
-}
-
-class MoonbeamDemocracyGroup extends HelperGroup<MoonbeamHelper> {
-  notePreimagePallet: string;
-
-  constructor(helper: MoonbeamHelper, options: { [key: string]: any } = {}) {
-    super(helper);
-    this.notePreimagePallet = options.notePreimagePallet;
-  }
-
-  async notePreimage(signer: TSigner, encodedProposal: string) {
-    await this.helper.executeExtrinsic(signer, `api.tx.${this.notePreimagePallet}.notePreimage`, [encodedProposal], true);
-  }
-
-  externalProposeMajority(proposal: any) {
-    return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [proposal]);
-  }
-
-  fastTrack(proposalHash: string, votingPeriod: number, delayPeriod: number) {
-    return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);
-  }
-
-  async referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) {
-    await this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true);
-  }
-}
-
-class MoonbeamCollectiveGroup extends HelperGroup<MoonbeamHelper> {
-  collective: string;
-
-  constructor(helper: MoonbeamHelper, collective: string) {
-    super(helper);
-
-    this.collective = collective;
-  }
-
-  async propose(signer: TSigner, threshold: number, proposalHash: string, lengthBound: number) {
-    await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [threshold, proposalHash, lengthBound], true);
-  }
-
-  async vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) {
-    await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve], true);
-  }
-
-  async close(signer: TSigner, proposalHash: string, proposalIndex: number, weightBound: any, lengthBound: number) {
-    await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [proposalHash, proposalIndex, weightBound, lengthBound], true);
-  }
-
-  async proposalCount() {
-    return Number(await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, []));
-  }
-}
-
 export type ChainHelperBaseConstructor = new (...args: any[]) => ChainHelperBase;
 export type UniqueHelperConstructor = new (...args: any[]) => UniqueHelper;
 
@@ -3846,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 } = {}) {
@@ -3868,303 +2969,11 @@
     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);
-  }
-
-  getSudo<T extends UniqueHelper>() {
-    // eslint-disable-next-line @typescript-eslint/naming-convention
-    const SudoHelperType = SudoHelper(this.helperBase);
-    return this.clone(SudoHelperType) as T;
-  }
-}
-
-export class XcmChainHelper extends ChainHelperBase {
-  async connect(wsEndpoint: string, _listeners?: any): Promise<void> {
-    const wsProvider = new WsProvider(wsEndpoint);
-    this.api = new ApiPromise({
-      provider: wsProvider,
-    });
-    await this.api.isReadyOrError;
-    this.network = await UniqueHelper.detectNetwork(this.api);
-  }
-}
-
-export class RelayHelper extends XcmChainHelper {
-  balance: SubstrateBalanceGroup<RelayHelper>;
-  xcm: XcmGroup<RelayHelper>;
-
-  constructor(logger?: ILogger, options: { [key: string]: any } = {}) {
-    super(logger, options.helperBase ?? RelayHelper);
-
-    this.balance = new SubstrateBalanceGroup(this);
-    this.xcm = new XcmGroup(this, 'xcmPallet');
-  }
-}
-
-export class WestmintHelper extends XcmChainHelper {
-  balance: SubstrateBalanceGroup<WestmintHelper>;
-  xcm: XcmGroup<WestmintHelper>;
-  assets: AssetsGroup<WestmintHelper>;
-  xTokens: XTokensGroup<WestmintHelper>;
-
-  constructor(logger?: ILogger, options: { [key: string]: any } = {}) {
-    super(logger, options.helperBase ?? WestmintHelper);
-
-    this.balance = new SubstrateBalanceGroup(this);
-    this.xcm = new XcmGroup(this, 'polkadotXcm');
-    this.assets = new AssetsGroup(this);
-    this.xTokens = new XTokensGroup(this);
-  }
-}
-
-export class MoonbeamHelper extends XcmChainHelper {
-  balance: EthereumBalanceGroup<MoonbeamHelper>;
-  assetManager: MoonbeamAssetManagerGroup;
-  assets: AssetsGroup<MoonbeamHelper>;
-  xTokens: XTokensGroup<MoonbeamHelper>;
-  democracy: MoonbeamDemocracyGroup;
-  collective: {
-    council: MoonbeamCollectiveGroup,
-    techCommittee: MoonbeamCollectiveGroup,
-  };
-
-  constructor(logger?: ILogger, options: { [key: string]: any } = {}) {
-    super(logger, options.helperBase ?? MoonbeamHelper);
-
-    this.balance = new EthereumBalanceGroup(this);
-    this.assetManager = new MoonbeamAssetManagerGroup(this);
-    this.assets = new AssetsGroup(this);
-    this.xTokens = new XTokensGroup(this);
-    this.democracy = new MoonbeamDemocracyGroup(this, options);
-    this.collective = {
-      council: new MoonbeamCollectiveGroup(this, 'councilCollective'),
-      techCommittee: new MoonbeamCollectiveGroup(this, 'techCommitteeCollective'),
-    };
   }
 }
 
-export class AstarHelper extends XcmChainHelper {
-  balance: SubstrateBalanceGroup<AstarHelper>;
-  assets: AssetsGroup<AstarHelper>;
-  xcm: XcmGroup<AstarHelper>;
-
-  constructor(logger?: ILogger, options: { [key: string]: any } = {}) {
-    super(logger, options.helperBase ?? AstarHelper);
-
-    this.balance = new SubstrateBalanceGroup(this);
-    this.assets = new AssetsGroup(this);
-    this.xcm = new XcmGroup(this, 'polkadotXcm');
-  }
-
-  getSudo<T extends UniqueHelper>() {
-    // eslint-disable-next-line @typescript-eslint/naming-convention
-    const SudoHelperType = SudoHelper(this.helperBase);
-    return this.clone(SudoHelperType) as T;
-  }
-}
-
-export class AcalaHelper extends XcmChainHelper {
-  balance: SubstrateBalanceGroup<AcalaHelper>;
-  assetRegistry: AcalaAssetRegistryGroup;
-  xTokens: XTokensGroup<AcalaHelper>;
-  tokens: TokensGroup<AcalaHelper>;
-  xcm: XcmGroup<AcalaHelper>;
-
-  constructor(logger?: ILogger, options: { [key: string]: any } = {}) {
-    super(logger, options.helperBase ?? AcalaHelper);
-
-    this.balance = new SubstrateBalanceGroup(this);
-    this.assetRegistry = new AcalaAssetRegistryGroup(this);
-    this.xTokens = new XTokensGroup(this);
-    this.tokens = new TokensGroup(this);
-    this.xcm = new XcmGroup(this, 'polkadotXcm');
-  }
-
-  getSudo<T extends AcalaHelper>() {
-    // eslint-disable-next-line @typescript-eslint/naming-convention
-    const SudoHelperType = SudoHelper(this.helperBase);
-    return this.clone(SudoHelperType) as T;
-  }
-}
-
-export class PolkadexHelper extends XcmChainHelper {
-  assets: AssetsGroup<PolkadexHelper>;
-  balance: SubstrateBalanceGroup<PolkadexHelper>;
-  xTokens: XTokensGroup<PolkadexHelper>;
-  xcm: XcmGroup<PolkadexHelper>;
-  xcmHelper: PolkadexXcmHelperGroup<PolkadexHelper>;
-
-  constructor(logger?: ILogger, options: { [key: string]: any } = {}) {
-    super(logger, options.helperBase ?? PolkadexHelper);
-
-    this.assets = new AssetsGroup(this);
-    this.balance = new SubstrateBalanceGroup(this);
-    this.xTokens = new XTokensGroup(this);
-    this.xcm = new XcmGroup(this, 'polkadotXcm');
-    this.xcmHelper = new PolkadexXcmHelperGroup(this);
-  }
-
-  getSudo<T extends PolkadexHelper>() {
-    // eslint-disable-next-line @typescript-eslint/naming-convention
-    const SudoHelperType = SudoHelper(this.helperBase);
-    return this.clone(SudoHelperType) as T;
-  }
-}
-
-// 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,
-      );
-    }
-  };
-}
-
-// eslint-disable-next-line @typescript-eslint/naming-convention
-function SudoHelper<T extends ChainHelperBaseConstructor>(Base: T) {
-  return class extends Base {
-    constructor(...args: any[]) {
-      super(...args);
-    }
-
-    async executeExtrinsic(
-      sender: IKeyringPair,
-      extrinsic: string,
-      params: any[],
-      expectSuccess?: boolean,
-      options: Partial<SignerOptions> | null = null,
-    ): Promise<ITransactionResult> {
-      const call = this.constructApiCall(extrinsic, params);
-      const result = await super.executeExtrinsic(
-        sender,
-        'api.tx.sudo.sudo',
-        [call],
-        expectSuccess,
-        options,
-      );
-
-      if(result.status === 'Fail') return result;
-
-      const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult;
-      if(data.isErr) {
-        if(data.asErr.isModule) {
-          const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule;
-          const metaError = super.getApi()?.registry.findMetaError(error);
-          throw new Error(`${metaError.section}.${metaError.name}`);
-        } else if(data.asErr.isToken) {
-          throw new Error(`Token: ${data.asErr.asToken}`);
-        }
-        // May be [object Object] in case of unhandled non-unit enum
-        throw new Error(`Misc: ${data.asErr.toHuman()}`);
-      }
-      return result;
-    }
-    async executeExtrinsicUncheckedWeight(
-      sender: IKeyringPair,
-      extrinsic: string,
-      params: any[],
-      expectSuccess?: boolean,
-      options: Partial<SignerOptions> | null = null,
-    ): Promise<ITransactionResult> {
-      const call = this.constructApiCall(extrinsic, params);
-      const result = await super.executeExtrinsic(
-        sender,
-        'api.tx.sudo.sudoUncheckedWeight',
-        [call, {refTime: 0, proofSize: 0}],
-        expectSuccess,
-        options,
-      );
-
-      if(result.status === 'Fail') return result;
-
-      const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult;
-      if(data.isErr) {
-        if(data.asErr.isModule) {
-          const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule;
-          const metaError = super.getApi()?.registry.findMetaError(error);
-          throw new Error(`${metaError.section}.${metaError.name}`);
-        } else if(data.asErr.isToken) {
-          throw new Error(`Token: ${data.asErr.asToken}`);
-        }
-        // May be [object Object] in case of unhandled non-unit enum
-        throw new Error(`Misc: ${data.asErr.toHuman()}`);
-      }
-      return result;
-    }
-  };
-}
-
 export class UniqueBaseCollection {
   helper: UniqueHelper;
   collectionId: number;
@@ -4272,29 +3081,8 @@
 
   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);
-  }
-
-  getSudo<T extends UniqueHelper>() {
-    return new UniqueBaseCollection(this.collectionId, this.helper.getSudo<T>());
   }
 }
-
 
 export class UniqueNFTCollection extends UniqueBaseCollection {
   getTokenObject(tokenId: number) {
@@ -4386,29 +3174,8 @@
 
   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);
-  }
-
-  getSudo<T extends UniqueHelper>() {
-    return new UniqueNFTCollection(this.collectionId, this.helper.getSudo<T>());
   }
 }
-
 
 export class UniqueRFTCollection extends UniqueBaseCollection {
   getTokenObject(tokenId: number) {
@@ -4512,29 +3279,8 @@
 
   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);
-  }
-
-  getSudo<T extends UniqueHelper>() {
-    return new UniqueRFTCollection(this.collectionId, this.helper.getSudo<T>());
   }
 }
-
 
 export class UniqueFTCollection extends UniqueBaseCollection {
   async getBalance(addressObj: ICrossAccountId) {
@@ -4579,29 +3325,8 @@
 
   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);
-  }
-
-  getSudo<T extends UniqueHelper>() {
-    return new UniqueFTCollection(this.collectionId, this.helper.getSudo<T>());
   }
 }
-
 
 export class UniqueBaseToken {
   collection: UniqueNFTCollection | UniqueRFTCollection;
@@ -4640,29 +3365,8 @@
 
   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);
-  }
-
-  getSudo<T extends UniqueHelper>() {
-    return new UniqueBaseToken(this.tokenId, this.collection.getSudo<T>());
   }
 }
-
 
 export class UniqueNFToken extends UniqueBaseToken {
   collection: UniqueNFTCollection;
@@ -4718,26 +3422,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);
-  }
-
-  getSudo<T extends UniqueHelper>() {
-    return new UniqueNFToken(this.tokenId, this.collection.getSudo<T>());
   }
 }
 
@@ -4807,25 +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);
-  }
-
-  getSudo<T extends UniqueHelper>() {
-    return new UniqueRFToken(this.tokenId, this.collection.getSudo<T>());
   }
 }
addedtests/src/util/playgrounds/unique.xcm.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/util/playgrounds/unique.xcm.ts
@@ -0,0 +1,371 @@
+import {ApiPromise, WsProvider} from '@polkadot/api';
+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> {
+    const wsProvider = new WsProvider(wsEndpoint);
+    this.api = new ApiPromise({
+      provider: wsProvider,
+    });
+    await this.api.isReadyOrError;
+    this.network = await UniqueHelper.detectNetwork(this.api);
+  }
+}
+
+class AcalaAssetRegistryGroup extends HelperGroup<AcalaHelper> {
+  async registerForeignAsset(signer: TSigner, destination: any, metadata: AcalaAssetMetadata) {
+    await this.helper.executeExtrinsic(signer, 'api.tx.assetRegistry.registerForeignAsset', [destination, metadata], true);
+  }
+}
+
+class MoonbeamAssetManagerGroup extends HelperGroup<MoonbeamHelper> {
+  makeRegisterForeignAssetProposal(assetInfo: MoonbeamAssetInfo) {
+    const apiPrefix = 'api.tx.assetManager.';
+
+    const registerTx = this.helper.constructApiCall(
+      apiPrefix + 'registerForeignAsset',
+      [assetInfo.location, assetInfo.metadata, assetInfo.existentialDeposit, assetInfo.isSufficient],
+    );
+
+    const setUnitsTx = this.helper.constructApiCall(
+      apiPrefix + 'setAssetUnitsPerSecond',
+      [assetInfo.location, assetInfo.unitsPerSecond, assetInfo.numAssetsWeightHint],
+    );
+
+    const batchCall = this.helper.getApi().tx.utility.batchAll([registerTx, setUnitsTx]);
+    const encodedProposal = batchCall?.method.toHex() || '';
+    return encodedProposal;
+  }
+
+  async assetTypeId(location: any) {
+    return await this.helper.callRpc('api.query.assetManager.assetTypeId', [location]);
+  }
+}
+
+class MoonbeamDemocracyGroup extends HelperGroup<MoonbeamHelper> {
+  notePreimagePallet: string;
+
+  constructor(helper: MoonbeamHelper, options: { [key: string]: any } = {}) {
+    super(helper);
+    this.notePreimagePallet = options.notePreimagePallet;
+  }
+
+  async notePreimage(signer: TSigner, encodedProposal: string) {
+    await this.helper.executeExtrinsic(signer, `api.tx.${this.notePreimagePallet}.notePreimage`, [encodedProposal], true);
+  }
+
+  externalProposeMajority(proposal: any) {
+    return this.helper.constructApiCall('api.tx.democracy.externalProposeMajority', [proposal]);
+  }
+
+  fastTrack(proposalHash: string, votingPeriod: number, delayPeriod: number) {
+    return this.helper.constructApiCall('api.tx.democracy.fastTrack', [proposalHash, votingPeriod, delayPeriod]);
+  }
+
+  async referendumVote(signer: TSigner, referendumIndex: number, accountVote: DemocracyStandardAccountVote) {
+    await this.helper.executeExtrinsic(signer, 'api.tx.democracy.vote', [referendumIndex, {Standard: accountVote}], true);
+  }
+}
+
+class MoonbeamCollectiveGroup extends HelperGroup<MoonbeamHelper> {
+  collective: string;
+
+  constructor(helper: MoonbeamHelper, collective: string) {
+    super(helper);
+
+    this.collective = collective;
+  }
+
+  async propose(signer: TSigner, threshold: number, proposalHash: string, lengthBound: number) {
+    await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.propose`, [threshold, proposalHash, lengthBound], true);
+  }
+
+  async vote(signer: TSigner, proposalHash: string, proposalIndex: number, approve: boolean) {
+    await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.vote`, [proposalHash, proposalIndex, approve], true);
+  }
+
+  async close(signer: TSigner, proposalHash: string, proposalIndex: number, weightBound: any, lengthBound: number) {
+    await this.helper.executeExtrinsic(signer, `api.tx.${this.collective}.close`, [proposalHash, proposalIndex, weightBound, lengthBound], true);
+  }
+
+  async proposalCount() {
+    return Number(await this.helper.callRpc(`api.query.${this.collective}.proposalCount`, []));
+  }
+}
+
+class PolkadexXcmHelperGroup<T extends ChainHelperBase> extends HelperGroup<T> {
+  async whitelistToken(signer: TSigner, assetId: any) {
+    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>;
+
+  constructor(logger?: ILogger, options: { [key: string]: any } = {}) {
+    super(logger, options.helperBase ?? RelayHelper);
+
+    this.balance = new SubstrateBalanceGroup(this);
+    this.xcm = new XcmGroup(this, 'xcmPallet');
+  }
+}
+
+export class WestmintHelper extends XcmChainHelper {
+  balance: SubstrateBalanceGroup<WestmintHelper>;
+  xcm: XcmGroup<WestmintHelper>;
+  assets: AssetsGroup<WestmintHelper>;
+  xTokens: XTokensGroup<WestmintHelper>;
+
+  constructor(logger?: ILogger, options: { [key: string]: any } = {}) {
+    super(logger, options.helperBase ?? WestmintHelper);
+
+    this.balance = new SubstrateBalanceGroup(this);
+    this.xcm = new XcmGroup(this, 'polkadotXcm');
+    this.assets = new AssetsGroup(this);
+    this.xTokens = new XTokensGroup(this);
+  }
+}
+
+export class MoonbeamHelper extends XcmChainHelper {
+  balance: EthereumBalanceGroup<MoonbeamHelper>;
+  assetManager: MoonbeamAssetManagerGroup;
+  assets: AssetsGroup<MoonbeamHelper>;
+  xTokens: XTokensGroup<MoonbeamHelper>;
+  democracy: MoonbeamDemocracyGroup;
+  collective: {
+    council: MoonbeamCollectiveGroup,
+    techCommittee: MoonbeamCollectiveGroup,
+  };
+
+  constructor(logger?: ILogger, options: { [key: string]: any } = {}) {
+    super(logger, options.helperBase ?? MoonbeamHelper);
+
+    this.balance = new EthereumBalanceGroup(this);
+    this.assetManager = new MoonbeamAssetManagerGroup(this);
+    this.assets = new AssetsGroup(this);
+    this.xTokens = new XTokensGroup(this);
+    this.democracy = new MoonbeamDemocracyGroup(this, options);
+    this.collective = {
+      council: new MoonbeamCollectiveGroup(this, 'councilCollective'),
+      techCommittee: new MoonbeamCollectiveGroup(this, 'techCommitteeCollective'),
+    };
+  }
+}
+
+export class AstarHelper extends XcmChainHelper {
+  balance: SubstrateBalanceGroup<AstarHelper>;
+  assets: AssetsGroup<AstarHelper>;
+  xcm: XcmGroup<AstarHelper>;
+
+  constructor(logger?: ILogger, options: { [key: string]: any } = {}) {
+    super(logger, options.helperBase ?? AstarHelper);
+
+    this.balance = new SubstrateBalanceGroup(this);
+    this.assets = new AssetsGroup(this);
+    this.xcm = new XcmGroup(this, 'polkadotXcm');
+  }
+}
+
+export class AcalaHelper extends XcmChainHelper {
+  balance: SubstrateBalanceGroup<AcalaHelper>;
+  assetRegistry: AcalaAssetRegistryGroup;
+  xTokens: XTokensGroup<AcalaHelper>;
+  tokens: TokensGroup<AcalaHelper>;
+  xcm: XcmGroup<AcalaHelper>;
+
+  constructor(logger?: ILogger, options: { [key: string]: any } = {}) {
+    super(logger, options.helperBase ?? AcalaHelper);
+
+    this.balance = new SubstrateBalanceGroup(this);
+    this.assetRegistry = new AcalaAssetRegistryGroup(this);
+    this.xTokens = new XTokensGroup(this);
+    this.tokens = new TokensGroup(this);
+    this.xcm = new XcmGroup(this, 'polkadotXcm');
+  }
+}
+
+export class PolkadexHelper extends XcmChainHelper {
+  assets: AssetsGroup<PolkadexHelper>;
+  balance: SubstrateBalanceGroup<PolkadexHelper>;
+  xTokens: XTokensGroup<PolkadexHelper>;
+  xcm: XcmGroup<PolkadexHelper>;
+  xcmHelper: PolkadexXcmHelperGroup<PolkadexHelper>;
+
+  constructor(logger?: ILogger, options: { [key: string]: any } = {}) {
+    super(logger, options.helperBase ?? PolkadexHelper);
+
+    this.assets = new AssetsGroup(this);
+    this.balance = new SubstrateBalanceGroup(this);
+    this.xTokens = new XTokensGroup(this);
+    this.xcm = new XcmGroup(this, 'polkadotXcm');
+    this.xcmHelper = new PolkadexXcmHelperGroup(this);
+  }
+}
+