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

difftreelog

refactor js-packages structure

Andy Smith2024-01-06parent: #a478235.patch.diff
in: master

154 files changed

modified.github/workflows/xcm.ymldiffbeforeafterboth
--- a/.github/workflows/xcm.yml
+++ b/.github/workflows/xcm.yml
@@ -368,9 +368,9 @@
           yarn add mochawesome
 
       - name: Call HRMP initialization
-        working-directory: js-packages/tests
+        working-directory: js-packages/scripts
         run: |
-          yarn node --no-warnings=ExperimentalWarning --loader ts-node/esm util/createHrmp.ts ${{matrix.network}}
+          yarn node --no-warnings=ExperimentalWarning --loader ts-node/esm createHrmp.ts ${{matrix.network}}
 
       - name: Run XCM tests
         working-directory: js-packages/tests
modifiedjs-packages/package.jsondiffbeforeafterboth
--- a/js-packages/package.json
+++ b/js-packages/package.json
@@ -26,8 +26,9 @@
     "@types/node": "^20.8.10",
     "@typescript-eslint/eslint-plugin": "^6.10.0",
     "@typescript-eslint/parser": "^6.10.0",
-    "@unique/opal-types": "workspace:*",
-    "@unique/playgrounds": "workspace:*",
+    "@unique-nft/opal-testnet-types": "workspace:*",
+    "@unique-nft/playgrounds": "workspace:*",
+    "@unique/test-utils": "workspace:*",
     "chai": "^4.3.10",
     "chai-subset": "^1.6.0",
     "eslint": "^8.53.0",
@@ -59,6 +60,7 @@
     "types",
     "playgrounds",
     "scripts",
+    "test-utils",
     "tests"
   ]
 }
modifiedjs-packages/playgrounds/package.jsondiffbeforeafterboth
--- a/js-packages/playgrounds/package.json
+++ b/js-packages/playgrounds/package.json
@@ -1,18 +1,17 @@
 {
-  "author": "",
-  "license": "SEE LICENSE IN ../../../LICENSE",
-  "description": "Playground scripts",
+  "author": "Unique Network",
+  "license": "Apache 2.0",
+  "description": "Helpers for Unique Network chain",
   "engines": {
-    "node": ">=16"
+    "node": ">=18"
   },
-  "name": "@unique/playgrounds",
+  "name": "@unique-nft/playgrounds",
   "type": "module",
   "version": "1.0.0",
   "main": "unique.js",
   "dependencies": {
     "@polkadot/api": "10.10.1",
     "@polkadot/util": "^12.5.1",
-    "@polkadot/util-crypto": "^12.5.1",
-    "@unique/opal-types": "workspace:*"
+    "@polkadot/util-crypto": "^12.5.1"
   }
 }
deletedjs-packages/playgrounds/types.xcm.tsdiffbeforeafterboth
--- a/js-packages/playgrounds/types.xcm.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-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,
-  },
-}
deletedjs-packages/playgrounds/unique.dev.tsdiffbeforeafterboth
--- a/js-packages/playgrounds/unique.dev.ts
+++ /dev/null
@@ -1,1646 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// SPDX-License-Identifier: Apache-2.0
-
-import '@unique/opal-types/augment-api.js';
-import '@unique/opal-types/augment-types.js';
-import '@unique/opal-types/types-lookup.js';
-
-import {stringToU8a} from '@polkadot/util';
-import {blake2AsHex, encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto';
-import type {ChainHelperBaseConstructor, UniqueHelperConstructor} from './unique.js';
-import {UniqueHelper, ChainHelperBase, HelperGroup} from './unique.js';
-import {ApiPromise, Keyring, WsProvider} from '@polkadot/api';
-import * as defs from '@unique/opal-types/definitions.js';
-import type {IKeyringPair} from '@polkadot/types/types';
-import type {EventRecord} from '@polkadot/types/interfaces';
-import type {ICrossAccountId, ILogger, IPovInfo, ISchedulerOptions, ITransactionResult, TSigner} from './types.js';
-import type {FrameSystemEventRecord, StagingXcmV2TraitsError, StagingXcmV3TraitsOutcome} from '@polkadot/types/lookup';
-import type {SignerOptions, VoidFn} from '@polkadot/api/types';
-import {spawnSync} from 'child_process';
-import {AcalaHelper, AstarHelper, MoonbeamHelper, PolkadexHelper, RelayHelper, WestmintHelper, ForeignAssetsGroup, XcmGroup, XTokensGroup, TokensGroup, HydraDxHelper} from './unique.xcm.js';
-import {CollectiveGroup, CollectiveMembershipGroup, DemocracyGroup, RankedCollectiveGroup, ReferendaGroup} from './unique.governance.js';
-import type {ICollectiveGroup, IFellowshipGroup} from './unique.governance.js';
-
-export class SilentLogger {
-  log(_msg: any, _level: any): void { }
-  level = {
-    ERROR: 'ERROR' as const,
-    WARNING: 'WARNING' as const,
-    INFO: 'INFO' as const,
-  };
-}
-
-export class SilentConsole {
-  // TODO: Remove, this is temporary: Filter unneeded API output
-  // (Jaco promised it will be removed in the next version)
-  consoleErr: any;
-  consoleLog: any;
-  consoleWarn: any;
-
-  constructor() {
-    this.consoleErr = console.error;
-    this.consoleLog = console.log;
-    this.consoleWarn = console.warn;
-  }
-
-  enable() {
-    const outFn = (printer: any) => (...args: any[]) => {
-      for(const arg of args) {
-        if(typeof arg !== 'string')
-          continue;
-        const skippedWarnings = ['1000:: Normal connection closure', 'Not decorating unknown runtime apis:', 'RPC methods not decorated:', 'Not decorating runtime apis', 'Bad input data provided to validate_transaction', 'account balance too low', '1006:: Abnormal Closure'];
-        const needToSkip = skippedWarnings.reduce((a,  b) => a || arg.includes(b), false);
-        if(needToSkip || arg === 'Normal connection closure')
-          return;
-      }
-      printer(...args);
-    };
-
-    console.error = outFn(this.consoleErr.bind(console));
-    console.log = outFn(this.consoleLog.bind(console));
-    console.warn = outFn(this.consoleWarn.bind(console));
-  }
-
-  disable() {
-    console.error = this.consoleErr;
-    console.log = this.consoleLog;
-    console.warn = this.consoleWarn;
-  }
-}
-
-export interface IEventHelper {
-  section(): string;
-
-  method(): string;
-
-  wrapEvent(data: any[]): any;
-}
-
-// eslint-disable-next-line @typescript-eslint/naming-convention
-function EventHelper(section: string, method: string, wrapEvent: (data: any[]) => any) {
-  const helperClass = class implements IEventHelper {
-    wrapEvent: (data: any[]) => any;
-    _section: string;
-    _method: string;
-
-    constructor() {
-      this.wrapEvent = wrapEvent;
-      this._section = section;
-      this._method = method;
-    }
-
-    section(): string {
-      return this._section;
-    }
-
-    method(): string {
-      return this._method;
-    }
-
-    filter(txres: ITransactionResult) {
-      return txres.result.events.filter(e => e.event.section === section && e.event.method === method)
-        .map(e => this.wrapEvent(e.event.data));
-    }
-
-    find(txres: ITransactionResult) {
-      const e = txres.result.events.find(e => e.event.section === section && e.event.method === method);
-      return e ? this.wrapEvent(e.event.data) : null;
-    }
-
-    expect(txres: ITransactionResult) {
-      const e = this.find(txres);
-      if(e) {
-        return e;
-      } else {
-        throw Error(`Expected event ${section}.${method}`);
-      }
-    }
-  };
-
-  return helperClass;
-}
-
-function eventJsonData<T = any>(data: any[], index: number) {
-  return data[index].toJSON() as T;
-}
-
-function eventHumanData(data: any[], index: number) {
-  return data[index].toHuman();
-}
-
-function eventData<T = any>(data: any[], index: number) {
-  return data[index] as T;
-}
-
-// eslint-disable-next-line @typescript-eslint/naming-convention
-function EventSection(section: string) {
-  return class Section {
-    static section = section;
-
-    static Method(name: string, wrapEvent: (data: any[]) => any = () => {}) {
-      const helperClass = EventHelper(Section.section, name, wrapEvent);
-      return new helperClass();
-    }
-  };
-}
-
-function schedulerSection(schedulerInstance: string) {
-  return class extends EventSection(schedulerInstance) {
-    static Dispatched = this.Method('Dispatched', data => ({
-      task: eventJsonData(data, 0),
-      id: eventHumanData(data, 1),
-      result: data[2],
-    }));
-
-    static PriorityChanged = this.Method('PriorityChanged', data => ({
-      task: eventJsonData(data, 0),
-      priority: eventJsonData(data, 1),
-    }));
-  };
-}
-
-export class Event {
-  static Democracy = class extends EventSection('democracy') {
-    static Proposed = this.Method('Proposed', data => ({
-      proposalIndex: eventJsonData<number>(data, 0),
-    }));
-
-    static ExternalTabled = this.Method('ExternalTabled');
-
-    static Started = this.Method('Started', data => ({
-      referendumIndex: eventJsonData<number>(data, 0),
-      threshold: eventHumanData(data, 1),
-    }));
-
-    static Voted = this.Method('Voted', data => ({
-      voter: eventJsonData(data, 0),
-      referendumIndex: eventJsonData<number>(data, 1),
-      vote: eventJsonData(data, 2),
-    }));
-
-    static Passed = this.Method('Passed', data => ({
-      referendumIndex: eventJsonData<number>(data, 0),
-    }));
-
-    static ProposalCanceled = this.Method('ProposalCanceled', data => ({
-      propIndex: eventJsonData<number>(data, 0),
-    }));
-
-    static Cancelled = this.Method('Cancelled', data => ({
-      propIndex: eventJsonData<number>(data, 0),
-    }));
-
-    static Vetoed = this.Method('Vetoed', data => ({
-      who: eventHumanData(data, 0),
-      proposalHash: eventHumanData(data, 1),
-      until: eventJsonData<number>(data, 1),
-    }));
-  };
-
-  static Council = class extends EventSection('council') {
-    static Proposed = this.Method('Proposed', data => ({
-      account: eventHumanData(data, 0),
-      proposalIndex: eventJsonData<number>(data, 1),
-      proposalHash: eventHumanData(data, 2),
-      threshold: eventJsonData<number>(data, 3),
-    }));
-    static Closed = this.Method('Closed', data => ({
-      proposalHash: eventHumanData(data, 0),
-      yes: eventJsonData<number>(data, 1),
-      no: eventJsonData<number>(data, 2),
-    }));
-    static Executed = this.Method('Executed', data => ({
-      proposalHash: eventHumanData(data, 0),
-    }));
-  };
-
-  static TechnicalCommittee = class extends EventSection('technicalCommittee') {
-    static Proposed = this.Method('Proposed', data => ({
-      account: eventHumanData(data, 0),
-      proposalIndex: eventJsonData<number>(data, 1),
-      proposalHash: eventHumanData(data, 2),
-      threshold: eventJsonData<number>(data, 3),
-    }));
-    static Closed = this.Method('Closed', data => ({
-      proposalHash: eventHumanData(data, 0),
-      yes: eventJsonData<number>(data, 1),
-      no: eventJsonData<number>(data, 2),
-    }));
-    static Approved = this.Method('Approved', data => ({
-      proposalHash: eventHumanData(data, 0),
-    }));
-    static Executed = this.Method('Executed', data => ({
-      proposalHash: eventHumanData(data, 0),
-      result: eventHumanData(data, 1),
-    }));
-  };
-
-  static FellowshipReferenda = class extends EventSection('fellowshipReferenda') {
-    static Submitted = this.Method('Submitted', data => ({
-      referendumIndex: eventJsonData<number>(data, 0),
-      trackId: eventJsonData<number>(data, 1),
-      proposal: eventJsonData(data, 2),
-    }));
-
-    static Cancelled = this.Method('Cancelled', data => ({
-      index: eventJsonData<number>(data, 0),
-      tally: eventJsonData(data, 1),
-    }));
-  };
-
-  static UniqueScheduler = schedulerSection('uniqueScheduler');
-  static Scheduler = schedulerSection('scheduler');
-
-  static XcmpQueue = class extends EventSection('xcmpQueue') {
-    static XcmpMessageSent = this.Method('XcmpMessageSent', data => ({
-      messageHash: eventJsonData(data, 0),
-    }));
-
-    static Success = this.Method('Success', data => ({
-      messageHash: eventJsonData(data, 0),
-    }));
-
-    static Fail = this.Method('Fail', data => ({
-      messageHash: eventJsonData(data, 0),
-      outcome: eventData<StagingXcmV2TraitsError>(data, 2),
-    }));
-  };
-
-  static DmpQueue = class extends EventSection('dmpQueue') {
-    static ExecutedDownward = this.Method('ExecutedDownward', data => ({
-      outcome: eventData<StagingXcmV3TraitsOutcome>(data, 2),
-    }));
-  };
-}
-
-// 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);
-    }
-
-    override 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;
-    }
-    override 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
-   */
-  arrange: ArrangeGroup;
-  wait: WaitGroup;
-  admin: AdminGroup;
-  session: SessionGroup;
-  testUtils: TestUtilGroup;
-  foreignAssets: ForeignAssetsGroup;
-  xcm: XcmGroup<DevUniqueHelper>;
-  xTokens: XTokensGroup<DevUniqueHelper>;
-  tokens: TokensGroup<DevUniqueHelper>;
-  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;
-
-    super(logger, options);
-    this.arrange = new ArrangeGroup(this);
-    this.wait = new WaitGroup(this);
-    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);
-  }
-
-  override async connect(wsEndpoint: string, _listeners?: any): Promise<void> {
-    if(!wsEndpoint) throw new Error('wsEndpoint was not set');
-    const wsProvider = new WsProvider(wsEndpoint);
-    this.api = new ApiPromise({
-      provider: wsProvider,
-      signedExtensions: {
-        ContractHelpers: {
-          extrinsic: {},
-          payload: {},
-        },
-        CheckMaintenance: {
-          extrinsic: {},
-          payload: {},
-        },
-        DisableIdentityCalls: {
-          extrinsic: {},
-          payload: {},
-        },
-        FakeTransactionFinalizer: {
-          extrinsic: {},
-          payload: {},
-        },
-      },
-      rpc: {
-        unique: defs.unique.rpc,
-        appPromotion: defs.appPromotion.rpc,
-        povinfo: defs.povinfo.rpc,
-        eth: {
-          feeHistory: {
-            description: 'Dummy',
-            params: [],
-            type: 'u8',
-          },
-          maxPriorityFeePerGas: {
-            description: 'Dummy',
-            params: [],
-            type: 'u8',
-          },
-        },
-      },
-    });
-    await this.api.isReadyOrError;
-    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 {
-  wait: WaitGroup;
-
-  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
-    options.helperBase = options.helperBase ?? DevRelayHelper;
-
-    super(logger, options);
-    this.wait = new WaitGroup(this);
-  }
-
-  getSudo() {
-    // eslint-disable-next-line @typescript-eslint/naming-convention
-    const SudoHelperType = SudoHelper(this.helperBase);
-    return this.clone(SudoHelperType) as DevRelayHelper;
-  }
-}
-
-export class DevWestmintHelper extends WestmintHelper {
-  wait: WaitGroup;
-
-  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
-    options.helperBase = options.helperBase ?? DevWestmintHelper;
-
-    super(logger, options);
-    this.wait = new WaitGroup(this);
-  }
-}
-
-export class DevStatemineHelper extends DevWestmintHelper {}
-
-export class DevStatemintHelper extends DevWestmintHelper {}
-
-export class DevMoonbeamHelper extends MoonbeamHelper {
-  account: MoonbeamAccountGroup;
-  wait: WaitGroup;
-  fastDemocracy: MoonbeamFastDemocracyGroup;
-
-  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
-    options.helperBase = options.helperBase ?? DevMoonbeamHelper;
-    options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';
-
-    super(logger, options);
-    this.account = new MoonbeamAccountGroup(this);
-    this.wait = new WaitGroup(this);
-    this.fastDemocracy = new MoonbeamFastDemocracyGroup(this);
-  }
-}
-
-export class DevMoonriverHelper extends DevMoonbeamHelper {
-  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
-    options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';
-    super(logger, options);
-  }
-}
-
-export class DevAstarHelper extends AstarHelper {
-  wait: WaitGroup;
-
-  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
-    options.helperBase = options.helperBase ?? DevAstarHelper;
-
-    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;
-
-  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
-    options.helperBase = options.helperBase ?? DevAcalaHelper;
-
-    super(logger, options);
-    this.wait = new WaitGroup(this);
-  }
-  getSudo() {
-    // eslint-disable-next-line @typescript-eslint/naming-convention
-    const SudoHelperType = SudoHelper(this.helperBase);
-    return this.clone(SudoHelperType) as DevAcalaHelper;
-  }
-}
-
-export class DevPolkadexHelper extends PolkadexHelper {
-  wait: WaitGroup;
-  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
-    options.helperBase = options.helperBase ?? PolkadexHelper;
-
-    super(logger, options);
-    this.wait = new WaitGroup(this);
-  }
-
-  getSudo() {
-    // eslint-disable-next-line @typescript-eslint/naming-convention
-    const SudoHelperType = SudoHelper(this.helperBase);
-    return this.clone(SudoHelperType) as DevPolkadexHelper;
-  }
-}
-
-export class DevHydraDxHelper extends HydraDxHelper {
-  wait: WaitGroup;
-  fastDemocracy: HydraFastDemocracyGroup;
-
-  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
-    options.helperBase = options.helperBase ?? DevHydraDxHelper;
-
-    super(logger, options);
-
-    this.wait = new WaitGroup(this);
-    this.fastDemocracy = new HydraFastDemocracyGroup(this);
-  }
-}
-
-export class DevKaruraHelper extends DevAcalaHelper {}
-
-export class ArrangeGroup {
-  helper: DevUniqueHelper;
-
-  scheduledIdSlider = 0;
-
-  constructor(helper: DevUniqueHelper) {
-    this.helper = helper;
-  }
-
-  /**
-   * Generates accounts with the specified UNQ token balance
-   * @param balances balances for generated accounts. Each balance will be multiplied by the token nominal.
-   * @param donor donor account for balances
-   * @returns array of newly created accounts
-   * @example const [acc1, acc2, acc3] = await createAccounts([0n, 10n, 20n], donor);
-   */
-  createAccounts = async (balances: bigint[], donor: IKeyringPair): Promise<IKeyringPair[]> => {
-    let nonce = await this.helper.chain.getNonce(donor.address);
-    const wait = new WaitGroup(this.helper);
-    const ss58Format = this.helper.chain.getChainProperties().ss58Format;
-    const tokenNominal = this.helper.balance.getOneTokenNominal();
-    const transactions = [];
-    const accounts: IKeyringPair[] = [];
-    for(const balance of balances) {
-      const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);
-      accounts.push(recipient);
-      if(balance !== 0n) {
-        const tx = this.helper.constructApiCall('api.tx.balances.transferKeepAlive', [{Id: recipient.address}, balance * tokenNominal]);
-        transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));
-        nonce++;
-      }
-    }
-
-    await Promise.all(transactions).catch(_e => {});
-
-    //#region TODO remove this region, when nonce problem will be solved
-    const checkBalances = async () => {
-      let isSuccess = true;
-      for(let i = 0; i < balances.length; i++) {
-        const balance = await this.helper.balance.getSubstrate(accounts[i].address);
-        if(balance !== balances[i] * tokenNominal) {
-          isSuccess = false;
-          break;
-        }
-      }
-      return isSuccess;
-    };
-
-    let accountsCreated = false;
-    const maxBlocksChecked = await this.helper.arrange.isDevNode() ? 50 : 5;
-    // checkBalances retry up to 5-50 blocks
-    for(let index = 0; index < maxBlocksChecked; index++) {
-      accountsCreated = await checkBalances();
-      if(accountsCreated) break;
-      await wait.newBlocks(1);
-    }
-
-    if(!accountsCreated) throw Error('Accounts generation failed');
-    //#endregion
-
-    return accounts;
-  };
-
-  // TODO combine this method and createAccounts into one
-  createCrowd = async (accountsToCreate: number, withBalance: bigint, donor: IKeyringPair): Promise<IKeyringPair[]> => {
-    const createAsManyAsCan = async () => {
-      let transactions: any = [];
-      const accounts: IKeyringPair[] = [];
-      let nonce = await this.helper.chain.getNonce(donor.address);
-      const tokenNominal = this.helper.balance.getOneTokenNominal();
-      const ss58Format = this.helper.chain.getChainProperties().ss58Format;
-      for(let i = 0; i < accountsToCreate; i++) {
-        if(i === 500) { // if there are too many accounts to create
-          await Promise.allSettled(transactions); // wait while first 500 (should be 100 for devnode) tx will be settled
-          transactions = []; //
-          nonce = await this.helper.chain.getNonce(donor.address); // update nonce
-        }
-        const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);
-        accounts.push(recipient);
-        if(withBalance !== 0n) {
-          const tx = this.helper.constructApiCall('api.tx.balances.transferKeepAlive', [{Id: recipient.address}, withBalance * tokenNominal]);
-          transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));
-          nonce++;
-        }
-      }
-
-      const fullfilledAccounts = [];
-      await Promise.allSettled(transactions);
-      for(const account of accounts) {
-        const accountBalance = await this.helper.balance.getSubstrate(account.address);
-        if(accountBalance === withBalance * tokenNominal) {
-          fullfilledAccounts.push(account);
-        }
-      }
-      return fullfilledAccounts;
-    };
-
-
-    const crowd: IKeyringPair[] = [];
-    // do up to 5 retries
-    for(let index = 0; index < 5 && accountsToCreate !== 0; index++) {
-      const asManyAsCan = await createAsManyAsCan();
-      crowd.push(...asManyAsCan);
-      accountsToCreate -= asManyAsCan.length;
-    }
-
-    if(accountsToCreate !== 0) throw Error(`Crowd generation failed: ${accountsToCreate} accounts left`);
-
-    return crowd;
-  };
-
-  /**
-   * Generates one account with zero balance
-   * @returns the newly generated account
-   * @example const account = await helper.arrange.createEmptyAccount();
-   */
-  createEmptyAccount = (): IKeyringPair => {
-    const ss58Format = this.helper.chain.getChainProperties().ss58Format;
-    return this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);
-  };
-
-  isDevNode = async () => {
-    let blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();
-    if(blockNumber == 0) {
-      await this.helper.wait.newBlocks(1);
-      blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();
-    }
-    const block2 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])]);
-    const block1 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber - 1])]);
-    const findCreationDate = (block: any) => {
-      const humanBlock = block.toHuman();
-      let date;
-      humanBlock.block.extrinsics.forEach((ext: any) => {
-        if(ext.method.section === 'timestamp') {
-          date = Number(ext.method.args.now.replaceAll(',', ''));
-        }
-      });
-      return date;
-    };
-    const block1date = await findCreationDate(block1);
-    const block2date = await findCreationDate(block2);
-    if(block2date! - block1date! < 9000) return true;
-    return false;
-  };
-
-  async calculcateFee(payer: ICrossAccountId, promise: () => Promise<any>): Promise<bigint> {
-    const address = 'Substrate' in payer ? payer.Substrate : this.helper.address.ethToSubstrate(payer.Ethereum);
-    let balance = await this.helper.balance.getSubstrate(address);
-
-    await promise();
-
-    balance -= await this.helper.balance.getSubstrate(address);
-
-    return balance;
-  }
-
-  async calculatePoVInfo(txs: any[]): Promise<IPovInfo> {
-    const rawPovInfo = await this.helper.callRpc('api.rpc.povinfo.estimateExtrinsicPoV', [txs]);
-
-    const kvJson: {[key: string]: string} = {};
-
-    for(const kv of rawPovInfo.keyValues) {
-      kvJson[kv.key.toHex()] = kv.value.toHex();
-    }
-
-    const kvStr = JSON.stringify(kvJson);
-
-    const chainql = spawnSync(
-      'chainql',
-      [
-        `--tla-code=data=${kvStr}`,
-        '-e', `function(data) cql.dump(cql.chain("${this.helper.getEndpoint()}").latest._meta, data, {omit_empty:true})`,
-      ],
-    );
-
-    if(!chainql.stdout) {
-      throw Error('unable to get an output from the `chainql`');
-    }
-
-    return {
-      proofSize: rawPovInfo.proofSize.toNumber(),
-      compactProofSize: rawPovInfo.compactProofSize.toNumber(),
-      compressedProofSize: rawPovInfo.compressedProofSize.toNumber(),
-      results: rawPovInfo.results,
-      kv: JSON.parse(chainql.stdout.toString()),
-    };
-  }
-
-  calculatePalletAddress(palletId: any) {
-    const address = stringToU8a(('modl' + palletId).padEnd(32, '\0'));
-    return encodeAddress(address, this.helper.chain.getChainProperties().ss58Format);
-  }
-
-  makeScheduledIds(num: number): string[] {
-    function makeId(slider: number) {
-      const scheduledIdSize = 64;
-      const hexId = slider.toString(16);
-      const prefixSize = scheduledIdSize - hexId.length;
-
-      const scheduledId = '0x' + '0'.repeat(prefixSize) + hexId;
-
-      return scheduledId;
-    }
-
-    const ids = [];
-    for(let i = 0; i < num; i++) {
-      ids.push(makeId(this.scheduledIdSlider));
-      this.scheduledIdSlider += 1;
-    }
-
-    return ids;
-  }
-
-  makeScheduledId(): string {
-    return (this.makeScheduledIds(1))[0];
-  }
-
-  async captureEvents(eventSection: string, eventMethod: string): Promise<EventCapture> {
-    const capture = new EventCapture(this.helper, eventSection, eventMethod);
-    await capture.startCapture();
-
-    return capture;
-  }
-
-  makeXcmProgramWithdrawDeposit(beneficiary: Uint8Array, id: any, amount: bigint) {
-    return {
-      V2: [
-        {
-          WithdrawAsset: [
-            {
-              id,
-              fun: {
-                Fungible: amount,
-              },
-            },
-          ],
-        },
-        {
-          BuyExecution: {
-            fees: {
-              id,
-              fun: {
-                Fungible: amount,
-              },
-            },
-            weightLimit: 'Unlimited',
-          },
-        },
-        {
-          DepositAsset: {
-            assets: {
-              Wild: 'All',
-            },
-            maxAssets: 1,
-            beneficiary: {
-              parents: 0,
-              interior: {
-                X1: {
-                  AccountId32: {
-                    network: 'Any',
-                    id: beneficiary,
-                  },
-                },
-              },
-            },
-          },
-        },
-      ],
-    };
-  }
-
-  makeXcmProgramReserveAssetDeposited(beneficiary: Uint8Array, id: any, amount: bigint) {
-    return {
-      V2: [
-        {
-          ReserveAssetDeposited: [
-            {
-              id,
-              fun: {
-                Fungible: amount,
-              },
-            },
-          ],
-        },
-        {
-          BuyExecution: {
-            fees: {
-              id,
-              fun: {
-                Fungible: amount,
-              },
-            },
-            weightLimit: 'Unlimited',
-          },
-        },
-        {
-          DepositAsset: {
-            assets: {
-              Wild: 'All',
-            },
-            maxAssets: 1,
-            beneficiary: {
-              parents: 0,
-              interior: {
-                X1: {
-                  AccountId32: {
-                    network: 'Any',
-                    id: beneficiary,
-                  },
-                },
-              },
-            },
-          },
-        },
-      ],
-    };
-  }
-
-  makeUnpaidSudoTransactProgram(info: {weightMultiplier: number, call: string}) {
-    return {
-      V3: [
-        {
-          UnpaidExecution: {
-            weightLimit: 'Unlimited',
-            checkOrigin: null,
-          },
-        },
-        {
-          Transact: {
-            originKind: 'Superuser',
-            requireWeightAtMost: {
-              refTime: info.weightMultiplier * 200000000,
-              proofSize: info.weightMultiplier * 3000,
-            },
-            call: {
-              encoded: info.call,
-            },
-          },
-        },
-      ],
-    };
-  }
-}
-
-class MoonbeamAccountGroup {
-  helper: MoonbeamHelper;
-
-  keyring: Keyring;
-  _alithAccount: IKeyringPair;
-  _baltatharAccount: IKeyringPair;
-  _dorothyAccount: IKeyringPair;
-
-  constructor(helper: MoonbeamHelper) {
-    this.helper = helper;
-
-    this.keyring = new Keyring({type: 'ethereum'});
-    const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';
-    const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';
-    const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';
-
-    this._alithAccount = this.keyring.addFromUri(alithPrivateKey, undefined, 'ethereum');
-    this._baltatharAccount = this.keyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');
-    this._dorothyAccount = this.keyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');
-  }
-
-  alithAccount() {
-    return this._alithAccount;
-  }
-
-  baltatharAccount() {
-    return this._baltatharAccount;
-  }
-
-  dorothyAccount() {
-    return this._dorothyAccount;
-  }
-
-  create() {
-    return this.keyring.addFromUri(mnemonicGenerate());
-  }
-}
-
-class MoonbeamFastDemocracyGroup {
-  helper: DevMoonbeamHelper;
-
-  constructor(helper: DevMoonbeamHelper) {
-    this.helper = helper;
-  }
-
-  async executeProposal(proposalDesciption: string, encodedProposal: string) {
-    const proposalHash = blake2AsHex(encodedProposal);
-
-    const alithAccount = this.helper.account.alithAccount();
-    const baltatharAccount = this.helper.account.baltatharAccount();
-    const dorothyAccount = this.helper.account.dorothyAccount();
-
-    const councilVotingThreshold = 2;
-    const technicalCommitteeThreshold = 2;
-    const fastTrackVotingPeriod = 3;
-    const fastTrackDelayPeriod = 0;
-
-    console.log(`[democracy] executing '${proposalDesciption}' proposal`);
-
-    // >>> Propose external motion through council >>>
-    console.log('\t* Propose external motion through council.......');
-    const externalMotion = this.helper.democracy.externalProposeMajority({Inline: encodedProposal});
-    const encodedMotion = externalMotion?.method.toHex() || '';
-    const motionHash = blake2AsHex(encodedMotion);
-    console.log('\t* Motion hash is %s', motionHash);
-
-    await this.helper.collective.council.propose(
-      baltatharAccount,
-      councilVotingThreshold,
-      externalMotion,
-      externalMotion.encodedLength,
-    );
-
-    const councilProposalIdx = await this.helper.collective.council.proposalCount() - 1;
-    await this.helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);
-    await this.helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);
-
-    await this.helper.collective.council.close(
-      dorothyAccount,
-      motionHash,
-      councilProposalIdx,
-      {
-        refTime: 1_000_000_000,
-        proofSize: 1_000_000,
-      },
-      externalMotion.encodedLength,
-    );
-    console.log('\t* Propose external motion through council.......DONE');
-    // <<< Propose external motion through council <<<
-
-    // >>> Fast track proposal through technical committee >>>
-    console.log('\t* Fast track proposal through technical committee.......');
-    const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod);
-    const encodedFastTrack = fastTrack?.method.toHex() || '';
-    const fastTrackHash = blake2AsHex(encodedFastTrack);
-    console.log('\t* FastTrack hash is %s', fastTrackHash);
-
-    await this.helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);
-
-    const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1;
-    await this.helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);
-    await this.helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);
-
-    await this.helper.collective.techCommittee.close(
-      baltatharAccount,
-      fastTrackHash,
-      techProposalIdx,
-      {
-        refTime: 1_000_000_000,
-        proofSize: 1_000_000,
-      },
-      fastTrack.encodedLength,
-    );
-    console.log('\t* Fast track proposal through technical committee.......DONE');
-    // <<< Fast track proposal through technical committee <<<
-
-    const democracyStarted = await this.helper.wait.expectEvent(3, Event.Democracy.Started);
-    const referendumIndex = democracyStarted.referendumIndex;
-
-    // >>> Referendum voting >>>
-    console.log(`\t* Referendum #${referendumIndex} voting.......`);
-    await this.helper.democracy.referendumVote(dorothyAccount, referendumIndex, {
-      balance: 10_000_000_000_000_000_000n,
-      vote: {aye: true, conviction: 1},
-    });
-    console.log(`\t* Referendum #${referendumIndex} voting.......DONE`);
-    // <<< Referendum voting <<<
-
-    // Wait the proposal to pass
-    await this.helper.wait.expectEvent(3, Event.Democracy.Passed, event => event.referendumIndex == referendumIndex);
-
-    await this.helper.wait.newBlocks(1);
-
-    console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`);
-  }
-}
-
-class HydraFastDemocracyGroup {
-  helper: DevHydraDxHelper;
-
-  constructor(helper: DevHydraDxHelper) {
-    this.helper = helper;
-  }
-
-  async executeProposal(proposalDesciption: string, encodedProposal: string) {
-    const proposalHash = blake2AsHex(encodedProposal);
-    const aliceAccount = this.helper.util.fromSeed('//Alice');
-    const bobAccount = this.helper.util.fromSeed('//Bob');
-    const eveAccount = this.helper.util.fromSeed('//Eve');
-
-    const councilVotingThreshold = 1;
-    const technicalCommitteeThreshold = 3;
-    const fastTrackVotingPeriod = 3;
-    const fastTrackDelayPeriod = 0;
-
-    console.log(`[democracy] executing '${proposalDesciption}' proposal`);
-
-    // >>> Propose external motion through council >>>
-    console.log('\t* Propose external motion through council.......');
-    const externalMotion = this.helper.democracy.externalProposeMajority({Inline: encodedProposal});
-    const encodedMotion = externalMotion?.method.toHex() || '';
-    const motionHash = blake2AsHex(encodedMotion);
-    console.log('\t* Motion hash is %s', motionHash);
-
-    await this.helper.collective.council.propose(
-      aliceAccount,
-      councilVotingThreshold,
-      externalMotion,
-      externalMotion.encodedLength,
-    );
-
-    console.log('\t* Propose external motion through council.......DONE');
-    // <<< Propose external motion through council <<<
-
-    // >>> Fast track proposal through technical committee >>>
-    console.log('\t* Fast track proposal through technical committee.......');
-    const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod);
-    const encodedFastTrack = fastTrack?.method.toHex() || '';
-    const fastTrackHash = blake2AsHex(encodedFastTrack);
-    console.log('\t* FastTrack hash is %s', fastTrackHash);
-
-    await this.helper.collective.techCommittee.propose(aliceAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);
-
-    const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1;
-    await this.helper.collective.techCommittee.vote(aliceAccount, fastTrackHash, techProposalIdx, true);
-    await this.helper.collective.techCommittee.vote(bobAccount, fastTrackHash, techProposalIdx, true);
-    await this.helper.collective.techCommittee.vote(eveAccount, fastTrackHash, techProposalIdx, true);
-
-    await this.helper.collective.techCommittee.close(
-      bobAccount,
-      fastTrackHash,
-      techProposalIdx,
-      {
-        refTime: 1_000_000_000,
-        proofSize: 1_000_000,
-      },
-      fastTrack.encodedLength,
-    );
-    console.log('\t* Fast track proposal through technical committee.......DONE');
-    // <<< Fast track proposal through technical committee <<<
-
-    const democracyStarted = await this.helper.wait.expectEvent(3, Event.Democracy.Started);
-    const referendumIndex = democracyStarted.referendumIndex;
-
-    // >>> Referendum voting >>>
-    console.log(`\t* Referendum #${referendumIndex} voting.......`);
-    await this.helper.democracy.referendumVote(eveAccount, referendumIndex, {
-      balance: 10_000_000_000_000_000_000n,
-      vote: {aye: true, conviction: 1},
-    });
-    console.log(`\t* Referendum #${referendumIndex} voting.......DONE`);
-    // <<< Referendum voting <<<
-
-    // Wait the proposal to pass
-    await this.helper.wait.expectEvent(3, Event.Democracy.Passed, event => event.referendumIndex == referendumIndex);
-
-    await this.helper.wait.newBlocks(1);
-
-    console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`);
-  }
-}
-
-class WaitGroup {
-  helper: ChainHelperBase;
-
-  constructor(helper: ChainHelperBase) {
-    this.helper = helper;
-  }
-
-  sleep(milliseconds: number) {
-    return new Promise((resolve) => setTimeout(resolve, milliseconds));
-  }
-
-  private async waitWithTimeout(promise: Promise<any>, timeout: number) {
-    let isBlock = false;
-    promise.then(() => isBlock = true).catch(() => isBlock = true);
-    let totalTime = 0;
-    const step = 100;
-    while(!isBlock) {
-      await this.sleep(step);
-      totalTime += step;
-      if(totalTime >= timeout) throw Error('Blocks production failed');
-    }
-    return promise;
-  }
-
-  /**
-   * Launch some async operation, or throw an error after some time. Note that it will still continue executing after the timeout.
-   * @param promise async operation to race against the timeout
-   * @param timeoutMS time after which to time out
-   * @param timeoutError error message to throw
-   * @returns promise of the same type the operation had
-   */
-  withTimeout<T>(
-    promise: Promise<T>,
-    timeoutMS = 30000,
-    timeoutError = 'The operation has timed out!',
-  ): Promise<T> {
-    const timeout = new Promise<never>((_, reject) => {
-      setTimeout(() => {
-        reject(new Error(timeoutError));
-      }, timeoutMS);
-    });
-
-    return Promise.race<T>([promise, timeout]).catch(e => {throw new Error(e);});
-  }
-
-  /**
-   * Wait for specified number of blocks
-   * @param blocksCount number of blocks to wait
-   * @returns
-   */
-  async newBlocks(blocksCount = 1, timeout?: number): Promise<void> {
-    timeout = timeout ?? blocksCount * 60_000;
-    // eslint-disable-next-line no-async-promise-executor
-    const promise = new Promise<void>(async (resolve) => {
-      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(() => {
-        if(blocksCount > 0) {
-          blocksCount--;
-        } else {
-          unsubscribe();
-          resolve();
-        }
-      });
-    });
-    await this.waitWithTimeout(promise, timeout);
-    return promise;
-  }
-
-  /**
-   * Wait for the specified number of sessions to pass.
-   * Only applicable if the Session pallet is turned on.
-   * @param sessionCount number of sessions to wait
-   * @param blockTimeout time in ms until panicking that the chain has stopped producing blocks
-   * @returns
-   */
-  async newSessions(sessionCount = 1, blockTimeout = 60000): Promise<void> {
-    console.log(`Waiting for ${sessionCount} new session${sessionCount > 1 ? 's' : ''}.`
-      + ' This might take a while -- check SessionPeriod in pallet_session::Config for session time.');
-
-    const expectedSessionIndex = await (this.helper as DevUniqueHelper).session.getIndex() + sessionCount;
-    let currentSessionIndex = -1;
-
-    while(currentSessionIndex < expectedSessionIndex) {
-      // eslint-disable-next-line no-async-promise-executor
-      currentSessionIndex = await this.withTimeout(new Promise(async (resolve) => {
-        await this.newBlocks(1);
-        const res = await (this.helper as DevUniqueHelper).session.getIndex();
-        resolve(res);
-      }), blockTimeout, 'The chain has stopped producing blocks!');
-    }
-  }
-
-  async forParachainBlockNumber(blockNumber: bigint | number, timeout?: number) {
-    timeout = timeout ?? 30 * 60 * 1000;
-    // eslint-disable-next-line no-async-promise-executor
-    const promise = new Promise<void>(async (resolve) => {
-      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {
-        if(data.number.toNumber() >= blockNumber) {
-          unsubscribe();
-          resolve();
-        }
-      });
-    });
-    await this.waitWithTimeout(promise, timeout);
-    return promise;
-  }
-
-  async forRelayBlockNumber(blockNumber: bigint | number, timeout?: number) {
-    timeout = timeout ?? 30 * 60 * 1000;
-    // eslint-disable-next-line no-async-promise-executor
-    const promise = new Promise<void>(async (resolve) => {
-      const unsubscribe = await this.helper.getApi().query.parachainSystem.validationData((data: any) => {
-        if(data.value.relayParentNumber.toNumber() >= blockNumber) {
-          // @ts-ignore
-          unsubscribe();
-          resolve();
-        }
-      });
-    });
-    await this.waitWithTimeout(promise, timeout);
-    return promise;
-  }
-
-  noScheduledTasks() {
-    const api = this.helper.getApi();
-
-    // eslint-disable-next-line no-async-promise-executor
-    const promise = new Promise<void>(async resolve => {
-      const unsubscribe = await api.rpc.chain.subscribeNewHeads(async () => {
-        const areThereScheduledTasks = await api.query.scheduler.lookup.entries();
-
-        if(areThereScheduledTasks.length == 0) {
-          unsubscribe();
-          resolve();
-        }
-      });
-    });
-
-    return promise;
-  }
-
-  parachainBlockMultiplesOf(val: bigint) {
-    // eslint-disable-next-line no-async-promise-executor
-    const promise = new Promise<void>(async resolve => {
-      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {
-        if(data.number.toBigInt() % val == 0n) {
-          console.log(`from waiter: ${data.number.toBigInt()}`);
-          unsubscribe();
-          resolve();
-        }
-      });
-    });
-    return promise;
-  }
-
-  event<T extends IEventHelper>(
-    maxBlocksToWait: number,
-    eventHelper: T,
-    filter: (_: any) => boolean = () => true,
-  ): any {
-    // eslint-disable-next-line no-async-promise-executor
-    const promise = new Promise<T | null>(async (resolve) => {
-      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => {
-        const blockNumber = header.number.toJSON();
-        const blockHash = header.hash;
-        const eventIdStr = `${eventHelper.section()}.${eventHelper.method()}`;
-        const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;
-
-        this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);
-
-        const apiAt = await this.helper.getApi().at(blockHash);
-        const eventRecords = (await apiAt.query.system.events()) as any;
-
-        const neededEvent = eventRecords.toArray()
-          .filter((r: FrameSystemEventRecord) => r.event.section == eventHelper.section() && r.event.method == eventHelper.method())
-          .map((r: FrameSystemEventRecord) => eventHelper.wrapEvent(r.event.data))
-          .find(filter);
-
-        if(neededEvent) {
-          unsubscribe();
-          resolve(neededEvent);
-        } else if(maxBlocksToWait > 0) {
-          maxBlocksToWait--;
-        } else {
-          this.helper.logger.log(`Eligible event \`${eventIdStr}\` is NOT found.
-          The wait lasted until block ${blockNumber} inclusive`);
-          unsubscribe();
-          resolve(null);
-        }
-      });
-    });
-    return promise;
-  }
-
-  async expectEvent<T extends IEventHelper>(
-    maxBlocksToWait: number,
-    eventHelper: T,
-    filter: (e: any) => boolean = () => true,
-  ) {
-    const e = await this.event(maxBlocksToWait, eventHelper, filter);
-    if(e == null) {
-      throw Error(`The event '${eventHelper.section()}.${eventHelper.method()}' is expected`);
-    } else {
-      return e;
-    }
-  }
-}
-
-class SessionGroup {
-  helper: ChainHelperBase;
-
-  constructor(helper: ChainHelperBase) {
-    this.helper = helper;
-  }
-
-  //todo:collator documentation
-  async getIndex(): Promise<number> {
-    return (await this.helper.callRpc('api.query.session.currentIndex', [])).toNumber();
-  }
-
-  newSessions(sessionCount = 1, blockTimeout = 24000): Promise<void> {
-    return (this.helper as DevUniqueHelper).wait.newSessions(sessionCount, blockTimeout);
-  }
-
-  setOwnKeys(signer: TSigner, key: string) {
-    return this.helper.executeExtrinsic(
-      signer,
-      'api.tx.session.setKeys',
-      [key, '0x0'],
-      true,
-    );
-  }
-
-  setOwnKeysFromAddress(signer: TSigner) {
-    return this.setOwnKeys(signer, '0x' + Buffer.from(signer.addressRaw).toString('hex'));
-  }
-}
-
-class TestUtilGroup {
-  helper: DevUniqueHelper;
-
-  constructor(helper: DevUniqueHelper) {
-    this.helper = helper;
-  }
-
-  async enable(testUtilsPalletName: string) {
-    if(this.helper.fetchMissingPalletNames([testUtilsPalletName]).length != 0) {
-      return;
-    }
-
-    const signer = this.helper.util.fromSeed('//Alice');
-    await this.helper.getSudo<DevUniqueHelper>().executeExtrinsic(signer, 'api.tx.testUtils.enable', [], true);
-  }
-
-  async setTestValue(signer: TSigner, testVal: number) {
-    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValue', [testVal], true);
-  }
-
-  async incTestValue(signer: TSigner) {
-    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.incTestValue', [], true);
-  }
-
-  async setTestValueAndRollback(signer: TSigner, testVal: number) {
-    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValueAndRollback', [testVal], true);
-  }
-
-  async testValue(blockIdx?: number) {
-    const api = blockIdx
-      ? await this.helper.getApi().at(await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockIdx]))
-      : this.helper.getApi();
-
-    return (await api.query.testUtils.testValue()).toJSON();
-  }
-
-  async justTakeFee(signer: TSigner) {
-    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.justTakeFee', [], true);
-  }
-
-  async selfCancelingInc(signer: TSigner, scheduledId: string, maxTestVal: number) {
-    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.selfCancelingInc', [scheduledId, maxTestVal], true);
-  }
-}
-
-class EventCapture {
-  helper: DevUniqueHelper;
-  eventSection: string;
-  eventMethod: string;
-  events: EventRecord[] = [];
-  unsubscribe: VoidFn | null = null;
-
-  constructor(
-    helper: DevUniqueHelper,
-    eventSection: string,
-    eventMethod: string,
-  ) {
-    this.helper = helper;
-    this.eventSection = eventSection;
-    this.eventMethod = eventMethod;
-  }
-
-  async startCapture() {
-    this.stopCapture();
-    this.unsubscribe = (await this.helper.getApi().query.system.events((eventRecords: FrameSystemEventRecord[]) => {
-      const newEvents = eventRecords.filter(r => r.event.section == this.eventSection && r.event.method == this.eventMethod);
-
-      this.events.push(...newEvents);
-    })) as any;
-  }
-
-  stopCapture() {
-    if(this.unsubscribe !== null) {
-      this.unsubscribe();
-    }
-  }
-
-  extractCapturedEvents() {
-    return this.events;
-  }
-}
-
-class AdminGroup {
-  helper: UniqueHelper;
-
-  constructor(helper: UniqueHelper) {
-    this.helper = helper;
-  }
-
-  async payoutStakers(signer: IKeyringPair, stakersToPayout: number):  Promise<{staker: string, stake: bigint, payout: bigint}[]> {
-    const payoutResult = await this.helper.executeExtrinsic(signer, 'api.tx.appPromotion.payoutStakers', [stakersToPayout], true);
-    return payoutResult.result.events.filter(e => e.event.method === 'StakingRecalculation').map(e => ({
-      staker: e.event.data[0].toString(),
-      stake: e.event.data[1].toBigInt(),
-      payout: e.event.data[2].toBigInt(),
-    }));
-  }
-}
-
-// 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;
-    }
-
-    override 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,
-      );
-    }
-  };
-}
deletedjs-packages/playgrounds/unique.governance.tsdiffbeforeafterboth
--- a/js-packages/playgrounds/unique.governance.ts
+++ /dev/null
@@ -1,531 +0,0 @@
-import {blake2AsHex} from '@polkadot/util-crypto';
-import type {PalletDemocracyConviction} from '@polkadot/types/lookup';
-import type {IPhasicEvent, TSigner} from './types.js';
-import {HelperGroup, UniqueHelper} from './unique.js';
-
-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);
-  }*/
-}
deletedjs-packages/playgrounds/unique.xcm.tsdiffbeforeafterboth
--- a/js-packages/playgrounds/unique.xcm.ts
+++ /dev/null
@@ -1,449 +0,0 @@
-import {ApiPromise, WsProvider} from '@polkadot/api';
-import type {IKeyringPair} from '@polkadot/types/types';
-import {ChainHelperBase, EthereumBalanceGroup, HelperGroup, SubstrateBalanceGroup, UniqueHelper} from './unique.js';
-import type {ILogger, TSigner, TSubstrateAccount} from './types.js';
-import type {AcalaAssetMetadata, DemocracyStandardAccountVote, MoonbeamAssetInfo} from './types.xcm.js';
-
-
-export class XcmChainHelper extends ChainHelperBase {
-  override 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 DemocracyGroup<T extends ChainHelperBase> extends HelperGroup<T> {
-  notePreimagePallet: string;
-
-  constructor(helper: T, 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 CollectiveGroup<T extends ChainHelperBase> extends HelperGroup<T> {
-  collective: string;
-
-  constructor(helper: T, palletName: string) {
-    super(helper);
-
-    this.collective = palletName;
-  }
-
-  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, assetId: any, name: string, tokenPrefix: string, mode: 'NFT' | { Fungible: number }) {
-    await this.helper.executeExtrinsic(
-      signer,
-      'api.tx.foreignAssets.forceRegisterForeignAsset',
-      [{V3: assetId}, this.helper.util.str2vec(name), tokenPrefix, mode],
-      true,
-    );
-  }
-
-  async foreignCollectionId(assetId: any) {
-    return (await this.helper.callRpc('api.query.foreignAssets.foreignAssetToCollection', [assetId])).toJSON();
-  }
-}
-
-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 | bigint, admin: string, minimalBalance: bigint) {
-    await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true);
-  }
-
-  async forceCreate(signer: TSigner, assetId: number | bigint, admin: string, minimalBalance: bigint, isSufficient = true) {
-    await this.helper.executeExtrinsic(signer, 'api.tx.assets.forceCreate', [assetId, admin, isSufficient, minimalBalance], true);
-  }
-
-  async setMetadata(signer: TSigner, assetId: number | bigint, 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 | bigint, beneficiary: string, amount: bigint) {
-    await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);
-  }
-
-  async assetInfo(assetId: number | bigint) {
-    return (await this.helper.callRpc('api.query.assets.asset', [assetId])).toJSON();
-  }
-
-  async account(assetId: string | number | bigint, 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);
-  }
-}
-
-export class HydraDxHelper extends XcmChainHelper {
-  balance: SubstrateBalanceGroup<HydraDxHelper>;
-  xcm: XcmGroup<HydraDxHelper>;
-  xTokens: XTokensGroup<HydraDxHelper>;
-  democracy: DemocracyGroup<HydraDxHelper>;
-  collective: {
-    council: CollectiveGroup<HydraDxHelper>,
-    techCommittee: CollectiveGroup<HydraDxHelper>,
-  };
-
-  constructor(logger?: ILogger, options: { [key: string]: any } = {}) {
-    super(logger, options.helperBase ?? HydraDxHelper);
-    options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';
-
-    this.balance = new SubstrateBalanceGroup(this);
-    this.xcm = new XcmGroup(this, 'polkadotXcm');
-    this.xTokens = new XTokensGroup(this);
-    this.democracy = new DemocracyGroup(this, options);
-    this.collective = {
-      council: new CollectiveGroup(this, 'council'),
-      techCommittee: new CollectiveGroup(this, 'technicalCommittee'),
-    };
-  }
-}
-
addedjs-packages/scripts/authorizeEnactUpgrade.tsdiffbeforeafterboth
--- /dev/null
+++ b/js-packages/scripts/authorizeEnactUpgrade.ts
@@ -0,0 +1,19 @@
+import {readFile} from 'fs/promises';
+import {u8aToHex} from '@polkadot/util';
+import {usingPlaygrounds} from '@unique/test-utils/util.js';
+import {blake2AsHex} from '@polkadot/util-crypto';
+
+
+const codePath = process.argv[2];
+if(!codePath) throw new Error('missing code path argument');
+
+const code = await readFile(codePath);
+
+await usingPlaygrounds(async (helper, privateKey) => {
+  const alice = await privateKey('//Alice');
+  const hex = blake2AsHex(code);
+  await helper.getSudo().executeExtrinsic(alice, 'api.tx.parachainSystem.authorizeUpgrade', [hex, true]);
+  await helper.getSudo().executeExtrinsicUncheckedWeight(alice, 'api.tx.parachainSystem.enactAuthorizedUpgrade', [u8aToHex(code)]);
+});
+// We miss disconnect/unref somewhere.
+process.exit(0);
modifiedjs-packages/scripts/benchmarks/mintFee/index.tsdiffbeforeafterboth
--- a/js-packages/scripts/benchmarks/mintFee/index.ts
+++ b/js-packages/scripts/benchmarks/mintFee/index.ts
@@ -1,13 +1,13 @@
 import {usingEthPlaygrounds} from '@unique/tests/eth/util/index.js';
 import {EthUniqueHelper} from '@unique/tests/eth/util/playgrounds/unique.dev.js';
 import {readFile} from 'fs/promises';
-import type {ICrossAccountId} from '@unique/playgrounds/types.js';
+import type {ICrossAccountId} from '@unique-nft/playgrounds/types.js';
 import type {IKeyringPair} from '@polkadot/types/types';
-import {UniqueNFTCollection} from '@unique/playgrounds/unique.js';
+import {UniqueNFTCollection} from '@unique-nft/playgrounds/unique.js';
 import {Contract} from 'web3-eth-contract';
 import {createObjectCsvWriter} from 'csv-writer';
 import {convertToTokens, createCollectionForBenchmarks, PERMISSIONS, PROPERTIES} from '../utils/common.js';
-import {makeNames} from '@unique/tests/util/index.js';
+import {makeNames} from '@unique/test-utils/util.js';
 import type {ContractImports} from '@unique/tests/eth/util/playgrounds/types.js';
 
 const {dirname} = makeNames(import.meta.url);
modifiedjs-packages/scripts/benchmarks/nesting/index.tsdiffbeforeafterboth
--- a/js-packages/scripts/benchmarks/nesting/index.ts
+++ b/js-packages/scripts/benchmarks/nesting/index.ts
@@ -4,7 +4,7 @@
 import type {IKeyringPair} from '@polkadot/types/types';
 import {Contract} from 'web3-eth-contract';
 import {convertToTokens} from '../utils/common.js';
-import {makeNames} from '@unique/tests/util/index.js';
+import {makeNames} from '@unique/test-utils/util.js';
 import type {ContractImports} from '@unique/tests/eth/util/playgrounds/types.js';
 import type {RMRKNestableMintable} from './ABIGEN/index.js';
 
modifiedjs-packages/scripts/benchmarks/opsFee/index.tsdiffbeforeafterboth
--- a/js-packages/scripts/benchmarks/opsFee/index.ts
+++ b/js-packages/scripts/benchmarks/opsFee/index.ts
@@ -3,13 +3,13 @@
 import {readFile} from 'fs/promises';
 import {CollectionLimitField,  CreateCollectionData,  TokenPermissionField} from '@unique/tests/eth/util/playgrounds/types.js';
 import type {IKeyringPair} from '@polkadot/types/types';
-import {UniqueFTCollection, UniqueNFTCollection} from '@unique/playgrounds/unique.js';
+import {UniqueFTCollection, UniqueNFTCollection} from '@unique-nft/playgrounds/unique.js';
 import {Contract} from 'web3-eth-contract';
 import {createObjectCsvWriter} from 'csv-writer';
 import {FunctionFeeVM} from '../utils/types.js';
 import type {IFunctionFee} from '../utils/types.js';
 import {convertToTokens, createCollectionForBenchmarks, PERMISSIONS, PROPERTIES, SUBS_PROPERTIES} from '../utils/common.js';
-import {makeNames} from '@unique/tests/util/index.js';
+import {makeNames} from '@unique/test-utils/util.js';
 
 
 const {dirname} = makeNames(import.meta.url);
modifiedjs-packages/scripts/benchmarks/utils/common.tsdiffbeforeafterboth
--- a/js-packages/scripts/benchmarks/utils/common.ts
+++ b/js-packages/scripts/benchmarks/utils/common.ts
@@ -1,6 +1,6 @@
 import {EthUniqueHelper} from '@unique/tests/eth/util/playgrounds/unique.dev.js';
-import {UniqueNFTCollection, UniqueRFTCollection} from '@unique/playgrounds/unique.js';
-import type {ITokenPropertyPermission, TCollectionMode} from '@unique/playgrounds/types.js';
+import {UniqueNFTCollection, UniqueRFTCollection} from '@unique-nft/playgrounds/unique.js';
+import type {ITokenPropertyPermission, TCollectionMode} from '@unique-nft/playgrounds/types.js';
 import type {IKeyringPair} from '@polkadot/types/types';
 
 export const PROPERTIES = Array(40)
modifiedjs-packages/scripts/calibrateApply.tsdiffbeforeafterboth
--- a/js-packages/scripts/calibrateApply.ts
+++ b/js-packages/scripts/calibrateApply.ts
@@ -1,6 +1,6 @@
 import {readFile, writeFile} from 'fs/promises';
 import path from 'path';
-import {makeNames, usingPlaygrounds} from '@unique/tests/util/index.js';
+import {makeNames, usingPlaygrounds} from '@unique/test-utils/util.js';
 
 const {dirname} = makeNames(import.meta.url);
 
addedjs-packages/scripts/createHrmp.tsdiffbeforeafterboth
--- /dev/null
+++ b/js-packages/scripts/createHrmp.ts
@@ -0,0 +1,37 @@
+import {usingPlaygrounds} from '@unique/test-utils/util.js';
+import config from '../tests/config.js';
+
+const profile = process.argv[2];
+if(!profile) throw new Error('missing profile/relay argument');
+
+await usingPlaygrounds(async (helper, privateKey) => {
+  const bidirOpen = async (a: number, b: number) => {
+    console.log(`Opening ${a} <=> ${b}`);
+    await helper.getSudo().executeExtrinsic(alice, 'api.tx.hrmp.forceOpenHrmpChannel', [a, b, 8, 512]);
+    await helper.getSudo().executeExtrinsic(alice, 'api.tx.hrmp.forceOpenHrmpChannel', [b, a, 8, 512]);
+  };
+  const alice = await privateKey('//Alice');
+  switch (profile) {
+    case 'opal':
+      await bidirOpen(1001, 1002);
+      break;
+    case 'quartz':
+      await bidirOpen(1001, 1002);
+      await bidirOpen(1001, 1003);
+      await bidirOpen(1001, 1004);
+      await bidirOpen(1001, 1005);
+      break;
+    case 'unique':
+      await bidirOpen(1001, 1002);
+      await bidirOpen(1001, 1003);
+      await bidirOpen(1001, 1004);
+      await bidirOpen(1001, 1005);
+      await bidirOpen(1001, 1006);
+      await bidirOpen(1001, 1007);
+      break;
+    default:
+      throw new Error(`unknown hrmp config profile: ${profile}`);
+  }
+}, config.relayUrl);
+// We miss disconnect/unref somewhere.
+process.exit(0);
modifiedjs-packages/scripts/generateEnv.tsdiffbeforeafterboth
--- a/js-packages/scripts/generateEnv.ts
+++ b/js-packages/scripts/generateEnv.ts
@@ -1,7 +1,7 @@
 import {ApiPromise, WsProvider} from '@polkadot/api';
 import {readFile} from 'fs/promises';
 import {join} from 'path';
-import {makeNames} from '@unique/tests/util/index.js';
+import {makeNames} from '@unique/test-utils/util.js';
 
 const {dirname} = makeNames(import.meta.url);
 
addedjs-packages/scripts/identitySetter.tsdiffbeforeafterboth
--- /dev/null
+++ b/js-packages/scripts/identitySetter.ts
@@ -0,0 +1,229 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+//
+// Pulls identities and sub-identities from a chain and then makes a preimage to later force upload them into another.
+// Only changed or previously non-existent data are inserted.
+//
+// Usage: `yarn setIdentities [relay WS URL] [parachain WS URL] [user key]
+// Example: `yarn setIdentities wss://polkadot-rpc.dwellir.com ws://localhost:9944 escape pattern miracle train sudden cart adapt embark wedding alien lamp mesh`
+
+import {encodeAddress} from '@polkadot/keyring';
+import type {IKeyringPair} from '@polkadot/types/types';
+import {usingPlaygrounds, Pallets} from '@unique/test-utils/util.js';
+import {ChainHelperBase} from '@unique-nft/playgrounds/unique.js';
+
+const relayUrl = process.argv[2] ?? 'ws://localhost:9844';
+const paraUrl = process.argv[3] ?? 'ws://localhost:9944';
+const key = process.argv.length > 4 ? process.argv.slice(4).join(' ') : '//Alice';
+
+export function extractAccountId(key: any): string {
+  return (key as any).toHuman()[0];
+}
+
+export function extractIdentityInfo(value: any): object {
+  const heart = (value as any).unwrap();
+  const identity = heart.toJSON();
+  identity.judgements = heart.judgements.toHuman();
+  return identity;
+}
+
+export function extractIdentity(key: any, value: any): [string, any] {
+  return [extractAccountId(key), extractIdentityInfo(value)];
+}
+
+export async function getIdentities(helper: ChainHelperBase, noneCasePredicate?: (key: any, value: any) => void) {
+  const identities: [string, any][] = [];
+  for(const [key, v] of await helper.getApi().query.identity.identityOf.entries()) {
+    const value = v as any;
+    if(value.isNone) {
+      if(noneCasePredicate) noneCasePredicate(key, value);
+      continue;
+    }
+    identities.push(extractIdentity(key, value));
+  }
+  return identities;
+}
+
+// whether the existing chain data is more important than the coming
+function isCurrentChainDataPriority(helper: ChainHelperBase, currentChainId: number | undefined, relayChainId: number) {
+  if(!currentChainId) return false;
+  // information has come from local chain, and is automatically superior
+  if(currentChainId == helper.chain.getChainProperties().ss58Format) return true;
+  // the lower the id, the more important it is (Polkadot has ss58 prefix = 0, Kusama has ss58 prefix = 2)
+  return currentChainId < relayChainId;
+}
+
+// construct an object with all data necessary for insertion from storage query results
+export function constructSubInfo(identityAccount: string, subQuery: any, supers: any[], ss58?: number): [string, any] {
+  const deposit = subQuery.toJSON()[0];
+  const subIdentities = subQuery.toHuman()[1];
+  subIdentities.map((sub: string) => supers.find((sup: any) => sup[0] === sub));
+
+  return [
+    encodeAddress(identityAccount, ss58), [
+      deposit,
+      subIdentities.map((sub: string): [string, object] | null => {
+        const sup = supers.find((sup: any) => sup[0] === sub);
+        if(!sup) {
+          console.error(`Error: Could not find info on super for \nsub-identity account\t${sub} of \nsuper account \t\t${identityAccount}, skipping.`);
+          return null;
+        }
+        return [encodeAddress(sub, ss58), sup[1].toJSON()[1]];
+      }).filter((x: any) => x),
+    ],
+  ];
+}
+
+export async function getSubs(helper: ChainHelperBase) {
+  return (await helper.getApi().query.identity.subsOf.entries()).map(([key, value]) => [extractAccountId(key), value as any]);
+}
+
+export async function getSupers(helper: ChainHelperBase) {
+  return (await helper.getApi().query.identity.superOf.entries()).map(([key, value]) => [extractAccountId(key), value as any]);
+}
+
+async function uploadPreimage(helper: ChainHelperBase, preimageMaker: IKeyringPair, preimage: string) {
+  try {
+    await helper.executeExtrinsic(preimageMaker, 'api.tx.preimage.notePreimage', [preimage]);
+  } catch (e: any) {
+    if(e.message.includes('AlreadyNoted')) {
+      console.warn('Warning: The same preimage already exists on the chain. Nothing was uploaded.');
+    } else {
+      console.error(e);
+    }
+  }
+}
+
+// The utility for pulling identity and sub-identity data
+const forceInsertIdentities = async (): Promise<void> => {
+  let relaySS58Prefix = 0;
+  const identitiesOnRelay: any[] = [];
+  const subsOnRelay: any[] = [];
+  const identitiesToRemove: string[] = [];
+  await usingPlaygrounds(async helper => {
+    try {
+      relaySS58Prefix = helper.chain.getChainProperties().ss58Format;
+      // iterate over every identity
+      for(const [key, value] of await getIdentities(helper, (key, _value) => identitiesToRemove.push((key as any).toHuman()[0]))) {
+        // if any of the judgements resulted in a good confirmed outcome, keep this identity
+        let knownGood = false, reasonable = false;
+        for(const [_id, judgement] of value.judgements) {
+          if(judgement == 'KnownGood') knownGood = true;
+          if(judgement == 'Reasonable') reasonable = true;
+        }
+        if(!(reasonable || knownGood)) continue;
+        // replace the registrator id with the relay chain's ss58 format
+        value.judgements = [[helper.chain.getChainProperties().ss58Format, knownGood ? 'KnownGood' : 'Reasonable']];
+        identitiesOnRelay.push([key, value]);
+      }
+
+      const sublessIdentities = [...identitiesOnRelay];
+      const supersOfSubs = await getSupers(helper);
+
+      // iterate over every sub-identity
+      for(const [key, value] of await getSubs(helper)) {
+        // only get subs of the identities interesting to us
+        const identityIndex = sublessIdentities.findIndex((x: any) => x[0] == key);
+        if(identityIndex == -1) continue;
+        sublessIdentities.splice(identityIndex, 1);
+        subsOnRelay.push(constructSubInfo(key, value, supersOfSubs));
+      }
+
+      // mark the rest of sub-identities for deletion with empty arrays
+      /*for(const [account, _identity] of sublessIdentities) {
+        subsOnRelay.push([account, ['0', []]]);
+      }*/
+    } catch (error) {
+      console.error(error);
+      throw Error('Error during fetching identities');
+    }
+  }, relayUrl);
+
+  await usingPlaygrounds(async (helper, privateKey) => {
+    if(helper.fetchMissingPalletNames([Pallets.Identity]).length != 0) console.error('pallet-identity is not included in parachain.');
+    if(helper.fetchMissingPalletNames([Pallets.Preimage]).length != 0) console.error('pallet-preimage is not included in parachain.');
+
+    try {
+      const preimageMaker = await privateKey(key);
+      const ss58Format = helper.chain.getChainProperties().ss58Format;
+      const paraIdentities = await getIdentities(helper);
+      const identitiesToAdd: any[] = [];
+      const paraAccountsRegistrators: {[name: string]: number} = {};
+
+      // cross-reference every account for changes
+      for(const [key, value] of identitiesOnRelay) {
+        const encodedKey = encodeAddress(key, ss58Format);
+
+        // only update if the identity info does not exist or is changed
+        const identity = paraIdentities.find(i => i[0] === encodedKey);
+        if(identity) {
+          const registratorId = identity[1].judgements[0][0];
+          paraAccountsRegistrators[encodedKey] = registratorId;
+          if(isCurrentChainDataPriority(helper, registratorId, value.judgements[0][0])
+            || JSON.stringify(value.info) === JSON.stringify(identity[1].info)) {
+            continue;
+          }
+        }
+
+        identitiesToAdd.push([key, value]);
+        // exercise caution - in case we have an identity and the realy doesn't, it might mean one of two things:
+        // 1) it was deleted on the relay;
+        // 2) it is our own identity, we don't want to delete it.
+        // identitiesToRemove.push((key as any).toHuman()[0]);
+      }
+
+      if(identitiesToRemove.length != 0)
+        await uploadPreimage(
+          helper,
+          preimageMaker,
+          helper.constructApiCall('api.tx.identity.forceRemoveIdentities', [identitiesToRemove]).method.toHex(),
+        );
+      if(identitiesToAdd.length != 0)
+        await uploadPreimage(
+          helper,
+          preimageMaker,
+          helper.constructApiCall('api.tx.identity.forceInsertIdentities', [identitiesToAdd]).method.toHex(),
+        );
+
+      console.log(`Tried to push ${identitiesToAdd.length} identities`
+        + ` and found ${identitiesToRemove.length} identities for potential removal.`
+        + ` Currently there are ${(await helper.getApi().query.identity.identityOf.keys()).length} identities on the chain.`);
+
+      // fill sub-identities
+      const paraSubs = await getSubs(helper);
+      const supersOfSubs = await getSupers(helper);
+      const subsToUpdate: any[] = [];
+
+      for(const [key, value] of subsOnRelay) {
+        const encodedKey = encodeAddress(key, ss58Format);
+        const sub = paraSubs.find(i => i[0] === encodedKey);
+        if(sub) {
+          // only update if the sub-identity info does not exist or is changed
+          if(isCurrentChainDataPriority(helper, paraAccountsRegistrators[encodedKey], relaySS58Prefix)
+            || JSON.stringify(value) === JSON.stringify(constructSubInfo(sub[0], sub[1], supersOfSubs)[1])) {
+            continue;
+          }
+        } else if(value[1].length == 0)
+          continue;
+
+        subsToUpdate.push([key, value]);
+      }
+
+      if(subsToUpdate.length != 0)
+        await uploadPreimage(
+          helper,
+          preimageMaker,
+          helper.constructApiCall('api.tx.identity.forceSetSubs', [subsToUpdate]).method.toHex(),
+        );
+
+      console.log(`Also tried to push ${subsToUpdate.length} identities with their sub-identities.`
+        + ` Currently there are ${(await helper.getApi().query.identity.subsOf.keys()).length} identities with subs.`);
+    } catch (error) {
+      console.error(error);
+      throw Error('Error during setting identities');
+    }
+  }, paraUrl);
+};
+
+if(process.argv[1] === module.filename)
+  forceInsertIdentities().catch(() => process.exit(1));
addedjs-packages/scripts/relayIdentitiesChecker.tsdiffbeforeafterboth
--- /dev/null
+++ b/js-packages/scripts/relayIdentitiesChecker.ts
@@ -0,0 +1,114 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+//
+// Checks and reports the differences between identities and sub-identities on two chains.
+//
+// Usage: `yarn checkRelayIdentities [relay-1 WS URL] [relay-2 WS URL]`
+// Example: `yarn checkRelayIdentities wss://polkadot-rpc.dwellir.com wss://kusama-rpc.dwellir.com`
+
+import {encodeAddress} from '@polkadot/keyring';
+import {usingPlaygrounds} from '@unique/test-utils/util.js';
+import {getIdentities, getSubs, getSupers, constructSubInfo} from './identitySetter.js';
+
+const relay1Url = process.argv[2] ?? 'ws://localhost:9844';
+const relay2Url = process.argv[3] ?? 'ws://localhost:9844';
+
+async function pullIdentities(relayUrl: string): Promise<[any[], any[]]> {
+  const identities: any[] = [];
+  const subs: any[] = [];
+
+  await usingPlaygrounds(async helper => {
+    try {
+      // iterate over every identity
+      for(const [key, value] of await getIdentities(helper)) {
+        // if any of the judgements resulted in a good confirmed outcome, keep this identity
+        if(value.toHuman().judgements.filter((x: any) => x[1] == 'Reasonable' || x[1] == 'KnownGood').length == 0) continue;
+        identities.push([key, value]);
+      }
+
+      const supersOfSubs = await getSupers(helper);
+
+      // iterate over every sub-identity
+      for(const [key, value] of await getSubs(helper)) {
+        // only get subs of the identities interesting to us
+        if(identities.find((x: any) => x[0] == key) == -1) continue;
+        subs.push(constructSubInfo(key, value, supersOfSubs));
+      }
+    } catch (error) {
+      console.error(error);
+      throw Error(`Error during fetching identities on ${relayUrl}`);
+    }
+  }, relayUrl);
+
+  return [identities, subs];
+}
+
+// The utility for pulling identity and sub-identity data
+const checkRelayIdentities = async (): Promise<void> => {
+  const [identitiesOnRelay1, subIdentitiesOnRelay1] = await pullIdentities(relay1Url);
+  const [identitiesOnRelay2, subIdentitiesOnRelay2] = await pullIdentities(relay2Url);
+
+  console.log('identities counts:\t', identitiesOnRelay1.length, identitiesOnRelay2.length);
+  console.log('sub-identities counts:\t', subIdentitiesOnRelay1.length, subIdentitiesOnRelay2.length);
+  console.log();
+
+  try {
+    const matchingAddresses: string[] = [];
+    const inequalIdentities: {[name: string]: [any, any]} = {};
+
+    for(const [key1, value1] of identitiesOnRelay1) {
+      const address = encodeAddress(key1);
+      const identity2 = identitiesOnRelay2.find(([key2, _value2]) => address === encodeAddress(key2));
+      if(!identity2) continue;
+      matchingAddresses.push(address);
+
+      //const [[key2, value2]] = identitiesOnRelay2.splice(index2, 1);
+      const [_key2, value2] = identity2;
+      if(JSON.stringify(value1.info) === JSON.stringify(value2.info)) continue;
+      inequalIdentities[address] = [value1, value2];
+    }
+
+    /*for (const [v1, v2] of Object.values(inequalIdentities)) {
+      console.log(v1.toHuman().info);
+      console.log();
+      console.log(v2.toHuman().info);
+      await new Promise(resolve => setTimeout(resolve, 4000));
+    }*/
+
+    console.log(`Accounts with identities on both relays:\t${matchingAddresses.length}`);
+    console.log(`Sub-identities with conflicting information:\t${Object.entries(inequalIdentities).length}`);
+    console.log();
+
+    const inequalSubIdentities = [];
+    let matchesFound = 0;
+    for(const address of matchingAddresses) {
+      const sub1 = subIdentitiesOnRelay1.find(([key1, _value1]) => address === encodeAddress(key1));
+      if(!sub1) continue;
+      const sub2 = subIdentitiesOnRelay2.find(([key2, _value2]) => address === encodeAddress(key2));
+      if(!sub2) continue;
+
+      const [value1, value2] = [sub1[1], sub2[1]];
+      matchesFound++;
+
+      if(JSON.stringify(value1[1]) === JSON.stringify(value2[1])) {
+        continue;
+      }
+      inequalSubIdentities.push([value1, value2]);
+    }
+
+    /*for (const [v1, v2] of inequalSubIdentities) {
+      console.log(v1[1]);
+      console.log();
+      console.log(v2[1]);
+      await new Promise(resolve => setTimeout(resolve, 300));
+    }*/
+    console.log(`Accounts with sub-identities on both relays:\t${matchesFound}`);
+    console.log(`Of them, those with conflicting sub-identities:\t${inequalSubIdentities.length}`);
+  } catch (error) {
+    console.error(error);
+    throw Error('Error during comparison');
+  }
+};
+
+if(process.argv[1] === module.filename)
+  checkRelayIdentities().catch(() => process.exit(1));
addedjs-packages/scripts/setCode.tsdiffbeforeafterboth
--- /dev/null
+++ b/js-packages/scripts/setCode.ts
@@ -0,0 +1,15 @@
+import {readFile} from 'fs/promises';
+import {u8aToHex} from '@polkadot/util';
+import {usingPlaygrounds} from '@unique/test-utils/util.js';
+
+const codePath = process.argv[2];
+if(!codePath) throw new Error('missing code path argument');
+
+const code = await readFile(codePath);
+
+await usingPlaygrounds(async (helper, privateKey) => {
+  const alice = await privateKey('//Alice');
+  await helper.getSudo().executeExtrinsicUncheckedWeight(alice, 'api.tx.system.setCode', [u8aToHex(code)]);
+});
+// We miss disconnect/unref somewhere.
+process.exit(0);
modifiedjs-packages/scripts/transfer.nload.tsdiffbeforeafterboth
--- a/js-packages/scripts/transfer.nload.ts
+++ b/js-packages/scripts/transfer.nload.ts
@@ -17,8 +17,8 @@
 /* eslint-disable @typescript-eslint/no-floating-promises */
 import os from 'os';
 import type {IKeyringPair} from '@polkadot/types/types';
-import {usingPlaygrounds} from '@unique/tests/util/index.js';
-import {UniqueHelper} from '@unique/playgrounds/unique.js';
+import {usingPlaygrounds} from '@unique/test-utils/util.js';
+import {UniqueHelper} from '@unique-nft/playgrounds/unique.js';
 import * as notReallyCluster from 'cluster'; // https://github.com/nodejs/node/issues/42271#issuecomment-1063415346
 const cluster = notReallyCluster as unknown as notReallyCluster.Cluster;
 
addedjs-packages/test-utils/globalSetup.tsdiffbeforeafterboth
--- /dev/null
+++ b/js-packages/test-utils/globalSetup.ts
@@ -0,0 +1,119 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+import {
+  usingPlaygrounds, Pallets, DONOR_FUNDING, MINIMUM_DONOR_FUND, LOCKING_PERIOD, UNLOCKING_PERIOD, makeNames,
+} from './util.js';
+import * as path from 'path';
+import {promises as fs} from 'fs';
+
+const {dirname} = makeNames(import.meta.url);
+
+// This function should be called before running test suites.
+const globalSetup = async (): Promise<void> => {
+  await usingPlaygrounds(async (helper, privateKey) => {
+    try {
+      // 1. Wait node producing blocks
+      await helper.wait.newBlocks(1, 600_000);
+
+      // 2. Create donors for test files
+      await fundFilenamesWithRetries(3)
+        .then((result) => {
+          if(!result) throw Error('Some problems with fundFilenamesWithRetries');
+        });
+
+      // 3. Configure App Promotion
+      const missingPallets = helper.fetchMissingPalletNames([Pallets.AppPromotion]);
+      if(missingPallets.length === 0) {
+        const superuser = await privateKey('//Alice');
+        const palletAddress = helper.arrange.calculatePalletAddress('appstake');
+        const palletAdmin = await privateKey('//PromotionAdmin');
+        const api = helper.getApi();
+        await helper.signTransaction(superuser, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})));
+        const nominal = helper.balance.getOneTokenNominal();
+        await helper.balance.transferToSubstrate(superuser, palletAdmin.address, 10000n * nominal);
+        await helper.balance.transferToSubstrate(superuser, palletAddress, 10000n * nominal);
+        await helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [api.tx.configuration
+          .setAppPromotionConfigurationOverride({
+            recalculationInterval: LOCKING_PERIOD,
+            pendingInterval: UNLOCKING_PERIOD})], true);
+      }
+    } catch (error) {
+      console.error(error);
+      throw Error('Error during globalSetup');
+    }
+  });
+};
+
+async function getFiles(rootPath: string): Promise<string[]> {
+  const files = await fs.readdir(rootPath, {withFileTypes: true});
+  const filenames: string[] = [];
+  for(const entry of files) {
+    const res = path.resolve(rootPath, entry.name);
+    if(entry.isDirectory()) {
+      filenames.push(...await getFiles(res));
+    } else {
+      filenames.push(res);
+    }
+  }
+  return filenames;
+}
+
+const fundFilenames = async () => {
+  await usingPlaygrounds(async (helper, privateKey) => {
+    const oneToken = helper.balance.getOneTokenNominal();
+    const alice = await privateKey('//Alice');
+    const nonce = await helper.chain.getNonce(alice.address);
+    const filenames = await getFiles(path.resolve(dirname, '..'));
+
+    // batching is actually undesireable, it takes away the time while all the transactions actually succeed
+    const batchSize = 300;
+    let balanceGrantedCounter = 0;
+    for(let b = 0; b < filenames.length; b += batchSize) {
+      const tx: Promise<boolean>[] = [];
+      let batchBalanceGrantedCounter = 0;
+      for(let i = 0; batchBalanceGrantedCounter < batchSize && b + i < filenames.length; i++) {
+        const f = filenames[b + i];
+        if(!f.endsWith('.test.ts') && !f.endsWith('seqtest.ts') || f.includes('.outdated')) continue;
+        const account = await privateKey({filename: f, ignoreFundsPresence: true});
+        const aliceBalance = await helper.balance.getSubstrate(account.address);
+
+        if(aliceBalance < MINIMUM_DONOR_FUND * oneToken) {
+          tx.push(helper.executeExtrinsic(
+            alice,
+            'api.tx.balances.transferKeepAlive',
+            [account.address, DONOR_FUNDING * oneToken],
+            true,
+            {nonce: nonce + balanceGrantedCounter++},
+          ).then(() => true).catch(() => {console.error(`Transaction to ${path.basename(f)} registered as failed. Strange.`); return false;}));
+          batchBalanceGrantedCounter++;
+        }
+      }
+
+      if(tx.length > 0) {
+        console.log(`Granting funds to ${batchBalanceGrantedCounter} filename accounts.`);
+        const result = await Promise.all(tx);
+        if(result && result.lastIndexOf(false) > -1) throw new Error('The transactions actually probably succeeded, should check the balances.');
+      }
+    }
+
+    if(balanceGrantedCounter == 0) console.log('No account needs additional funding.');
+  });
+};
+
+const fundFilenamesWithRetries = (retriesLeft: number): Promise<boolean> => {
+  if(retriesLeft <= 0) return Promise.resolve(false);
+  return fundFilenames()
+    .then(() => Promise.resolve(true))
+    .catch(e => {
+      console.error(e);
+      console.error(`Some transactions might have failed. ${retriesLeft > 1 ? 'Retrying...' : 'Something is wrong.'}\n`);
+      return fundFilenamesWithRetries(--retriesLeft);
+    });
+};
+
+globalSetup().catch(e => {
+  console.error('Setup error');
+  console.error(e);
+  process.exit(1);
+});
addedjs-packages/test-utils/governance.tsdiffbeforeafterboth
--- /dev/null
+++ b/js-packages/test-utils/governance.ts
@@ -0,0 +1,531 @@
+import {blake2AsHex} from '@polkadot/util-crypto';
+import type {PalletDemocracyConviction} from '@polkadot/types/lookup';
+import type {IPhasicEvent, TSigner} from '@unique-nft/playgrounds/types.js';
+import {HelperGroup, UniqueHelper} from '@unique-nft/playgrounds/unique.js';
+
+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);
+  }*/
+}
addedjs-packages/test-utils/index.tsdiffbeforeafterboth
--- /dev/null
+++ b/js-packages/test-utils/index.ts
@@ -0,0 +1,1646 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+import '@unique-nft/opal-testnet-types/augment-api.js';
+import '@unique-nft/opal-testnet-types/augment-types.js';
+import '@unique-nft/opal-testnet-types/types-lookup.js';
+
+import {stringToU8a} from '@polkadot/util';
+import {blake2AsHex, encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto';
+import type {ChainHelperBaseConstructor, UniqueHelperConstructor} from '@unique-nft/playgrounds/unique.js';
+import {UniqueHelper, ChainHelperBase, HelperGroup} from '@unique-nft/playgrounds/unique.js';
+import {ApiPromise, Keyring, WsProvider} from '@polkadot/api';
+import * as defs from '@unique-nft/opal-testnet-types/definitions.js';
+import type {IKeyringPair} from '@polkadot/types/types';
+import type {EventRecord} from '@polkadot/types/interfaces';
+import type {ICrossAccountId, ILogger, IPovInfo, ISchedulerOptions, ITransactionResult, TSigner} from '@unique-nft/playgrounds/types.js';
+import type {FrameSystemEventRecord, StagingXcmV2TraitsError, StagingXcmV3TraitsOutcome} from '@polkadot/types/lookup';
+import type {SignerOptions, VoidFn} from '@polkadot/api/types';
+import {spawnSync} from 'child_process';
+import {AcalaHelper, AstarHelper, MoonbeamHelper, PolkadexHelper, RelayHelper, WestmintHelper, ForeignAssetsGroup, XcmGroup, XTokensGroup, TokensGroup, HydraDxHelper} from './xcm/index.js';
+import {CollectiveGroup, CollectiveMembershipGroup, DemocracyGroup, RankedCollectiveGroup, ReferendaGroup} from './governance.js';
+import type {ICollectiveGroup, IFellowshipGroup} from './governance.js';
+
+export class SilentLogger {
+  log(_msg: any, _level: any): void { }
+  level = {
+    ERROR: 'ERROR' as const,
+    WARNING: 'WARNING' as const,
+    INFO: 'INFO' as const,
+  };
+}
+
+export class SilentConsole {
+  // TODO: Remove, this is temporary: Filter unneeded API output
+  // (Jaco promised it will be removed in the next version)
+  consoleErr: any;
+  consoleLog: any;
+  consoleWarn: any;
+
+  constructor() {
+    this.consoleErr = console.error;
+    this.consoleLog = console.log;
+    this.consoleWarn = console.warn;
+  }
+
+  enable() {
+    const outFn = (printer: any) => (...args: any[]) => {
+      for(const arg of args) {
+        if(typeof arg !== 'string')
+          continue;
+        const skippedWarnings = ['1000:: Normal connection closure', 'Not decorating unknown runtime apis:', 'RPC methods not decorated:', 'Not decorating runtime apis', 'Bad input data provided to validate_transaction', 'account balance too low', '1006:: Abnormal Closure'];
+        const needToSkip = skippedWarnings.reduce((a,  b) => a || arg.includes(b), false);
+        if(needToSkip || arg === 'Normal connection closure')
+          return;
+      }
+      printer(...args);
+    };
+
+    console.error = outFn(this.consoleErr.bind(console));
+    console.log = outFn(this.consoleLog.bind(console));
+    console.warn = outFn(this.consoleWarn.bind(console));
+  }
+
+  disable() {
+    console.error = this.consoleErr;
+    console.log = this.consoleLog;
+    console.warn = this.consoleWarn;
+  }
+}
+
+export interface IEventHelper {
+  section(): string;
+
+  method(): string;
+
+  wrapEvent(data: any[]): any;
+}
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+function EventHelper(section: string, method: string, wrapEvent: (data: any[]) => any) {
+  const helperClass = class implements IEventHelper {
+    wrapEvent: (data: any[]) => any;
+    _section: string;
+    _method: string;
+
+    constructor() {
+      this.wrapEvent = wrapEvent;
+      this._section = section;
+      this._method = method;
+    }
+
+    section(): string {
+      return this._section;
+    }
+
+    method(): string {
+      return this._method;
+    }
+
+    filter(txres: ITransactionResult) {
+      return txres.result.events.filter(e => e.event.section === section && e.event.method === method)
+        .map(e => this.wrapEvent(e.event.data));
+    }
+
+    find(txres: ITransactionResult) {
+      const e = txres.result.events.find(e => e.event.section === section && e.event.method === method);
+      return e ? this.wrapEvent(e.event.data) : null;
+    }
+
+    expect(txres: ITransactionResult) {
+      const e = this.find(txres);
+      if(e) {
+        return e;
+      } else {
+        throw Error(`Expected event ${section}.${method}`);
+      }
+    }
+  };
+
+  return helperClass;
+}
+
+function eventJsonData<T = any>(data: any[], index: number) {
+  return data[index].toJSON() as T;
+}
+
+function eventHumanData(data: any[], index: number) {
+  return data[index].toHuman();
+}
+
+function eventData<T = any>(data: any[], index: number) {
+  return data[index] as T;
+}
+
+// eslint-disable-next-line @typescript-eslint/naming-convention
+function EventSection(section: string) {
+  return class Section {
+    static section = section;
+
+    static Method(name: string, wrapEvent: (data: any[]) => any = () => {}) {
+      const helperClass = EventHelper(Section.section, name, wrapEvent);
+      return new helperClass();
+    }
+  };
+}
+
+function schedulerSection(schedulerInstance: string) {
+  return class extends EventSection(schedulerInstance) {
+    static Dispatched = this.Method('Dispatched', data => ({
+      task: eventJsonData(data, 0),
+      id: eventHumanData(data, 1),
+      result: data[2],
+    }));
+
+    static PriorityChanged = this.Method('PriorityChanged', data => ({
+      task: eventJsonData(data, 0),
+      priority: eventJsonData(data, 1),
+    }));
+  };
+}
+
+export class Event {
+  static Democracy = class extends EventSection('democracy') {
+    static Proposed = this.Method('Proposed', data => ({
+      proposalIndex: eventJsonData<number>(data, 0),
+    }));
+
+    static ExternalTabled = this.Method('ExternalTabled');
+
+    static Started = this.Method('Started', data => ({
+      referendumIndex: eventJsonData<number>(data, 0),
+      threshold: eventHumanData(data, 1),
+    }));
+
+    static Voted = this.Method('Voted', data => ({
+      voter: eventJsonData(data, 0),
+      referendumIndex: eventJsonData<number>(data, 1),
+      vote: eventJsonData(data, 2),
+    }));
+
+    static Passed = this.Method('Passed', data => ({
+      referendumIndex: eventJsonData<number>(data, 0),
+    }));
+
+    static ProposalCanceled = this.Method('ProposalCanceled', data => ({
+      propIndex: eventJsonData<number>(data, 0),
+    }));
+
+    static Cancelled = this.Method('Cancelled', data => ({
+      propIndex: eventJsonData<number>(data, 0),
+    }));
+
+    static Vetoed = this.Method('Vetoed', data => ({
+      who: eventHumanData(data, 0),
+      proposalHash: eventHumanData(data, 1),
+      until: eventJsonData<number>(data, 1),
+    }));
+  };
+
+  static Council = class extends EventSection('council') {
+    static Proposed = this.Method('Proposed', data => ({
+      account: eventHumanData(data, 0),
+      proposalIndex: eventJsonData<number>(data, 1),
+      proposalHash: eventHumanData(data, 2),
+      threshold: eventJsonData<number>(data, 3),
+    }));
+    static Closed = this.Method('Closed', data => ({
+      proposalHash: eventHumanData(data, 0),
+      yes: eventJsonData<number>(data, 1),
+      no: eventJsonData<number>(data, 2),
+    }));
+    static Executed = this.Method('Executed', data => ({
+      proposalHash: eventHumanData(data, 0),
+    }));
+  };
+
+  static TechnicalCommittee = class extends EventSection('technicalCommittee') {
+    static Proposed = this.Method('Proposed', data => ({
+      account: eventHumanData(data, 0),
+      proposalIndex: eventJsonData<number>(data, 1),
+      proposalHash: eventHumanData(data, 2),
+      threshold: eventJsonData<number>(data, 3),
+    }));
+    static Closed = this.Method('Closed', data => ({
+      proposalHash: eventHumanData(data, 0),
+      yes: eventJsonData<number>(data, 1),
+      no: eventJsonData<number>(data, 2),
+    }));
+    static Approved = this.Method('Approved', data => ({
+      proposalHash: eventHumanData(data, 0),
+    }));
+    static Executed = this.Method('Executed', data => ({
+      proposalHash: eventHumanData(data, 0),
+      result: eventHumanData(data, 1),
+    }));
+  };
+
+  static FellowshipReferenda = class extends EventSection('fellowshipReferenda') {
+    static Submitted = this.Method('Submitted', data => ({
+      referendumIndex: eventJsonData<number>(data, 0),
+      trackId: eventJsonData<number>(data, 1),
+      proposal: eventJsonData(data, 2),
+    }));
+
+    static Cancelled = this.Method('Cancelled', data => ({
+      index: eventJsonData<number>(data, 0),
+      tally: eventJsonData(data, 1),
+    }));
+  };
+
+  static UniqueScheduler = schedulerSection('uniqueScheduler');
+  static Scheduler = schedulerSection('scheduler');
+
+  static XcmpQueue = class extends EventSection('xcmpQueue') {
+    static XcmpMessageSent = this.Method('XcmpMessageSent', data => ({
+      messageHash: eventJsonData(data, 0),
+    }));
+
+    static Success = this.Method('Success', data => ({
+      messageHash: eventJsonData(data, 0),
+    }));
+
+    static Fail = this.Method('Fail', data => ({
+      messageHash: eventJsonData(data, 0),
+      outcome: eventData<StagingXcmV2TraitsError>(data, 2),
+    }));
+  };
+
+  static DmpQueue = class extends EventSection('dmpQueue') {
+    static ExecutedDownward = this.Method('ExecutedDownward', data => ({
+      outcome: eventData<StagingXcmV3TraitsOutcome>(data, 2),
+    }));
+  };
+}
+
+// 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);
+    }
+
+    override 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;
+    }
+    override 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
+   */
+  arrange: ArrangeGroup;
+  wait: WaitGroup;
+  admin: AdminGroup;
+  session: SessionGroup;
+  testUtils: TestUtilGroup;
+  foreignAssets: ForeignAssetsGroup;
+  xcm: XcmGroup<DevUniqueHelper>;
+  xTokens: XTokensGroup<DevUniqueHelper>;
+  tokens: TokensGroup<DevUniqueHelper>;
+  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;
+
+    super(logger, options);
+    this.arrange = new ArrangeGroup(this);
+    this.wait = new WaitGroup(this);
+    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);
+  }
+
+  override async connect(wsEndpoint: string, _listeners?: any): Promise<void> {
+    if(!wsEndpoint) throw new Error('wsEndpoint was not set');
+    const wsProvider = new WsProvider(wsEndpoint);
+    this.api = new ApiPromise({
+      provider: wsProvider,
+      signedExtensions: {
+        ContractHelpers: {
+          extrinsic: {},
+          payload: {},
+        },
+        CheckMaintenance: {
+          extrinsic: {},
+          payload: {},
+        },
+        DisableIdentityCalls: {
+          extrinsic: {},
+          payload: {},
+        },
+        FakeTransactionFinalizer: {
+          extrinsic: {},
+          payload: {},
+        },
+      },
+      rpc: {
+        unique: defs.unique.rpc,
+        appPromotion: defs.appPromotion.rpc,
+        povinfo: defs.povinfo.rpc,
+        eth: {
+          feeHistory: {
+            description: 'Dummy',
+            params: [],
+            type: 'u8',
+          },
+          maxPriorityFeePerGas: {
+            description: 'Dummy',
+            params: [],
+            type: 'u8',
+          },
+        },
+      },
+    });
+    await this.api.isReadyOrError;
+    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 {
+  wait: WaitGroup;
+
+  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
+    options.helperBase = options.helperBase ?? DevRelayHelper;
+
+    super(logger, options);
+    this.wait = new WaitGroup(this);
+  }
+
+  getSudo() {
+    // eslint-disable-next-line @typescript-eslint/naming-convention
+    const SudoHelperType = SudoHelper(this.helperBase);
+    return this.clone(SudoHelperType) as DevRelayHelper;
+  }
+}
+
+export class DevWestmintHelper extends WestmintHelper {
+  wait: WaitGroup;
+
+  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
+    options.helperBase = options.helperBase ?? DevWestmintHelper;
+
+    super(logger, options);
+    this.wait = new WaitGroup(this);
+  }
+}
+
+export class DevStatemineHelper extends DevWestmintHelper {}
+
+export class DevStatemintHelper extends DevWestmintHelper {}
+
+export class DevMoonbeamHelper extends MoonbeamHelper {
+  account: MoonbeamAccountGroup;
+  wait: WaitGroup;
+  fastDemocracy: MoonbeamFastDemocracyGroup;
+
+  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
+    options.helperBase = options.helperBase ?? DevMoonbeamHelper;
+    options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';
+
+    super(logger, options);
+    this.account = new MoonbeamAccountGroup(this);
+    this.wait = new WaitGroup(this);
+    this.fastDemocracy = new MoonbeamFastDemocracyGroup(this);
+  }
+}
+
+export class DevMoonriverHelper extends DevMoonbeamHelper {
+  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
+    options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';
+    super(logger, options);
+  }
+}
+
+export class DevAstarHelper extends AstarHelper {
+  wait: WaitGroup;
+
+  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
+    options.helperBase = options.helperBase ?? DevAstarHelper;
+
+    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;
+
+  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
+    options.helperBase = options.helperBase ?? DevAcalaHelper;
+
+    super(logger, options);
+    this.wait = new WaitGroup(this);
+  }
+  getSudo() {
+    // eslint-disable-next-line @typescript-eslint/naming-convention
+    const SudoHelperType = SudoHelper(this.helperBase);
+    return this.clone(SudoHelperType) as DevAcalaHelper;
+  }
+}
+
+export class DevPolkadexHelper extends PolkadexHelper {
+  wait: WaitGroup;
+  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
+    options.helperBase = options.helperBase ?? PolkadexHelper;
+
+    super(logger, options);
+    this.wait = new WaitGroup(this);
+  }
+
+  getSudo() {
+    // eslint-disable-next-line @typescript-eslint/naming-convention
+    const SudoHelperType = SudoHelper(this.helperBase);
+    return this.clone(SudoHelperType) as DevPolkadexHelper;
+  }
+}
+
+export class DevHydraDxHelper extends HydraDxHelper {
+  wait: WaitGroup;
+  fastDemocracy: HydraFastDemocracyGroup;
+
+  constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
+    options.helperBase = options.helperBase ?? DevHydraDxHelper;
+
+    super(logger, options);
+
+    this.wait = new WaitGroup(this);
+    this.fastDemocracy = new HydraFastDemocracyGroup(this);
+  }
+}
+
+export class DevKaruraHelper extends DevAcalaHelper {}
+
+export class ArrangeGroup {
+  helper: DevUniqueHelper;
+
+  scheduledIdSlider = 0;
+
+  constructor(helper: DevUniqueHelper) {
+    this.helper = helper;
+  }
+
+  /**
+   * Generates accounts with the specified UNQ token balance
+   * @param balances balances for generated accounts. Each balance will be multiplied by the token nominal.
+   * @param donor donor account for balances
+   * @returns array of newly created accounts
+   * @example const [acc1, acc2, acc3] = await createAccounts([0n, 10n, 20n], donor);
+   */
+  createAccounts = async (balances: bigint[], donor: IKeyringPair): Promise<IKeyringPair[]> => {
+    let nonce = await this.helper.chain.getNonce(donor.address);
+    const wait = new WaitGroup(this.helper);
+    const ss58Format = this.helper.chain.getChainProperties().ss58Format;
+    const tokenNominal = this.helper.balance.getOneTokenNominal();
+    const transactions = [];
+    const accounts: IKeyringPair[] = [];
+    for(const balance of balances) {
+      const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);
+      accounts.push(recipient);
+      if(balance !== 0n) {
+        const tx = this.helper.constructApiCall('api.tx.balances.transferKeepAlive', [{Id: recipient.address}, balance * tokenNominal]);
+        transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));
+        nonce++;
+      }
+    }
+
+    await Promise.all(transactions).catch(_e => {});
+
+    //#region TODO remove this region, when nonce problem will be solved
+    const checkBalances = async () => {
+      let isSuccess = true;
+      for(let i = 0; i < balances.length; i++) {
+        const balance = await this.helper.balance.getSubstrate(accounts[i].address);
+        if(balance !== balances[i] * tokenNominal) {
+          isSuccess = false;
+          break;
+        }
+      }
+      return isSuccess;
+    };
+
+    let accountsCreated = false;
+    const maxBlocksChecked = await this.helper.arrange.isDevNode() ? 50 : 5;
+    // checkBalances retry up to 5-50 blocks
+    for(let index = 0; index < maxBlocksChecked; index++) {
+      accountsCreated = await checkBalances();
+      if(accountsCreated) break;
+      await wait.newBlocks(1);
+    }
+
+    if(!accountsCreated) throw Error('Accounts generation failed');
+    //#endregion
+
+    return accounts;
+  };
+
+  // TODO combine this method and createAccounts into one
+  createCrowd = async (accountsToCreate: number, withBalance: bigint, donor: IKeyringPair): Promise<IKeyringPair[]> => {
+    const createAsManyAsCan = async () => {
+      let transactions: any = [];
+      const accounts: IKeyringPair[] = [];
+      let nonce = await this.helper.chain.getNonce(donor.address);
+      const tokenNominal = this.helper.balance.getOneTokenNominal();
+      const ss58Format = this.helper.chain.getChainProperties().ss58Format;
+      for(let i = 0; i < accountsToCreate; i++) {
+        if(i === 500) { // if there are too many accounts to create
+          await Promise.allSettled(transactions); // wait while first 500 (should be 100 for devnode) tx will be settled
+          transactions = []; //
+          nonce = await this.helper.chain.getNonce(donor.address); // update nonce
+        }
+        const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);
+        accounts.push(recipient);
+        if(withBalance !== 0n) {
+          const tx = this.helper.constructApiCall('api.tx.balances.transferKeepAlive', [{Id: recipient.address}, withBalance * tokenNominal]);
+          transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation'));
+          nonce++;
+        }
+      }
+
+      const fullfilledAccounts = [];
+      await Promise.allSettled(transactions);
+      for(const account of accounts) {
+        const accountBalance = await this.helper.balance.getSubstrate(account.address);
+        if(accountBalance === withBalance * tokenNominal) {
+          fullfilledAccounts.push(account);
+        }
+      }
+      return fullfilledAccounts;
+    };
+
+
+    const crowd: IKeyringPair[] = [];
+    // do up to 5 retries
+    for(let index = 0; index < 5 && accountsToCreate !== 0; index++) {
+      const asManyAsCan = await createAsManyAsCan();
+      crowd.push(...asManyAsCan);
+      accountsToCreate -= asManyAsCan.length;
+    }
+
+    if(accountsToCreate !== 0) throw Error(`Crowd generation failed: ${accountsToCreate} accounts left`);
+
+    return crowd;
+  };
+
+  /**
+   * Generates one account with zero balance
+   * @returns the newly generated account
+   * @example const account = await helper.arrange.createEmptyAccount();
+   */
+  createEmptyAccount = (): IKeyringPair => {
+    const ss58Format = this.helper.chain.getChainProperties().ss58Format;
+    return this.helper.util.fromSeed(mnemonicGenerate(), ss58Format);
+  };
+
+  isDevNode = async () => {
+    let blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();
+    if(blockNumber == 0) {
+      await this.helper.wait.newBlocks(1);
+      blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON();
+    }
+    const block2 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])]);
+    const block1 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber - 1])]);
+    const findCreationDate = (block: any) => {
+      const humanBlock = block.toHuman();
+      let date;
+      humanBlock.block.extrinsics.forEach((ext: any) => {
+        if(ext.method.section === 'timestamp') {
+          date = Number(ext.method.args.now.replaceAll(',', ''));
+        }
+      });
+      return date;
+    };
+    const block1date = await findCreationDate(block1);
+    const block2date = await findCreationDate(block2);
+    if(block2date! - block1date! < 9000) return true;
+    return false;
+  };
+
+  async calculcateFee(payer: ICrossAccountId, promise: () => Promise<any>): Promise<bigint> {
+    const address = 'Substrate' in payer ? payer.Substrate : this.helper.address.ethToSubstrate(payer.Ethereum);
+    let balance = await this.helper.balance.getSubstrate(address);
+
+    await promise();
+
+    balance -= await this.helper.balance.getSubstrate(address);
+
+    return balance;
+  }
+
+  async calculatePoVInfo(txs: any[]): Promise<IPovInfo> {
+    const rawPovInfo = await this.helper.callRpc('api.rpc.povinfo.estimateExtrinsicPoV', [txs]);
+
+    const kvJson: {[key: string]: string} = {};
+
+    for(const kv of rawPovInfo.keyValues) {
+      kvJson[kv.key.toHex()] = kv.value.toHex();
+    }
+
+    const kvStr = JSON.stringify(kvJson);
+
+    const chainql = spawnSync(
+      'chainql',
+      [
+        `--tla-code=data=${kvStr}`,
+        '-e', `function(data) cql.dump(cql.chain("${this.helper.getEndpoint()}").latest._meta, data, {omit_empty:true})`,
+      ],
+    );
+
+    if(!chainql.stdout) {
+      throw Error('unable to get an output from the `chainql`');
+    }
+
+    return {
+      proofSize: rawPovInfo.proofSize.toNumber(),
+      compactProofSize: rawPovInfo.compactProofSize.toNumber(),
+      compressedProofSize: rawPovInfo.compressedProofSize.toNumber(),
+      results: rawPovInfo.results,
+      kv: JSON.parse(chainql.stdout.toString()),
+    };
+  }
+
+  calculatePalletAddress(palletId: any) {
+    const address = stringToU8a(('modl' + palletId).padEnd(32, '\0'));
+    return encodeAddress(address, this.helper.chain.getChainProperties().ss58Format);
+  }
+
+  makeScheduledIds(num: number): string[] {
+    function makeId(slider: number) {
+      const scheduledIdSize = 64;
+      const hexId = slider.toString(16);
+      const prefixSize = scheduledIdSize - hexId.length;
+
+      const scheduledId = '0x' + '0'.repeat(prefixSize) + hexId;
+
+      return scheduledId;
+    }
+
+    const ids = [];
+    for(let i = 0; i < num; i++) {
+      ids.push(makeId(this.scheduledIdSlider));
+      this.scheduledIdSlider += 1;
+    }
+
+    return ids;
+  }
+
+  makeScheduledId(): string {
+    return (this.makeScheduledIds(1))[0];
+  }
+
+  async captureEvents(eventSection: string, eventMethod: string): Promise<EventCapture> {
+    const capture = new EventCapture(this.helper, eventSection, eventMethod);
+    await capture.startCapture();
+
+    return capture;
+  }
+
+  makeXcmProgramWithdrawDeposit(beneficiary: Uint8Array, id: any, amount: bigint) {
+    return {
+      V2: [
+        {
+          WithdrawAsset: [
+            {
+              id,
+              fun: {
+                Fungible: amount,
+              },
+            },
+          ],
+        },
+        {
+          BuyExecution: {
+            fees: {
+              id,
+              fun: {
+                Fungible: amount,
+              },
+            },
+            weightLimit: 'Unlimited',
+          },
+        },
+        {
+          DepositAsset: {
+            assets: {
+              Wild: 'All',
+            },
+            maxAssets: 1,
+            beneficiary: {
+              parents: 0,
+              interior: {
+                X1: {
+                  AccountId32: {
+                    network: 'Any',
+                    id: beneficiary,
+                  },
+                },
+              },
+            },
+          },
+        },
+      ],
+    };
+  }
+
+  makeXcmProgramReserveAssetDeposited(beneficiary: Uint8Array, id: any, amount: bigint) {
+    return {
+      V2: [
+        {
+          ReserveAssetDeposited: [
+            {
+              id,
+              fun: {
+                Fungible: amount,
+              },
+            },
+          ],
+        },
+        {
+          BuyExecution: {
+            fees: {
+              id,
+              fun: {
+                Fungible: amount,
+              },
+            },
+            weightLimit: 'Unlimited',
+          },
+        },
+        {
+          DepositAsset: {
+            assets: {
+              Wild: 'All',
+            },
+            maxAssets: 1,
+            beneficiary: {
+              parents: 0,
+              interior: {
+                X1: {
+                  AccountId32: {
+                    network: 'Any',
+                    id: beneficiary,
+                  },
+                },
+              },
+            },
+          },
+        },
+      ],
+    };
+  }
+
+  makeUnpaidSudoTransactProgram(info: {weightMultiplier: number, call: string}) {
+    return {
+      V3: [
+        {
+          UnpaidExecution: {
+            weightLimit: 'Unlimited',
+            checkOrigin: null,
+          },
+        },
+        {
+          Transact: {
+            originKind: 'Superuser',
+            requireWeightAtMost: {
+              refTime: info.weightMultiplier * 200000000,
+              proofSize: info.weightMultiplier * 3000,
+            },
+            call: {
+              encoded: info.call,
+            },
+          },
+        },
+      ],
+    };
+  }
+}
+
+class MoonbeamAccountGroup {
+  helper: MoonbeamHelper;
+
+  keyring: Keyring;
+  _alithAccount: IKeyringPair;
+  _baltatharAccount: IKeyringPair;
+  _dorothyAccount: IKeyringPair;
+
+  constructor(helper: MoonbeamHelper) {
+    this.helper = helper;
+
+    this.keyring = new Keyring({type: 'ethereum'});
+    const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133';
+    const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b';
+    const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68';
+
+    this._alithAccount = this.keyring.addFromUri(alithPrivateKey, undefined, 'ethereum');
+    this._baltatharAccount = this.keyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum');
+    this._dorothyAccount = this.keyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum');
+  }
+
+  alithAccount() {
+    return this._alithAccount;
+  }
+
+  baltatharAccount() {
+    return this._baltatharAccount;
+  }
+
+  dorothyAccount() {
+    return this._dorothyAccount;
+  }
+
+  create() {
+    return this.keyring.addFromUri(mnemonicGenerate());
+  }
+}
+
+class MoonbeamFastDemocracyGroup {
+  helper: DevMoonbeamHelper;
+
+  constructor(helper: DevMoonbeamHelper) {
+    this.helper = helper;
+  }
+
+  async executeProposal(proposalDesciption: string, encodedProposal: string) {
+    const proposalHash = blake2AsHex(encodedProposal);
+
+    const alithAccount = this.helper.account.alithAccount();
+    const baltatharAccount = this.helper.account.baltatharAccount();
+    const dorothyAccount = this.helper.account.dorothyAccount();
+
+    const councilVotingThreshold = 2;
+    const technicalCommitteeThreshold = 2;
+    const fastTrackVotingPeriod = 3;
+    const fastTrackDelayPeriod = 0;
+
+    console.log(`[democracy] executing '${proposalDesciption}' proposal`);
+
+    // >>> Propose external motion through council >>>
+    console.log('\t* Propose external motion through council.......');
+    const externalMotion = this.helper.democracy.externalProposeMajority({Inline: encodedProposal});
+    const encodedMotion = externalMotion?.method.toHex() || '';
+    const motionHash = blake2AsHex(encodedMotion);
+    console.log('\t* Motion hash is %s', motionHash);
+
+    await this.helper.collective.council.propose(
+      baltatharAccount,
+      councilVotingThreshold,
+      externalMotion,
+      externalMotion.encodedLength,
+    );
+
+    const councilProposalIdx = await this.helper.collective.council.proposalCount() - 1;
+    await this.helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);
+    await this.helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);
+
+    await this.helper.collective.council.close(
+      dorothyAccount,
+      motionHash,
+      councilProposalIdx,
+      {
+        refTime: 1_000_000_000,
+        proofSize: 1_000_000,
+      },
+      externalMotion.encodedLength,
+    );
+    console.log('\t* Propose external motion through council.......DONE');
+    // <<< Propose external motion through council <<<
+
+    // >>> Fast track proposal through technical committee >>>
+    console.log('\t* Fast track proposal through technical committee.......');
+    const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod);
+    const encodedFastTrack = fastTrack?.method.toHex() || '';
+    const fastTrackHash = blake2AsHex(encodedFastTrack);
+    console.log('\t* FastTrack hash is %s', fastTrackHash);
+
+    await this.helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);
+
+    const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1;
+    await this.helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);
+    await this.helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);
+
+    await this.helper.collective.techCommittee.close(
+      baltatharAccount,
+      fastTrackHash,
+      techProposalIdx,
+      {
+        refTime: 1_000_000_000,
+        proofSize: 1_000_000,
+      },
+      fastTrack.encodedLength,
+    );
+    console.log('\t* Fast track proposal through technical committee.......DONE');
+    // <<< Fast track proposal through technical committee <<<
+
+    const democracyStarted = await this.helper.wait.expectEvent(3, Event.Democracy.Started);
+    const referendumIndex = democracyStarted.referendumIndex;
+
+    // >>> Referendum voting >>>
+    console.log(`\t* Referendum #${referendumIndex} voting.......`);
+    await this.helper.democracy.referendumVote(dorothyAccount, referendumIndex, {
+      balance: 10_000_000_000_000_000_000n,
+      vote: {aye: true, conviction: 1},
+    });
+    console.log(`\t* Referendum #${referendumIndex} voting.......DONE`);
+    // <<< Referendum voting <<<
+
+    // Wait the proposal to pass
+    await this.helper.wait.expectEvent(3, Event.Democracy.Passed, event => event.referendumIndex == referendumIndex);
+
+    await this.helper.wait.newBlocks(1);
+
+    console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`);
+  }
+}
+
+class HydraFastDemocracyGroup {
+  helper: DevHydraDxHelper;
+
+  constructor(helper: DevHydraDxHelper) {
+    this.helper = helper;
+  }
+
+  async executeProposal(proposalDesciption: string, encodedProposal: string) {
+    const proposalHash = blake2AsHex(encodedProposal);
+    const aliceAccount = this.helper.util.fromSeed('//Alice');
+    const bobAccount = this.helper.util.fromSeed('//Bob');
+    const eveAccount = this.helper.util.fromSeed('//Eve');
+
+    const councilVotingThreshold = 1;
+    const technicalCommitteeThreshold = 3;
+    const fastTrackVotingPeriod = 3;
+    const fastTrackDelayPeriod = 0;
+
+    console.log(`[democracy] executing '${proposalDesciption}' proposal`);
+
+    // >>> Propose external motion through council >>>
+    console.log('\t* Propose external motion through council.......');
+    const externalMotion = this.helper.democracy.externalProposeMajority({Inline: encodedProposal});
+    const encodedMotion = externalMotion?.method.toHex() || '';
+    const motionHash = blake2AsHex(encodedMotion);
+    console.log('\t* Motion hash is %s', motionHash);
+
+    await this.helper.collective.council.propose(
+      aliceAccount,
+      councilVotingThreshold,
+      externalMotion,
+      externalMotion.encodedLength,
+    );
+
+    console.log('\t* Propose external motion through council.......DONE');
+    // <<< Propose external motion through council <<<
+
+    // >>> Fast track proposal through technical committee >>>
+    console.log('\t* Fast track proposal through technical committee.......');
+    const fastTrack = this.helper.democracy.fastTrack(proposalHash, fastTrackVotingPeriod, fastTrackDelayPeriod);
+    const encodedFastTrack = fastTrack?.method.toHex() || '';
+    const fastTrackHash = blake2AsHex(encodedFastTrack);
+    console.log('\t* FastTrack hash is %s', fastTrackHash);
+
+    await this.helper.collective.techCommittee.propose(aliceAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);
+
+    const techProposalIdx = await this.helper.collective.techCommittee.proposalCount() - 1;
+    await this.helper.collective.techCommittee.vote(aliceAccount, fastTrackHash, techProposalIdx, true);
+    await this.helper.collective.techCommittee.vote(bobAccount, fastTrackHash, techProposalIdx, true);
+    await this.helper.collective.techCommittee.vote(eveAccount, fastTrackHash, techProposalIdx, true);
+
+    await this.helper.collective.techCommittee.close(
+      bobAccount,
+      fastTrackHash,
+      techProposalIdx,
+      {
+        refTime: 1_000_000_000,
+        proofSize: 1_000_000,
+      },
+      fastTrack.encodedLength,
+    );
+    console.log('\t* Fast track proposal through technical committee.......DONE');
+    // <<< Fast track proposal through technical committee <<<
+
+    const democracyStarted = await this.helper.wait.expectEvent(3, Event.Democracy.Started);
+    const referendumIndex = democracyStarted.referendumIndex;
+
+    // >>> Referendum voting >>>
+    console.log(`\t* Referendum #${referendumIndex} voting.......`);
+    await this.helper.democracy.referendumVote(eveAccount, referendumIndex, {
+      balance: 10_000_000_000_000_000_000n,
+      vote: {aye: true, conviction: 1},
+    });
+    console.log(`\t* Referendum #${referendumIndex} voting.......DONE`);
+    // <<< Referendum voting <<<
+
+    // Wait the proposal to pass
+    await this.helper.wait.expectEvent(3, Event.Democracy.Passed, event => event.referendumIndex == referendumIndex);
+
+    await this.helper.wait.newBlocks(1);
+
+    console.log(`[democracy] executing '${proposalDesciption}' proposal.......DONE`);
+  }
+}
+
+class WaitGroup {
+  helper: ChainHelperBase;
+
+  constructor(helper: ChainHelperBase) {
+    this.helper = helper;
+  }
+
+  sleep(milliseconds: number) {
+    return new Promise((resolve) => setTimeout(resolve, milliseconds));
+  }
+
+  private async waitWithTimeout(promise: Promise<any>, timeout: number) {
+    let isBlock = false;
+    promise.then(() => isBlock = true).catch(() => isBlock = true);
+    let totalTime = 0;
+    const step = 100;
+    while(!isBlock) {
+      await this.sleep(step);
+      totalTime += step;
+      if(totalTime >= timeout) throw Error('Blocks production failed');
+    }
+    return promise;
+  }
+
+  /**
+   * Launch some async operation, or throw an error after some time. Note that it will still continue executing after the timeout.
+   * @param promise async operation to race against the timeout
+   * @param timeoutMS time after which to time out
+   * @param timeoutError error message to throw
+   * @returns promise of the same type the operation had
+   */
+  withTimeout<T>(
+    promise: Promise<T>,
+    timeoutMS = 30000,
+    timeoutError = 'The operation has timed out!',
+  ): Promise<T> {
+    const timeout = new Promise<never>((_, reject) => {
+      setTimeout(() => {
+        reject(new Error(timeoutError));
+      }, timeoutMS);
+    });
+
+    return Promise.race<T>([promise, timeout]).catch(e => {throw new Error(e);});
+  }
+
+  /**
+   * Wait for specified number of blocks
+   * @param blocksCount number of blocks to wait
+   * @returns
+   */
+  async newBlocks(blocksCount = 1, timeout?: number): Promise<void> {
+    timeout = timeout ?? blocksCount * 60_000;
+    // eslint-disable-next-line no-async-promise-executor
+    const promise = new Promise<void>(async (resolve) => {
+      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(() => {
+        if(blocksCount > 0) {
+          blocksCount--;
+        } else {
+          unsubscribe();
+          resolve();
+        }
+      });
+    });
+    await this.waitWithTimeout(promise, timeout);
+    return promise;
+  }
+
+  /**
+   * Wait for the specified number of sessions to pass.
+   * Only applicable if the Session pallet is turned on.
+   * @param sessionCount number of sessions to wait
+   * @param blockTimeout time in ms until panicking that the chain has stopped producing blocks
+   * @returns
+   */
+  async newSessions(sessionCount = 1, blockTimeout = 60000): Promise<void> {
+    console.log(`Waiting for ${sessionCount} new session${sessionCount > 1 ? 's' : ''}.`
+      + ' This might take a while -- check SessionPeriod in pallet_session::Config for session time.');
+
+    const expectedSessionIndex = await (this.helper as DevUniqueHelper).session.getIndex() + sessionCount;
+    let currentSessionIndex = -1;
+
+    while(currentSessionIndex < expectedSessionIndex) {
+      // eslint-disable-next-line no-async-promise-executor
+      currentSessionIndex = await this.withTimeout(new Promise(async (resolve) => {
+        await this.newBlocks(1);
+        const res = await (this.helper as DevUniqueHelper).session.getIndex();
+        resolve(res);
+      }), blockTimeout, 'The chain has stopped producing blocks!');
+    }
+  }
+
+  async forParachainBlockNumber(blockNumber: bigint | number, timeout?: number) {
+    timeout = timeout ?? 30 * 60 * 1000;
+    // eslint-disable-next-line no-async-promise-executor
+    const promise = new Promise<void>(async (resolve) => {
+      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {
+        if(data.number.toNumber() >= blockNumber) {
+          unsubscribe();
+          resolve();
+        }
+      });
+    });
+    await this.waitWithTimeout(promise, timeout);
+    return promise;
+  }
+
+  async forRelayBlockNumber(blockNumber: bigint | number, timeout?: number) {
+    timeout = timeout ?? 30 * 60 * 1000;
+    // eslint-disable-next-line no-async-promise-executor
+    const promise = new Promise<void>(async (resolve) => {
+      const unsubscribe = await this.helper.getApi().query.parachainSystem.validationData((data: any) => {
+        if(data.value.relayParentNumber.toNumber() >= blockNumber) {
+          // @ts-ignore
+          unsubscribe();
+          resolve();
+        }
+      });
+    });
+    await this.waitWithTimeout(promise, timeout);
+    return promise;
+  }
+
+  noScheduledTasks() {
+    const api = this.helper.getApi();
+
+    // eslint-disable-next-line no-async-promise-executor
+    const promise = new Promise<void>(async resolve => {
+      const unsubscribe = await api.rpc.chain.subscribeNewHeads(async () => {
+        const areThereScheduledTasks = await api.query.scheduler.lookup.entries();
+
+        if(areThereScheduledTasks.length == 0) {
+          unsubscribe();
+          resolve();
+        }
+      });
+    });
+
+    return promise;
+  }
+
+  parachainBlockMultiplesOf(val: bigint) {
+    // eslint-disable-next-line no-async-promise-executor
+    const promise = new Promise<void>(async resolve => {
+      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads((data: any) => {
+        if(data.number.toBigInt() % val == 0n) {
+          console.log(`from waiter: ${data.number.toBigInt()}`);
+          unsubscribe();
+          resolve();
+        }
+      });
+    });
+    return promise;
+  }
+
+  event<T extends IEventHelper>(
+    maxBlocksToWait: number,
+    eventHelper: T,
+    filter: (_: any) => boolean = () => true,
+  ): any {
+    // eslint-disable-next-line no-async-promise-executor
+    const promise = new Promise<T | null>(async (resolve) => {
+      const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async header => {
+        const blockNumber = header.number.toJSON();
+        const blockHash = header.hash;
+        const eventIdStr = `${eventHelper.section()}.${eventHelper.method()}`;
+        const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`;
+
+        this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`);
+
+        const apiAt = await this.helper.getApi().at(blockHash);
+        const eventRecords = (await apiAt.query.system.events()) as any;
+
+        const neededEvent = eventRecords.toArray()
+          .filter((r: FrameSystemEventRecord) => r.event.section == eventHelper.section() && r.event.method == eventHelper.method())
+          .map((r: FrameSystemEventRecord) => eventHelper.wrapEvent(r.event.data))
+          .find(filter);
+
+        if(neededEvent) {
+          unsubscribe();
+          resolve(neededEvent);
+        } else if(maxBlocksToWait > 0) {
+          maxBlocksToWait--;
+        } else {
+          this.helper.logger.log(`Eligible event \`${eventIdStr}\` is NOT found.
+          The wait lasted until block ${blockNumber} inclusive`);
+          unsubscribe();
+          resolve(null);
+        }
+      });
+    });
+    return promise;
+  }
+
+  async expectEvent<T extends IEventHelper>(
+    maxBlocksToWait: number,
+    eventHelper: T,
+    filter: (e: any) => boolean = () => true,
+  ) {
+    const e = await this.event(maxBlocksToWait, eventHelper, filter);
+    if(e == null) {
+      throw Error(`The event '${eventHelper.section()}.${eventHelper.method()}' is expected`);
+    } else {
+      return e;
+    }
+  }
+}
+
+class SessionGroup {
+  helper: ChainHelperBase;
+
+  constructor(helper: ChainHelperBase) {
+    this.helper = helper;
+  }
+
+  //todo:collator documentation
+  async getIndex(): Promise<number> {
+    return (await this.helper.callRpc('api.query.session.currentIndex', [])).toNumber();
+  }
+
+  newSessions(sessionCount = 1, blockTimeout = 24000): Promise<void> {
+    return (this.helper as DevUniqueHelper).wait.newSessions(sessionCount, blockTimeout);
+  }
+
+  setOwnKeys(signer: TSigner, key: string) {
+    return this.helper.executeExtrinsic(
+      signer,
+      'api.tx.session.setKeys',
+      [key, '0x0'],
+      true,
+    );
+  }
+
+  setOwnKeysFromAddress(signer: TSigner) {
+    return this.setOwnKeys(signer, '0x' + Buffer.from(signer.addressRaw).toString('hex'));
+  }
+}
+
+class TestUtilGroup {
+  helper: DevUniqueHelper;
+
+  constructor(helper: DevUniqueHelper) {
+    this.helper = helper;
+  }
+
+  async enable(testUtilsPalletName: string) {
+    if(this.helper.fetchMissingPalletNames([testUtilsPalletName]).length != 0) {
+      return;
+    }
+
+    const signer = this.helper.util.fromSeed('//Alice');
+    await this.helper.getSudo<DevUniqueHelper>().executeExtrinsic(signer, 'api.tx.testUtils.enable', [], true);
+  }
+
+  async setTestValue(signer: TSigner, testVal: number) {
+    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValue', [testVal], true);
+  }
+
+  async incTestValue(signer: TSigner) {
+    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.incTestValue', [], true);
+  }
+
+  async setTestValueAndRollback(signer: TSigner, testVal: number) {
+    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.setTestValueAndRollback', [testVal], true);
+  }
+
+  async testValue(blockIdx?: number) {
+    const api = blockIdx
+      ? await this.helper.getApi().at(await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockIdx]))
+      : this.helper.getApi();
+
+    return (await api.query.testUtils.testValue()).toJSON();
+  }
+
+  async justTakeFee(signer: TSigner) {
+    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.justTakeFee', [], true);
+  }
+
+  async selfCancelingInc(signer: TSigner, scheduledId: string, maxTestVal: number) {
+    await this.helper.executeExtrinsic(signer, 'api.tx.testUtils.selfCancelingInc', [scheduledId, maxTestVal], true);
+  }
+}
+
+class EventCapture {
+  helper: DevUniqueHelper;
+  eventSection: string;
+  eventMethod: string;
+  events: EventRecord[] = [];
+  unsubscribe: VoidFn | null = null;
+
+  constructor(
+    helper: DevUniqueHelper,
+    eventSection: string,
+    eventMethod: string,
+  ) {
+    this.helper = helper;
+    this.eventSection = eventSection;
+    this.eventMethod = eventMethod;
+  }
+
+  async startCapture() {
+    this.stopCapture();
+    this.unsubscribe = (await this.helper.getApi().query.system.events((eventRecords: FrameSystemEventRecord[]) => {
+      const newEvents = eventRecords.filter(r => r.event.section == this.eventSection && r.event.method == this.eventMethod);
+
+      this.events.push(...newEvents);
+    })) as any;
+  }
+
+  stopCapture() {
+    if(this.unsubscribe !== null) {
+      this.unsubscribe();
+    }
+  }
+
+  extractCapturedEvents() {
+    return this.events;
+  }
+}
+
+class AdminGroup {
+  helper: UniqueHelper;
+
+  constructor(helper: UniqueHelper) {
+    this.helper = helper;
+  }
+
+  async payoutStakers(signer: IKeyringPair, stakersToPayout: number):  Promise<{staker: string, stake: bigint, payout: bigint}[]> {
+    const payoutResult = await this.helper.executeExtrinsic(signer, 'api.tx.appPromotion.payoutStakers', [stakersToPayout], true);
+    return payoutResult.result.events.filter(e => e.event.method === 'StakingRecalculation').map(e => ({
+      staker: e.event.data[0].toString(),
+      stake: e.event.data[1].toBigInt(),
+      payout: e.event.data[2].toBigInt(),
+    }));
+  }
+}
+
+// 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;
+    }
+
+    override 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,
+      );
+    }
+  };
+}
addedjs-packages/test-utils/package.jsondiffbeforeafterboth
--- /dev/null
+++ b/js-packages/test-utils/package.json
@@ -0,0 +1,18 @@
+{
+  "author": "",
+  "license": "SEE LICENSE IN ../../../LICENSE",
+  "description": "Playground scripts",
+  "engines": {
+    "node": ">=16"
+  },
+  "name": "@unique/test-utils",
+  "type": "module",
+  "version": "1.0.0",
+  "main": "index.js",
+  "dependencies": {
+    "@polkadot/api": "10.10.1",
+    "@polkadot/util": "^12.5.1",
+    "@polkadot/util-crypto": "^12.5.1",
+    "@unique-nft/opal-testnet-types": "workspace:*"
+  }
+}
addedjs-packages/test-utils/util.tsdiffbeforeafterboth

no content

addedjs-packages/test-utils/xcm/index.tsdiffbeforeafterboth
--- /dev/null
+++ b/js-packages/test-utils/xcm/index.ts
@@ -0,0 +1,449 @@
+import {ApiPromise, WsProvider} from '@polkadot/api';
+import type {IKeyringPair} from '@polkadot/types/types';
+import {ChainHelperBase, EthereumBalanceGroup, HelperGroup, SubstrateBalanceGroup, UniqueHelper} from '@unique-nft/playgrounds/unique.js';
+import type {ILogger, TSigner} from '@unique-nft/playgrounds/types.js';
+import type {AcalaAssetMetadata, DemocracyStandardAccountVote, MoonbeamAssetInfo} from './types.js';
+
+
+export class XcmChainHelper extends ChainHelperBase {
+  override 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 DemocracyGroup<T extends ChainHelperBase> extends HelperGroup<T> {
+  notePreimagePallet: string;
+
+  constructor(helper: T, 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 CollectiveGroup<T extends ChainHelperBase> extends HelperGroup<T> {
+  collective: string;
+
+  constructor(helper: T, palletName: string) {
+    super(helper);
+
+    this.collective = palletName;
+  }
+
+  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, assetId: any, name: string, tokenPrefix: string, mode: 'NFT' | { Fungible: number }) {
+    await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.foreignAssets.forceRegisterForeignAsset',
+      [{V3: assetId}, this.helper.util.str2vec(name), tokenPrefix, mode],
+      true,
+    );
+  }
+
+  async foreignCollectionId(assetId: any) {
+    return (await this.helper.callRpc('api.query.foreignAssets.foreignAssetToCollection', [assetId])).toJSON();
+  }
+}
+
+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 | bigint, admin: string, minimalBalance: bigint) {
+    await this.helper.executeExtrinsic(signer, 'api.tx.assets.create', [assetId, admin, minimalBalance], true);
+  }
+
+  async forceCreate(signer: TSigner, assetId: number | bigint, admin: string, minimalBalance: bigint, isSufficient = true) {
+    await this.helper.executeExtrinsic(signer, 'api.tx.assets.forceCreate', [assetId, admin, isSufficient, minimalBalance], true);
+  }
+
+  async setMetadata(signer: TSigner, assetId: number | bigint, 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 | bigint, beneficiary: string, amount: bigint) {
+    await this.helper.executeExtrinsic(signer, 'api.tx.assets.mint', [assetId, beneficiary, amount], true);
+  }
+
+  async assetInfo(assetId: number | bigint) {
+    return (await this.helper.callRpc('api.query.assets.asset', [assetId])).toJSON();
+  }
+
+  async account(assetId: string | number | bigint, 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);
+  }
+}
+
+export class HydraDxHelper extends XcmChainHelper {
+  balance: SubstrateBalanceGroup<HydraDxHelper>;
+  xcm: XcmGroup<HydraDxHelper>;
+  xTokens: XTokensGroup<HydraDxHelper>;
+  democracy: DemocracyGroup<HydraDxHelper>;
+  collective: {
+    council: CollectiveGroup<HydraDxHelper>,
+    techCommittee: CollectiveGroup<HydraDxHelper>,
+  };
+
+  constructor(logger?: ILogger, options: { [key: string]: any } = {}) {
+    super(logger, options.helperBase ?? HydraDxHelper);
+    options.notePreimagePallet = options.notePreimagePallet ?? 'preimage';
+
+    this.balance = new SubstrateBalanceGroup(this);
+    this.xcm = new XcmGroup(this, 'polkadotXcm');
+    this.xTokens = new XTokensGroup(this);
+    this.democracy = new DemocracyGroup(this, options);
+    this.collective = {
+      council: new CollectiveGroup(this, 'council'),
+      techCommittee: new CollectiveGroup(this, 'technicalCommittee'),
+    };
+  }
+}
+
addedjs-packages/test-utils/xcm/types.tsdiffbeforeafterboth
--- /dev/null
+++ b/js-packages/test-utils/xcm/types.ts
@@ -0,0 +1,29 @@
+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,
+  },
+}
modifiedjs-packages/tests/addCollectionAdmin.test.tsdiffbeforeafterboth
--- a/js-packages/tests/addCollectionAdmin.test.ts
+++ b/js-packages/tests/addCollectionAdmin.test.ts
@@ -15,8 +15,8 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {itSub, usingPlaygrounds, expect} from './util/index.js';
-import {NON_EXISTENT_COLLECTION_ID} from '@unique/playgrounds/types.js';
+import {itSub, usingPlaygrounds, expect} from '@unique/test-utils/util.js';
+import {NON_EXISTENT_COLLECTION_ID} from '@unique-nft/playgrounds/types.js';
 
 describe('Integration Test addCollectionAdmin(collection_id, new_admin_id):', () => {
   let donor: IKeyringPair;
modifiedjs-packages/tests/adminTransferAndBurn.test.tsdiffbeforeafterboth
--- a/js-packages/tests/adminTransferAndBurn.test.ts
+++ b/js-packages/tests/adminTransferAndBurn.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {usingPlaygrounds, expect, itSub} from './util/index.js';
+import {usingPlaygrounds, expect, itSub} from '@unique/test-utils/util.js';
 
 describe('Integration Test: ownerCanTransfer allows admins to use only transferFrom/burnFrom:', () => {
   let alice: IKeyringPair;
modifiedjs-packages/tests/allowLists.test.tsdiffbeforeafterboth
--- a/js-packages/tests/allowLists.test.ts
+++ b/js-packages/tests/allowLists.test.ts
@@ -15,9 +15,9 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {usingPlaygrounds, expect, itSub} from './util/index.js';
-import type {ICollectionPermissions} from '@unique/playgrounds/types.js';
-import {NON_EXISTENT_COLLECTION_ID} from '@unique/playgrounds/types.js';
+import {usingPlaygrounds, expect, itSub} from '@unique/test-utils/util.js';
+import type {ICollectionPermissions} from '@unique-nft/playgrounds/types.js';
+import {NON_EXISTENT_COLLECTION_ID} from '@unique-nft/playgrounds/types.js';
 
 describe('Integration Test ext. Allow list tests', () => {
   let alice: IKeyringPair;
modifiedjs-packages/tests/apiConsts.test.tsdiffbeforeafterboth
--- a/js-packages/tests/apiConsts.test.ts
+++ b/js-packages/tests/apiConsts.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import {ApiPromise} from '@polkadot/api';
-import {usingPlaygrounds, itSub, expect, COLLECTION_HELPER, CONTRACT_HELPER} from './util/index.js';
+import {usingPlaygrounds, itSub, expect, COLLECTION_HELPER, CONTRACT_HELPER} from '@unique/test-utils/util.js';
 
 
 const MAX_COLLECTION_DESCRIPTION_LENGTH = 256n;
modifiedjs-packages/tests/approve.test.tsdiffbeforeafterboth
--- a/js-packages/tests/approve.test.ts
+++ b/js-packages/tests/approve.test.ts
@@ -15,8 +15,8 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {expect, itSub, Pallets, usingPlaygrounds} from './util/index.js';
-import {CrossAccountId} from '@unique/playgrounds/unique.js';
+import {expect, itSub, Pallets, usingPlaygrounds} from '@unique/test-utils/util.js';
+import {CrossAccountId} from '@unique-nft/playgrounds/unique.js';
 
 
 
modifiedjs-packages/tests/burnItem.test.tsdiffbeforeafterboth
--- a/js-packages/tests/burnItem.test.ts
+++ b/js-packages/tests/burnItem.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {expect, itSub, usingPlaygrounds} from './util/index.js';
+import {expect, itSub, usingPlaygrounds} from '@unique/test-utils/util.js';
 
 
 describe('integration test: ext. burnItem():', () => {
modifiedjs-packages/tests/change-collection-owner.test.tsdiffbeforeafterboth
--- a/js-packages/tests/change-collection-owner.test.ts
+++ b/js-packages/tests/change-collection-owner.test.ts
@@ -15,8 +15,8 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {usingPlaygrounds, expect, itSub} from './util/index.js';
-import {NON_EXISTENT_COLLECTION_ID} from '@unique/playgrounds/types.js';
+import {usingPlaygrounds, expect, itSub} from '@unique/test-utils/util.js';
+import {NON_EXISTENT_COLLECTION_ID} from '@unique-nft/playgrounds/types.js';
 
 describe('Integration Test changeCollectionOwner(collection_id, new_owner):', () => {
   let alice: IKeyringPair;
modifiedjs-packages/tests/confirmSponsorship.test.tsdiffbeforeafterboth
--- a/js-packages/tests/confirmSponsorship.test.ts
+++ b/js-packages/tests/confirmSponsorship.test.ts
@@ -15,8 +15,8 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {usingPlaygrounds, expect, itSub, Pallets} from './util/index.js';
-import {NON_EXISTENT_COLLECTION_ID} from '@unique/playgrounds/types.js';
+import {usingPlaygrounds, expect, itSub, Pallets} from '@unique/test-utils/util.js';
+import {NON_EXISTENT_COLLECTION_ID} from '@unique-nft/playgrounds/types.js';
 
 async function setSponsorHelper(collection: any, signer: IKeyringPair, sponsorAddress: string) {
   await collection.setSponsor(signer, sponsorAddress);
modifiedjs-packages/tests/connection.test.tsdiffbeforeafterboth
--- a/js-packages/tests/connection.test.ts
+++ b/js-packages/tests/connection.test.ts
@@ -14,7 +14,7 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-import {itSub, expect, usingPlaygrounds} from './util/index.js';
+import {itSub, expect, usingPlaygrounds} from '@unique/test-utils/util.js';
 
 describe('Connection smoke test', () => {
   itSub('Connection can be established', async ({helper}) => {
modifiedjs-packages/tests/createCollection.test.tsdiffbeforeafterboth
--- a/js-packages/tests/createCollection.test.ts
+++ b/js-packages/tests/createCollection.test.ts
@@ -15,10 +15,10 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {usingPlaygrounds, expect, itSub, Pallets} from './util/index.js';
-import {CollectionFlag} from '@unique/playgrounds/types.js';
-import type {ICollectionCreationOptions, IProperty} from '@unique/playgrounds/types.js';
-import {UniqueHelper} from '@unique/playgrounds/unique.js';
+import {usingPlaygrounds, expect, itSub, Pallets} from '@unique/test-utils/util.js';
+import {CollectionFlag} from '@unique-nft/playgrounds/types.js';
+import type {ICollectionCreationOptions, IProperty} from '@unique-nft/playgrounds/types.js';
+import {UniqueHelper} from '@unique-nft/playgrounds/unique.js';
 
 async function mintCollectionHelper(helper: UniqueHelper, signer: IKeyringPair, options: ICollectionCreationOptions, type?: 'nft' | 'fungible' | 'refungible') {
   let collection;
modifiedjs-packages/tests/createItem.test.tsdiffbeforeafterboth
--- a/js-packages/tests/createItem.test.ts
+++ b/js-packages/tests/createItem.test.ts
@@ -15,9 +15,9 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {usingPlaygrounds, expect, itSub, Pallets} from './util/index.js';
-import type {IProperty, ICrossAccountId} from '@unique/playgrounds/types.js';
-import {UniqueHelper} from '@unique/playgrounds/unique.js';
+import {usingPlaygrounds, expect, itSub, Pallets} from '@unique/test-utils/util.js';
+import type {IProperty, ICrossAccountId} from '@unique-nft/playgrounds/types.js';
+import {UniqueHelper} from '@unique-nft/playgrounds/unique.js';
 
 async function mintTokenHelper(helper: UniqueHelper, collection: any, signer: IKeyringPair, owner: ICrossAccountId, type: 'nft' | 'fungible' | 'refungible'='nft', properties?: IProperty[]) {
   let token;
modifiedjs-packages/tests/createMultipleItems.test.tsdiffbeforeafterboth
--- a/js-packages/tests/createMultipleItems.test.ts
+++ b/js-packages/tests/createMultipleItems.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {usingPlaygrounds, expect, Pallets, itSub} from './util/index.js';
+import {usingPlaygrounds, expect, Pallets, itSub} from '@unique/test-utils/util.js';
 
 describe('Integration Test createMultipleItems(collection_id, owner, items_data):', () => {
   let alice: IKeyringPair;
modifiedjs-packages/tests/createMultipleItemsEx.test.tsdiffbeforeafterboth
--- a/js-packages/tests/createMultipleItemsEx.test.ts
+++ b/js-packages/tests/createMultipleItemsEx.test.ts
@@ -15,8 +15,8 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {usingPlaygrounds, expect, Pallets, itSub} from './util/index.js';
-import type {IProperty} from '@unique/playgrounds/types.js';
+import {usingPlaygrounds, expect, Pallets, itSub} from '@unique/test-utils/util.js';
+import type {IProperty} from '@unique-nft/playgrounds/types.js';
 
 describe('Integration Test: createMultipleItemsEx', () => {
   let alice: IKeyringPair;
modifiedjs-packages/tests/creditFeesToTreasury.seqtest.tsdiffbeforeafterboth
--- a/js-packages/tests/creditFeesToTreasury.seqtest.ts
+++ b/js-packages/tests/creditFeesToTreasury.seqtest.ts
@@ -16,7 +16,7 @@
 
 import type {IKeyringPair} from '@polkadot/types/types';
 import {ApiPromise} from '@polkadot/api';
-import {usingPlaygrounds, expect, itSub} from './util/index.js';
+import {usingPlaygrounds, expect, itSub} from '@unique/test-utils/util.js';
 import type {u32} from '@polkadot/types-codec';
 
 const TREASURY = '5EYCAe5ijiYfyeZ2JJCGq56LmPyNRAKzpG4QkoQkkQNB5e6Z';
modifiedjs-packages/tests/destroyCollection.test.tsdiffbeforeafterboth
--- a/js-packages/tests/destroyCollection.test.ts
+++ b/js-packages/tests/destroyCollection.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {itSub, expect, usingPlaygrounds, Pallets} from './util/index.js';
+import {itSub, expect, usingPlaygrounds, Pallets} from '@unique/test-utils/util.js';
 
 describe('integration test: ext. destroyCollection():', () => {
   let alice: IKeyringPair;
modifiedjs-packages/tests/enableDisableTransfer.test.tsdiffbeforeafterboth
--- a/js-packages/tests/enableDisableTransfer.test.ts
+++ b/js-packages/tests/enableDisableTransfer.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {itSub, usingPlaygrounds, expect} from './util/index.js';
+import {itSub, usingPlaygrounds, expect} from '@unique/test-utils/util.js';
 
 describe('Enable/Disable Transfers', () => {
   let alice: IKeyringPair;
modifiedjs-packages/tests/eth/allowlist.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/allowlist.test.ts
+++ b/js-packages/tests/eth/allowlist.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {Pallets} from '../util/index.js';
+import {Pallets} from '@unique/test-utils/util.js';
 import {itEth, usingEthPlaygrounds, expect, SponsoringMode} from './util/index.js';
 import {CreateCollectionData} from './util/playgrounds/types.js';
 
modifiedjs-packages/tests/eth/collectionAdmin.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/collectionAdmin.test.ts
+++ b/js-packages/tests/eth/collectionAdmin.test.ts
@@ -15,8 +15,8 @@
 
 import type {IKeyringPair} from '@polkadot/types/types';
 import {expect} from 'chai';
-import {Pallets} from '../util/index.js';
-import type {IEthCrossAccountId} from '@unique/playgrounds/types.js';
+import {Pallets} from '@unique/test-utils/util.js';
+import type {IEthCrossAccountId} from '@unique-nft/playgrounds/types.js';
 import {usingEthPlaygrounds, itEth} from './util/index.js';
 import {EthUniqueHelper} from './util/playgrounds/unique.dev.js';
 import {CreateCollectionData} from './util/playgrounds/types.js';
modifiedjs-packages/tests/eth/collectionHelperAddress.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/collectionHelperAddress.test.ts
+++ b/js-packages/tests/eth/collectionHelperAddress.test.ts
@@ -16,7 +16,7 @@
 
 import {itEth, usingEthPlaygrounds, expect} from './util/index.js';
 import type {IKeyringPair} from '@polkadot/types/types';
-import {COLLECTION_HELPER, Pallets} from '../util/index.js';
+import {COLLECTION_HELPER, Pallets} from '@unique/test-utils/util.js';
 
 describe('[eth]CollectionHelperAddress test: ERC20/ERC721 ', () => {
   let donor: IKeyringPair;
modifiedjs-packages/tests/eth/collectionLimits.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/collectionLimits.test.ts
+++ b/js-packages/tests/eth/collectionLimits.test.ts
@@ -1,5 +1,5 @@
 import type {IKeyringPair} from '@polkadot/types/types';
-import {Pallets} from '../util/index.js';
+import {Pallets} from '@unique/test-utils/util.js';
 import {expect, itEth, usingEthPlaygrounds} from './util/index.js';
 import {CollectionLimitField, CreateCollectionData} from './util/playgrounds/types.js';
 
modifiedjs-packages/tests/eth/collectionProperties.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/collectionProperties.test.ts
+++ b/js-packages/tests/eth/collectionProperties.test.ts
@@ -15,8 +15,8 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import {itEth, usingEthPlaygrounds, expect} from './util/index.js';
-import {Pallets} from '../util/index.js';
-import type {IProperty, ITokenPropertyPermission} from '@unique/playgrounds/types.js';
+import {Pallets} from '@unique/test-utils/util.js';
+import type {IProperty, ITokenPropertyPermission} from '@unique-nft/playgrounds/types.js';
 import type {IKeyringPair} from '@polkadot/types/types';
 
 describe('EVM collection properties', () => {
modifiedjs-packages/tests/eth/collectionSponsoring.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/collectionSponsoring.test.ts
+++ b/js-packages/tests/eth/collectionSponsoring.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {Pallets, requirePalletsOrSkip, usingPlaygrounds} from '../util/index.js';
+import {Pallets, requirePalletsOrSkip, usingPlaygrounds} from '@unique/test-utils/util.js';
 import {itEth, expect} from './util/index.js';
 import {CollectionLimitField, TokenPermissionField} from './util/playgrounds/types.js';
 
modifiedjs-packages/tests/eth/contractSponsoring.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/contractSponsoring.test.ts
+++ b/js-packages/tests/eth/contractSponsoring.test.ts
@@ -17,7 +17,7 @@
 import type {IKeyringPair} from '@polkadot/types/types';
 import {EthUniqueHelper} from './util/playgrounds/unique.dev.js';
 import {itEth, expect, SponsoringMode, usingEthPlaygrounds} from './util/index.js';
-import {usingPlaygrounds} from '../util/index.js';
+import {usingPlaygrounds} from '@unique/test-utils/util.js';
 import type {CompiledContract} from './util/playgrounds/types.js';
 
 describe('Sponsoring EVM contracts', () => {
modifiedjs-packages/tests/eth/createCollection.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/createCollection.test.ts
+++ b/js-packages/tests/eth/createCollection.test.ts
@@ -16,11 +16,11 @@
 
 import type {IKeyringPair} from '@polkadot/types/types';
 import {evmToAddress} from '@polkadot/util-crypto';
-import {Pallets, requirePalletsOrSkip} from '../util/index.js';
+import {Pallets, requirePalletsOrSkip} from '@unique/test-utils/util.js';
 import {expect, itEth, usingEthPlaygrounds} from './util/index.js';
 import {CREATE_COLLECTION_DATA_DEFAULTS, CollectionLimitField, CollectionMode, CreateCollectionData, TokenPermissionField, emptyAddress} from './util/playgrounds/types.js';
-import {CollectionFlag} from '@unique/playgrounds/types.js';
-import type {IEthCrossAccountId, TCollectionMode} from '@unique/playgrounds/types.js';
+import {CollectionFlag} from '@unique-nft/playgrounds/types.js';
+import type {IEthCrossAccountId, TCollectionMode} from '@unique-nft/playgrounds/types.js';
 
 const DECIMALS = 18;
 const CREATE_COLLECTION_DATA_DEFAULTS_ARRAY = [
modifiedjs-packages/tests/eth/createFTCollection.seqtest.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/createFTCollection.seqtest.ts
+++ b/js-packages/tests/eth/createFTCollection.seqtest.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {Pallets, requirePalletsOrSkip} from '../util/index.js';
+import {Pallets, requirePalletsOrSkip} from '@unique/test-utils/util.js';
 import {expect, itEth, usingEthPlaygrounds} from './util/index.js';
 
 const DECIMALS = 18;
modifiedjs-packages/tests/eth/createFTCollection.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/createFTCollection.test.ts
+++ b/js-packages/tests/eth/createFTCollection.test.ts
@@ -16,7 +16,7 @@
 
 import type {IKeyringPair} from '@polkadot/types/types';
 import {evmToAddress} from '@polkadot/util-crypto';
-import {Pallets, requirePalletsOrSkip} from '../util/index.js';
+import {Pallets, requirePalletsOrSkip} from '@unique/test-utils/util.js';
 import {expect, itEth, usingEthPlaygrounds} from './util/index.js';
 import {CollectionLimitField} from './util/playgrounds/types.js';
 
modifiedjs-packages/tests/eth/createRFTCollection.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/createRFTCollection.test.ts
+++ b/js-packages/tests/eth/createRFTCollection.test.ts
@@ -16,7 +16,7 @@
 
 import {evmToAddress} from '@polkadot/util-crypto';
 import type {IKeyringPair} from '@polkadot/types/types';
-import {Pallets, requirePalletsOrSkip} from '../util/index.js';
+import {Pallets, requirePalletsOrSkip} from '@unique/test-utils/util.js';
 import {expect, itEth, usingEthPlaygrounds} from './util/index.js';
 import {CollectionLimitField} from './util/playgrounds/types.js';
 
modifiedjs-packages/tests/eth/crossTransfer.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/crossTransfer.test.ts
+++ b/js-packages/tests/eth/crossTransfer.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import {itEth, usingEthPlaygrounds} from './util/index.js';
-import {CrossAccountId} from '@unique/playgrounds/unique.js';
+import {CrossAccountId} from '@unique-nft/playgrounds/unique.js';
 import type {IKeyringPair} from '@polkadot/types/types';
 
 describe('Token transfer between substrate address and EVM address. Fungible', () => {
modifiedjs-packages/tests/eth/destroyCollection.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/destroyCollection.test.ts
+++ b/js-packages/tests/eth/destroyCollection.test.ts
@@ -15,9 +15,9 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {Pallets} from '../util/index.js';
+import {Pallets} from '@unique/test-utils/util.js';
 import {expect, itEth, usingEthPlaygrounds} from './util/index.js';
-import type {TCollectionMode} from '@unique/playgrounds/types.js';
+import type {TCollectionMode} from '@unique-nft/playgrounds/types.js';
 import {CreateCollectionData} from './util/playgrounds/types.js';
 
 describe('Destroy Collection from EVM', function() {
modifiedjs-packages/tests/eth/events.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/events.test.ts
+++ b/js-packages/tests/eth/events.test.ts
@@ -18,8 +18,8 @@
 import type {IKeyringPair} from '@polkadot/types/types';
 import {itEth, usingEthPlaygrounds} from './util/index.js';
 import {EthUniqueHelper} from './util/playgrounds/unique.dev.js';
-import type {IEvent, TCollectionMode} from '@unique/playgrounds/types.js';
-import {Pallets, requirePalletsOrSkip} from '../util/index.js';
+import type {IEvent, TCollectionMode} from '@unique-nft/playgrounds/types.js';
+import {Pallets, requirePalletsOrSkip} from '@unique/test-utils/util.js';
 import {CollectionLimitField, TokenPermissionField, CreateCollectionData} from './util/playgrounds/types.js';
 import type {NormalizedEvent} from './util/playgrounds/types.js';
 
modifiedjs-packages/tests/eth/fractionalizer/fractionalizer.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/fractionalizer/fractionalizer.test.ts
+++ b/js-packages/tests/eth/fractionalizer/fractionalizer.test.ts
@@ -25,7 +25,7 @@
 import {usingEthPlaygrounds, expect, itEth} from '../util/index.js';
 import {EthUniqueHelper} from '../util/playgrounds/unique.dev.js';
 import type {CompiledContract} from '../util/playgrounds/types.js';
-import {requirePalletsOrSkip, Pallets, makeNames} from '../../util/index.js';
+import {requirePalletsOrSkip, Pallets, makeNames} from '@unique/test-utils/util.js';
 
 const {dirname} = makeNames(import.meta.url);
 
modifiedjs-packages/tests/eth/getCode.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/getCode.test.ts
+++ b/js-packages/tests/eth/getCode.test.ts
@@ -16,7 +16,7 @@
 
 import {expect, itEth, usingEthPlaygrounds} from './util/index.js';
 import type {IKeyringPair} from '@polkadot/types/types';
-import {COLLECTION_HELPER, CONTRACT_HELPER} from '../util/index.js';
+import {COLLECTION_HELPER, CONTRACT_HELPER} from '@unique/test-utils/util.js';
 
 describe('RPC eth_getCode', () => {
   let donor: IKeyringPair;
modifiedjs-packages/tests/eth/marketplace-v2/marketplace.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/marketplace-v2/marketplace.test.ts
+++ b/js-packages/tests/eth/marketplace-v2/marketplace.test.ts
@@ -19,8 +19,7 @@
 import {readFile} from 'fs/promises';
 import {SponsoringMode, itEth, usingEthPlaygrounds} from '../util/index.js';
 import {EthUniqueHelper} from '../util/playgrounds/unique.dev.js';
-import {makeNames} from '../../util/index.js';
-import {expect} from 'chai';
+import {makeNames, expect} from '@unique/test-utils/util.js';
 
 const {dirname} = makeNames(import.meta.url);
 
modifiedjs-packages/tests/eth/marketplace/marketplace.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/marketplace/marketplace.test.ts
+++ b/js-packages/tests/eth/marketplace/marketplace.test.ts
@@ -17,7 +17,7 @@
 import type {IKeyringPair} from '@polkadot/types/types';
 import {readFile} from 'fs/promises';
 import {itEth, usingEthPlaygrounds, expect, SponsoringMode} from '../util/index.js';
-import {makeNames} from '../../util/index.js';
+import {makeNames} from '@unique/test-utils/util.js';
 
 const {dirname} = makeNames(import.meta.url);
 
modifiedjs-packages/tests/eth/migration.seqtest.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/migration.seqtest.ts
+++ b/js-packages/tests/eth/migration.seqtest.ts
@@ -18,7 +18,7 @@
 import type {IKeyringPair} from '@polkadot/types/types';
 import {Struct} from '@polkadot/types';
 
-import type {IEvent} from '@unique/playgrounds/types.js';
+import type {IEvent} from '@unique-nft/playgrounds/types.js';
 import type {InterfaceTypes} from '@polkadot/types/types/registry';
 import {ApiPromise} from '@polkadot/api';
 
modifiedjs-packages/tests/eth/nativeFungible.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/nativeFungible.test.ts
+++ b/js-packages/tests/eth/nativeFungible.test.ts
@@ -16,7 +16,7 @@
 
 import type {IKeyringPair} from '@polkadot/types/types';
 import {expect, itEth, usingEthPlaygrounds} from './util/index.js';
-import {UniqueHelper} from '@unique/playgrounds/unique.js';
+import {UniqueHelper} from '@unique-nft/playgrounds/unique.js';
 
 describe('NativeFungible: ERC20 calls', () => {
   let donor: IKeyringPair;
modifiedjs-packages/tests/eth/nonFungible.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/nonFungible.test.ts
+++ b/js-packages/tests/eth/nonFungible.test.ts
@@ -18,7 +18,7 @@
 import {EthUniqueHelper} from './util/playgrounds/unique.dev.js';
 import type {IKeyringPair} from '@polkadot/types/types';
 import {Contract} from 'web3-eth-contract';
-import type {ITokenPropertyPermission} from '@unique/playgrounds/types.js';
+import type {ITokenPropertyPermission} from '@unique-nft/playgrounds/types.js';
 import {CREATE_COLLECTION_DATA_DEFAULTS, TokenPermissionField} from './util/playgrounds/types.js';
 
 describe('Check ERC721 token URI for NFT', () => {
modifiedjs-packages/tests/eth/payable.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/payable.test.ts
+++ b/js-packages/tests/eth/payable.test.ts
@@ -18,7 +18,7 @@
 
 import {itEth, expect, usingEthPlaygrounds} from './util/index.js';
 import {EthUniqueHelper} from './util/playgrounds/unique.dev.js';
-import {makeNames} from '../util/index.js';
+import {makeNames} from '@unique/test-utils/util.js';
 
 const {dirname} = makeNames(import.meta.url);
 
modifiedjs-packages/tests/eth/proxy/fungibleProxy.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/proxy/fungibleProxy.test.ts
+++ b/js-packages/tests/eth/proxy/fungibleProxy.test.ts
@@ -19,7 +19,7 @@
 import type {IKeyringPair} from '@polkadot/types/types';
 import {itEth, usingEthPlaygrounds} from '../util/index.js';
 import {EthUniqueHelper} from '../util/playgrounds/unique.dev.js';
-import {makeNames} from '../../util/index.js';
+import {makeNames} from '@unique/test-utils/util.js';
 
 const {dirname} = makeNames(import.meta.url);
 
modifiedjs-packages/tests/eth/proxy/nonFungibleProxy.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/proxy/nonFungibleProxy.test.ts
+++ b/js-packages/tests/eth/proxy/nonFungibleProxy.test.ts
@@ -18,7 +18,7 @@
 import type {IKeyringPair} from '@polkadot/types/types';
 import {itEth, usingEthPlaygrounds, expect} from '../util/index.js';
 import {EthUniqueHelper} from '../util/playgrounds/unique.dev.js';
-import {makeNames} from '../../util/index.js';
+import {makeNames} from '@unique/test-utils/util.js';
 
 const {dirname} = makeNames(import.meta.url);
 
modifiedjs-packages/tests/eth/reFungible.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/reFungible.test.ts
+++ b/js-packages/tests/eth/reFungible.test.ts
@@ -14,10 +14,10 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-import {Pallets, requirePalletsOrSkip} from '../util/index.js';
+import {Pallets, requirePalletsOrSkip} from '@unique/test-utils/util.js';
 import {expect, itEth, usingEthPlaygrounds} from './util/index.js';
 import type {IKeyringPair} from '@polkadot/types/types';
-import type {ITokenPropertyPermission} from '@unique/playgrounds/types.js';
+import type {ITokenPropertyPermission} from '@unique-nft/playgrounds/types.js';
 import {CREATE_COLLECTION_DATA_DEFAULTS, TokenPermissionField} from './util/playgrounds/types.js';
 
 describe('Refungible: Plain calls', () => {
modifiedjs-packages/tests/eth/reFungibleToken.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/reFungibleToken.test.ts
+++ b/js-packages/tests/eth/reFungibleToken.test.ts
@@ -14,7 +14,7 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-import {Pallets, requirePalletsOrSkip} from '../util/index.js';
+import {Pallets, requirePalletsOrSkip} from '@unique/test-utils/util.js';
 import {expect, itEth, usingEthPlaygrounds} from './util/index.js';
 import {EthUniqueHelper} from './util/playgrounds/unique.dev.js';
 import type {IKeyringPair} from '@polkadot/types/types';
modifiedjs-packages/tests/eth/scheduling.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/scheduling.test.ts
+++ b/js-packages/tests/eth/scheduling.test.ts
@@ -17,7 +17,7 @@
 import {expect} from 'chai';
 import {itSchedEth} from './util/index.js';
 import {EthUniqueHelper} from './util/playgrounds/unique.dev.js';
-import {Pallets, usingPlaygrounds} from '../util/index.js';
+import {Pallets, usingPlaygrounds} from '@unique/test-utils/util.js';
 
 describe('Scheduing EVM smart contracts', () => {
 
modifiedjs-packages/tests/eth/sponsoring.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/sponsoring.test.ts
+++ b/js-packages/tests/eth/sponsoring.test.ts
@@ -16,7 +16,7 @@
 
 import type {IKeyringPair} from '@polkadot/types/types';
 import {itEth, expect, SponsoringMode} from './util/index.js';
-import {usingPlaygrounds} from '../util/index.js';
+import {usingPlaygrounds} from '@unique/test-utils/util.js';
 
 describe('EVM sponsoring', () => {
   let donor: IKeyringPair;
modifiedjs-packages/tests/eth/tokenProperties.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/tokenProperties.test.ts
+++ b/js-packages/tests/eth/tokenProperties.test.ts
@@ -17,9 +17,9 @@
 import type {IKeyringPair} from '@polkadot/types/types';
 import {Contract} from 'web3-eth-contract';
 import {itEth, usingEthPlaygrounds, expect} from './util/index.js';
-import type {ITokenPropertyPermission} from '@unique/playgrounds/types.js';
-import {Pallets} from '../util/index.js';
-import {UniqueNFTCollection, UniqueNFToken, UniqueRFTCollection} from '@unique/playgrounds/unique.js';
+import type {ITokenPropertyPermission} from '@unique-nft/playgrounds/types.js';
+import {Pallets} from '@unique/test-utils/util.js';
+import {UniqueNFTCollection, UniqueNFToken, UniqueRFTCollection} from '@unique-nft/playgrounds/unique.js';
 import {CreateCollectionData, TokenPermissionField} from './util/playgrounds/types.js';
 
 describe('EVM token properties', () => {
modifiedjs-packages/tests/eth/tokens/callMethodsERC20.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/tokens/callMethodsERC20.test.ts
+++ b/js-packages/tests/eth/tokens/callMethodsERC20.test.ts
@@ -14,7 +14,7 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-import {Pallets, requirePalletsOrSkip} from '../../util/index.js';
+import {Pallets, requirePalletsOrSkip} from '@unique/test-utils/util.js';
 import {expect, itEth, usingEthPlaygrounds} from '../util/index.js';
 import type {IKeyringPair} from '@polkadot/types/types';
 import {CreateCollectionData} from '../util/playgrounds/types.js';
modifiedjs-packages/tests/eth/tokens/callMethodsERC721.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/tokens/callMethodsERC721.test.ts
+++ b/js-packages/tests/eth/tokens/callMethodsERC721.test.ts
@@ -14,7 +14,7 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-import {Pallets} from '../../util/index.js';
+import {Pallets} from '@unique/test-utils/util.js';
 import {expect, itEth, usingEthPlaygrounds} from '../util/index.js';
 import type {IKeyringPair} from '@polkadot/types/types';
 import {CreateCollectionData} from '../util/playgrounds/types.js';
modifiedjs-packages/tests/eth/tokens/minting.test.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/tokens/minting.test.ts
+++ b/js-packages/tests/eth/tokens/minting.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {Pallets} from '../../util/index.js';
+import {Pallets} from '@unique/test-utils/util.js';
 import {expect, itEth, usingEthPlaygrounds} from '../util/index.js';
 import {CreateCollectionData} from '../util/playgrounds/types.js';
 
modifiedjs-packages/tests/eth/util/index.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/util/index.ts
+++ b/js-packages/tests/eth/util/index.ts
@@ -7,14 +7,13 @@
 import config from '../../config.js';
 
 import {EthUniqueHelper} from './playgrounds/unique.dev.js';
-import {SilentLogger, SilentConsole} from '@unique/playgrounds/unique.dev.js';
-import {makeNames} from '../../util/index.js';
-import type {SchedKind} from '../../util/index.js';
+import {SilentLogger, SilentConsole} from '@unique/test-utils';
+import type {SchedKind} from '@unique/test-utils/util.js';
 
 import chai from 'chai';
 import chaiAsPromised from 'chai-as-promised';
 import chaiLike from 'chai-like';
-import {getTestSeed, MINIMUM_DONOR_FUND, requirePalletsOrSkip} from '../../util/index.js';
+import {getTestSeed, MINIMUM_DONOR_FUND, requirePalletsOrSkip, makeNames} from '@unique/test-utils/util.js';
 
 chai.use(chaiAsPromised);
 chai.use(chaiLike);
modifiedjs-packages/tests/eth/util/playgrounds/types.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/util/playgrounds/types.ts
+++ b/js-packages/tests/eth/util/playgrounds/types.ts
@@ -1,5 +1,5 @@
-import {CollectionFlag} from '@unique/playgrounds/types.js';
-import type {TCollectionMode} from '@unique/playgrounds/types.js';
+import {CollectionFlag} from '@unique-nft/playgrounds/types.js';
+import type {TCollectionMode} from '@unique-nft/playgrounds/types.js';
 
 export interface ContractImports {
   solPath: string;
modifiedjs-packages/tests/eth/util/playgrounds/unique.dev.tsdiffbeforeafterboth
--- a/js-packages/tests/eth/util/playgrounds/unique.dev.ts
+++ b/js-packages/tests/eth/util/playgrounds/unique.dev.ts
@@ -15,7 +15,7 @@
 import {evmToAddress} from '@polkadot/util-crypto';
 import type {IKeyringPair} from '@polkadot/types/types';
 
-import {ArrangeGroup, DevUniqueHelper} from '@unique/playgrounds/unique.dev.js';
+import {ArrangeGroup, DevUniqueHelper} from '@unique/test-utils/index.js';
 
 import type {ContractImports, CompiledContract, CrossAddress, NormalizedEvent, EthProperty} from './types.js';
 import {CollectionMode, CreateCollectionData} from './types.js';
@@ -32,7 +32,7 @@
 import refungibleTokenAbi from '../../abi/reFungibleToken.json' assert {type: 'json'};
 import refungibleTokenDeprecatedAbi from '../../abi/reFungibleTokenDeprecated.json' assert {type: 'json'};
 import contractHelpersAbi from '../../abi/contractHelpers.json' assert {type: 'json'};
-import type {ICrossAccountId, TEthereumAccount, TCollectionMode} from '@unique/playgrounds/types.js';
+import type {ICrossAccountId, TEthereumAccount, TCollectionMode} from '@unique-nft/playgrounds/types.js';
 
 class EthGroupBase {
   helper: EthUniqueHelper;
modifiedjs-packages/tests/fungible.test.tsdiffbeforeafterboth
--- a/js-packages/tests/fungible.test.ts
+++ b/js-packages/tests/fungible.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {itSub, usingPlaygrounds, expect, requirePalletsOrSkip, Pallets} from './util/index.js';
+import {itSub, usingPlaygrounds, expect, requirePalletsOrSkip, Pallets} from '@unique/test-utils/util.js';
 
 const U128_MAX = (1n << 128n) - 1n;
 
modifiedjs-packages/tests/getPropertiesRpc.test.tsdiffbeforeafterboth
--- a/js-packages/tests/getPropertiesRpc.test.ts
+++ b/js-packages/tests/getPropertiesRpc.test.ts
@@ -15,8 +15,8 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {itSub, usingPlaygrounds, expect} from './util/index.js';
-import {UniqueHelper, UniqueNFTCollection} from '@unique/playgrounds/unique.js';
+import {itSub, usingPlaygrounds, expect} from '@unique/test-utils/util.js';
+import {UniqueHelper, UniqueNFTCollection} from '@unique-nft/playgrounds/unique.js';
 
 const collectionProps = [
   {key: 'col-0', value: 'col-0-value'},
modifiedjs-packages/tests/inflation.seqtest.tsdiffbeforeafterboth
--- a/js-packages/tests/inflation.seqtest.ts
+++ b/js-packages/tests/inflation.seqtest.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {expect, itSub, usingPlaygrounds} from './util/index.js';
+import {expect, itSub, usingPlaygrounds} from '@unique/test-utils/util.js';
 
 const TREASURY = '5EYCAe5ijiYfyeZ2JJCGq56LmPyNRAKzpG4QkoQkkQNB5e6Z';
 
modifiedjs-packages/tests/limits.test.tsdiffbeforeafterboth
--- a/js-packages/tests/limits.test.ts
+++ b/js-packages/tests/limits.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {expect, itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds} from './util/index.js';
+import {expect, itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds} from '@unique/test-utils/util.js';
 
 describe('Number of tokens per address (NFT)', () => {
   let alice: IKeyringPair;
modifiedjs-packages/tests/maintenance.seqtest.tsdiffbeforeafterboth
--- a/js-packages/tests/maintenance.seqtest.ts
+++ b/js-packages/tests/maintenance.seqtest.ts
@@ -16,7 +16,7 @@
 
 import type {IKeyringPair} from '@polkadot/types/types';
 import {ApiPromise} from '@polkadot/api';
-import {expect, itSched, itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds} from './util/index.js';
+import {expect, itSched, itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds} from '@unique/test-utils/util.js';
 import {itEth} from './eth/util/index.js';
 import {main as correctState} from './migrations/correctStateAfterMaintenance.js';
 import type {PalletBalancesIdAmount} from '@polkadot/types/lookup';
modifiedjs-packages/tests/migrations/942057-appPromotion/index.tsdiffbeforeafterboth
--- a/js-packages/tests/migrations/942057-appPromotion/index.ts
+++ b/js-packages/tests/migrations/942057-appPromotion/index.ts
@@ -1,4 +1,4 @@
-import type {Migration} from '../../util/frankensteinMigrate.js';
+import type {Migration} from '../index.js';
 import {collectData} from './collectData.js';
 import {migrateLockedToFreeze} from './lockedToFreeze.js';
 
modifiedjs-packages/tests/migrations/942057-appPromotion/lockedToFreeze.tsdiffbeforeafterboth
--- a/js-packages/tests/migrations/942057-appPromotion/lockedToFreeze.ts
+++ b/js-packages/tests/migrations/942057-appPromotion/lockedToFreeze.ts
@@ -1,6 +1,6 @@
 // import { usingApi, privateKey, onlySign } from "./../../load/lib";
 import * as fs from 'fs';
-import {usingPlaygrounds} from '../../util/index.js';
+import {usingPlaygrounds} from '@unique/test-utils/util.js';
 import path, {dirname} from 'path';
 import {isInteger, parse} from 'lossless-json';
 import {fileURLToPath} from 'url';
modifiedjs-packages/tests/migrations/correctStateAfterMaintenance.tsdiffbeforeafterboth
--- a/js-packages/tests/migrations/correctStateAfterMaintenance.ts
+++ b/js-packages/tests/migrations/correctStateAfterMaintenance.ts
@@ -1,5 +1,5 @@
 import config from '../config.js';
-import {usingPlaygrounds} from '../util/index.js';
+import {usingPlaygrounds} from '@unique/test-utils/util.js';
 import type {u32} from '@polkadot/types-codec';
 
 const WS_ENDPOINT = config.substrateUrl;
addedjs-packages/tests/migrations/index.tsdiffbeforeafterboth
--- /dev/null
+++ b/js-packages/tests/migrations/index.ts
@@ -0,0 +1,9 @@
+import {migration as locksToFreezesMigration} from './942057-appPromotion/index.js';
+export interface Migration {
+  before: () => Promise<void>,
+  after: () => Promise<void>,
+}
+
+export const migrations: {[key: string]: Migration} = {
+  'v942057': locksToFreezesMigration,
+};
\ No newline at end of file
modifiedjs-packages/tests/nativeFungible.test.tsdiffbeforeafterboth
--- a/js-packages/tests/nativeFungible.test.ts
+++ b/js-packages/tests/nativeFungible.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {expect, itSub, usingPlaygrounds} from './util/index.js';
+import {expect, itSub, usingPlaygrounds} from '@unique/test-utils/util.js';
 
 describe('Native fungible', () => {
   let root: IKeyringPair;
modifiedjs-packages/tests/nextSponsoring.test.tsdiffbeforeafterboth
--- a/js-packages/tests/nextSponsoring.test.ts
+++ b/js-packages/tests/nextSponsoring.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {expect, itSub, Pallets, usingPlaygrounds} from './util/index.js';
+import {expect, itSub, Pallets, usingPlaygrounds} from '@unique/test-utils/util.js';
 
 const SPONSORING_TIMEOUT = 5;
 
modifiedjs-packages/tests/package.jsondiffbeforeafterboth
--- a/js-packages/tests/package.json
+++ b/js-packages/tests/package.json
@@ -13,9 +13,9 @@
         "mocha": "^10.1.0"
     },
     "scripts": {
-        "setup": "yarn node --no-warnings=ExperimentalWarning --loader ts-node/esm ./util/globalSetup.ts",
-        "setIdentities": "yarn node --no-warnings=ExperimentalWarning --loader ts-node/esm ./util/identitySetter.ts",
-        "checkRelayIdentities": "yarn node --no-warnings=ExperimentalWarning --loader ts-node/esm ./util/relayIdentitiesChecker.ts",
+        "setup": "yarn node --no-warnings=ExperimentalWarning --loader ts-node/esm ../test-utils/globalSetup.ts",
+        "setIdentities": "yarn node --no-warnings=ExperimentalWarning --loader ts-node/esm ../scripts/identitySetter.ts",
+        "checkRelayIdentities": "yarn node --no-warnings=ExperimentalWarning --loader ts-node/esm ../scripts/relayIdentitiesChecker.ts",
         "_test": "yarn setup && yarn mocha --timeout 9999999 --loader=ts-node/esm.mjs",
         "_testParallel": "yarn setup && yarn mocha --timeout 9999999 --parallel --loader=ts-node/esm.mjs",
         "test": "yarn _test './**/*.*test.ts'",
modifiedjs-packages/tests/pallet-presence.test.tsdiffbeforeafterboth
--- a/js-packages/tests/pallet-presence.test.ts
+++ b/js-packages/tests/pallet-presence.test.ts
@@ -14,7 +14,7 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-import {itSub, usingPlaygrounds, expect} from './util/index.js';
+import {itSub, usingPlaygrounds, expect} from '@unique/test-utils/util.js';
 
 // Pallets that must always be present
 const requiredPallets = [
modifiedjs-packages/tests/performance.seq.test.tsdiffbeforeafterboth
--- a/js-packages/tests/performance.seq.test.ts
+++ b/js-packages/tests/performance.seq.test.ts
@@ -16,9 +16,9 @@
 
 import {ApiPromise} from '@polkadot/api';
 import type {IKeyringPair} from '@polkadot/types/types';
-import {expect, itSub, usingPlaygrounds} from './util/index.js';
-import type {ICrossAccountId, IProperty} from '@unique/playgrounds/types.js';
-import {UniqueHelper} from '@unique/playgrounds/unique.js';
+import {expect, itSub, usingPlaygrounds} from '@unique/test-utils/util.js';
+import type {ICrossAccountId, IProperty} from '@unique-nft/playgrounds/types.js';
+import {UniqueHelper} from '@unique-nft/playgrounds/unique.js';
 
 describe('Performace tests', () => {
   let alice: IKeyringPair;
modifiedjs-packages/tests/refungible.test.tsdiffbeforeafterboth
--- a/js-packages/tests/refungible.test.ts
+++ b/js-packages/tests/refungible.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from './util/index.js';
+import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '@unique/test-utils/util.js';
 
 const MAX_REFUNGIBLE_PIECES = 1_000_000_000_000_000_000_000n;
 
modifiedjs-packages/tests/removeCollectionAdmin.test.tsdiffbeforeafterboth
--- a/js-packages/tests/removeCollectionAdmin.test.ts
+++ b/js-packages/tests/removeCollectionAdmin.test.ts
@@ -15,8 +15,8 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {itSub, usingPlaygrounds, expect} from './util/index.js';
-import {NON_EXISTENT_COLLECTION_ID} from '@unique/playgrounds/types.js';
+import {itSub, usingPlaygrounds, expect} from '@unique/test-utils/util.js';
+import {NON_EXISTENT_COLLECTION_ID} from '@unique-nft/playgrounds/types.js';
 
 describe('Integration Test removeCollectionAdmin(collection_id, account_id):', () => {
   let alice: IKeyringPair;
modifiedjs-packages/tests/removeCollectionSponsor.test.tsdiffbeforeafterboth
--- a/js-packages/tests/removeCollectionSponsor.test.ts
+++ b/js-packages/tests/removeCollectionSponsor.test.ts
@@ -15,8 +15,8 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {itSub, usingPlaygrounds, expect} from './util/index.js';
-import {NON_EXISTENT_COLLECTION_ID} from '@unique/playgrounds/types.js';
+import {itSub, usingPlaygrounds, expect} from '@unique/test-utils/util.js';
+import {NON_EXISTENT_COLLECTION_ID} from '@unique-nft/playgrounds/types.js';
 
 describe('integration test: ext. removeCollectionSponsor():', () => {
   let donor: IKeyringPair;
modifiedjs-packages/tests/rpc.test.tsdiffbeforeafterboth
--- a/js-packages/tests/rpc.test.ts
+++ b/js-packages/tests/rpc.test.ts
@@ -15,8 +15,8 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {usingPlaygrounds, itSub, expect} from './util/index.js';
-import {ICrossAccountId} from '@unique/playgrounds/types.js';
+import {usingPlaygrounds, itSub, expect} from '@unique/test-utils/util.js';
+import {ICrossAccountId} from '@unique-nft/playgrounds/types.js';
 
 describe('integration test: RPC methods', () => {
   let donor: IKeyringPair;
modifiedjs-packages/tests/scheduler.seqtest.tsdiffbeforeafterboth
--- a/js-packages/tests/scheduler.seqtest.ts
+++ b/js-packages/tests/scheduler.seqtest.ts
@@ -14,9 +14,9 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-import {expect, itSched, itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds} from './util/index.js';
+import {expect, itSched, itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds} from '@unique/test-utils/util.js';
 import type {IKeyringPair} from '@polkadot/types/types';
-import {DevUniqueHelper, Event} from '@unique/playgrounds/unique.dev.js';
+import {DevUniqueHelper, Event} from '@unique/test-utils';
 
 describe('Scheduling token and balance transfers', () => {
   let superuser: IKeyringPair;
modifiedjs-packages/tests/setCollectionLimits.test.tsdiffbeforeafterboth
--- a/js-packages/tests/setCollectionLimits.test.ts
+++ b/js-packages/tests/setCollectionLimits.test.ts
@@ -16,8 +16,8 @@
 
 // https://unique-network.readthedocs.io/en/latest/jsapi.html#setchainlimits
 import type {IKeyringPair} from '@polkadot/types/types';
-import {itSub, usingPlaygrounds, expect} from './util/index.js';
-import {NON_EXISTENT_COLLECTION_ID} from '@unique/playgrounds/types.js';
+import {itSub, usingPlaygrounds, expect} from '@unique/test-utils/util.js';
+import {NON_EXISTENT_COLLECTION_ID} from '@unique-nft/playgrounds/types.js';
 
 const accountTokenOwnershipLimit = 0;
 const sponsoredDataSize = 0;
modifiedjs-packages/tests/setCollectionSponsor.test.tsdiffbeforeafterboth
--- a/js-packages/tests/setCollectionSponsor.test.ts
+++ b/js-packages/tests/setCollectionSponsor.test.ts
@@ -15,8 +15,8 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {itSub, usingPlaygrounds, expect, Pallets} from './util/index.js';
-import {NON_EXISTENT_COLLECTION_ID} from '@unique/playgrounds/types.js';
+import {itSub, usingPlaygrounds, expect, Pallets} from '@unique/test-utils/util.js';
+import {NON_EXISTENT_COLLECTION_ID} from '@unique-nft/playgrounds/types.js';
 
 describe('integration test: ext. setCollectionSponsor():', () => {
   let alice: IKeyringPair;
modifiedjs-packages/tests/setPermissions.test.tsdiffbeforeafterboth
--- a/js-packages/tests/setPermissions.test.ts
+++ b/js-packages/tests/setPermissions.test.ts
@@ -15,8 +15,8 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {itSub, usingPlaygrounds, expect} from './util/index.js';
-import {NON_EXISTENT_COLLECTION_ID} from '@unique/playgrounds/types.js';
+import {itSub, usingPlaygrounds, expect} from '@unique/test-utils/util.js';
+import {NON_EXISTENT_COLLECTION_ID} from '@unique-nft/playgrounds/types.js';
 
 describe('Integration Test: Set Permissions', () => {
   let alice: IKeyringPair;
modifiedjs-packages/tests/sub/appPromotion/appPromotion.seqtest.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/appPromotion/appPromotion.seqtest.ts
+++ b/js-packages/tests/sub/appPromotion/appPromotion.seqtest.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {itSub, usingPlaygrounds, Pallets, requirePalletsOrSkip} from '../../util/index.js';
+import {itSub, usingPlaygrounds, Pallets, requirePalletsOrSkip} from '@unique/test-utils/util.js';
 import {expect} from '../../eth/util/index.js';
 
 let superuser: IKeyringPair;
modifiedjs-packages/tests/sub/appPromotion/appPromotion.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/appPromotion/appPromotion.test.ts
+++ b/js-packages/tests/sub/appPromotion/appPromotion.test.ts
@@ -17,8 +17,8 @@
 import type {IKeyringPair} from '@polkadot/types/types';
 import {
   itSub, usingPlaygrounds, Pallets, requirePalletsOrSkip, LOCKING_PERIOD, UNLOCKING_PERIOD,
-} from '../../util/index.js';
-import {DevUniqueHelper} from '@unique/playgrounds/unique.dev.js';
+} from '@unique/test-utils/util.js';
+import {DevUniqueHelper} from '@unique/test-utils';
 import {itEth, expect, SponsoringMode} from '../../eth/util/index.js';
 
 let donor: IKeyringPair;
modifiedjs-packages/tests/sub/check-event/burnItemEvent.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/check-event/burnItemEvent.test.ts
+++ b/js-packages/tests/sub/check-event/burnItemEvent.test.ts
@@ -16,8 +16,8 @@
 
 // https://unique-network.readthedocs.io/en/latest/jsapi.html#setchainlimits
 import type {IKeyringPair} from '@polkadot/types/types';
-import {usingPlaygrounds, expect, itSub} from '../../util/index.js';
-import type {IEvent} from '@unique/playgrounds/types.js';
+import {usingPlaygrounds, expect, itSub} from '@unique/test-utils/util.js';
+import type {IEvent} from '@unique-nft/playgrounds/types.js';
 
 
 describe('Burn Item event ', () => {
modifiedjs-packages/tests/sub/check-event/createCollectionEvent.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/check-event/createCollectionEvent.test.ts
+++ b/js-packages/tests/sub/check-event/createCollectionEvent.test.ts
@@ -16,8 +16,8 @@
 
 // https://unique-network.readthedocs.io/en/latest/jsapi.html#setchainlimits
 import type {IKeyringPair} from '@polkadot/types/types';
-import {usingPlaygrounds, itSub, expect} from '../../util/index.js';
-import type {IEvent} from '@unique/playgrounds/types.js';
+import {usingPlaygrounds, itSub, expect} from '@unique/test-utils/util.js';
+import type {IEvent} from '@unique-nft/playgrounds/types.js';
 
 describe('Create collection event ', () => {
   let alice: IKeyringPair;
modifiedjs-packages/tests/sub/check-event/createItemEvent.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/check-event/createItemEvent.test.ts
+++ b/js-packages/tests/sub/check-event/createItemEvent.test.ts
@@ -16,8 +16,8 @@
 
 // https://unique-network.readthedocs.io/en/latest/jsapi.html#setchainlimits
 import type {IKeyringPair} from '@polkadot/types/types';
-import {itSub, usingPlaygrounds, expect} from '../../util/index.js';
-import type {IEvent} from '@unique/playgrounds/types.js';
+import {itSub, usingPlaygrounds, expect} from '@unique/test-utils/util.js';
+import type {IEvent} from '@unique-nft/playgrounds/types.js';
 
 describe('Create Item event ', () => {
   let alice: IKeyringPair;
modifiedjs-packages/tests/sub/check-event/createMultipleItemsEvent.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/check-event/createMultipleItemsEvent.test.ts
+++ b/js-packages/tests/sub/check-event/createMultipleItemsEvent.test.ts
@@ -16,8 +16,8 @@
 
 // https://unique-network.readthedocs.io/en/latest/jsapi.html#setchainlimits
 import type {IKeyringPair} from '@polkadot/types/types';
-import {usingPlaygrounds, itSub, expect} from '../../util/index.js';
-import type {IEvent} from '@unique/playgrounds/types.js';
+import {usingPlaygrounds, itSub, expect} from '@unique/test-utils/util.js';
+import type {IEvent} from '@unique-nft/playgrounds/types.js';
 
 describe('Create Multiple Items Event event ', () => {
   let alice: IKeyringPair;
modifiedjs-packages/tests/sub/check-event/destroyCollectionEvent.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/check-event/destroyCollectionEvent.test.ts
+++ b/js-packages/tests/sub/check-event/destroyCollectionEvent.test.ts
@@ -16,8 +16,8 @@
 
 // https://unique-network.readthedocs.io/en/latest/jsapi.html#setchainlimits
 import type {IKeyringPair} from '@polkadot/types/types';
-import {itSub, usingPlaygrounds, expect} from '../../util/index.js';
-import type {IEvent} from '@unique/playgrounds/types.js';
+import {itSub, usingPlaygrounds, expect} from '@unique/test-utils/util.js';
+import type {IEvent} from '@unique-nft/playgrounds/types.js';
 
 describe('Destroy collection event ', () => {
   let alice: IKeyringPair;
modifiedjs-packages/tests/sub/check-event/transferEvent.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/check-event/transferEvent.test.ts
+++ b/js-packages/tests/sub/check-event/transferEvent.test.ts
@@ -16,8 +16,8 @@
 
 // https://unique-network.readthedocs.io/en/latest/jsapi.html#setchainlimits
 import type {IKeyringPair} from '@polkadot/types/types';
-import {usingPlaygrounds, expect, itSub} from '../../util/index.js';
-import type {IEvent} from '@unique/playgrounds/types.js';
+import {usingPlaygrounds, expect, itSub} from '@unique/test-utils/util.js';
+import type {IEvent} from '@unique-nft/playgrounds/types.js';
 
 describe('Transfer event ', () => {
   let alice: IKeyringPair;
modifiedjs-packages/tests/sub/check-event/transferFromEvent.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/check-event/transferFromEvent.test.ts
+++ b/js-packages/tests/sub/check-event/transferFromEvent.test.ts
@@ -16,8 +16,8 @@
 
 // https://unique-network.readthedocs.io/en/latest/jsapi.html#setchainlimits
 import type {IKeyringPair} from '@polkadot/types/types';
-import {usingPlaygrounds, expect, itSub} from '../../util/index.js';
-import type {IEvent} from '@unique/playgrounds/types.js';
+import {usingPlaygrounds, expect, itSub} from '@unique/test-utils/util.js';
+import type {IEvent} from '@unique-nft/playgrounds/types.js';
 
 describe('Transfer event ', () => {
   let alice: IKeyringPair;
modifiedjs-packages/tests/sub/collator-selection/collatorSelection.seqtest.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/collator-selection/collatorSelection.seqtest.ts
+++ b/js-packages/tests/sub/collator-selection/collatorSelection.seqtest.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {usingPlaygrounds, expect, itSub, Pallets, requirePalletsOrSkip} from '../../util/index.js';
+import {usingPlaygrounds, expect, itSub, Pallets, requirePalletsOrSkip} from '@unique/test-utils/util.js';
 
 async function nodeAddress(name: string) {
   // eslint-disable-next-line require-await
modifiedjs-packages/tests/sub/collator-selection/identity.seqtest.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/collator-selection/identity.seqtest.ts
+++ b/js-packages/tests/sub/collator-selection/identity.seqtest.ts
@@ -15,8 +15,8 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {usingPlaygrounds, expect, itSub, Pallets, requirePalletsOrSkip} from '../../util/index.js';
-import {UniqueHelper} from '@unique/playgrounds/unique.js';
+import {usingPlaygrounds, expect, itSub, Pallets, requirePalletsOrSkip} from '@unique/test-utils/util.js';
+import {UniqueHelper} from '@unique-nft/playgrounds/unique.js';
 
 async function getIdentities(helper: UniqueHelper) {
   const identities: [string, any][] = [];
modifiedjs-packages/tests/sub/governance/council.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/governance/council.test.ts
+++ b/js-packages/tests/sub/governance/council.test.ts
@@ -1,7 +1,7 @@
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '../../util/index.js';
-import {Event} from '@unique/playgrounds/unique.dev.js';
+import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '@unique/test-utils/util.js';
+import {Event} from '@unique/test-utils';
 import {initCouncil, democracyLaunchPeriod, democracyVotingPeriod, democracyEnactmentPeriod, councilMotionDuration, democracyFastTrackVotingPeriod, fellowshipRankLimit, clearCouncil, clearTechComm, initTechComm, clearFellowship, dummyProposal, dummyProposalCall, initFellowship, defaultEnactmentMoment, fellowshipPropositionOrigin} from './util.js';
 import type {ICounselors} from './util.js';
 
modifiedjs-packages/tests/sub/governance/democracy.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/governance/democracy.test.ts
+++ b/js-packages/tests/sub/governance/democracy.test.ts
@@ -1,7 +1,7 @@
 import type {IKeyringPair} from '@polkadot/types/types';
-import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '../../util/index.js';
+import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '@unique/test-utils/util.js';
 import {clearFellowship, democracyLaunchPeriod, democracyTrackMinRank, dummyProposalCall, fellowshipConfirmPeriod, fellowshipMinEnactPeriod, fellowshipPreparePeriod, fellowshipPropositionOrigin, initFellowship, voteUnanimouslyInFellowship} from './util.js';
-import {Event} from '@unique/playgrounds/unique.dev.js';
+import {Event} from '@unique/test-utils';
 
 describeGov('Governance: Democracy tests', () => {
   let regularUser: IKeyringPair;
modifiedjs-packages/tests/sub/governance/electsudo.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/governance/electsudo.test.ts
+++ b/js-packages/tests/sub/governance/electsudo.test.ts
@@ -1,6 +1,6 @@
 import type {IKeyringPair} from '@polkadot/types/types';
-import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '../../util/index.js';
-import {Event} from '@unique/playgrounds/unique.dev.js';
+import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '@unique/test-utils/util.js';
+import {Event} from '@unique/test-utils';
 import {initCouncil, democracyLaunchPeriod, democracyVotingPeriod, democracyEnactmentPeriod, clearCouncil, clearTechComm, initTechComm, ITechComms} from './util.js';
 import type {ICounselors} from './util.js';
 
modifiedjs-packages/tests/sub/governance/fellowship.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/governance/fellowship.test.ts
+++ b/js-packages/tests/sub/governance/fellowship.test.ts
@@ -1,6 +1,6 @@
 import type {IKeyringPair} from '@polkadot/types/types';
-import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '../../util/index.js';
-import {DevUniqueHelper, Event} from '@unique/playgrounds/unique.dev.js';
+import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '@unique/test-utils/util.js';
+import {DevUniqueHelper, Event} from '@unique/test-utils';
 import {initCouncil, democracyLaunchPeriod, democracyVotingPeriod, democracyFastTrackVotingPeriod, fellowshipRankLimit, clearCouncil, clearTechComm, clearFellowship, defaultEnactmentMoment, dummyProposal, dummyProposalCall, fellowshipPropositionOrigin, initFellowship, initTechComm, voteUnanimouslyInFellowship, democracyTrackMinRank, fellowshipPreparePeriod, fellowshipConfirmPeriod, fellowshipMinEnactPeriod, democracyTrackId, hardResetFellowshipReferenda, hardResetDemocracy, hardResetGovScheduler} from './util.js';
 import type {ICounselors, ITechComms} from './util.js';
 
modifiedjs-packages/tests/sub/governance/init.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/governance/init.test.ts
+++ b/js-packages/tests/sub/governance/init.test.ts
@@ -1,6 +1,6 @@
 import type {IKeyringPair} from '@polkadot/types/types';
-import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '../../util/index.js';
-import {Event} from '@unique/playgrounds/unique.dev.js';
+import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '@unique/test-utils/util.js';
+import {Event} from '@unique/test-utils';
 import {democracyLaunchPeriod, democracyVotingPeriod, democracyEnactmentPeriod, clearCouncil, clearTechComm, clearFellowship} from './util.js';
 import type {ICounselors, ITechComms} from './util.js';
 
modifiedjs-packages/tests/sub/governance/technicalCommittee.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/governance/technicalCommittee.test.ts
+++ b/js-packages/tests/sub/governance/technicalCommittee.test.ts
@@ -1,6 +1,6 @@
 import type {IKeyringPair} from '@polkadot/types/types';
-import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '../../util/index.js';
-import {Event} from '@unique/playgrounds/unique.dev.js';
+import {usingPlaygrounds, itSub, expect, Pallets, requirePalletsOrSkip, describeGov} from '@unique/test-utils/util.js';
+import {Event} from '@unique/test-utils';
 import {initCouncil, democracyLaunchPeriod, democracyFastTrackVotingPeriod, clearCouncil, clearTechComm, clearFellowship, defaultEnactmentMoment, dummyProposal, dummyProposalCall, fellowshipPropositionOrigin, initFellowship, initTechComm, hardResetFellowshipReferenda, hardResetDemocracy, hardResetGovScheduler} from './util.js';
 import type {ITechComms} from './util.js';
 
modifiedjs-packages/tests/sub/governance/util.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/governance/util.ts
+++ b/js-packages/tests/sub/governance/util.ts
@@ -1,9 +1,9 @@
 import type {IKeyringPair} from '@polkadot/types/types';
 import {xxhashAsHex} from '@polkadot/util-crypto';
 import type {u32} from '@polkadot/types-codec';
-import {usingPlaygrounds, expect} from '../../util/index.js';
-import {UniqueHelper} from '@unique/playgrounds/unique.js';
-import {DevUniqueHelper} from '@unique/playgrounds/unique.dev.js';
+import {usingPlaygrounds, expect} from '@unique/test-utils/util.js';
+import {UniqueHelper} from '@unique-nft/playgrounds/unique.js';
+import {DevUniqueHelper} from '@unique/test-utils';
 
 export const democracyLaunchPeriod = 35;
 export const democracyVotingPeriod = 35;
modifiedjs-packages/tests/sub/nesting/admin.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/nesting/admin.test.ts
+++ b/js-packages/tests/sub/nesting/admin.test.ts
@@ -15,8 +15,8 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {expect, itSub, usingPlaygrounds} from '../../util/index.js';
-import {CrossAccountId} from '@unique/playgrounds/unique.js';
+import {expect, itSub, usingPlaygrounds} from '@unique/test-utils/util.js';
+import {CrossAccountId} from '@unique-nft/playgrounds/unique.js';
 
 describe('Nesting by collection admin', () => {
   let alice: IKeyringPair;
modifiedjs-packages/tests/sub/nesting/collectionProperties.seqtest.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/nesting/collectionProperties.seqtest.ts
+++ b/js-packages/tests/sub/nesting/collectionProperties.seqtest.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {itSub, Pallets, usingPlaygrounds, expect, requirePalletsOrSkip, sizeOfProperty} from '../../util/index.js';
+import {itSub, Pallets, usingPlaygrounds, expect, requirePalletsOrSkip, sizeOfProperty} from '@unique/test-utils/util.js';
 
 describe('Integration Test: Collection Properties with sudo', () => {
   let superuser: IKeyringPair;
modifiedjs-packages/tests/sub/nesting/collectionProperties.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/nesting/collectionProperties.test.ts
+++ b/js-packages/tests/sub/nesting/collectionProperties.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {itSub, Pallets, usingPlaygrounds, expect, requirePalletsOrSkip, sizeOfProperty} from '../../util/index.js';
+import {itSub, Pallets, usingPlaygrounds, expect, requirePalletsOrSkip, sizeOfProperty} from '@unique/test-utils/util.js';
 
 describe('Integration Test: Collection Properties', () => {
   let alice: IKeyringPair;
modifiedjs-packages/tests/sub/nesting/common.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/nesting/common.test.ts
+++ b/js-packages/tests/sub/nesting/common.test.ts
@@ -15,8 +15,8 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {expect, itSub, Pallets, usingPlaygrounds} from '../../util/index.js';
-import {CrossAccountId, UniqueNFTCollection, UniqueRFTCollection} from '@unique/playgrounds/unique.js';
+import {expect, itSub, Pallets, usingPlaygrounds} from '@unique/test-utils/util.js';
+import {CrossAccountId, UniqueNFTCollection, UniqueRFTCollection} from '@unique-nft/playgrounds/unique.js';
 
 let alice: IKeyringPair;
 let bob: IKeyringPair;
modifiedjs-packages/tests/sub/nesting/e2e.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/nesting/e2e.test.ts
+++ b/js-packages/tests/sub/nesting/e2e.test.ts
@@ -15,8 +15,8 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {expect, itSub, usingPlaygrounds} from '../../util/index.js';
-import {CrossAccountId} from '@unique/playgrounds/unique.js';
+import {expect, itSub, usingPlaygrounds} from '@unique/test-utils/util.js';
+import {CrossAccountId} from '@unique-nft/playgrounds/unique.js';
 
 describe('Composite nesting tests', () => {
   let alice: IKeyringPair;
modifiedjs-packages/tests/sub/nesting/graphs.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/nesting/graphs.test.ts
+++ b/js-packages/tests/sub/nesting/graphs.test.ts
@@ -15,8 +15,8 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {expect, itSub, usingPlaygrounds} from '../../util/index.js';
-import {UniqueHelper, UniqueNFTCollection, UniqueNFToken} from '@unique/playgrounds/unique.js';
+import {expect, itSub, usingPlaygrounds} from '@unique/test-utils/util.js';
+import {UniqueHelper, UniqueNFTCollection, UniqueNFToken} from '@unique-nft/playgrounds/unique.js';
 
 /**
  * ```dot
modifiedjs-packages/tests/sub/nesting/nesting.negative.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/nesting/nesting.negative.test.ts
+++ b/js-packages/tests/sub/nesting/nesting.negative.test.ts
@@ -15,8 +15,8 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {expect, itSub, Pallets, usingPlaygrounds} from '../../util/index.js';
-import {UniqueFTCollection, UniqueNFTCollection, UniqueNFToken, UniqueRFTCollection, UniqueRFToken} from '@unique/playgrounds/unique.js';
+import {expect, itSub, Pallets, usingPlaygrounds} from '@unique/test-utils/util.js';
+import {UniqueFTCollection, UniqueNFTCollection, UniqueNFToken, UniqueRFTCollection, UniqueRFToken} from '@unique-nft/playgrounds/unique.js';
 import {itEth} from '../../eth/util/index.js';
 
 let alice: IKeyringPair;
modifiedjs-packages/tests/sub/nesting/propertyPermissions.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/nesting/propertyPermissions.test.ts
+++ b/js-packages/tests/sub/nesting/propertyPermissions.test.ts
@@ -15,8 +15,8 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {itSub, Pallets, usingPlaygrounds, expect} from '../../util/index.js';
-import {UniqueNFTCollection, UniqueRFTCollection} from '@unique/playgrounds/unique.js';
+import {itSub, Pallets, usingPlaygrounds, expect} from '@unique/test-utils/util.js';
+import {UniqueNFTCollection, UniqueRFTCollection} from '@unique-nft/playgrounds/unique.js';
 
 describe('Integration Test: Access Rights to Token Properties', () => {
   let alice: IKeyringPair;
modifiedjs-packages/tests/sub/nesting/refungible.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/nesting/refungible.test.ts
+++ b/js-packages/tests/sub/nesting/refungible.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {expect, itSub, Pallets, usingPlaygrounds} from '../../util/index.js';
+import {expect, itSub, Pallets, usingPlaygrounds} from '@unique/test-utils/util.js';
 
 describe('ReFungible-specific nesting tests', () => {
   let alice: IKeyringPair;
modifiedjs-packages/tests/sub/nesting/tokenProperties.seqtest.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/nesting/tokenProperties.seqtest.ts
+++ b/js-packages/tests/sub/nesting/tokenProperties.seqtest.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {itSub, Pallets, usingPlaygrounds, expect, requirePalletsOrSkip, sizeOfProperty} from '../../util/index.js';
+import {itSub, Pallets, usingPlaygrounds, expect, requirePalletsOrSkip, sizeOfProperty} from '@unique/test-utils/util.js';
 
 describe('Integration Test: Token Properties with sudo', () => {
   let superuser: IKeyringPair;
modifiedjs-packages/tests/sub/nesting/tokenProperties.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/nesting/tokenProperties.test.ts
+++ b/js-packages/tests/sub/nesting/tokenProperties.test.ts
@@ -15,8 +15,8 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect, sizeOfProperty} from '../../util/index.js';
-import {UniqueHelper, UniqueNFToken, UniqueRFToken} from '@unique/playgrounds/unique.js';
+import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect, sizeOfProperty} from '@unique/test-utils/util.js';
+import {UniqueHelper, UniqueNFToken, UniqueRFToken} from '@unique-nft/playgrounds/unique.js';
 
 describe('Integration Test: Token Properties', () => {
   let alice: IKeyringPair; // collection owner
modifiedjs-packages/tests/sub/nesting/unnest.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/nesting/unnest.test.ts
+++ b/js-packages/tests/sub/nesting/unnest.test.ts
@@ -15,8 +15,8 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {expect, itSub, Pallets, usingPlaygrounds} from '../../util/index.js';
-import {CrossAccountId, UniqueFTCollection, UniqueNFToken, UniqueRFToken} from '@unique/playgrounds/unique.js';
+import {expect, itSub, Pallets, usingPlaygrounds} from '@unique/test-utils/util.js';
+import {CrossAccountId, UniqueFTCollection, UniqueNFToken, UniqueRFToken} from '@unique-nft/playgrounds/unique.js';
 
 describe('Integration Test: Unnesting', () => {
   let alice: IKeyringPair;
modifiedjs-packages/tests/sub/nesting/unnesting.negative.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/nesting/unnesting.negative.test.ts
+++ b/js-packages/tests/sub/nesting/unnesting.negative.test.ts
@@ -15,8 +15,8 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {expect, itSub, usingPlaygrounds} from '../../util/index.js';
-import {CrossAccountId} from '@unique/playgrounds/unique.js';
+import {expect, itSub, usingPlaygrounds} from '@unique/test-utils/util.js';
+import {CrossAccountId} from '@unique-nft/playgrounds/unique.js';
 
 describe('Negative Test: Unnesting', () => {
   let alice: IKeyringPair;
modifiedjs-packages/tests/sub/refungible/burn.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/refungible/burn.test.ts
+++ b/js-packages/tests/sub/refungible/burn.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '../../util/index.js';
+import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '@unique/test-utils/util.js';
 
 describe('Refungible: burn', () => {
   let donor: IKeyringPair;
modifiedjs-packages/tests/sub/refungible/nesting.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/refungible/nesting.test.ts
+++ b/js-packages/tests/sub/refungible/nesting.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {expect, itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds} from '../../util/index.js';
+import {expect, itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds} from '@unique/test-utils/util.js';
 
 describe('Refungible nesting', () => {
   let alice: IKeyringPair;
modifiedjs-packages/tests/sub/refungible/repartition.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/refungible/repartition.test.ts
+++ b/js-packages/tests/sub/refungible/repartition.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '../../util/index.js';
+import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '@unique/test-utils/util.js';
 
 describe('integration test: Refungible functionality:', () => {
   let donor: IKeyringPair;
modifiedjs-packages/tests/sub/refungible/transfer.test.tsdiffbeforeafterboth
--- a/js-packages/tests/sub/refungible/transfer.test.ts
+++ b/js-packages/tests/sub/refungible/transfer.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '../../util/index.js';
+import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '@unique/test-utils/util.js';
 
 describe('Refungible transfer tests', () => {
   let donor: IKeyringPair;
modifiedjs-packages/tests/transfer.test.tsdiffbeforeafterboth
--- a/js-packages/tests/transfer.test.ts
+++ b/js-packages/tests/transfer.test.ts
@@ -16,8 +16,8 @@
 
 import type {IKeyringPair} from '@polkadot/types/types';
 import {itEth, usingEthPlaygrounds} from './eth/util/index.js';
-import {itSub, Pallets, usingPlaygrounds, expect} from './util/index.js';
-import {NON_EXISTENT_COLLECTION_ID} from '@unique/playgrounds/types.js';
+import {itSub, Pallets, usingPlaygrounds, expect} from '@unique/test-utils/util.js';
+import {NON_EXISTENT_COLLECTION_ID} from '@unique-nft/playgrounds/types.js';
 
 describe('Integration Test Transfer(recipient, collection_id, item_id, value)', () => {
   let donor: IKeyringPair;
modifiedjs-packages/tests/transferFrom.test.tsdiffbeforeafterboth
--- a/js-packages/tests/transferFrom.test.ts
+++ b/js-packages/tests/transferFrom.test.ts
@@ -15,8 +15,8 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {itSub, Pallets, usingPlaygrounds, expect} from './util/index.js';
-import {NON_EXISTENT_COLLECTION_ID} from '@unique/playgrounds/types.js';
+import {itSub, Pallets, usingPlaygrounds, expect} from '@unique/test-utils/util.js';
+import {NON_EXISTENT_COLLECTION_ID} from '@unique-nft/playgrounds/types.js';
 
 describe('Integration Test transferFrom(from, recipient, collection_id, item_id, value):', () => {
   let alice: IKeyringPair;
modifiedjs-packages/tests/tx-version-presence.test.tsdiffbeforeafterboth
--- a/js-packages/tests/tx-version-presence.test.ts
+++ b/js-packages/tests/tx-version-presence.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import {Metadata} from '@polkadot/types';
-import {itSub, usingPlaygrounds, expect} from './util/index.js';
+import {itSub, usingPlaygrounds, expect} from '@unique/test-utils/util.js';
 
 let metadata: Metadata;
 
deletedjs-packages/tests/util/authorizeEnactUpgrade.tsdiffbeforeafterboth
--- a/js-packages/tests/util/authorizeEnactUpgrade.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import {readFile} from 'fs/promises';
-import {u8aToHex} from '@polkadot/util';
-import {usingPlaygrounds} from './index.js';
-import {blake2AsHex} from '@polkadot/util-crypto';
-
-
-const codePath = process.argv[2];
-if(!codePath) throw new Error('missing code path argument');
-
-const code = await readFile(codePath);
-
-await usingPlaygrounds(async (helper, privateKey) => {
-  const alice = await privateKey('//Alice');
-  const hex = blake2AsHex(code);
-  await helper.getSudo().executeExtrinsic(alice, 'api.tx.parachainSystem.authorizeUpgrade', [hex, true]);
-  await helper.getSudo().executeExtrinsicUncheckedWeight(alice, 'api.tx.parachainSystem.enactAuthorizedUpgrade', [u8aToHex(code)]);
-});
-// We miss disconnect/unref somewhere.
-process.exit(0);
deletedjs-packages/tests/util/createHrmp.tsdiffbeforeafterboth
--- a/js-packages/tests/util/createHrmp.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import {usingPlaygrounds} from './index.js';
-import config from '../config.js';
-
-const profile = process.argv[2];
-if(!profile) throw new Error('missing profile/relay argument');
-
-await usingPlaygrounds(async (helper, privateKey) => {
-  const bidirOpen = async (a: number, b: number) => {
-    console.log(`Opening ${a} <=> ${b}`);
-    await helper.getSudo().executeExtrinsic(alice, 'api.tx.hrmp.forceOpenHrmpChannel', [a, b, 8, 512]);
-    await helper.getSudo().executeExtrinsic(alice, 'api.tx.hrmp.forceOpenHrmpChannel', [b, a, 8, 512]);
-  };
-  const alice = await privateKey('//Alice');
-  switch (profile) {
-    case 'opal':
-      await bidirOpen(1001, 1002);
-      break;
-    case 'quartz':
-      await bidirOpen(1001, 1002);
-      await bidirOpen(1001, 1003);
-      await bidirOpen(1001, 1004);
-      await bidirOpen(1001, 1005);
-      break;
-    case 'unique':
-      await bidirOpen(1001, 1002);
-      await bidirOpen(1001, 1003);
-      await bidirOpen(1001, 1004);
-      await bidirOpen(1001, 1005);
-      await bidirOpen(1001, 1006);
-      await bidirOpen(1001, 1007);
-      break;
-    default:
-      throw new Error(`unknown hrmp config profile: ${profile}`);
-  }
-}, config.relayUrl);
-// We miss disconnect/unref somewhere.
-process.exit(0);
deletedjs-packages/tests/util/frankensteinMigrate.tsdiffbeforeafterboth
--- a/js-packages/tests/util/frankensteinMigrate.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import {migration as locksToFreezesMigration} from '../migrations/942057-appPromotion/index.js';
-export interface Migration {
-  before: () => Promise<void>,
-  after: () => Promise<void>,
-}
-
-export const migrations: {[key: string]: Migration} = {
-  'v942057': locksToFreezesMigration,
-};
deletedjs-packages/tests/util/globalSetup.tsdiffbeforeafterboth
--- a/js-packages/tests/util/globalSetup.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// SPDX-License-Identifier: Apache-2.0
-
-import {
-  usingPlaygrounds, Pallets, DONOR_FUNDING, MINIMUM_DONOR_FUND, LOCKING_PERIOD, UNLOCKING_PERIOD, makeNames,
-} from './index.js';
-import * as path from 'path';
-import {promises as fs} from 'fs';
-
-const {dirname} = makeNames(import.meta.url);
-
-// This function should be called before running test suites.
-const globalSetup = async (): Promise<void> => {
-  await usingPlaygrounds(async (helper, privateKey) => {
-    try {
-      // 1. Wait node producing blocks
-      await helper.wait.newBlocks(1, 600_000);
-
-      // 2. Create donors for test files
-      await fundFilenamesWithRetries(3)
-        .then((result) => {
-          if(!result) throw Error('Some problems with fundFilenamesWithRetries');
-        });
-
-      // 3. Configure App Promotion
-      const missingPallets = helper.fetchMissingPalletNames([Pallets.AppPromotion]);
-      if(missingPallets.length === 0) {
-        const superuser = await privateKey('//Alice');
-        const palletAddress = helper.arrange.calculatePalletAddress('appstake');
-        const palletAdmin = await privateKey('//PromotionAdmin');
-        const api = helper.getApi();
-        await helper.signTransaction(superuser, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})));
-        const nominal = helper.balance.getOneTokenNominal();
-        await helper.balance.transferToSubstrate(superuser, palletAdmin.address, 10000n * nominal);
-        await helper.balance.transferToSubstrate(superuser, palletAddress, 10000n * nominal);
-        await helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [api.tx.configuration
-          .setAppPromotionConfigurationOverride({
-            recalculationInterval: LOCKING_PERIOD,
-            pendingInterval: UNLOCKING_PERIOD})], true);
-      }
-    } catch (error) {
-      console.error(error);
-      throw Error('Error during globalSetup');
-    }
-  });
-};
-
-async function getFiles(rootPath: string): Promise<string[]> {
-  const files = await fs.readdir(rootPath, {withFileTypes: true});
-  const filenames: string[] = [];
-  for(const entry of files) {
-    const res = path.resolve(rootPath, entry.name);
-    if(entry.isDirectory()) {
-      filenames.push(...await getFiles(res));
-    } else {
-      filenames.push(res);
-    }
-  }
-  return filenames;
-}
-
-const fundFilenames = async () => {
-  await usingPlaygrounds(async (helper, privateKey) => {
-    const oneToken = helper.balance.getOneTokenNominal();
-    const alice = await privateKey('//Alice');
-    const nonce = await helper.chain.getNonce(alice.address);
-    const filenames = await getFiles(path.resolve(dirname, '..'));
-
-    // batching is actually undesireable, it takes away the time while all the transactions actually succeed
-    const batchSize = 300;
-    let balanceGrantedCounter = 0;
-    for(let b = 0; b < filenames.length; b += batchSize) {
-      const tx: Promise<boolean>[] = [];
-      let batchBalanceGrantedCounter = 0;
-      for(let i = 0; batchBalanceGrantedCounter < batchSize && b + i < filenames.length; i++) {
-        const f = filenames[b + i];
-        if(!f.endsWith('.test.ts') && !f.endsWith('seqtest.ts') || f.includes('.outdated')) continue;
-        const account = await privateKey({filename: f, ignoreFundsPresence: true});
-        const aliceBalance = await helper.balance.getSubstrate(account.address);
-
-        if(aliceBalance < MINIMUM_DONOR_FUND * oneToken) {
-          tx.push(helper.executeExtrinsic(
-            alice,
-            'api.tx.balances.transferKeepAlive',
-            [account.address, DONOR_FUNDING * oneToken],
-            true,
-            {nonce: nonce + balanceGrantedCounter++},
-          ).then(() => true).catch(() => {console.error(`Transaction to ${path.basename(f)} registered as failed. Strange.`); return false;}));
-          batchBalanceGrantedCounter++;
-        }
-      }
-
-      if(tx.length > 0) {
-        console.log(`Granting funds to ${batchBalanceGrantedCounter} filename accounts.`);
-        const result = await Promise.all(tx);
-        if(result && result.lastIndexOf(false) > -1) throw new Error('The transactions actually probably succeeded, should check the balances.');
-      }
-    }
-
-    if(balanceGrantedCounter == 0) console.log('No account needs additional funding.');
-  });
-};
-
-const fundFilenamesWithRetries = (retriesLeft: number): Promise<boolean> => {
-  if(retriesLeft <= 0) return Promise.resolve(false);
-  return fundFilenames()
-    .then(() => Promise.resolve(true))
-    .catch(e => {
-      console.error(e);
-      console.error(`Some transactions might have failed. ${retriesLeft > 1 ? 'Retrying...' : 'Something is wrong.'}\n`);
-      return fundFilenamesWithRetries(--retriesLeft);
-    });
-};
-
-globalSetup().catch(e => {
-  console.error('Setup error');
-  console.error(e);
-  process.exit(1);
-});
deletedjs-packages/tests/util/identitySetter.tsdiffbeforeafterboth
--- a/js-packages/tests/util/identitySetter.ts
+++ /dev/null
@@ -1,229 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// SPDX-License-Identifier: Apache-2.0
-//
-// Pulls identities and sub-identities from a chain and then makes a preimage to later force upload them into another.
-// Only changed or previously non-existent data are inserted.
-//
-// Usage: `yarn setIdentities [relay WS URL] [parachain WS URL] [user key]
-// Example: `yarn setIdentities wss://polkadot-rpc.dwellir.com ws://localhost:9944 escape pattern miracle train sudden cart adapt embark wedding alien lamp mesh`
-
-import {encodeAddress} from '@polkadot/keyring';
-import type {IKeyringPair} from '@polkadot/types/types';
-import {usingPlaygrounds, Pallets} from './index.js';
-import {ChainHelperBase} from '@unique/playgrounds/unique.js';
-
-const relayUrl = process.argv[2] ?? 'ws://localhost:9844';
-const paraUrl = process.argv[3] ?? 'ws://localhost:9944';
-const key = process.argv.length > 4 ? process.argv.slice(4).join(' ') : '//Alice';
-
-export function extractAccountId(key: any): string {
-  return (key as any).toHuman()[0];
-}
-
-export function extractIdentityInfo(value: any): object {
-  const heart = (value as any).unwrap();
-  const identity = heart.toJSON();
-  identity.judgements = heart.judgements.toHuman();
-  return identity;
-}
-
-export function extractIdentity(key: any, value: any): [string, any] {
-  return [extractAccountId(key), extractIdentityInfo(value)];
-}
-
-export async function getIdentities(helper: ChainHelperBase, noneCasePredicate?: (key: any, value: any) => void) {
-  const identities: [string, any][] = [];
-  for(const [key, v] of await helper.getApi().query.identity.identityOf.entries()) {
-    const value = v as any;
-    if(value.isNone) {
-      if(noneCasePredicate) noneCasePredicate(key, value);
-      continue;
-    }
-    identities.push(extractIdentity(key, value));
-  }
-  return identities;
-}
-
-// whether the existing chain data is more important than the coming
-function isCurrentChainDataPriority(helper: ChainHelperBase, currentChainId: number | undefined, relayChainId: number) {
-  if(!currentChainId) return false;
-  // information has come from local chain, and is automatically superior
-  if(currentChainId == helper.chain.getChainProperties().ss58Format) return true;
-  // the lower the id, the more important it is (Polkadot has ss58 prefix = 0, Kusama has ss58 prefix = 2)
-  return currentChainId < relayChainId;
-}
-
-// construct an object with all data necessary for insertion from storage query results
-export function constructSubInfo(identityAccount: string, subQuery: any, supers: any[], ss58?: number): [string, any] {
-  const deposit = subQuery.toJSON()[0];
-  const subIdentities = subQuery.toHuman()[1];
-  subIdentities.map((sub: string) => supers.find((sup: any) => sup[0] === sub));
-
-  return [
-    encodeAddress(identityAccount, ss58), [
-      deposit,
-      subIdentities.map((sub: string): [string, object] | null => {
-        const sup = supers.find((sup: any) => sup[0] === sub);
-        if(!sup) {
-          console.error(`Error: Could not find info on super for \nsub-identity account\t${sub} of \nsuper account \t\t${identityAccount}, skipping.`);
-          return null;
-        }
-        return [encodeAddress(sub, ss58), sup[1].toJSON()[1]];
-      }).filter((x: any) => x),
-    ],
-  ];
-}
-
-export async function getSubs(helper: ChainHelperBase) {
-  return (await helper.getApi().query.identity.subsOf.entries()).map(([key, value]) => [extractAccountId(key), value as any]);
-}
-
-export async function getSupers(helper: ChainHelperBase) {
-  return (await helper.getApi().query.identity.superOf.entries()).map(([key, value]) => [extractAccountId(key), value as any]);
-}
-
-async function uploadPreimage(helper: ChainHelperBase, preimageMaker: IKeyringPair, preimage: string) {
-  try {
-    await helper.executeExtrinsic(preimageMaker, 'api.tx.preimage.notePreimage', [preimage]);
-  } catch (e: any) {
-    if(e.message.includes('AlreadyNoted')) {
-      console.warn('Warning: The same preimage already exists on the chain. Nothing was uploaded.');
-    } else {
-      console.error(e);
-    }
-  }
-}
-
-// The utility for pulling identity and sub-identity data
-const forceInsertIdentities = async (): Promise<void> => {
-  let relaySS58Prefix = 0;
-  const identitiesOnRelay: any[] = [];
-  const subsOnRelay: any[] = [];
-  const identitiesToRemove: string[] = [];
-  await usingPlaygrounds(async helper => {
-    try {
-      relaySS58Prefix = helper.chain.getChainProperties().ss58Format;
-      // iterate over every identity
-      for(const [key, value] of await getIdentities(helper, (key, _value) => identitiesToRemove.push((key as any).toHuman()[0]))) {
-        // if any of the judgements resulted in a good confirmed outcome, keep this identity
-        let knownGood = false, reasonable = false;
-        for(const [_id, judgement] of value.judgements) {
-          if(judgement == 'KnownGood') knownGood = true;
-          if(judgement == 'Reasonable') reasonable = true;
-        }
-        if(!(reasonable || knownGood)) continue;
-        // replace the registrator id with the relay chain's ss58 format
-        value.judgements = [[helper.chain.getChainProperties().ss58Format, knownGood ? 'KnownGood' : 'Reasonable']];
-        identitiesOnRelay.push([key, value]);
-      }
-
-      const sublessIdentities = [...identitiesOnRelay];
-      const supersOfSubs = await getSupers(helper);
-
-      // iterate over every sub-identity
-      for(const [key, value] of await getSubs(helper)) {
-        // only get subs of the identities interesting to us
-        const identityIndex = sublessIdentities.findIndex((x: any) => x[0] == key);
-        if(identityIndex == -1) continue;
-        sublessIdentities.splice(identityIndex, 1);
-        subsOnRelay.push(constructSubInfo(key, value, supersOfSubs));
-      }
-
-      // mark the rest of sub-identities for deletion with empty arrays
-      /*for(const [account, _identity] of sublessIdentities) {
-        subsOnRelay.push([account, ['0', []]]);
-      }*/
-    } catch (error) {
-      console.error(error);
-      throw Error('Error during fetching identities');
-    }
-  }, relayUrl);
-
-  await usingPlaygrounds(async (helper, privateKey) => {
-    if(helper.fetchMissingPalletNames([Pallets.Identity]).length != 0) console.error('pallet-identity is not included in parachain.');
-    if(helper.fetchMissingPalletNames([Pallets.Preimage]).length != 0) console.error('pallet-preimage is not included in parachain.');
-
-    try {
-      const preimageMaker = await privateKey(key);
-      const ss58Format = helper.chain.getChainProperties().ss58Format;
-      const paraIdentities = await getIdentities(helper);
-      const identitiesToAdd: any[] = [];
-      const paraAccountsRegistrators: {[name: string]: number} = {};
-
-      // cross-reference every account for changes
-      for(const [key, value] of identitiesOnRelay) {
-        const encodedKey = encodeAddress(key, ss58Format);
-
-        // only update if the identity info does not exist or is changed
-        const identity = paraIdentities.find(i => i[0] === encodedKey);
-        if(identity) {
-          const registratorId = identity[1].judgements[0][0];
-          paraAccountsRegistrators[encodedKey] = registratorId;
-          if(isCurrentChainDataPriority(helper, registratorId, value.judgements[0][0])
-            || JSON.stringify(value.info) === JSON.stringify(identity[1].info)) {
-            continue;
-          }
-        }
-
-        identitiesToAdd.push([key, value]);
-        // exercise caution - in case we have an identity and the realy doesn't, it might mean one of two things:
-        // 1) it was deleted on the relay;
-        // 2) it is our own identity, we don't want to delete it.
-        // identitiesToRemove.push((key as any).toHuman()[0]);
-      }
-
-      if(identitiesToRemove.length != 0)
-        await uploadPreimage(
-          helper,
-          preimageMaker,
-          helper.constructApiCall('api.tx.identity.forceRemoveIdentities', [identitiesToRemove]).method.toHex(),
-        );
-      if(identitiesToAdd.length != 0)
-        await uploadPreimage(
-          helper,
-          preimageMaker,
-          helper.constructApiCall('api.tx.identity.forceInsertIdentities', [identitiesToAdd]).method.toHex(),
-        );
-
-      console.log(`Tried to push ${identitiesToAdd.length} identities`
-        + ` and found ${identitiesToRemove.length} identities for potential removal.`
-        + ` Currently there are ${(await helper.getApi().query.identity.identityOf.keys()).length} identities on the chain.`);
-
-      // fill sub-identities
-      const paraSubs = await getSubs(helper);
-      const supersOfSubs = await getSupers(helper);
-      const subsToUpdate: any[] = [];
-
-      for(const [key, value] of subsOnRelay) {
-        const encodedKey = encodeAddress(key, ss58Format);
-        const sub = paraSubs.find(i => i[0] === encodedKey);
-        if(sub) {
-          // only update if the sub-identity info does not exist or is changed
-          if(isCurrentChainDataPriority(helper, paraAccountsRegistrators[encodedKey], relaySS58Prefix)
-            || JSON.stringify(value) === JSON.stringify(constructSubInfo(sub[0], sub[1], supersOfSubs)[1])) {
-            continue;
-          }
-        } else if(value[1].length == 0)
-          continue;
-
-        subsToUpdate.push([key, value]);
-      }
-
-      if(subsToUpdate.length != 0)
-        await uploadPreimage(
-          helper,
-          preimageMaker,
-          helper.constructApiCall('api.tx.identity.forceSetSubs', [subsToUpdate]).method.toHex(),
-        );
-
-      console.log(`Also tried to push ${subsToUpdate.length} identities with their sub-identities.`
-        + ` Currently there are ${(await helper.getApi().query.identity.subsOf.keys()).length} identities with subs.`);
-    } catch (error) {
-      console.error(error);
-      throw Error('Error during setting identities');
-    }
-  }, paraUrl);
-};
-
-if(process.argv[1] === module.filename)
-  forceInsertIdentities().catch(() => process.exit(1));
deletedjs-packages/tests/util/index.tsdiffbeforeafterboth
--- a/js-packages/tests/util/index.ts
+++ /dev/null
@@ -1,223 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// SPDX-License-Identifier: Apache-2.0
-
-import * as path from 'path';
-import * as crypto from 'crypto';
-import type {IKeyringPair} from '@polkadot/types/types/interfaces';
-import chai from 'chai';
-import chaiAsPromised from 'chai-as-promised';
-import chaiSubset from 'chai-subset';
-import {Context} from 'mocha';
-import config from '../config.js';
-import {ChainHelperBase} from '@unique/playgrounds/unique.js';
-import type {ILogger} from '@unique/playgrounds/types.js';
-import {DevUniqueHelper, SilentLogger, SilentConsole, DevMoonbeamHelper, DevMoonriverHelper, DevAcalaHelper, DevKaruraHelper, DevRelayHelper, DevWestmintHelper, DevStatemineHelper, DevStatemintHelper, DevAstarHelper, DevShidenHelper, DevPolkadexHelper, DevHydraDxHelper} from '@unique/playgrounds/unique.dev.js';
-import {dirname} from 'path';
-import {fileURLToPath} from 'url';
-
-chai.config.truncateThreshold = 0;
-chai.use(chaiAsPromised);
-chai.use(chaiSubset);
-export const expect = chai.expect;
-
-const getTestHash = (filename: string) => crypto.createHash('md5').update(filename).digest('hex');
-
-export const getTestSeed = (filename: string) => `//Alice+${getTestHash(filename)}`;
-
-async function usingPlaygroundsGeneral<T extends ChainHelperBase, R = void>(
-  helperType: new (logger: ILogger) => T,
-  url: string,
-  code: (helper: T, privateKey: (seed: string | { filename?: string, url?: string, ignoreFundsPresence?: boolean }) => Promise<IKeyringPair>) => Promise<R>,
-): Promise<R> {
-  const silentConsole = new SilentConsole();
-  silentConsole.enable();
-
-  const helper = new helperType(new SilentLogger());
-  let result;
-  try {
-    await helper.connect(url);
-    const ss58Format = helper.chain.getChainProperties().ss58Format;
-    const privateKey = async (seed: string | {filename?: string, url?: string, ignoreFundsPresence?: boolean}) => {
-      if(typeof seed === 'string') {
-        return helper.util.fromSeed(seed, ss58Format);
-      }
-      if(seed.url) {
-        const {filename} = makeNames(seed.url);
-        seed.filename = filename;
-      } else if(seed.filename) {
-        // Pass
-      } else {
-        throw new Error('no url nor filename set');
-      }
-      const actualSeed = getTestSeed(seed.filename);
-      let account = helper.util.fromSeed(actualSeed, ss58Format);
-      // here's to hoping that no
-      if(!seed.ignoreFundsPresence && ((helper as any)['balance'] == undefined || await (helper as any).balance.getSubstrate(account.address) < MINIMUM_DONOR_FUND)) {
-        console.warn(`${path.basename(seed.filename)}: Not enough funds present on the filename account. Using the default one as the donor instead.`);
-        account = helper.util.fromSeed('//Alice', ss58Format);
-      }
-      return account;
-    };
-    result = await code(helper, privateKey);
-  }
-  finally {
-    await helper.disconnect();
-    silentConsole.disable();
-  }
-  return result as any as R;
-}
-
-export const usingPlaygrounds = <R = void>(code: (helper: DevUniqueHelper, privateKey: (seed: string | {filename?: string, url?: string, ignoreFundsPresence?: boolean}) => Promise<IKeyringPair>) => Promise<R>, url: string = config.substrateUrl) => usingPlaygroundsGeneral<DevUniqueHelper, R>(DevUniqueHelper, url, code);
-
-export const usingWestmintPlaygrounds = (url: string, code: (helper: DevWestmintHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevWestmintHelper>(DevWestmintHelper, url, code);
-
-export const usingStateminePlaygrounds = (url: string, code: (helper: DevWestmintHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevStatemineHelper>(DevWestmintHelper, url, code);
-
-export const usingStatemintPlaygrounds = (url: string, code: (helper: DevWestmintHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevStatemintHelper>(DevWestmintHelper, url, code);
-
-export const usingRelayPlaygrounds = (url: string, code: (helper: DevRelayHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevRelayHelper>(DevRelayHelper, url, code);
-
-export const usingAcalaPlaygrounds = (url: string, code: (helper: DevAcalaHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevAcalaHelper>(DevAcalaHelper, url, code);
-
-export const usingKaruraPlaygrounds = (url: string, code: (helper: DevKaruraHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevKaruraHelper>(DevAcalaHelper, url, code);
-
-export const usingMoonbeamPlaygrounds = (url: string, code: (helper: DevMoonbeamHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevMoonbeamHelper>(DevMoonbeamHelper, url, code);
-
-export const usingMoonriverPlaygrounds = (url: string, code: (helper: DevMoonbeamHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevMoonriverHelper>(DevMoonriverHelper, url, code);
-
-export const usingAstarPlaygrounds = (url: string, code: (helper: DevAstarHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevAstarHelper>(DevAstarHelper, url, code);
-
-export const usingShidenPlaygrounds = (url: string, code: (helper: DevShidenHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevShidenHelper>(DevShidenHelper, url, code);
-
-export const usingPolkadexPlaygrounds = (url: string, code: (helper: DevPolkadexHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevPolkadexHelper>(DevPolkadexHelper, url, code);
-
-export const usingHydraDxPlaygrounds = (url: string, code: (helper: DevHydraDxHelper, privateKey: (seed: string) => Promise<IKeyringPair>) => Promise<void>) => usingPlaygroundsGeneral<DevHydraDxHelper>(DevHydraDxHelper, url, code);
-
-export const MINIMUM_DONOR_FUND = 4_000_000n;
-export const DONOR_FUNDING = 4_000_000n;
-
-// App-promotion periods:
-export const LOCKING_PERIOD = 12n; // 12 blocks of relay
-export const UNLOCKING_PERIOD = 6n; // 6 blocks of parachain
-
-// Native contracts
-export const COLLECTION_HELPER = '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f';
-export const CONTRACT_HELPER = '0x842899ECF380553E8a4de75bF534cdf6fBF64049';
-
-export enum Pallets {
-  Inflation = 'inflation',
-  ReFungible = 'refungible',
-  Fungible = 'fungible',
-  NFT = 'nonfungible',
-  Scheduler = 'scheduler',
-  UniqueScheduler = 'uniqueScheduler',
-  AppPromotion = 'apppromotion',
-  CollatorSelection = 'collatorselection',
-  Session = 'session',
-  Identity = 'identity',
-  Democracy = 'democracy',
-  Council = 'council',
-  //CouncilMembership = 'councilmembership',
-  TechnicalCommittee = 'technicalcommittee',
-  Fellowship = 'fellowshipcollective',
-  Preimage = 'preimage',
-  Maintenance = 'maintenance',
-  TestUtils = 'testutils',
-}
-
-export function requirePalletsOrSkip(test: Context, helper: DevUniqueHelper, requiredPallets: readonly string[]) {
-  const missingPallets = helper.fetchMissingPalletNames(requiredPallets);
-
-  if(missingPallets.length > 0) {
-    const skipMsg = `\tSkipping test '${test.test?.title}'.\n\tThe following pallets are missing:\n\t- ${missingPallets.join('\n\t- ')}`;
-    console.warn('\x1b[38:5:208m%s\x1b[0m', skipMsg);
-    test.skip();
-  }
-}
-
-export function itSub(name: string, cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: readonly string[] } = {}) {
-  (opts.only ? it.only :
-    opts.skip ? it.skip : it)(name, async function () {
-    await usingPlaygrounds(async (helper, privateKey) => {
-      if(opts.requiredPallets) {
-        requirePalletsOrSkip(this, helper, opts.requiredPallets);
-      }
-
-      await cb({helper, privateKey});
-    });
-  });
-}
-export function itSubIfWithPallet(name: string, required: readonly string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: readonly string[] } = {}) {
-  return itSub(name, cb, {requiredPallets: required, ...opts});
-}
-itSub.only = (name: string, cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any) => itSub(name, cb, {only: true});
-itSub.skip = (name: string, cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any) => itSub(name, cb, {skip: true});
-
-itSubIfWithPallet.only = (name: string, required: readonly string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any) => itSubIfWithPallet(name, required, cb, {only: true});
-itSubIfWithPallet.skip = (name: string, required: readonly string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any) => itSubIfWithPallet(name, required, cb, {skip: true});
-itSub.ifWithPallets = itSubIfWithPallet;
-
-export type SchedKind = 'anon' | 'named';
-
-export function itSched(
-  name: string,
-  cb: (schedKind: SchedKind, apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any,
-  opts: { only?: boolean, skip?: boolean, requiredPallets?: string[] } = {},
-) {
-  itSub(name + ' (anonymous scheduling)', (apis) => cb('anon', apis), opts);
-  itSub(name + ' (named scheduling)', (apis) => cb('named', apis), opts);
-}
-itSched.only = (name: string, cb: (schedKind: SchedKind, apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any) => itSched(name, cb, {only: true});
-itSched.skip = (name: string, cb: (schedKind: SchedKind, apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any) => itSched(name, cb, {skip: true});
-itSched.ifWithPallets = itSchedIfWithPallets;
-
-function itSchedIfWithPallets(name: string, required: string[], cb: (schedKind: SchedKind, apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise<IKeyringPair> }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: string[] } = {}) {
-  return itSched(name, cb, {requiredPallets: required, ...opts});
-}
-
-export function describeXCM(title: string, fn: (this: Mocha.Suite) => void, opts: {skip?: boolean} = {}) {
-  (process.env.RUN_XCM_TESTS && !opts.skip
-    ? describe
-    : describe.skip)(title, fn);
-}
-
-describeXCM.skip = (name: string, fn: (this: Mocha.Suite) => void) => describeXCM(name, fn, {skip: true});
-
-export function describeGov(title: string, fn: (this: Mocha.Suite) => void, opts: {skip?: boolean} = {}) {
-  (process.env.RUN_GOV_TESTS && !opts.skip
-    ? describe
-    : describe.skip)(title, fn);
-}
-
-describeGov.skip = (name: string, fn: (this: Mocha.Suite) => void) => describeGov(name, fn, {skip: true});
-
-export function sizeOfInt(i: number) {
-  if(i < 0 || i > 0xffffffff) throw new Error('out of range');
-  if(i < 0b11_1111) {
-    return 1;
-  } else if(i < 0b11_1111_1111_1111) {
-    return 2;
-  } else if(i < 0b11_1111_1111_1111_1111_1111_1111_1111) {
-    return 4;
-  } else {
-    return 5;
-  }
-}
-
-const UTF8_ENCODER = new TextEncoder();
-export function sizeOfEncodedStr(v: string) {
-  const encoded = UTF8_ENCODER.encode(v);
-  return sizeOfInt(encoded.length) + encoded.length;
-}
-
-export function sizeOfProperty(prop: {key: string, value: string}) {
-  return sizeOfEncodedStr(prop.key) + sizeOfEncodedStr(prop.value);
-}
-
-export function makeNames(url: string) {
-  const filename = fileURLToPath(url);
-  return {
-    filename,
-    dirname: dirname(filename),
-  };
-}
deletedjs-packages/tests/util/relayIdentitiesChecker.tsdiffbeforeafterboth
--- a/js-packages/tests/util/relayIdentitiesChecker.ts
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// SPDX-License-Identifier: Apache-2.0
-//
-// Checks and reports the differences between identities and sub-identities on two chains.
-//
-// Usage: `yarn checkRelayIdentities [relay-1 WS URL] [relay-2 WS URL]`
-// Example: `yarn checkRelayIdentities wss://polkadot-rpc.dwellir.com wss://kusama-rpc.dwellir.com`
-
-import {encodeAddress} from '@polkadot/keyring';
-import {usingPlaygrounds} from './index.js';
-import {getIdentities, getSubs, getSupers, constructSubInfo} from './identitySetter.js';
-
-const relay1Url = process.argv[2] ?? 'ws://localhost:9844';
-const relay2Url = process.argv[3] ?? 'ws://localhost:9844';
-
-async function pullIdentities(relayUrl: string): Promise<[any[], any[]]> {
-  const identities: any[] = [];
-  const subs: any[] = [];
-
-  await usingPlaygrounds(async helper => {
-    try {
-      // iterate over every identity
-      for(const [key, value] of await getIdentities(helper)) {
-        // if any of the judgements resulted in a good confirmed outcome, keep this identity
-        if(value.toHuman().judgements.filter((x: any) => x[1] == 'Reasonable' || x[1] == 'KnownGood').length == 0) continue;
-        identities.push([key, value]);
-      }
-
-      const supersOfSubs = await getSupers(helper);
-
-      // iterate over every sub-identity
-      for(const [key, value] of await getSubs(helper)) {
-        // only get subs of the identities interesting to us
-        if(identities.find((x: any) => x[0] == key) == -1) continue;
-        subs.push(constructSubInfo(key, value, supersOfSubs));
-      }
-    } catch (error) {
-      console.error(error);
-      throw Error(`Error during fetching identities on ${relayUrl}`);
-    }
-  }, relayUrl);
-
-  return [identities, subs];
-}
-
-// The utility for pulling identity and sub-identity data
-const checkRelayIdentities = async (): Promise<void> => {
-  const [identitiesOnRelay1, subIdentitiesOnRelay1] = await pullIdentities(relay1Url);
-  const [identitiesOnRelay2, subIdentitiesOnRelay2] = await pullIdentities(relay2Url);
-
-  console.log('identities counts:\t', identitiesOnRelay1.length, identitiesOnRelay2.length);
-  console.log('sub-identities counts:\t', subIdentitiesOnRelay1.length, subIdentitiesOnRelay2.length);
-  console.log();
-
-  try {
-    const matchingAddresses: string[] = [];
-    const inequalIdentities: {[name: string]: [any, any]} = {};
-
-    for(const [key1, value1] of identitiesOnRelay1) {
-      const address = encodeAddress(key1);
-      const identity2 = identitiesOnRelay2.find(([key2, _value2]) => address === encodeAddress(key2));
-      if(!identity2) continue;
-      matchingAddresses.push(address);
-
-      //const [[key2, value2]] = identitiesOnRelay2.splice(index2, 1);
-      const [_key2, value2] = identity2;
-      if(JSON.stringify(value1.info) === JSON.stringify(value2.info)) continue;
-      inequalIdentities[address] = [value1, value2];
-    }
-
-    /*for (const [v1, v2] of Object.values(inequalIdentities)) {
-      console.log(v1.toHuman().info);
-      console.log();
-      console.log(v2.toHuman().info);
-      await new Promise(resolve => setTimeout(resolve, 4000));
-    }*/
-
-    console.log(`Accounts with identities on both relays:\t${matchingAddresses.length}`);
-    console.log(`Sub-identities with conflicting information:\t${Object.entries(inequalIdentities).length}`);
-    console.log();
-
-    const inequalSubIdentities = [];
-    let matchesFound = 0;
-    for(const address of matchingAddresses) {
-      const sub1 = subIdentitiesOnRelay1.find(([key1, _value1]) => address === encodeAddress(key1));
-      if(!sub1) continue;
-      const sub2 = subIdentitiesOnRelay2.find(([key2, _value2]) => address === encodeAddress(key2));
-      if(!sub2) continue;
-
-      const [value1, value2] = [sub1[1], sub2[1]];
-      matchesFound++;
-
-      if(JSON.stringify(value1[1]) === JSON.stringify(value2[1])) {
-        continue;
-      }
-      inequalSubIdentities.push([value1, value2]);
-    }
-
-    /*for (const [v1, v2] of inequalSubIdentities) {
-      console.log(v1[1]);
-      console.log();
-      console.log(v2[1]);
-      await new Promise(resolve => setTimeout(resolve, 300));
-    }*/
-    console.log(`Accounts with sub-identities on both relays:\t${matchesFound}`);
-    console.log(`Of them, those with conflicting sub-identities:\t${inequalSubIdentities.length}`);
-  } catch (error) {
-    console.error(error);
-    throw Error('Error during comparison');
-  }
-};
-
-if(process.argv[1] === module.filename)
-  checkRelayIdentities().catch(() => process.exit(1));
deletedjs-packages/tests/util/setCode.tsdiffbeforeafterboth
--- a/js-packages/tests/util/setCode.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import {readFile} from 'fs/promises';
-import {u8aToHex} from '@polkadot/util';
-import {usingPlaygrounds} from './index.js';
-
-const codePath = process.argv[2];
-if(!codePath) throw new Error('missing code path argument');
-
-const code = await readFile(codePath);
-
-await usingPlaygrounds(async (helper, privateKey) => {
-  const alice = await privateKey('//Alice');
-  await helper.getSudo().executeExtrinsicUncheckedWeight(alice, 'api.tx.system.setCode', [u8aToHex(code)]);
-});
-// We miss disconnect/unref somewhere.
-process.exit(0);
modifiedjs-packages/tests/vesting.test.tsdiffbeforeafterboth
--- a/js-packages/tests/vesting.test.ts
+++ b/js-packages/tests/vesting.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {itSub, usingPlaygrounds, expect} from './util/index.js';
+import {itSub, usingPlaygrounds, expect} from '@unique/test-utils/util.js';
 
 describe('Vesting', () => {
   let donor: IKeyringPair;
modifiedjs-packages/tests/xcm/lowLevelXcmQuartz.test.tsdiffbeforeafterboth
--- a/js-packages/tests/xcm/lowLevelXcmQuartz.test.ts
+++ b/js-packages/tests/xcm/lowLevelXcmQuartz.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {itSub, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds,  usingMoonriverPlaygrounds, usingShidenPlaygrounds, usingRelayPlaygrounds} from '../util/index.js';
+import {itSub, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds,  usingMoonriverPlaygrounds, usingShidenPlaygrounds} from '@unique/test-utils/util.js';
 import {QUARTZ_CHAIN,  QTZ_DECIMALS,  SHIDEN_DECIMALS, karuraUrl, moonriverUrl,  shidenUrl,  SAFE_XCM_VERSION, XcmTestHelper, TRANSFER_AMOUNT, SENDER_BUDGET, relayUrl} from './xcm.types.js';
 import {hexToString} from '@polkadot/util';
 
modifiedjs-packages/tests/xcm/lowLevelXcmUnique.test.tsdiffbeforeafterboth
--- a/js-packages/tests/xcm/lowLevelXcmUnique.test.ts
+++ b/js-packages/tests/xcm/lowLevelXcmUnique.test.ts
@@ -16,7 +16,7 @@
 
 import type {IKeyringPair} from '@polkadot/types/types';
 import config from '../config.js';
-import {itSub, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingHydraDxPlaygrounds} from '../util/index.js';
+import {itSub, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingHydraDxPlaygrounds} from '@unique/test-utils/util.js';
 import {nToBigInt} from '@polkadot/util';
 import {hexToString} from '@polkadot/util';
 import {ASTAR_DECIMALS, SAFE_XCM_VERSION, SENDER_BUDGET, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper, acalaUrl, astarUrl,  hydraDxUrl,  moonbeamUrl, polkadexUrl, uniqueAssetId} from './xcm.types.js';
modifiedjs-packages/tests/xcm/xcm.types.tsdiffbeforeafterboth
--- a/js-packages/tests/xcm/xcm.types.ts
+++ b/js-packages/tests/xcm/xcm.types.ts
@@ -1,7 +1,6 @@
 import type {IKeyringPair} from '@polkadot/types/types';
-import {hexToString} from '@polkadot/util';
-import {expect, usingAcalaPlaygrounds, usingAstarPlaygrounds, usingHydraDxPlaygrounds, usingKaruraPlaygrounds, usingMoonbeamPlaygrounds, usingMoonriverPlaygrounds, usingPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, usingShidenPlaygrounds} from '../util/index.js';
-import {DevUniqueHelper, Event} from '@unique/playgrounds/unique.dev.js';
+import {expect, usingAcalaPlaygrounds, usingAstarPlaygrounds, usingHydraDxPlaygrounds, usingKaruraPlaygrounds, usingMoonbeamPlaygrounds, usingMoonriverPlaygrounds, usingPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, usingShidenPlaygrounds} from '@unique/test-utils/util.js';
+import {DevUniqueHelper, Event} from '@unique/test-utils';
 import config from '../config.js';
 
 export const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);
modifiedjs-packages/tests/xcm/xcmOpal.test.tsdiffbeforeafterboth
--- a/js-packages/tests/xcm/xcmOpal.test.ts
+++ b/js-packages/tests/xcm/xcmOpal.test.ts
@@ -16,7 +16,7 @@
 
 import type {IKeyringPair} from '@polkadot/types/types';
 import config from '../config.js';
-import {itSub, expect, describeXCM, usingPlaygrounds, usingWestmintPlaygrounds, usingRelayPlaygrounds} from '../util/index.js';
+import {itSub, expect, describeXCM, usingPlaygrounds, usingWestmintPlaygrounds, usingRelayPlaygrounds} from '@unique/test-utils/util.js';
 import {XcmTestHelper} from './xcm.types.js';
 
 const STATEMINE_CHAIN = +(process.env.RELAY_WESTMINT_ID || 1000);
modifiedjs-packages/tests/xcm/xcmQuartz.test.tsdiffbeforeafterboth
--- a/js-packages/tests/xcm/xcmQuartz.test.ts
+++ b/js-packages/tests/xcm/xcmQuartz.test.ts
@@ -15,8 +15,8 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import type {IKeyringPair} from '@polkadot/types/types';
-import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, usingShidenPlaygrounds} from '../util/index.js';
-import {DevUniqueHelper, Event} from '@unique/playgrounds/unique.dev.js';
+import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, usingShidenPlaygrounds} from '@unique/test-utils/util.js';
+import {DevUniqueHelper, Event} from '@unique/test-utils';
 import {STATEMINE_CHAIN, QUARTZ_CHAIN, KARURA_CHAIN, MOONRIVER_CHAIN, SHIDEN_CHAIN, STATEMINE_DECIMALS, KARURA_DECIMALS, QTZ_DECIMALS, RELAY_DECIMALS, SHIDEN_DECIMALS, karuraUrl, moonriverUrl, relayUrl, shidenUrl, statemineUrl} from './xcm.types.js';
 import {hexToString} from '@polkadot/util';
 import {XcmTestHelper} from './xcm.types.js';
modifiedjs-packages/tests/xcm/xcmUnique.test.tsdiffbeforeafterboth
--- a/js-packages/tests/xcm/xcmUnique.test.ts
+++ b/js-packages/tests/xcm/xcmUnique.test.ts
@@ -16,8 +16,8 @@
 
 import type {IKeyringPair} from '@polkadot/types/types';
 import config from '../config.js';
-import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '../util/index.js';
-import {Event} from '@unique/playgrounds/unique.dev.js';
+import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '@unique/test-utils/util.js';
+import {Event} from '@unique/test-utils';
 import {hexToString, nToBigInt} from '@polkadot/util';
 import {ACALA_CHAIN, ASTAR_CHAIN, MOONBEAM_CHAIN, POLKADEX_CHAIN, SAFE_XCM_VERSION, STATEMINT_CHAIN, UNIQUE_CHAIN, expectFailedToTransact, expectUntrustedReserveLocationFail, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types.js';
 import {XcmTestHelper} from './xcm.types.js';
modifiedjs-packages/types/package.jsondiffbeforeafterboth
--- a/js-packages/types/package.json
+++ b/js-packages/types/package.json
@@ -5,7 +5,7 @@
     "engines": {
         "node": ">=16"
     },
-    "name": "@unique/opal-types",
+    "name": "@unique-nft/opal-testnet-types",
     "type": "module",
     "version": "1.0.0",
     "main": "index.js",
modifiedjs-packages/yarn.lockdiffbeforeafterboth
--- a/js-packages/yarn.lock
+++ b/js-packages/yarn.lock
@@ -1242,22 +1242,21 @@
   languageName: node
   linkType: hard
 
-"@unique/opal-types@workspace:*, @unique/opal-types@workspace:types":
+"@unique-nft/opal-testnet-types@workspace:*, @unique-nft/opal-testnet-types@workspace:types":
   version: 0.0.0-use.local
-  resolution: "@unique/opal-types@workspace:types"
+  resolution: "@unique-nft/opal-testnet-types@workspace:types"
   dependencies:
     "@polkadot/typegen": ^10.10.1
   languageName: unknown
   linkType: soft
 
-"@unique/playgrounds@workspace:*, @unique/playgrounds@workspace:playgrounds":
+"@unique-nft/playgrounds@workspace:*, @unique-nft/playgrounds@workspace:playgrounds":
   version: 0.0.0-use.local
-  resolution: "@unique/playgrounds@workspace:playgrounds"
+  resolution: "@unique-nft/playgrounds@workspace:playgrounds"
   dependencies:
     "@polkadot/api": 10.10.1
     "@polkadot/util": ^12.5.1
     "@polkadot/util-crypto": ^12.5.1
-    "@unique/opal-types": "workspace:*"
   languageName: unknown
   linkType: soft
 
@@ -1267,6 +1266,17 @@
   languageName: unknown
   linkType: soft
 
+"@unique/test-utils@workspace:*, @unique/test-utils@workspace:test-utils":
+  version: 0.0.0-use.local
+  resolution: "@unique/test-utils@workspace:test-utils"
+  dependencies:
+    "@polkadot/api": 10.10.1
+    "@polkadot/util": ^12.5.1
+    "@polkadot/util-crypto": ^12.5.1
+    "@unique-nft/opal-testnet-types": "workspace:*"
+  languageName: unknown
+  linkType: soft
+
 "@unique/tests@workspace:tests":
   version: 0.0.0-use.local
   resolution: "@unique/tests@workspace:tests"
@@ -6001,8 +6011,9 @@
     "@types/node": ^20.8.10
     "@typescript-eslint/eslint-plugin": ^6.10.0
     "@typescript-eslint/parser": ^6.10.0
-    "@unique/opal-types": "workspace:*"
-    "@unique/playgrounds": "workspace:*"
+    "@unique-nft/opal-testnet-types": "workspace:*"
+    "@unique-nft/playgrounds": "workspace:*"
+    "@unique/test-utils": "workspace:*"
     chai: ^4.3.10
     chai-as-promised: ^7.1.1
     chai-like: ^1.1.1