git.delta.rocks / unique-network / refs/commits / 6a580826c34a

difftreelog

Merge pull request #1024 from UniqueNetwork/refactor/foreign-assets-as-thin-proxy

Yaroslav Bolyukin2023-11-29parents: #bfab603 #d561386.patch.diff
in: master
Refactor/foreign assets as thin proxy

50 files changed

modified.envdiffbeforeafterboth
--- a/.env
+++ b/.env
@@ -16,7 +16,7 @@
 
 KUSAMA_MAINNET_BRANCH=release-v1.0.0
 STATEMINE_BUILD_BRANCH=release-parachains-v9430
-KARURA_BUILD_BRANCH=release-karura-2.21.0
+KARURA_BUILD_BRANCH=xnft-poc
 MOONRIVER_BUILD_BRANCH=runtime-2500
 SHIDEN_BUILD_BRANCH=v5.18.0
 QUARTZ_MAINNET_BRANCH=release-v10010063
modified.github/workflows/ci-develop.ymldiffbeforeafterboth
--- a/.github/workflows/ci-develop.yml
+++ b/.github/workflows/ci-develop.yml
@@ -35,6 +35,11 @@
     if: github.event.pull_request.draft == false && contains(github.event.pull_request.labels.*.name, 'CI-xcm')
     uses: ./.github/workflows/xcm.yml
     secrets: inherit
+
+  xnft:
+    if: github.event.pull_request.draft == false && contains(github.event.pull_request.labels.*.name, 'CI-xnft')
+    uses: ./.github/workflows/xnft.yml
+    secrets: inherit
     
   collator-selection:
     if: github.event.pull_request.draft == false && contains(github.event.pull_request.labels.*.name, 'CI-collator-selection')
modified.github/workflows/ci-master.ymldiffbeforeafterboth
--- a/.github/workflows/ci-master.yml
+++ b/.github/workflows/ci-master.yml
@@ -31,6 +31,10 @@
   xcm:
     uses: ./.github/workflows/xcm.yml
     secrets: inherit # pass all secrets from initial workflow to nested
+
+  xnft:
+    uses: ./.github/workflows/xnft.yml
+    secrets: inherit # pass all secrets from initial workflow to nested
     
   collator-selection:
     uses: ./.github/workflows/collator-selection.yml
added.github/workflows/xnft.ymldiffbeforeafterboth
--- /dev/null
+++ b/.github/workflows/xnft.yml
@@ -0,0 +1,215 @@
+name: xnft-testnet
+
+# Controls when the action will run.
+on:
+  workflow_call:
+
+  # Allows you to run this workflow manually from the Actions tab
+  workflow_dispatch:
+
+#Define Workflow variables
+env:
+  REPO_URL: ${{ github.server_url }}/${{ github.repository }}
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel
+jobs:
+  prepare-execution-marix:
+    name: Prepare execution matrix
+
+    runs-on: [self-hosted-ci]
+    outputs:
+      matrix: ${{ steps.create_matrix.outputs.matrix }}
+
+    steps:
+      - name: Clean Workspace
+        uses: AutoModality/action-clean@v1.1.0
+
+      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
+      - uses: actions/checkout@v3.1.0
+        with:
+          ref: ${{ github.head_ref }} #Checking out head commit
+
+      - name: Read .env file
+        uses: xom9ikk/dotenv@v2
+
+      - name: Create Execution matrix
+        uses: CertainLach/create-matrix-action@v4
+        id: create_matrix
+        with:
+          matrix: |
+            network {quartz}, relay_branch {${{ env.KUSAMA_MAINNET_BRANCH }}}, acala_version {${{ env.KARURA_BUILD_BRANCH }}}, runtest {all-quartz}, runtime_features {quartz-runtime}
+
+  xnft:
+    needs: prepare-execution-marix
+    # The type of runner that the job will run on
+    runs-on: [XL]
+
+    timeout-minutes: 600
+
+    name: ${{ matrix.network }}
+
+    continue-on-error: true #Do not stop testing of matrix runs failed.  As it decided during PR review - it required 50/50& Let's check it with false.
+
+    strategy:
+      matrix:
+        include: ${{fromJson(needs.prepare-execution-marix.outputs.matrix)}}
+
+    steps:
+      - name: Skip if pull request is in Draft
+        if: github.event.pull_request.draft == true
+        run: exit 1
+
+      - name: Clean Workspace
+        uses: AutoModality/action-clean@v1.1.0
+
+      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
+      - uses: actions/checkout@v3.1.0
+        with:
+          ref: ${{ github.head_ref }} #Checking out head commit
+
+      # Prepare SHA
+      - name: Prepare SHA
+        uses: ./.github/actions/prepare
+
+      - name: Read .env file
+        uses: xom9ikk/dotenv@v2
+
+      - name: Log in to Docker Hub
+        uses: docker/login-action@v2.1.0
+        with:
+          username: ${{ secrets.CORE_DOCKERHUB_USERNAME }}
+          password: ${{ secrets.CORE_DOCKERHUB_TOKEN }}
+
+      # Check POLKADOT version and build it if it doesn't exist in repository
+      - name: Generate ENV related extend Dockerfile file for POLKADOT
+        uses: cuchi/jinja2-action@v1.2.0
+        with:
+          template: .docker/Dockerfile-polkadot.j2
+          output_file: .docker/Dockerfile-polkadot.${{ matrix.relay_branch }}.yml
+          variables: |
+            RUST_TOOLCHAIN=${{ env.RUST_TOOLCHAIN }}
+            POLKADOT_BUILD_BRANCH=${{ matrix.relay_branch }}
+
+      - name: Check if the dockerhub repository contains the needed version POLKADOT
+        run: |
+          # aquire token
+            TOKEN=$(curl -s -H "Content-Type: application/json" -X POST -d '{"username": "'${{ secrets.CORE_DOCKERHUB_USERNAME }}'", "password": "'${{ secrets.CORE_DOCKERHUB_TOKEN }}'"}' https://hub.docker.com/v2/users/login/ | jq -r .token)
+            export TOKEN=$TOKEN
+
+          # Get TAGS from DOCKERHUB POLKADOT repository
+            POLKADOT_TAGS=$(curl -s -H "Authorization: JWT ${TOKEN}" https://hub.docker.com/v2/repositories/uniquenetwork/builder-polkadot/tags/?page_size=100 | jq -r '."results"[]["name"]')
+          # Show TAGS
+            echo "POLKADOT TAGS:"
+            echo $POLKADOT_TAGS
+          # Check correct version POLKADOT and build it if it doesn't exist in POLKADOT TAGS
+            if [[ ${POLKADOT_TAGS[*]} =~ (^|[[:space:]])"${{ matrix.relay_branch }}"($|[[:space:]]) ]]; then
+                echo "Repository has needed POLKADOT version";
+                docker pull uniquenetwork/builder-polkadot:${{ matrix.relay_branch }}
+            else
+                echo "Repository has not needed POLKADOT version, so build it";
+                cd .docker/ && docker build --file ./Dockerfile-polkadot.${{ matrix.relay_branch }}.yml --tag uniquenetwork/builder-polkadot:${{ matrix.relay_branch }} .
+                echo "Push needed POLKADOT version to the repository";
+                docker push uniquenetwork/builder-polkadot:${{ matrix.relay_branch }}
+            fi
+        shell: bash
+
+      # Check ACALA version and build it if it doesn't exist in repository
+      - name: Generate ENV related extend Dockerfile file for ACALA
+        uses: cuchi/jinja2-action@v1.2.0
+        with:
+          template: .docker/Dockerfile-acala.j2
+          output_file: .docker/Dockerfile-acala.${{ matrix.acala_version }}.yml
+          variables: |
+            RUST_TOOLCHAIN=${{ env.RUST_TOOLCHAIN }}
+            ACALA_BUILD_BRANCH=${{ matrix.acala_version }}
+
+      - name: Check if the dockerhub repository contains the needed ACALA version
+        run: |
+          # aquire token
+            TOKEN=$(curl -s -H "Content-Type: application/json" -X POST -d '{"username": "'${{ secrets.CORE_DOCKERHUB_USERNAME }}'", "password": "'${{ secrets.CORE_DOCKERHUB_TOKEN }}'"}' https://hub.docker.com/v2/users/login/ | jq -r .token)
+            export TOKEN=$TOKEN
+
+          # Get TAGS from DOCKERHUB repository
+            ACALA_TAGS=$(curl -s -H "Authorization: JWT ${TOKEN}" https://hub.docker.com/v2/repositories/uniquenetwork/builder-acala/tags/?page_size=100 | jq -r '."results"[]["name"]')
+          # Show TAGS
+            echo "ACALA TAGS:"
+            echo $ACALA_TAGS
+          # Check correct version ACALA and build it if it doesn't exist in ACALA TAGS
+            if [[ ${ACALA_TAGS[*]} =~ (^|[[:space:]])"${{ matrix.acala_version }}"($|[[:space:]]) ]]; then
+                echo "Repository has needed ACALA version";
+                docker pull uniquenetwork/builder-acala:${{ matrix.acala_version }}
+            else
+                echo "Repository has not needed ACALA version, so build it";
+                cd .docker/ && docker build --file ./Dockerfile-acala.${{ matrix.acala_version }}.yml --tag uniquenetwork/builder-acala:${{ matrix.acala_version }} .
+                echo "Push needed ACALA version to the repository";
+                docker push uniquenetwork/builder-acala:${{ matrix.acala_version }}
+            fi
+        shell: bash
+
+      - name: Build unique-chain
+        run: |
+          docker build --file .docker/Dockerfile-unique \
+            --build-arg RUNTIME_FEATURES=${{ matrix.runtime_features }} \
+            --build-arg RUST_TOOLCHAIN=${{ env.RUST_TOOLCHAIN }} \
+            --tag uniquenetwork/ci-xnft-local:${{ matrix.network }}-${{ env.REF_SLUG }}-${{ env.BUILD_SHA }} \
+            .
+
+      - name: Push docker image version
+        run: docker push uniquenetwork/ci-xnft-local:${{ matrix.network }}-${{ env.REF_SLUG }}-${{ env.BUILD_SHA }}
+
+      - uses: actions/setup-node@v3.5.1
+        with:
+          node-version: 18
+
+      - name: Clone xnft-tests
+        run: git clone https://github.com/UniqueNetwork/xnft-tests.git
+
+      - name: Install baedeker
+        uses: UniqueNetwork/baedeker-action/setup@built
+
+      - name: Setup library
+        run: mkdir -p .baedeker/vendor/ && git clone https://github.com/UniqueNetwork/baedeker-library .baedeker/vendor/baedeker-library
+
+      - name: Start network
+        uses: UniqueNetwork/baedeker-action@built
+        id: bdk
+        with:
+          jpath: |
+            .baedeker/vendor
+          tla-str: |
+            relay_spec=rococo-local
+          inputs: |
+            xnft-tests/.baedeker/testnets.jsonnet
+            snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/polkadot':{dockerImage:'uniquenetwork/builder-polkadot:${{ matrix.relay_branch }}'}})
+            snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/quartz':{dockerImage:'uniquenetwork/ci-xnft-local:${{ matrix.network }}-${{ env.REF_SLUG }}-${{ env.BUILD_SHA }}'}})
+            snippet:(import 'baedeker-library/ops/rewrites.libsonnet').rewriteNodePaths({'bin/karura':{dockerImage:'uniquenetwork/builder-acala:${{ matrix.acala_version }}'}})
+
+      - name: Yarn install
+        working-directory: xnft-tests
+        run: |
+          yarn install
+          yarn add mochawesome
+
+      - name: Run XNFT Tests
+        working-directory: xnft-tests
+        run: |
+          NOW=$(date +%s) && yarn ${{ matrix.runtest }} --reporter mochawesome --reporter-options reportFilename=test-${NOW}
+
+      - name: XNFT Tests Report
+        uses: phoenix-actions/test-reporting@v10
+        id: test-report
+        if: success() || failure()
+        with:
+          name: XNFT Tests ${{ matrix.network }}
+          path: xnft-tests/mochawesome-report/test-*.json
+          reporter: mochawesome-json
+          fail-on-error: 'false'
+
+      - name: Clean Workspace
+        if: always()
+        uses: AutoModality/action-clean@v1.1.0
+
+      - name: Remove builder cache
+        if: always()
+        run: |
+          docker system prune -a -f
modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2567,15 +2567,15 @@
 
 [[package]]
 name = "data-encoding"
-version = "2.4.0"
+version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
+checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
 
 [[package]]
 name = "data-encoding-macro"
-version = "0.1.13"
+version = "0.1.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c904b33cc60130e1aeea4956ab803d08a3f4a0ca82d64ed757afac3891f2bb99"
+checksum = "20c01c06f5f429efdf2bae21eb67c28b3df3cf85b7dd2d8ef09c0838dac5d33e"
 dependencies = [
  "data-encoding",
  "data-encoding-macro-internal",
@@ -2583,9 +2583,9 @@
 
 [[package]]
 name = "data-encoding-macro-internal"
-version = "0.1.11"
+version = "0.1.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fdf3fce3ce863539ec1d7fd1b6dcc3c645663376b43ed376bbf887733e4f772"
+checksum = "0047d07f2c89b17dd631c80450d69841a6b5d7fb17278cbc43d7e4cfcf2576f3"
 dependencies = [
  "data-encoding",
  "syn 1.0.109",
@@ -4678,21 +4678,21 @@
 
 [[package]]
 name = "if-addrs"
-version = "0.7.0"
+version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cbc0fa01ffc752e9dbc72818cdb072cd028b86be5e09dd04c5a643704fe101a9"
+checksum = "cabb0019d51a643781ff15c9c8a3e5dedc365c47211270f4e8f82812fedd8f0a"
 dependencies = [
  "libc",
- "winapi",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
 name = "if-watch"
-version = "3.1.0"
+version = "3.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbb892e5777fe09e16f3d44de7802f4daa7267ecbe8c466f19d94e25bb0c303e"
+checksum = "d6b0422c86d7ce0e97169cc42e04ae643caf278874a7a3c87b8150a220dc7e1e"
 dependencies = [
- "async-io 1.13.0",
+ "async-io 2.2.0",
  "core-foundation",
  "fnv",
  "futures",
@@ -6557,7 +6557,6 @@
  "impl-trait-for-tuples",
  "log",
  "num_enum",
- "orml-tokens",
  "orml-traits",
  "orml-vesting",
  "orml-xcm-support",
@@ -6706,26 +6705,9 @@
 ]
 
 [[package]]
-name = "orml-tokens"
-version = "0.5.0-dev"
-source = "git+https://github.com/uniquenetwork/open-runtime-module-library?branch=unique-polkadot-v1.3.0#6f36a9e79b75421ac56b259fd47d03db6c3e1603"
-dependencies = [
- "frame-support",
- "frame-system",
- "log",
- "orml-traits",
- "parity-scale-codec",
- "scale-info",
- "serde",
- "sp-arithmetic",
- "sp-runtime",
- "sp-std",
-]
-
-[[package]]
 name = "orml-traits"
-version = "0.5.0-dev"
-source = "git+https://github.com/uniquenetwork/open-runtime-module-library?branch=unique-polkadot-v1.3.0#6f36a9e79b75421ac56b259fd47d03db6c3e1603"
+version = "0.6.1"
+source = "git+https://github.com/uniquenetwork/open-runtime-module-library?branch=unique-polkadot-v1.3.0#99604b000be6252597cfcb82eee82554f6a07a0e"
 dependencies = [
  "frame-support",
  "impl-trait-for-tuples",
@@ -6744,8 +6726,8 @@
 
 [[package]]
 name = "orml-utilities"
-version = "0.5.0-dev"
-source = "git+https://github.com/uniquenetwork/open-runtime-module-library?branch=unique-polkadot-v1.3.0#6f36a9e79b75421ac56b259fd47d03db6c3e1603"
+version = "0.6.1"
+source = "git+https://github.com/uniquenetwork/open-runtime-module-library?branch=unique-polkadot-v1.3.0#99604b000be6252597cfcb82eee82554f6a07a0e"
 dependencies = [
  "frame-support",
  "parity-scale-codec",
@@ -6759,8 +6741,8 @@
 
 [[package]]
 name = "orml-vesting"
-version = "0.5.0-dev"
-source = "git+https://github.com/uniquenetwork/open-runtime-module-library?branch=unique-polkadot-v1.3.0#6f36a9e79b75421ac56b259fd47d03db6c3e1603"
+version = "0.6.1"
+source = "git+https://github.com/uniquenetwork/open-runtime-module-library?branch=unique-polkadot-v1.3.0#99604b000be6252597cfcb82eee82554f6a07a0e"
 dependencies = [
  "frame-support",
  "frame-system",
@@ -6774,8 +6756,8 @@
 
 [[package]]
 name = "orml-xcm-support"
-version = "0.5.0-dev"
-source = "git+https://github.com/uniquenetwork/open-runtime-module-library?branch=unique-polkadot-v1.3.0#6f36a9e79b75421ac56b259fd47d03db6c3e1603"
+version = "0.6.1"
+source = "git+https://github.com/uniquenetwork/open-runtime-module-library?branch=unique-polkadot-v1.3.0#99604b000be6252597cfcb82eee82554f6a07a0e"
 dependencies = [
  "frame-support",
  "orml-traits",
@@ -6788,8 +6770,8 @@
 
 [[package]]
 name = "orml-xtokens"
-version = "0.5.0-dev"
-source = "git+https://github.com/uniquenetwork/open-runtime-module-library?branch=unique-polkadot-v1.3.0#6f36a9e79b75421ac56b259fd47d03db6c3e1603"
+version = "0.6.1"
+source = "git+https://github.com/uniquenetwork/open-runtime-module-library?branch=unique-polkadot-v1.3.0#99604b000be6252597cfcb82eee82554f6a07a0e"
 dependencies = [
  "cumulus-primitives-core",
  "frame-support",
@@ -7446,7 +7428,6 @@
  "frame-support",
  "frame-system",
  "log",
- "orml-tokens",
  "pallet-balances",
  "pallet-common",
  "pallet-fungible",
@@ -10150,7 +10131,6 @@
  "impl-trait-for-tuples",
  "log",
  "num_enum",
- "orml-tokens",
  "orml-traits",
  "orml-vesting",
  "orml-xcm-support",
@@ -13695,9 +13675,9 @@
 
 [[package]]
 name = "spinners"
-version = "4.1.0"
+version = "4.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08615eea740067d9899969bc2891c68a19c315cb1f66640af9a9ecb91b13bcab"
+checksum = "a0ef947f358b9c238923f764c72a4a9d42f2d637c46e059dbd319d6e7cfb4f82"
 dependencies = [
  "lazy_static",
  "maplit",
@@ -15028,7 +15008,6 @@
  "impl-trait-for-tuples",
  "log",
  "num_enum",
- "orml-tokens",
  "orml-traits",
  "orml-vesting",
  "orml-xcm-support",
modifiedCargo.tomldiffbeforeafterboth
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -199,7 +199,6 @@
 try-runtime-cli = "0.35.0"
 
 # ORML
-orml-tokens = { default-features = false, git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "unique-polkadot-v1.3.0" }
 orml-traits = { default-features = false, git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "unique-polkadot-v1.3.0" }
 orml-vesting = { default-features = false, git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "unique-polkadot-v1.3.0" }
 orml-xcm-support = { default-features = false, git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "unique-polkadot-v1.3.0" }
modifiedjs-packages/playgrounds/types.tsdiffbeforeafterboth
--- a/js-packages/playgrounds/types.ts
+++ b/js-packages/playgrounds/types.ts
@@ -160,7 +160,7 @@
   External = 1,
   /// Supports ERC721Metadata
   Erc721metadata = 64,
-  /// Tokens in foreign collections can be transferred, but not burnt
+  /// A collection of foreign assets
   Foreign = 128,
 }
 
modifiedjs-packages/playgrounds/types.xcm.tsdiffbeforeafterboth
--- a/js-packages/playgrounds/types.xcm.ts
+++ b/js-packages/playgrounds/types.xcm.ts
@@ -27,10 +27,3 @@
     conviction: number,
   },
 }
-
-export interface IForeignAssetMetadata {
-  name?: number | Uint8Array,
-  symbol?: string,
-  decimals?: number,
-  minimalBalance?: bigint,
-}
\ No newline at end of file
modifiedjs-packages/playgrounds/unique.xcm.tsdiffbeforeafterboth
--- a/js-packages/playgrounds/unique.xcm.ts
+++ b/js-packages/playgrounds/unique.xcm.ts
@@ -2,7 +2,7 @@
 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, IForeignAssetMetadata, MoonbeamAssetInfo} from './types.xcm.js';
+import type {AcalaAssetMetadata, DemocracyStandardAccountVote, MoonbeamAssetInfo} from './types.xcm.js';
 
 
 export class XcmChainHelper extends ChainHelperBase {
@@ -104,22 +104,17 @@
 }
 
 export class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {
-  async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) {
+  async register(signer: TSigner, assetId: any, name: string, tokenPrefix: string, mode: 'NFT' | { Fungible: number }) {
     await this.helper.executeExtrinsic(
       signer,
-      'api.tx.foreignAssets.registerForeignAsset',
-      [ownerAddress, location, metadata],
+      'api.tx.foreignAssets.forceRegisterForeignAsset',
+      [{V3: assetId}, this.helper.util.str2vec(name), tokenPrefix, mode],
       true,
     );
   }
 
-  async update(signer: TSigner, foreignAssetId: number, location: any, metadata: IForeignAssetMetadata) {
-    await this.helper.executeExtrinsic(
-      signer,
-      'api.tx.foreignAssets.updateForeignAsset',
-      [foreignAssetId, location, metadata],
-      true,
-    );
+  async foreignCollectionId(assetId: any) {
+    return (await this.helper.callRpc('api.query.foreignAssets.foreignAssetToCollection', [assetId])).toJSON();
   }
 }
 
@@ -256,6 +251,10 @@
     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])
modifiedjs-packages/tests/eth/api/CollectionHelpers.soldiffbeforeafterboth
--- a/js-packages/tests/eth/api/CollectionHelpers.sol
+++ b/js-packages/tests/eth/api/CollectionHelpers.sol
@@ -132,7 +132,7 @@
 type CollectionFlags is uint8;
 
 library CollectionFlagsLib {
-	/// Tokens in foreign collections can be transferred, but not burnt
+	/// A collection of foreign assets
 	CollectionFlags constant foreignField = CollectionFlags.wrap(128);
 	/// Supports ERC721Metadata
 	CollectionFlags constant erc721metadataField = CollectionFlags.wrap(64);
modifiedjs-packages/tests/pallet-presence.test.tsdiffbeforeafterboth
--- a/js-packages/tests/pallet-presence.test.ts
+++ b/js-packages/tests/pallet-presence.test.ts
@@ -47,7 +47,6 @@
   'nonfungible',
   'charging',
   'configuration',
-  'tokens',
   'xtokens',
   'maintenance',
 ];
modifiedjs-packages/tests/xcm/xcm.types.tsdiffbeforeafterboth
--- a/js-packages/tests/xcm/xcm.types.ts
+++ b/js-packages/tests/xcm/xcm.types.ts
@@ -505,4 +505,27 @@
       await expectFailedToTransact(helper, messageSent);
     });
   }
+
+  async registerRelayNativeTokenOnUnique(alice: IKeyringPair) {
+    return await usingPlaygrounds(async (helper) => {
+      const relayLocation = {
+        parents: 1,
+        interior: 'Here',
+      };
+      const relayAssetId = {Concrete: relayLocation};
+
+      const relayCollectionId = await helper.foreignAssets.foreignCollectionId(relayAssetId);
+      if(relayCollectionId == null) {
+        const name = 'Relay Tokens';
+        const tokenPrefix = 'xDOT';
+        const decimals = 10;
+        await helper.getSudo().foreignAssets.register(alice, relayAssetId, name, tokenPrefix, {Fungible: decimals});
+
+        return await helper.foreignAssets.foreignCollectionId(relayAssetId);
+      } else {
+        console.log('Relay foreign collection is already registered');
+        return relayCollectionId;
+      }
+    });
+  }
 }
modifiedjs-packages/tests/xcm/xcmOpal.test.tsdiffbeforeafterboth
--- a/js-packages/tests/xcm/xcmOpal.test.ts
+++ b/js-packages/tests/xcm/xcmOpal.test.ts
@@ -17,6 +17,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 {XcmTestHelper} from './xcm.types.js';
 
 const STATEMINE_CHAIN = +(process.env.RELAY_WESTMINT_ID || 1000);
 const UNIQUE_CHAIN = +(process.env.RELAY_OPAL_ID || 2095);
@@ -25,11 +26,11 @@
 const westmintUrl = config.westmintUrl;
 
 const STATEMINE_PALLET_INSTANCE = 50;
-const ASSET_ID = 100;
-const ASSET_METADATA_DECIMALS = 18;
-const ASSET_METADATA_NAME = 'USDT';
-const ASSET_METADATA_DESCRIPTION = 'USDT';
-const ASSET_METADATA_MINIMAL_BALANCE = 1n;
+const USDT_ASSET_ID = 100;
+const USDT_ASSET_METADATA_DECIMALS = 18;
+const USDT_ASSET_METADATA_NAME = 'USDT';
+const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';
+const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;
 
 const RELAY_DECIMALS = 12;
 const WESTMINT_DECIMALS = 12;
@@ -37,7 +38,9 @@
 const TRANSFER_AMOUNT = 1_000_000_000_000_000_000n;
 
 // 10,000.00 (ten thousands) USDT
-const ASSET_AMOUNT = 1_000_000_000_000_000_000_000n;
+const USDT_ASSET_AMOUNT = 1_000_000_000_000_000_000_000n;
+
+const testHelper = new XcmTestHelper('opal');
 
 describeXCM('[XCM] Integration test: Exchanging USDT with Westmint', () => {
   let alice: IKeyringPair;
@@ -57,57 +60,85 @@
   let balanceBobRelayTokenBefore: bigint;
   let balanceBobRelayTokenAfter: bigint;
 
+  let usdtCollectionId: number;
+  let relayCollectionId: number;
 
   before(async () => {
     await usingPlaygrounds(async (_helper, privateKey) => {
       alice = await privateKey('//Alice');
       bob = await privateKey('//Bob'); // funds donor
+
+      relayCollectionId = await testHelper.registerRelayNativeTokenOnUnique(alice);
     });
 
     await usingWestmintPlaygrounds(westmintUrl, async (helper) => {
-      // 350.00 (three hundred fifty) DOT
-      const fundingAmount = 3_500_000_000_000n;
+      const assetInfo = await helper.assets.assetInfo(USDT_ASSET_ID);
+      if(assetInfo == null) {
+        await helper.assets.create(
+          alice,
+          USDT_ASSET_ID,
+          alice.address,
+          USDT_ASSET_METADATA_MINIMAL_BALANCE,
+        );
+        await helper.assets.setMetadata(
+          alice,
+          USDT_ASSET_ID,
+          USDT_ASSET_METADATA_NAME,
+          USDT_ASSET_METADATA_DESCRIPTION,
+          USDT_ASSET_METADATA_DECIMALS,
+        );
+      } else {
+        console.log('The USDT asset is already registered on AssetHub');
+      }
+
+      await helper.assets.mint(
+        alice,
+        USDT_ASSET_ID,
+        alice.address,
+        USDT_ASSET_AMOUNT,
+      );
 
-      await helper.assets.create(alice, ASSET_ID, alice.address, ASSET_METADATA_MINIMAL_BALANCE);
-      await helper.assets.setMetadata(alice, ASSET_ID, ASSET_METADATA_NAME, ASSET_METADATA_DESCRIPTION, ASSET_METADATA_DECIMALS);
-      await helper.assets.mint(alice, ASSET_ID, alice.address, ASSET_AMOUNT);
+      const sovereignFundingAmount = 3_500_000_000n;
 
       // funding parachain sovereing account (Parachain: 2095)
       const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN);
-      await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, fundingAmount);
+      await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);
     });
-
 
     await usingPlaygrounds(async (helper) => {
       const location = {
-        V2: {
-          parents: 1,
-          interior: {X3: [
-            {
-              Parachain: STATEMINE_CHAIN,
-            },
-            {
-              PalletInstance: STATEMINE_PALLET_INSTANCE,
-            },
-            {
-              GeneralIndex: ASSET_ID,
-            },
-          ]},
-        },
+        parents: 1,
+        interior: {X3: [
+          {
+            Parachain: STATEMINE_CHAIN,
+          },
+          {
+            PalletInstance: STATEMINE_PALLET_INSTANCE,
+          },
+          {
+            GeneralIndex: USDT_ASSET_ID,
+          },
+        ]},
       };
+      const assetId = {Concrete: location};
 
-      const metadata =
-      {
-        name: ASSET_ID,
-        symbol: ASSET_METADATA_NAME,
-        decimals: ASSET_METADATA_DECIMALS,
-        minimalBalance: ASSET_METADATA_MINIMAL_BALANCE,
-      };
-      await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);
+      if(await helper.foreignAssets.foreignCollectionId(assetId) == null) {
+        const tokenPrefix = USDT_ASSET_METADATA_NAME;
+        await helper.getSudo().foreignAssets.register(
+          alice,
+          assetId,
+          USDT_ASSET_METADATA_NAME,
+          tokenPrefix,
+          {Fungible: USDT_ASSET_METADATA_DECIMALS},
+        );
+      } else {
+        console.log('Foreign collection is already registered on Opal');
+      }
+
       balanceOpalBefore = await helper.balance.getSubstrate(alice.address);
+      usdtCollectionId = await helper.foreignAssets.foreignCollectionId(assetId);
     });
 
-
     // Providing the relay currency to the unique sender account
     await usingRelayPlaygrounds(relayUrl, async (helper) => {
       const destination = {
@@ -189,7 +220,7 @@
                       PalletInstance: STATEMINE_PALLET_INSTANCE,
                     },
                     {
-                      GeneralIndex: ASSET_ID,
+                      GeneralIndex: USDT_ASSET_ID,
                     },
                   ]},
               },
@@ -216,19 +247,17 @@
       expect(balanceStmnBefore > balanceStmnAfter).to.be.true;
 
     });
-
 
     // ensure that asset has been delivered
     await helper.wait.newBlocks(3);
 
-    // expext collection id will be with id 1
-    const free = await helper.ft.getBalance(1, {Substrate: alice.address});
+    const free = await helper.ft.getBalance(usdtCollectionId, {Substrate: alice.address});
 
     balanceOpalAfter = await helper.balance.getSubstrate(alice.address);
 
     console.log(
       '[Westmint -> Opal] transaction fees on Opal: %s USDT',
-      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, ASSET_METADATA_DECIMALS),
+      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),
     );
     console.log(
       '[Westmint -> Opal] transaction fees on Opal: %s OPL',
@@ -261,16 +290,11 @@
 
     const currencies: [any, bigint][] = [
       [
-        {
-          ForeignAssetId: 0,
-        },
-        //10_000_000_000_000_000n,
+        usdtCollectionId,
         TRANSFER_AMOUNT,
       ],
       [
-        {
-          NativeAssetId: 'Parent',
-        },
+        relayCollectionId,
         400_000_000_000_000n,
       ],
     ];
@@ -288,7 +312,7 @@
 
       // The USDT token never paid fees. Its amount not changed from begin value.
       // Also check that xcm transfer has been succeeded
-      expect((await helper.assets.account(ASSET_ID, alice.address))! == ASSET_AMOUNT).to.be.true;
+      expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;
     });
   });
 
@@ -296,7 +320,7 @@
     const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;
 
     balanceBobBefore = await helper.balance.getSubstrate(bob.address);
-    balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});
+    balanceBobRelayTokenBefore = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});
 
     // Providing the relay currency to the unique sender account
     await usingRelayPlaygrounds(relayUrl, async (helper) => {
@@ -345,7 +369,7 @@
     await helper.wait.newBlocks(3);
 
     balanceBobAfter = await helper.balance.getSubstrate(bob.address);
-    balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});
+    balanceBobRelayTokenAfter = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});
 
     const wndFee = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;
     console.log(
@@ -383,9 +407,7 @@
 
     const currencies: any = [
       [
-        {
-          NativeAssetId: 'Parent',
-        },
+        relayCollectionId,
         50_000_000_000_000_000n,
       ],
     ];
modifiedjs-packages/tests/xcm/xcmQuartz.test.tsdiffbeforeafterboth
--- a/js-packages/tests/xcm/xcmQuartz.test.ts
+++ b/js-packages/tests/xcm/xcmQuartz.test.ts
@@ -19,9 +19,8 @@
 import {DevUniqueHelper, Event} from '@unique/playgrounds/unique.dev.js';
 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';
 
-
 const STATEMINE_PALLET_INSTANCE = 50;
 
 const TRANSFER_AMOUNT = 2000000000000000000000000n;
@@ -39,6 +38,8 @@
 
 const SAFE_XCM_VERSION = 2;
 
+const testHelper = new XcmTestHelper('quartz');
+
 describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {
   let alice: IKeyringPair;
   let bob: IKeyringPair;
@@ -57,6 +58,8 @@
   let balanceBobRelayTokenBefore: bigint;
   let balanceBobRelayTokenAfter: bigint;
 
+  let usdtCollectionId: number;
+  let relayCollectionId: number;
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
@@ -65,6 +68,8 @@
 
       // Set the default version to wrap the first message to other chains.
       await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
+
+      relayCollectionId = await testHelper.registerRelayNativeTokenOnUnique(alice);
     });
 
     await usingRelayPlaygrounds(relayUrl, async (helper) => {
@@ -74,21 +79,25 @@
     });
 
     await usingStateminePlaygrounds(statemineUrl, async (helper) => {
-      const sovereignFundingAmount = 3_500_000_000n;
+      const assetInfo = await helper.assets.assetInfo(USDT_ASSET_ID);
+      if(assetInfo == null) {
+        await helper.assets.create(
+          alice,
+          USDT_ASSET_ID,
+          alice.address,
+          USDT_ASSET_METADATA_MINIMAL_BALANCE,
+        );
+        await helper.assets.setMetadata(
+          alice,
+          USDT_ASSET_ID,
+          USDT_ASSET_METADATA_NAME,
+          USDT_ASSET_METADATA_DESCRIPTION,
+          USDT_ASSET_METADATA_DECIMALS,
+        );
+      } else {
+        console.log('The USDT asset is already registered on AssetHub');
+      }
 
-      await helper.assets.create(
-        alice,
-        USDT_ASSET_ID,
-        alice.address,
-        USDT_ASSET_METADATA_MINIMAL_BALANCE,
-      );
-      await helper.assets.setMetadata(
-        alice,
-        USDT_ASSET_ID,
-        USDT_ASSET_METADATA_NAME,
-        USDT_ASSET_METADATA_DESCRIPTION,
-        USDT_ASSET_METADATA_DECIMALS,
-      );
       await helper.assets.mint(
         alice,
         USDT_ASSET_ID,
@@ -96,6 +105,8 @@
         USDT_ASSET_AMOUNT,
       );
 
+      const sovereignFundingAmount = 3_500_000_000n;
+
       // funding parachain sovereing account on Statemine(t).
       // The sovereign account should be created before any action
       // (the assets pallet on Statemine(t) check if the sovereign account exists)
@@ -106,31 +117,30 @@
 
     await usingPlaygrounds(async (helper) => {
       const location = {
-        V2: {
-          parents: 1,
-          interior: {X3: [
-            {
-              Parachain: STATEMINE_CHAIN,
-            },
-            {
-              PalletInstance: STATEMINE_PALLET_INSTANCE,
-            },
-            {
-              GeneralIndex: USDT_ASSET_ID,
-            },
-          ]},
-        },
+        parents: 1,
+        interior: {X3: [
+          {
+            Parachain: STATEMINE_CHAIN,
+          },
+          {
+            PalletInstance: STATEMINE_PALLET_INSTANCE,
+          },
+          {
+            GeneralIndex: USDT_ASSET_ID,
+          },
+        ]},
       };
+      const assetId = {Concrete: location};
 
-      const metadata =
-      {
-        name: USDT_ASSET_ID,
-        symbol: USDT_ASSET_METADATA_NAME,
-        decimals: USDT_ASSET_METADATA_DECIMALS,
-        minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,
-      };
-      await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);
+      if(await helper.foreignAssets.foreignCollectionId(assetId) == null) {
+        const tokenPrefix = USDT_ASSET_METADATA_NAME;
+        await helper.getSudo().foreignAssets.register(alice, assetId, USDT_ASSET_METADATA_NAME, tokenPrefix, {Fungible: USDT_ASSET_METADATA_DECIMALS});
+      } else {
+        console.log('Foreign collection is already registered on Quartz');
+      }
+
       balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);
+      usdtCollectionId = await helper.foreignAssets.foreignCollectionId(assetId);
     });
 
 
@@ -248,8 +258,7 @@
     // ensure that asset has been delivered
     await helper.wait.newBlocks(3);
 
-    // expext collection id will be with id 1
-    const free = await helper.ft.getBalance(1, {Substrate: alice.address});
+    const free = await helper.ft.getBalance(usdtCollectionId, {Substrate: alice.address});
 
     balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);
 
@@ -288,15 +297,11 @@
     const relayFee = 400_000_000_000_000n;
     const currencies: [any, bigint][] = [
       [
-        {
-          ForeignAssetId: 0,
-        },
+        usdtCollectionId,
         TRANSFER_AMOUNT,
       ],
       [
-        {
-          NativeAssetId: 'Parent',
-        },
+        relayCollectionId,
         relayFee,
       ],
     ];
@@ -321,7 +326,7 @@
 
   itSub('Should connect and send Relay token to Quartz', async ({helper}) => {
     balanceBobBefore = await helper.balance.getSubstrate(bob.address);
-    balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});
+    balanceBobRelayTokenBefore = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});
 
     await usingRelayPlaygrounds(relayUrl, async (helper) => {
       const destination = {
@@ -369,7 +374,7 @@
     await helper.wait.newBlocks(3);
 
     balanceBobAfter = await helper.balance.getSubstrate(bob.address);
-    balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});
+    balanceBobRelayTokenAfter = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});
 
     const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;
     const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;
@@ -409,9 +414,7 @@
 
     const currencies: any = [
       [
-        {
-          NativeAssetId: 'Parent',
-        },
+        relayCollectionId,
         TRANSFER_AMOUNT_RELAY,
       ],
     ];
@@ -1018,9 +1021,7 @@
   });
 
   itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {
-    const currencyId = {
-      NativeAssetId: 'Here',
-    };
+    const currencyId = 0;
     const dest = {
       V2: {
         parents: 1,
modifiedjs-packages/tests/xcm/xcmUnique.test.tsdiffbeforeafterboth
after · js-packages/tests/xcm/xcmUnique.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import type {IKeyringPair} from '@polkadot/types/types';18import config from '../config.js';19import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '../util/index.js';20import {Event} from '@unique/playgrounds/unique.dev.js';21import {hexToString, nToBigInt} from '@polkadot/util';22import {ACALA_CHAIN, ASTAR_CHAIN, MOONBEAM_CHAIN, POLKADEX_CHAIN, SAFE_XCM_VERSION, STATEMINT_CHAIN, UNIQUE_CHAIN, expectFailedToTransact, expectUntrustedReserveLocationFail, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types.js';23import {XcmTestHelper} from './xcm.types.js';2425const STATEMINT_PALLET_INSTANCE = 50;2627const relayUrl = config.relayUrl;28const statemintUrl = config.statemintUrl;29const acalaUrl = config.acalaUrl;30const moonbeamUrl = config.moonbeamUrl;31const astarUrl = config.astarUrl;32const polkadexUrl = config.polkadexUrl;3334const RELAY_DECIMALS = 12;35const STATEMINT_DECIMALS = 12;36const ACALA_DECIMALS = 12;37const ASTAR_DECIMALS = 18n;38const UNQ_DECIMALS = 18n;3940const TRANSFER_AMOUNT = 2000000000000000000000000n;4142const FUNDING_AMOUNT = 3_500_000_0000_000_000n;4344const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;4546const USDT_ASSET_ID = 100;47const USDT_ASSET_METADATA_DECIMALS = 18;48const USDT_ASSET_METADATA_NAME = 'USDT';49const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';50const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;51const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5253const testHelper = new XcmTestHelper('unique');5455describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => {56  let alice: IKeyringPair;57  let bob: IKeyringPair;5859  let balanceStmnBefore: bigint;60  let balanceStmnAfter: bigint;6162  let balanceUniqueBefore: bigint;63  let balanceUniqueAfter: bigint;64  let balanceUniqueFinal: bigint;6566  let balanceBobBefore: bigint;67  let balanceBobAfter: bigint;68  let balanceBobFinal: bigint;6970  let balanceBobRelayTokenBefore: bigint;71  let balanceBobRelayTokenAfter: bigint;7273  let usdtCollectionId: number;74  let relayCollectionId: number;7576  before(async () => {77    await usingPlaygrounds(async (helper, privateKey) => {78      alice = await privateKey('//Alice');79      bob = await privateKey('//Bob'); // sovereign account on Statemint funds donor8081      // Set the default version to wrap the first message to other chains.82      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);8384      relayCollectionId = await testHelper.registerRelayNativeTokenOnUnique(alice);85    });8687    await usingRelayPlaygrounds(relayUrl, async (helper) => {88      // Fund accounts on Statemint89      await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, alice.addressRaw, FUNDING_AMOUNT);90      await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, bob.addressRaw, FUNDING_AMOUNT);91    });9293    await usingStatemintPlaygrounds(statemintUrl, async (helper) => {94      const assetInfo = await helper.assets.assetInfo(USDT_ASSET_ID);95      if(assetInfo == null) {96        await helper.assets.create(97          alice,98          USDT_ASSET_ID,99          alice.address,100          USDT_ASSET_METADATA_MINIMAL_BALANCE,101        );102        await helper.assets.setMetadata(103          alice,104          USDT_ASSET_ID,105          USDT_ASSET_METADATA_NAME,106          USDT_ASSET_METADATA_DESCRIPTION,107          USDT_ASSET_METADATA_DECIMALS,108        );109      } else {110        console.log('The USDT asset is already registered on AssetHub');111      }112113      await helper.assets.mint(114        alice,115        USDT_ASSET_ID,116        alice.address,117        USDT_ASSET_AMOUNT,118      );119120      const sovereignFundingAmount = 3_500_000_000n;121122      // funding parachain sovereing account on Statemint.123      // The sovereign account should be created before any action124      // (the assets pallet on Statemint check if the sovereign account exists)125      const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN);126      await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);127    });128129130    await usingPlaygrounds(async (helper) => {131      const location = {132        parents: 1,133        interior: {X3: [134          {135            Parachain: STATEMINT_CHAIN,136          },137          {138            PalletInstance: STATEMINT_PALLET_INSTANCE,139          },140          {141            GeneralIndex: USDT_ASSET_ID,142          },143        ]},144      };145      const assetId = {Concrete: location};146147      if(await helper.foreignAssets.foreignCollectionId(assetId) == null) {148        const tokenPrefix = USDT_ASSET_METADATA_NAME;149        await helper.getSudo().foreignAssets.register(alice, assetId, USDT_ASSET_METADATA_NAME, tokenPrefix, {Fungible: USDT_ASSET_METADATA_DECIMALS});150      } else {151        console.log('Foreign collection is already registered on Unique');152      }153154      balanceUniqueBefore = await helper.balance.getSubstrate(alice.address);155      usdtCollectionId = await helper.foreignAssets.foreignCollectionId(assetId);156    });157158159    // Providing the relay currency to the unique sender account160    // (fee for USDT XCM are paid in relay tokens)161    await usingRelayPlaygrounds(relayUrl, async (helper) => {162      const destination = {163        V2: {164          parents: 0,165          interior: {X1: {166            Parachain: UNIQUE_CHAIN,167          },168          },169        }};170171      const beneficiary = {172        V2: {173          parents: 0,174          interior: {X1: {175            AccountId32: {176              network: 'Any',177              id: alice.addressRaw,178            },179          }},180        },181      };182183      const assets = {184        V2: [185          {186            id: {187              Concrete: {188                parents: 0,189                interior: 'Here',190              },191            },192            fun: {193              Fungible: TRANSFER_AMOUNT_RELAY,194            },195          },196        ],197      };198199      const feeAssetItem = 0;200201      await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');202    });203204  });205206  itSub('Should connect and send USDT from Statemint to Unique', async ({helper}) => {207    await usingStatemintPlaygrounds(statemintUrl, async (helper) => {208      const dest = {209        V2: {210          parents: 1,211          interior: {X1: {212            Parachain: UNIQUE_CHAIN,213          },214          },215        }};216217      const beneficiary = {218        V2: {219          parents: 0,220          interior: {X1: {221            AccountId32: {222              network: 'Any',223              id: alice.addressRaw,224            },225          }},226        },227      };228229      const assets = {230        V2: [231          {232            id: {233              Concrete: {234                parents: 0,235                interior: {236                  X2: [237                    {238                      PalletInstance: STATEMINT_PALLET_INSTANCE,239                    },240                    {241                      GeneralIndex: USDT_ASSET_ID,242                    },243                  ]},244              },245            },246            fun: {247              Fungible: TRANSFER_AMOUNT,248            },249          },250        ],251      };252253      const feeAssetItem = 0;254255      balanceStmnBefore = await helper.balance.getSubstrate(alice.address);256      await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');257258      balanceStmnAfter = await helper.balance.getSubstrate(alice.address);259260      // common good parachain take commission in it native token261      console.log(262        '[Statemint -> Unique] transaction fees on Statemint: %s WND',263        helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINT_DECIMALS),264      );265      expect(balanceStmnBefore > balanceStmnAfter).to.be.true;266267    });268269270    // ensure that asset has been delivered271    await helper.wait.newBlocks(3);272273    const free = await helper.ft.getBalance(usdtCollectionId, {Substrate: alice.address});274275    balanceUniqueAfter = await helper.balance.getSubstrate(alice.address);276277    console.log(278      '[Statemint -> Unique] transaction fees on Unique: %s USDT',279      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),280    );281    console.log(282      '[Statemint -> Unique] transaction fees on Unique: %s UNQ',283      helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueBefore),284    );285    // commission has not paid in USDT token286    expect(free).to.be.equal(TRANSFER_AMOUNT);287    // ... and parachain native token288    expect(balanceUniqueAfter == balanceUniqueBefore).to.be.true;289  });290291  itSub('Should connect and send USDT from Unique to Statemint back', async ({helper}) => {292    const destination = {293      V2: {294        parents: 1,295        interior: {X2: [296          {297            Parachain: STATEMINT_CHAIN,298          },299          {300            AccountId32: {301              network: 'Any',302              id: alice.addressRaw,303            },304          },305        ]},306      },307    };308309    const relayFee = 400_000_000_000_000n;310    const currencies: [any, bigint][] = [311      [312        usdtCollectionId,313        TRANSFER_AMOUNT,314      ],315      [316        relayCollectionId,317        relayFee,318      ],319    ];320321    const feeItem = 1;322323    await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');324325    // the commission has been paid in parachain native token326    balanceUniqueFinal = await helper.balance.getSubstrate(alice.address);327    console.log('[Unique -> Statemint] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueFinal));328    expect(balanceUniqueAfter > balanceUniqueFinal).to.be.true;329330    await usingStatemintPlaygrounds(statemintUrl, async (helper) => {331      await helper.wait.newBlocks(3);332333      // The USDT token never paid fees. Its amount not changed from begin value.334      // Also check that xcm transfer has been succeeded335      expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;336    });337  });338339  itSub('Should connect and send Relay token to Unique', async ({helper}) => {340    balanceBobBefore = await helper.balance.getSubstrate(bob.address);341    balanceBobRelayTokenBefore = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});342343    await usingRelayPlaygrounds(relayUrl, async (helper) => {344      const destination = {345        V2: {346          parents: 0,347          interior: {X1: {348            Parachain: UNIQUE_CHAIN,349          },350          },351        }};352353      const beneficiary = {354        V2: {355          parents: 0,356          interior: {X1: {357            AccountId32: {358              network: 'Any',359              id: bob.addressRaw,360            },361          }},362        },363      };364365      const assets = {366        V2: [367          {368            id: {369              Concrete: {370                parents: 0,371                interior: 'Here',372              },373            },374            fun: {375              Fungible: TRANSFER_AMOUNT_RELAY,376            },377          },378        ],379      };380381      const feeAssetItem = 0;382383      await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');384    });385386    await helper.wait.newBlocks(3);387388    balanceBobAfter = await helper.balance.getSubstrate(bob.address);389    balanceBobRelayTokenAfter = await helper.ft.getBalance(relayCollectionId, {Substrate: bob.address});390391    const wndFeeOnUnique = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;392    const wndDiffOnUnique = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;393    console.log(394      '[Relay (Westend) -> Unique] transaction fees: %s UNQ',395      helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),396    );397    console.log(398      '[Relay (Westend) -> Unique] transaction fees: %s WND',399      helper.util.bigIntToDecimals(wndFeeOnUnique, STATEMINT_DECIMALS),400    );401    console.log('[Relay (Westend) -> Unique] actually delivered: %s WND', wndDiffOnUnique);402    expect(wndFeeOnUnique == 0n, 'No incoming WND fees should be taken').to.be.true;403    expect(balanceBobBefore == balanceBobAfter, 'No incoming UNQ fees should be taken').to.be.true;404  });405406  itSub('Should connect and send Relay token back', async ({helper}) => {407    let relayTokenBalanceBefore: bigint;408    let relayTokenBalanceAfter: bigint;409    await usingRelayPlaygrounds(relayUrl, async (helper) => {410      relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);411    });412413    const destination = {414      V2: {415        parents: 1,416        interior: {417          X1:{418            AccountId32: {419              network: 'Any',420              id: bob.addressRaw,421            },422          },423        },424      },425    };426427    const currencies: any = [428      [429        relayCollectionId,430        TRANSFER_AMOUNT_RELAY,431      ],432    ];433434    const feeItem = 0;435436    await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');437438    balanceBobFinal = await helper.balance.getSubstrate(bob.address);439    console.log('[Unique -> Relay (Westend)] transaction fees: %s UNQ',  helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));440441    await usingRelayPlaygrounds(relayUrl, async (helper) => {442      await helper.wait.newBlocks(10);443      relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);444445      const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;446      console.log('[Unique -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));447      expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;448    });449  });450});451452describeXCM('[XCM] Integration test: Exchanging tokens with Acala', () => {453  let alice: IKeyringPair;454  let randomAccount: IKeyringPair;455456  let balanceUniqueTokenInit: bigint;457  let balanceUniqueTokenMiddle: bigint;458  let balanceUniqueTokenFinal: bigint;459  let balanceAcalaTokenInit: bigint;460  let balanceAcalaTokenMiddle: bigint;461  let balanceAcalaTokenFinal: bigint;462  let balanceUniqueForeignTokenInit: bigint;463  let balanceUniqueForeignTokenMiddle: bigint;464  let balanceUniqueForeignTokenFinal: bigint;465466  // computed by a test transfer from prod Unique to prod Acala.467  // 2 UNQ sent https://unique.subscan.io/xcm_message/polkadot-bad0b68847e2398af25d482e9ee6f9c1f9ec2a48468  // 1.898970000000000000 UNQ received (you can check Acala's chain state in the corresponding block)469  const expectedAcalaIncomeFee = 2000000000000000000n - 1898970000000000000n;470  const acalaEps = 8n * 10n ** 16n;471472  let acalaBackwardTransferAmount: bigint;473474  before(async () => {475    await usingPlaygrounds(async (helper, privateKey) => {476      alice = await privateKey('//Alice');477      [randomAccount] = await helper.arrange.createAccounts([0n], alice);478479      // Set the default version to wrap the first message to other chains.480      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);481    });482483    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {484      const destination = {485        V2: {486          parents: 1,487          interior: {488            X1: {489              Parachain: UNIQUE_CHAIN,490            },491          },492        },493      };494495      const metadata = {496        name: 'Unique Network',497        symbol: 'UNQ',498        decimals: 18,499        minimalBalance: 1250000000000000000n,500      };501      const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v] : [any, any]) =>502        hexToString(v.toJSON()['symbol'])) as string[];503504      if(!assets.includes('UNQ')) {505        await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);506      } else {507        console.log('UNQ token already registered on Acala assetRegistry pallet');508      }509      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);510      balanceAcalaTokenInit = await helper.balance.getSubstrate(randomAccount.address);511      balanceUniqueForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});512    });513514    await usingPlaygrounds(async (helper) => {515      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);516      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);517    });518  });519520  itSub('Should connect and send UNQ to Acala', async ({helper}) => {521522    const destination = {523      V2: {524        parents: 1,525        interior: {526          X1: {527            Parachain: ACALA_CHAIN,528          },529        },530      },531    };532533    const beneficiary = {534      V2: {535        parents: 0,536        interior: {537          X1: {538            AccountId32: {539              network: 'Any',540              id: randomAccount.addressRaw,541            },542          },543        },544      },545    };546547    const assets = {548      V2: [549        {550          id: {551            Concrete: {552              parents: 0,553              interior: 'Here',554            },555          },556          fun: {557            Fungible: TRANSFER_AMOUNT,558          },559        },560      ],561    };562563    const feeAssetItem = 0;564565    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');566    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);567568    const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;569    console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));570    expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;571572    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {573      await helper.wait.newBlocks(3);574575      balanceUniqueForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});576      balanceAcalaTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);577578      const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;579      const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;580      acalaBackwardTransferAmount = unqIncomeTransfer;581582      const acaUnqFees = TRANSFER_AMOUNT - unqIncomeTransfer;583584      console.log(585        '[Unique -> Acala] transaction fees on Acala: %s ACA',586        helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),587      );588      console.log(589        '[Unique -> Acala] transaction fees on Acala: %s UNQ',590        helper.util.bigIntToDecimals(acaUnqFees),591      );592      console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));593      expect(acaFees == 0n).to.be.true;594595      const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;596597      expect(598        bigintAbs(acaUnqFees - expectedAcalaIncomeFee) < acalaEps,599        'Acala took different income fee, check the Acala foreign asset config',600      ).to.be.true;601    });602  });603604  itSub('Should connect to Acala and send UNQ back', async ({helper}) => {605    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {606      const destination = {607        V2: {608          parents: 1,609          interior: {610            X2: [611              {Parachain: UNIQUE_CHAIN},612              {613                AccountId32: {614                  network: 'Any',615                  id: randomAccount.addressRaw,616                },617              },618            ],619          },620        },621      };622623      const id = {624        ForeignAsset: 0,625      };626627      await helper.xTokens.transfer(randomAccount, id, acalaBackwardTransferAmount, destination, 'Unlimited');628      balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);629      balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);630631      const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;632      const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;633634      console.log(635        '[Acala -> Unique] transaction fees on Acala: %s ACA',636        helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),637      );638      console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));639640      expect(acaFees > 0, 'Negative fees ACA, looks like nothing was transferred').to.be.true;641      expect(unqOutcomeTransfer == acalaBackwardTransferAmount).to.be.true;642    });643644    await helper.wait.newBlocks(3);645646    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);647    const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;648    expect(actuallyDelivered > 0).to.be.true;649650    console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));651652    const unqFees = acalaBackwardTransferAmount - actuallyDelivered;653    console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));654    expect(unqFees == 0n).to.be.true;655  });656657  itSub('Acala can send only up to its balance', async ({helper}) => {658    // set Acala's sovereign account's balance659    const acalaBalance = 10000n * (10n ** UNQ_DECIMALS);660    const acalaSovereignAccount = helper.address.paraSiblingSovereignAccount(ACALA_CHAIN);661    await helper.getSudo().balance.setBalanceSubstrate(alice, acalaSovereignAccount, acalaBalance);662663    const moreThanAcalaHas = acalaBalance * 2n;664665    let targetAccountBalance = 0n;666    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);667668    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(669      targetAccount.addressRaw,670      {671        Concrete: {672          parents: 0,673          interior: 'Here',674        },675      },676      moreThanAcalaHas,677    );678679    let maliciousXcmProgramSent: any;680    const maxWaitBlocks = 3;681682    // Try to trick Unique683    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {684      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);685686      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);687    });688689    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash690        && event.outcome.isFailedToTransactAsset);691692    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);693    expect(targetAccountBalance).to.be.equal(0n);694695    // But Acala still can send the correct amount696    const validTransferAmount = acalaBalance / 2n;697    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(698      targetAccount.addressRaw,699      {700        Concrete: {701          parents: 0,702          interior: 'Here',703        },704      },705      validTransferAmount,706    );707708    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {709      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, validXcmProgram);710    });711712    await helper.wait.newBlocks(maxWaitBlocks);713714    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);715    expect(targetAccountBalance).to.be.equal(validTransferAmount);716  });717718  itSub('Should not accept reserve transfer of UNQ from Acala', async ({helper}) => {719    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);720    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);721722    const uniqueMultilocation = {723      V2: {724        parents: 1,725        interior: {726          X1: {727            Parachain: UNIQUE_CHAIN,728          },729        },730      },731    };732733    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(734      targetAccount.addressRaw,735      uniqueAssetId,736      testAmount,737    );738739    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(740      targetAccount.addressRaw,741      {742        Concrete: {743          parents: 0,744          interior: 'Here',745        },746      },747      testAmount,748    );749750    let maliciousXcmProgramFullIdSent: any;751    let maliciousXcmProgramHereIdSent: any;752    const maxWaitBlocks = 3;753754    // Try to trick Unique using full UNQ identification755    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {756      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);757758      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);759    });760761    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash762        && event.outcome.isUntrustedReserveLocation);763764    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);765    expect(accountBalance).to.be.equal(0n);766767    // Try to trick Unique using shortened UNQ identification768    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {769      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);770771      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);772    });773774    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash775        && event.outcome.isUntrustedReserveLocation);776777    accountBalance = await helper.balance.getSubstrate(targetAccount.address);778    expect(accountBalance).to.be.equal(0n);779  });780});781782describeXCM('[XCM] Integration test: Exchanging tokens with Polkadex', () => {783  let alice: IKeyringPair;784  let randomAccount: IKeyringPair;785  let unqFees: bigint;786  let balanceUniqueTokenInit: bigint;787  let balanceUniqueTokenMiddle: bigint;788  let balanceUniqueTokenFinal: bigint;789  const maxWaitBlocks = 6;790791  before(async () => {792    await usingPlaygrounds(async (helper, privateKey) => {793      alice = await privateKey('//Alice');794      [randomAccount] = await helper.arrange.createAccounts([0n], alice);795796      // Set the default version to wrap the first message to other chains.797      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);798    });799800    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {801      const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', []))802        .toJSON() as [])803        .map(nToBigInt).length != 0;804      /*805      Check whether the Unique token has been added806      to the whitelist, since an error will occur807      if it is added again. Needed for debugging808      when this test is run multiple times.809      */810      if(isWhitelisted) {811        console.log('UNQ token is already whitelisted on Polkadex');812      } else {813        await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);814      }815816      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);817    });818819    await usingPlaygrounds(async (helper) => {820      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);821      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);822    });823  });824825  itSub('Should connect and send UNQ to Polkadex', async ({helper}) => {826827    const destination = {828      V2: {829        parents: 1,830        interior: {831          X1: {832            Parachain: POLKADEX_CHAIN,833          },834        },835      },836    };837838    const beneficiary = {839      V2: {840        parents: 0,841        interior: {842          X1: {843            AccountId32: {844              network: 'Any',845              id: randomAccount.addressRaw,846            },847          },848        },849      },850    };851852    const assets = {853      V2: [854        {855          id: {856            Concrete: {857              parents: 0,858              interior: 'Here',859            },860          },861          fun: {862            Fungible: TRANSFER_AMOUNT,863          },864        },865      ],866    };867868    const feeAssetItem = 0;869870    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');871    const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);872    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);873874    unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;875    console.log('[Unique -> Polkadex] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));876    expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;877878    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {879      /*880        Since only the parachain part of the Polkadex881        infrastructure is launched (without their882        solochain validators), processing incoming883        assets will lead to an error.884        This error indicates that the Polkadex chain885        received a message from the Unique network,886        since the hash is being checked to ensure887        it matches what was sent.888      */889      await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);890    });891  });892893894  itSub('Should connect to Polkadex and send UNQ back', async ({helper}) => {895896    const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(897      randomAccount.addressRaw,898      uniqueAssetId,899      TRANSFER_AMOUNT,900    );901902    let xcmProgramSent: any;903904905    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {906      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, xcmProgram);907908      xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);909    });910911    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);912913    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);914915    expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees);916  });917918  itSub('Polkadex can send only up to its balance', async ({helper}) => {919    const polkadexBalance = 10000n * (10n ** UNQ_DECIMALS);920    const polkadexSovereignAccount = helper.address.paraSiblingSovereignAccount(POLKADEX_CHAIN);921    await helper.getSudo().balance.setBalanceSubstrate(alice, polkadexSovereignAccount, polkadexBalance);922    const moreThanPolkadexHas = 2n * polkadexBalance;923924    const targetAccount = helper.arrange.createEmptyAccount();925926    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(927      targetAccount.addressRaw,928      {929        Concrete: {930          parents: 0,931          interior: 'Here',932        },933      },934      moreThanPolkadexHas,935    );936937    let maliciousXcmProgramSent: any;938939940    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {941      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);942943      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);944    });945946    await expectFailedToTransact(helper, maliciousXcmProgramSent);947948    const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);949    expect(targetAccountBalance).to.be.equal(0n);950  });951952  itSub('Should not accept reserve transfer of UNQ from Polkadex', async ({helper}) => {953    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);954    const targetAccount = helper.arrange.createEmptyAccount();955956    const uniqueMultilocation = {957      V2: {958        parents: 1,959        interior: {960          X1: {961            Parachain: UNIQUE_CHAIN,962          },963        },964      },965    };966967    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(968      targetAccount.addressRaw,969      uniqueAssetId,970      testAmount,971    );972973    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(974      targetAccount.addressRaw,975      {976        Concrete: {977          parents: 0,978          interior: 'Here',979        },980      },981      testAmount,982    );983984    let maliciousXcmProgramFullIdSent: any;985    let maliciousXcmProgramHereIdSent: any;986    const maxWaitBlocks = 3;987988    // Try to trick Unique using full UNQ identification989    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {990      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);991992      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);993    });994995    await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);996997    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);998    expect(accountBalance).to.be.equal(0n);9991000    // Try to trick Unique using shortened UNQ identification1001    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1002      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);10031004      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1005    });10061007    await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);10081009    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1010    expect(accountBalance).to.be.equal(0n);1011  });1012});10131014// These tests are relevant only when1015// the the corresponding foreign assets are not registered1016describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => {1017  let alice: IKeyringPair;1018  let alith: IKeyringPair;10191020  const testAmount = 100_000_000_000n;1021  let uniqueParachainJunction;1022  let uniqueAccountJunction;10231024  let uniqueParachainMultilocation: any;1025  let uniqueAccountMultilocation: any;1026  let uniqueCombinedMultilocation: any;1027  let uniqueCombinedMultilocationAcala: any; // TODO remove when Acala goes V210281029  let messageSent: any;10301031  const maxWaitBlocks = 3;10321033  before(async () => {1034    await usingPlaygrounds(async (helper, privateKey) => {1035      alice = await privateKey('//Alice');10361037      uniqueParachainJunction = {Parachain: UNIQUE_CHAIN};1038      uniqueAccountJunction = {1039        AccountId32: {1040          network: 'Any',1041          id: alice.addressRaw,1042        },1043      };10441045      uniqueParachainMultilocation = {1046        V2: {1047          parents: 1,1048          interior: {1049            X1: uniqueParachainJunction,1050          },1051        },1052      };10531054      uniqueAccountMultilocation = {1055        V2: {1056          parents: 0,1057          interior: {1058            X1: uniqueAccountJunction,1059          },1060        },1061      };10621063      uniqueCombinedMultilocation = {1064        V2: {1065          parents: 1,1066          interior: {1067            X2: [uniqueParachainJunction, uniqueAccountJunction],1068          },1069        },1070      };10711072      uniqueCombinedMultilocationAcala = {1073        V2: {1074          parents: 1,1075          interior: {1076            X2: [uniqueParachainJunction, uniqueAccountJunction],1077          },1078        },1079      };10801081      // Set the default version to wrap the first message to other chains.1082      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1083    });10841085    // eslint-disable-next-line require-await1086    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1087      alith = helper.account.alithAccount();1088    });1089  });1090109110921093  itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {1094    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {1095      const id = {1096        Token: 'ACA',1097      };1098      const destination = uniqueCombinedMultilocationAcala;1099      await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');11001101      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1102    });11031104    await expectFailedToTransact(helper, messageSent);1105  });11061107  itSub('Unique rejects GLMR tokens from Moonbeam', async ({helper}) => {1108    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1109      const id = 'SelfReserve';1110      const destination = uniqueCombinedMultilocation;1111      await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');11121113      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1114    });11151116    await expectFailedToTransact(helper, messageSent);1117  });11181119  itSub('Unique rejects ASTR tokens from Astar', async ({helper}) => {1120    await usingAstarPlaygrounds(astarUrl, async (helper) => {1121      const destinationParachain = uniqueParachainMultilocation;1122      const beneficiary = uniqueAccountMultilocation;1123      const assets = {1124        V2: [{1125          id: {1126            Concrete: {1127              parents: 0,1128              interior: 'Here',1129            },1130          },1131          fun: {1132            Fungible: testAmount,1133          },1134        }],1135      };1136      const feeAssetItem = 0;11371138      await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [1139        destinationParachain,1140        beneficiary,1141        assets,1142        feeAssetItem,1143      ]);11441145      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1146    });11471148    await expectFailedToTransact(helper, messageSent);1149  });11501151  itSub('Unique rejects PDX tokens from Polkadex', async ({helper}) => {11521153    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1154      helper.arrange.createEmptyAccount().addressRaw,1155      {1156        Concrete: {1157          parents: 1,1158          interior: {1159            X1: {1160              Parachain: POLKADEX_CHAIN,1161            },1162          },1163        },1164      },1165      testAmount,1166    );11671168    await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1169      await helper.getSudo().xcm.send(alice, uniqueParachainMultilocation, maliciousXcmProgramFullId);1170      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1171    });11721173    await expectFailedToTransact(helper, messageSent);1174  });1175});11761177describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {1178  // Unique constants1179  let alice: IKeyringPair;1180  let uniqueAssetLocation;11811182  let randomAccountUnique: IKeyringPair;1183  let randomAccountMoonbeam: IKeyringPair;11841185  // Moonbeam constants1186  let assetId: string;11871188  const uniqueAssetMetadata = {1189    name: 'xcUnique',1190    symbol: 'xcUNQ',1191    decimals: 18,1192    isFrozen: false,1193    minimalBalance: 1n,1194  };11951196  let balanceUniqueTokenInit: bigint;1197  let balanceUniqueTokenMiddle: bigint;1198  let balanceUniqueTokenFinal: bigint;1199  let balanceForeignUnqTokenInit: bigint;1200  let balanceForeignUnqTokenMiddle: bigint;1201  let balanceForeignUnqTokenFinal: bigint;1202  let balanceGlmrTokenInit: bigint;1203  let balanceGlmrTokenMiddle: bigint;1204  let balanceGlmrTokenFinal: bigint;12051206  before(async () => {1207    await usingPlaygrounds(async (helper, privateKey) => {1208      alice = await privateKey('//Alice');1209      [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);12101211      balanceForeignUnqTokenInit = 0n;12121213      // Set the default version to wrap the first message to other chains.1214      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1215    });12161217    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1218      const alithAccount = helper.account.alithAccount();1219      const baltatharAccount = helper.account.baltatharAccount();1220      const dorothyAccount = helper.account.dorothyAccount();12211222      randomAccountMoonbeam = helper.account.create();12231224      // >>> Sponsoring Dorothy >>>1225      console.log('Sponsoring Dorothy.......');1226      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);1227      console.log('Sponsoring Dorothy.......DONE');1228      // <<< Sponsoring Dorothy <<<12291230      uniqueAssetLocation = {1231        XCM: {1232          parents: 1,1233          interior: {X1: {Parachain: UNIQUE_CHAIN}},1234        },1235      };1236      const existentialDeposit = 1n;1237      const isSufficient = true;1238      const unitsPerSecond = 1n;1239      const numAssetsWeightHint = 0;12401241      if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) {1242        console.log('Unique asset is already registered on MoonBeam');1243      } else {1244        const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({1245          location: uniqueAssetLocation,1246          metadata: uniqueAssetMetadata,1247          existentialDeposit,1248          isSufficient,1249          unitsPerSecond,1250          numAssetsWeightHint,1251        });12521253        console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);12541255        await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);1256      }12571258      // >>> Acquire Unique AssetId Info on Moonbeam >>>1259      console.log('Acquire Unique AssetId Info on Moonbeam.......');12601261      assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();1262      console.log('UNQ asset ID is %s', assetId);1263      console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');1264      // >>> Acquire Unique AssetId Info on Moonbeam >>>12651266      // >>> Sponsoring random Account >>>1267      console.log('Sponsoring random Account.......');1268      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);1269      console.log('Sponsoring random Account.......DONE');1270      // <<< Sponsoring random Account <<<12711272      balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);1273    });12741275    await usingPlaygrounds(async (helper) => {1276      await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);1277      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);1278    });1279  });12801281  itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {1282    const currencyId = 0;1283    const dest = {1284      V2: {1285        parents: 1,1286        interior: {1287          X2: [1288            {Parachain: MOONBEAM_CHAIN},1289            {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},1290          ],1291        },1292      },1293    };1294    const amount = TRANSFER_AMOUNT;12951296    await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');12971298    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);1299    expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;13001301    const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;1302    console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));1303    expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;13041305    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1306      await helper.wait.newBlocks(3);1307      balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);13081309      const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;1310      console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1311      expect(glmrFees == 0n).to.be.true;13121313      balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;13141315      const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;1316      console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));1317      expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1318    });1319  });13201321  itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {1322    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1323      const asset = {1324        V2: {1325          id: {1326            Concrete: {1327              parents: 1,1328              interior: {1329                X1: {Parachain: UNIQUE_CHAIN},1330              },1331            },1332          },1333          fun: {1334            Fungible: TRANSFER_AMOUNT,1335          },1336        },1337      };1338      const destination = {1339        V2: {1340          parents: 1,1341          interior: {1342            X2: [1343              {Parachain: UNIQUE_CHAIN},1344              {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},1345            ],1346          },1347        },1348      };13491350      await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, 'Unlimited');13511352      balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);13531354      const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;1355      console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1356      expect(glmrFees > 0, 'Negative fees GLMR, looks like nothing was transferred').to.be.true;13571358      const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);13591360      expect(unqRandomAccountAsset).to.be.null;13611362      balanceForeignUnqTokenFinal = 0n;13631364      const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;1365      console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));1366      expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1367    });13681369    await helper.wait.newBlocks(3);13701371    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);1372    const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;1373    expect(actuallyDelivered > 0).to.be.true;13741375    console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));13761377    const unqFees = TRANSFER_AMOUNT - actuallyDelivered;1378    console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));1379    expect(unqFees == 0n).to.be.true;1380  });13811382  itSub('Moonbeam can send only up to its balance', async ({helper}) => {1383    // set Moonbeam's sovereign account's balance1384    const moonbeamBalance = 10000n * (10n ** UNQ_DECIMALS);1385    const moonbeamSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONBEAM_CHAIN);1386    await helper.getSudo().balance.setBalanceSubstrate(alice, moonbeamSovereignAccount, moonbeamBalance);13871388    const moreThanMoonbeamHas = moonbeamBalance * 2n;13891390    let targetAccountBalance = 0n;1391    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);13921393    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1394      targetAccount.addressRaw,1395      {1396        Concrete: {1397          parents: 0,1398          interior: 'Here',1399        },1400      },1401      moreThanMoonbeamHas,1402    );14031404    let maliciousXcmProgramSent: any;1405    const maxWaitBlocks = 3;14061407    // Try to trick Unique1408    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1409      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);14101411      // Needed to bypass the call filter.1412      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1413      await helper.fastDemocracy.executeProposal('try to spend more UNQ than Moonbeam has', batchCall);14141415      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1416    });14171418    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1419        && event.outcome.isFailedToTransactAsset);14201421    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1422    expect(targetAccountBalance).to.be.equal(0n);14231424    // But Moonbeam still can send the correct amount1425    const validTransferAmount = moonbeamBalance / 2n;1426    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1427      targetAccount.addressRaw,1428      {1429        Concrete: {1430          parents: 0,1431          interior: 'Here',1432        },1433      },1434      validTransferAmount,1435    );14361437    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1438      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, validXcmProgram]);14391440      // Needed to bypass the call filter.1441      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1442      await helper.fastDemocracy.executeProposal('Spend the correct amount of UNQ', batchCall);1443    });14441445    await helper.wait.newBlocks(maxWaitBlocks);14461447    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1448    expect(targetAccountBalance).to.be.equal(validTransferAmount);1449  });14501451  itSub('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => {1452    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1453    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);14541455    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1456      targetAccount.addressRaw,1457      uniqueAssetId,1458      testAmount,1459    );14601461    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1462      targetAccount.addressRaw,1463      {1464        Concrete: {1465          parents: 0,1466          interior: 'Here',1467        },1468      },1469      testAmount,1470    );14711472    let maliciousXcmProgramFullIdSent: any;1473    let maliciousXcmProgramHereIdSent: any;1474    const maxWaitBlocks = 3;14751476    // Try to trick Unique using full UNQ identification1477    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1478      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);14791480      // Needed to bypass the call filter.1481      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1482      await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);14831484      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1485    });14861487    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1488        && event.outcome.isUntrustedReserveLocation);14891490    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1491    expect(accountBalance).to.be.equal(0n);14921493    // Try to trick Unique using shortened UNQ identification1494    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1495      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);14961497      // Needed to bypass the call filter.1498      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1499      await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);15001501      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1502    });15031504    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1505        && event.outcome.isUntrustedReserveLocation);15061507    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1508    expect(accountBalance).to.be.equal(0n);1509  });1510});15111512describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => {1513  let alice: IKeyringPair;1514  let randomAccount: IKeyringPair;15151516  const UNQ_ASSET_ID_ON_ASTAR = 18_446_744_073_709_551_631n; // The value is taken from the live Astar1517  const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n; // The value is taken from the live Astar15181519  // Unique -> Astar1520  const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.1521  const unitsPerSecond = 9_451_000_000_000_000_000n; // The value is taken from the live Astar1522  const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ1523  const unqToAstarArrived = 9_962_196_000_000_000_000n; // 9.962 ... UNQ, Astar takes a commision in foreign tokens15241525  // Astar -> Unique1526  const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); // 5 UNQ1527  const unqOnAstarLeft = unqToAstarArrived - unqFromAstarTransfered; // 4.962_219_600_000_000_000n UNQ15281529  let balanceAfterUniqueToAstarXCM: bigint;15301531  before(async () => {1532    await usingPlaygrounds(async (helper, privateKey) => {1533      alice = await privateKey('//Alice');1534      [randomAccount] = await helper.arrange.createAccounts([100n], alice);1535      console.log('randomAccount', randomAccount.address);15361537      // Set the default version to wrap the first message to other chains.1538      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1539    });15401541    await usingAstarPlaygrounds(astarUrl, async (helper) => {1542      if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {1543        console.log('1. Create foreign asset and metadata');1544        await helper.getSudo().assets.forceCreate(1545          alice,1546          UNQ_ASSET_ID_ON_ASTAR,1547          alice.address,1548          UNQ_MINIMAL_BALANCE_ON_ASTAR,1549        );15501551        await helper.assets.setMetadata(1552          alice,1553          UNQ_ASSET_ID_ON_ASTAR,1554          'Unique Network',1555          'UNQ',1556          Number(UNQ_DECIMALS),1557        );15581559        console.log('2. Register asset location on Astar');1560        const assetLocation = {1561          V2: {1562            parents: 1,1563            interior: {1564              X1: {1565                Parachain: UNIQUE_CHAIN,1566              },1567            },1568          },1569        };15701571        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);15721573        console.log('3. Set UNQ payment for XCM execution on Astar');1574        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);1575      } else {1576        console.log('UNQ is already registered on Astar');1577      }1578      console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');1579      await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);1580    });1581  });15821583  itSub('Should connect and send UNQ to Astar', async ({helper}) => {1584    const destination = {1585      V2: {1586        parents: 1,1587        interior: {1588          X1: {1589            Parachain: ASTAR_CHAIN,1590          },1591        },1592      },1593    };15941595    const beneficiary = {1596      V2: {1597        parents: 0,1598        interior: {1599          X1: {1600            AccountId32: {1601              network: 'Any',1602              id: randomAccount.addressRaw,1603            },1604          },1605        },1606      },1607    };16081609    const assets = {1610      V2: [1611        {1612          id: {1613            Concrete: {1614              parents: 0,1615              interior: 'Here',1616            },1617          },1618          fun: {1619            Fungible: unqToAstarTransferred,1620          },1621        },1622      ],1623    };16241625    // Initial balance is 100 UNQ1626    const balanceBefore = await helper.balance.getSubstrate(randomAccount.address);1627    console.log(`Initial balance is: ${balanceBefore}`);16281629    const feeAssetItem = 0;1630    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');16311632    // Balance after reserve transfer is less than 901633    balanceAfterUniqueToAstarXCM = await helper.balance.getSubstrate(randomAccount.address);1634    console.log(`UNQ Balance on Unique after XCM is: ${balanceAfterUniqueToAstarXCM}`);1635    console.log(`Unique's UNQ commission is: ${balanceBefore - balanceAfterUniqueToAstarXCM}`);1636    expect(balanceBefore - balanceAfterUniqueToAstarXCM > 0).to.be.true;16371638    await usingAstarPlaygrounds(astarUrl, async (helper) => {1639      await helper.wait.newBlocks(3);1640      const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1641      const astarBalance = await helper.balance.getSubstrate(randomAccount.address);16421643      console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);1644      console.log(`Astar's UNQ commission is: ${unqToAstarTransferred - xcUNQbalance!}`);16451646      expect(xcUNQbalance).to.eq(unqToAstarArrived);1647      // Astar balance does not changed1648      expect(astarBalance).to.eq(astarInitialBalance);1649    });1650  });16511652  itSub('Should connect to Astar and send UNQ back', async ({helper}) => {1653    await usingAstarPlaygrounds(astarUrl, async (helper) => {1654      const destination = {1655        V2: {1656          parents: 1,1657          interior: {1658            X1: {1659              Parachain: UNIQUE_CHAIN,1660            },1661          },1662        },1663      };16641665      const beneficiary = {1666        V2: {1667          parents: 0,1668          interior: {1669            X1: {1670              AccountId32: {1671                network: 'Any',1672                id: randomAccount.addressRaw,1673              },1674            },1675          },1676        },1677      };16781679      const assets = {1680        V2: [1681          {1682            id: {1683              Concrete: {1684                parents: 1,1685                interior: {1686                  X1: {1687                    Parachain: UNIQUE_CHAIN,1688                  },1689                },1690              },1691            },1692            fun: {1693              Fungible: unqFromAstarTransfered,1694            },1695          },1696        ],1697      };16981699      // Initial balance is 1 ASTR1700      const balanceASTRbefore = await helper.balance.getSubstrate(randomAccount.address);1701      console.log(`ASTR balance is: ${balanceASTRbefore}, it does not changed`);1702      expect(balanceASTRbefore).to.eq(astarInitialBalance);17031704      const feeAssetItem = 0;1705      // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1706      await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);17071708      const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1709      const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);1710      console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);17111712      // Assert: xcUNQ balance correctly decreased1713      expect(xcUNQbalance).to.eq(unqOnAstarLeft);1714      // Assert: ASTR balance is 0.996...1715      expect(balanceAstar / (10n ** (ASTAR_DECIMALS - 3n))).to.eq(996n);1716    });17171718    await helper.wait.newBlocks(3);1719    const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address);1720    console.log(`UNQ Balance on Unique after XCM is: ${balanceUNQ}`);1721    expect(balanceUNQ).to.eq(balanceAfterUniqueToAstarXCM + unqFromAstarTransfered);1722  });17231724  itSub('Astar can send only up to its balance', async ({helper}) => {1725    // set Astar's sovereign account's balance1726    const astarBalance = 10000n * (10n ** UNQ_DECIMALS);1727    const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);1728    await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);17291730    const moreThanAstarHas = astarBalance * 2n;17311732    let targetAccountBalance = 0n;1733    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);17341735    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1736      targetAccount.addressRaw,1737      {1738        Concrete: {1739          parents: 0,1740          interior: 'Here',1741        },1742      },1743      moreThanAstarHas,1744    );17451746    let maliciousXcmProgramSent: any;1747    const maxWaitBlocks = 3;17481749    // Try to trick Unique1750    await usingAstarPlaygrounds(astarUrl, async (helper) => {1751      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);17521753      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1754    });17551756    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1757        && event.outcome.isFailedToTransactAsset);17581759    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1760    expect(targetAccountBalance).to.be.equal(0n);17611762    // But Astar still can send the correct amount1763    const validTransferAmount = astarBalance / 2n;1764    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1765      targetAccount.addressRaw,1766      {1767        Concrete: {1768          parents: 0,1769          interior: 'Here',1770        },1771      },1772      validTransferAmount,1773    );17741775    await usingAstarPlaygrounds(astarUrl, async (helper) => {1776      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, validXcmProgram);1777    });17781779    await helper.wait.newBlocks(maxWaitBlocks);17801781    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1782    expect(targetAccountBalance).to.be.equal(validTransferAmount);1783  });17841785  itSub('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {1786    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1787    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);17881789    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1790      targetAccount.addressRaw,1791      uniqueAssetId,1792      testAmount,1793    );17941795    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1796      targetAccount.addressRaw,1797      {1798        Concrete: {1799          parents: 0,1800          interior: 'Here',1801        },1802      },1803      testAmount,1804    );18051806    let maliciousXcmProgramFullIdSent: any;1807    let maliciousXcmProgramHereIdSent: any;1808    const maxWaitBlocks = 3;18091810    // Try to trick Unique using full UNQ identification1811    await usingAstarPlaygrounds(astarUrl, async (helper) => {1812      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramFullId);18131814      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1815    });18161817    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1818        && event.outcome.isUntrustedReserveLocation);18191820    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1821    expect(accountBalance).to.be.equal(0n);18221823    // Try to trick Unique using shortened UNQ identification1824    await usingAstarPlaygrounds(astarUrl, async (helper) => {1825      await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramHereId);18261827      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1828    });18291830    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1831        && event.outcome.isUntrustedReserveLocation);18321833    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1834    expect(accountBalance).to.be.equal(0n);1835  });1836});
modifiednode/cli/src/chain_spec.rsdiffbeforeafterboth
--- a/node/cli/src/chain_spec.rs
+++ b/node/cli/src/chain_spec.rs
@@ -167,7 +167,6 @@
 					.map(|k| (k, 1 << 100))
 					.collect(),
 			},
-			tokens: TokensConfig { balances: vec![] },
 			sudo: SudoConfig {
 				key: Some($root_key),
 			},
@@ -231,7 +230,6 @@
 					.map(|k| (k, 1 << 100))
 					.collect(),
 			},
-			tokens: TokensConfig { balances: vec![] },
 			sudo: SudoConfig {
 				key: Some($root_key),
 			},
modifiedpallets/balances-adapter/src/common.rsdiffbeforeafterboth
--- a/pallets/balances-adapter/src/common.rs
+++ b/pallets/balances-adapter/src/common.rs
@@ -1,10 +1,16 @@
 use alloc::{vec, vec::Vec};
 use core::marker::PhantomData;
 
-use frame_support::{ensure, fail, weights::Weight};
+use frame_support::{
+	ensure, fail,
+	traits::tokens::{fungible::Mutate, Fortitude, Precision},
+	weights::Weight,
+};
 use pallet_balances::{weights::SubstrateWeight as BalancesWeight, WeightInfo};
-use pallet_common::{CommonCollectionOperations, CommonWeightInfo, Error as CommonError};
-use up_data_structs::TokenId;
+use pallet_common::{
+	erc::CrossAccountId, CommonCollectionOperations, CommonWeightInfo, Error as CommonError,
+};
+use up_data_structs::{budget::Budget, TokenId};
 
 use crate::{Config, NativeFungibleHandle, Pallet};
 
@@ -332,8 +338,8 @@
 		0
 	}
 
-	fn refungible_extensions(&self) -> Option<&dyn pallet_common::RefungibleExtensions<T>> {
-		None
+	fn xcm_extensions(&self) -> Option<&dyn pallet_common::XcmExtensions<T>> {
+		Some(self)
 	}
 
 	fn set_allowance_for_all(
@@ -360,3 +366,72 @@
 		fail!(<CommonError<T>>::UnsupportedOperation);
 	}
 }
+
+impl<T: Config> pallet_common::XcmExtensions<T> for NativeFungibleHandle<T> {
+	fn create_item_internal(
+		&self,
+		_depositor: &<T>::CrossAccountId,
+		to: <T>::CrossAccountId,
+		data: up_data_structs::CreateItemData,
+		_nesting_budget: &dyn Budget,
+	) -> Result<TokenId, sp_runtime::DispatchError> {
+		match &data {
+			up_data_structs::CreateItemData::Fungible(fungible_data) => {
+				T::Mutate::mint_into(
+					to.as_sub(),
+					fungible_data
+						.value
+						.try_into()
+						.map_err(|_| sp_runtime::ArithmeticError::Overflow)?,
+				)?;
+
+				Ok(TokenId::default())
+			}
+			_ => {
+				fail!(<CommonError<T>>::NotFungibleDataUsedToMintFungibleCollectionToken)
+			}
+		}
+	}
+
+	fn transfer_item_internal(
+		&self,
+		_depositor: &<T>::CrossAccountId,
+		from: &<T>::CrossAccountId,
+		to: &<T>::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+		_nesting_budget: &dyn Budget,
+	) -> sp_runtime::DispatchResult {
+		ensure!(
+			token == TokenId::default(),
+			<CommonError<T>>::FungibleItemsHaveNoId
+		);
+
+		<Pallet<T>>::transfer(from, to, amount)
+			.map(|_| ())
+			.map_err(|post_info| post_info.error)
+	}
+
+	fn burn_item_internal(
+		&self,
+		from: T::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+	) -> sp_runtime::DispatchResult {
+		ensure!(
+			token == TokenId::default(),
+			<CommonError<T>>::FungibleItemsHaveNoId
+		);
+
+		T::Mutate::burn_from(
+			from.as_sub(),
+			amount
+				.try_into()
+				.map_err(|_| sp_runtime::ArithmeticError::Overflow)?,
+			Precision::Exact,
+			Fortitude::Polite,
+		)?;
+
+		Ok(())
+	}
+}
modifiedpallets/common/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/common/src/benchmarking.rs
+++ b/pallets/common/src/benchmarking.rs
@@ -33,7 +33,7 @@
 	MAX_COLLECTION_NAME_LENGTH, MAX_PROPERTIES_PER_ITEM, MAX_TOKEN_PREFIX_LENGTH,
 };
 
-use crate::{BenchmarkPropertyWriter, CollectionHandle, Config, Pallet};
+use crate::{BenchmarkPropertyWriter, CollectionHandle, CollectionIssuer, Config, Pallet};
 
 const SEED: u32 = 1;
 
@@ -120,7 +120,9 @@
 	create_collection_raw(
 		owner,
 		CollectionMode::NFT,
-		|owner: T::CrossAccountId, data| <Pallet<T>>::init_collection(owner.clone(), owner, data),
+		|owner: T::CrossAccountId, data| {
+			<Pallet<T>>::init_collection(owner.clone(), CollectionIssuer::User(owner), data)
+		},
 		|h| h,
 	)
 }
modifiedpallets/common/src/dispatch.rsdiffbeforeafterboth
--- a/pallets/common/src/dispatch.rs
+++ b/pallets/common/src/dispatch.rs
@@ -11,7 +11,7 @@
 use sp_weights::Weight;
 use up_data_structs::{CollectionId, CreateCollectionData};
 
-use crate::{pallet::Config, CommonCollectionOperations};
+use crate::{pallet::Config, CollectionIssuer, CommonCollectionOperations};
 
 // TODO: move to benchmarking
 /// Price of [`dispatch_tx`] call with noop `call` argument
@@ -69,13 +69,14 @@
 	/// Check if the collection is internal.
 	fn check_is_internal(&self) -> DispatchResult;
 
-	/// Create a collection. The collection will be created according to the value of [`data.mode`](CreateCollectionData::mode).
+	/// Create a regular collection. The collection will be created according to the value of [`data.mode`](CreateCollectionData::mode).
 	///
 	/// * `sender` - The user who will become the owner of the collection.
+	/// * `issuer` - An entity that creates the collection.
 	/// * `data` - Description of the created collection.
 	fn create(
 		sender: T::CrossAccountId,
-		payer: T::CrossAccountId,
+		issuer: CollectionIssuer<T::CrossAccountId>,
 		data: CreateCollectionData<T::CrossAccountId>,
 	) -> Result<CollectionId, DispatchError>;
 
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -80,15 +80,16 @@
 use sp_std::vec::Vec;
 use sp_weights::Weight;
 use up_data_structs::{
-	budget::Budget, AccessMode, Collection, CollectionId, CollectionLimits, CollectionMode,
-	CollectionPermissions, CollectionProperties as CollectionPropertiesT, CollectionStats,
-	CreateCollectionData, CreateItemData, CreateItemExData, PhantomType, PropertiesError,
-	PropertiesPermissionMap, Property, PropertyKey, PropertyKeyPermission, PropertyPermission,
-	PropertyScope, PropertyValue, RpcCollection, RpcCollectionFlags, SponsoringRateLimit,
-	SponsorshipState, TokenChild, TokenData, TokenId, TokenOwnerError, TokenProperties,
-	TrySetProperty, COLLECTION_ADMINS_LIMIT, COLLECTION_NUMBER_LIMIT, CUSTOM_DATA_LIMIT,
-	FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP,
-	MAX_TOKEN_PREFIX_LENGTH, NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+	budget::Budget, mapping::TokenAddressMapping, AccessMode, Collection, CollectionId,
+	CollectionLimits, CollectionMode, CollectionPermissions,
+	CollectionProperties as CollectionPropertiesT, CollectionStats, CreateCollectionData,
+	CreateItemData, CreateItemExData, PhantomType, PropertiesError, PropertiesPermissionMap,
+	Property, PropertyKey, PropertyKeyPermission, PropertyPermission, PropertyScope, PropertyValue,
+	RpcCollection, RpcCollectionFlags, SponsoringRateLimit, SponsorshipState, TokenChild,
+	TokenData, TokenId, TokenOwnerError, TokenProperties, TrySetProperty, COLLECTION_ADMINS_LIMIT,
+	COLLECTION_NUMBER_LIMIT, CUSTOM_DATA_LIMIT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+	MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_TOKEN_PREFIX_LENGTH,
+	NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
 };
 use up_pov_estimate_rpc::PovInfo;
 
@@ -786,6 +787,9 @@
 
 		/// Fungible tokens hold no ID, and the default value of TokenId for a fungible collection is 0.
 		FungibleItemsHaveNoId,
+
+		/// Not Fungible item data used to mint in Fungible collection.
+		NotFungibleDataUsedToMintFungibleCollectionToken,
 	}
 
 	/// Storage of the count of created collections. Essentially contains the last collection ID.
@@ -940,6 +944,15 @@
 	}
 }
 
+/// An issuer of a collection.
+pub enum CollectionIssuer<CrossAccountId> {
+	/// A user who creates the collection.
+	User(CrossAccountId),
+
+	/// The internal mechanisms are creating the collection.
+	Internals,
+}
+
 fn check_token_permissions<T: Config>(
 	collection_admin_permitted: bool,
 	token_owner_permitted: bool,
@@ -1124,33 +1137,36 @@
 	/// Create new collection.
 	///
 	/// * `owner` - The owner of the collection.
-	/// * `data` - Description of the created collection.
-	/// * `flags` - Extra flags to store.
+	/// * `issuer` - An entity that creates the collection.
+	/// * `is_special_collection` -- Whether this collection is a special one, i.e. can have special flags set.
 	pub fn init_collection(
 		owner: T::CrossAccountId,
-		payer: T::CrossAccountId,
+		issuer: CollectionIssuer<T::CrossAccountId>,
 		data: CreateCollectionData<T::CrossAccountId>,
 	) -> Result<CollectionId, DispatchError> {
-		ensure!(data.flags.is_allowed_for_user(), <Error<T>>::NoPermission);
-		Self::init_collection_internal(owner, payer, data)
-	}
+		match issuer {
+			CollectionIssuer::User(payer) => {
+				ensure!(data.flags.is_allowed_for_user(), <Error<T>>::NoPermission);
+
+				// Take a (non-refundable) deposit of collection creation
+				let mut imbalance = <Debt<T::AccountId, <T as Config>::Currency>>::zero();
+				imbalance.subsume(<T as Config>::Currency::deposit(
+					&T::TreasuryAccountId::get(),
+					T::CollectionCreationPrice::get(),
+					Precision::Exact,
+				)?);
+				let credit = <T as Config>::Currency::settle(
+					payer.as_sub(),
+					imbalance,
+					Preservation::Preserve,
+				)
+				.map_err(|_| Error::<T>::NotSufficientFounds)?;
 
-	/// Initializes the collection with ForeignCollection flag. Returns [CollectionId] on success, [DispatchError] otherwise.
-	pub fn init_foreign_collection(
-		owner: T::CrossAccountId,
-		payer: T::CrossAccountId,
-		mut data: CreateCollectionData<T::CrossAccountId>,
-	) -> Result<CollectionId, DispatchError> {
-		data.flags.foreign = true;
-		let id = Self::init_collection_internal(owner, payer, data)?;
-		Ok(id)
-	}
+				debug_assert!(credit.peek().is_zero());
+			}
+			CollectionIssuer::Internals => {}
+		}
 
-	fn init_collection_internal(
-		owner: T::CrossAccountId,
-		payer: T::CrossAccountId,
-		data: CreateCollectionData<T::CrossAccountId>,
-	) -> Result<CollectionId, DispatchError> {
 		{
 			ensure!(
 				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,
@@ -1225,21 +1241,6 @@
 		);
 		<AdminAmount<T>>::insert(id, admin_amount);
 
-		// Take a (non-refundable) deposit of collection creation
-		{
-			let mut imbalance = <Debt<T::AccountId, <T as Config>::Currency>>::zero();
-			imbalance.subsume(<T as Config>::Currency::deposit(
-				&T::TreasuryAccountId::get(),
-				T::CollectionCreationPrice::get(),
-				Precision::Exact,
-			)?);
-			let credit =
-				<T as Config>::Currency::settle(payer.as_sub(), imbalance, Preservation::Preserve)
-					.map_err(|_| Error::<T>::NotSufficientFounds)?;
-
-			debug_assert!(credit.peek().is_zero())
-		}
-
 		<CreatedCollectionCount<T>>::put(created_count);
 		<Pallet<T>>::deposit_event(Event::CollectionCreated(
 			id,
@@ -2293,7 +2294,14 @@
 	) -> u128;
 
 	/// Get extension for RFT collection.
-	fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;
+	fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>> {
+		None
+	}
+
+	/// Get XCM extensions.
+	fn xcm_extensions(&self) -> Option<&dyn XcmExtensions<T>> {
+		None
+	}
 
 	/// The `operator` is allowed to transfer all tokens of the `owner` on their behalf.
 	/// * `owner` - Token owner
@@ -2333,6 +2341,117 @@
 	) -> DispatchResultWithPostInfo;
 }
 
+/// XCM extensions for fungible and NFT collections
+pub trait XcmExtensions<T>
+where
+	T: Config,
+{
+	/// Does the token have children?
+	fn token_has_children(&self, _token: TokenId) -> bool {
+		false
+	}
+
+	/// Create a collection's item using a transaction.
+	///
+	/// This function performs additional XCM-related checks before the actual creation.
+	///
+	/// The `transactional` attribute is needed because the inbound XCM messages
+	/// are processed in a non-transactional context.
+	/// To perform the needed logic, we use the internal pallets' functions
+	/// that are not inherently safe to use outside a transaction.
+	///
+	/// This requirement is temporary until XCM message processing becomes transactional:
+	/// https://github.com/paritytech/polkadot-sdk/issues/490
+	#[transactional]
+	fn create_item(
+		&self,
+		depositor: &T::CrossAccountId,
+		to: T::CrossAccountId,
+		data: CreateItemData,
+		nesting_budget: &dyn Budget,
+	) -> Result<TokenId, DispatchError> {
+		if T::CrossTokenAddressMapping::is_token_address(&to) {
+			return unsupported!(T);
+		}
+
+		self.create_item_internal(depositor, to, data, nesting_budget)
+	}
+
+	/// Create a collection's item.
+	fn create_item_internal(
+		&self,
+		depositor: &T::CrossAccountId,
+		to: T::CrossAccountId,
+		data: CreateItemData,
+		nesting_budget: &dyn Budget,
+	) -> Result<TokenId, DispatchError>;
+
+	/// Transfer an item from the `from` account to the `to` account using a transaction.
+	///
+	/// This function performs additional XCM-related checks before the actual transfer.
+	///
+	/// The `transactional` attribute is needed because the inbound XCM messages
+	/// are processed in a non-transactional context.
+	/// To perform the needed logic, we use the internal pallets' functions
+	/// that are not inherently safe to use outside a transaction.
+	///
+	/// This requirement is temporary until XCM message processing becomes transactional:
+	/// https://github.com/paritytech/polkadot-sdk/issues/490
+	#[transactional]
+	fn transfer_item(
+		&self,
+		depositor: &T::CrossAccountId,
+		from: &T::CrossAccountId,
+		to: &T::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+		nesting_budget: &dyn Budget,
+	) -> DispatchResult {
+		if T::CrossTokenAddressMapping::is_token_address(to) {
+			return unsupported!(T);
+		}
+
+		if self.token_has_children(token) {
+			return unsupported!(T);
+		}
+
+		self.transfer_item_internal(depositor, from, to, token, amount, nesting_budget)
+	}
+
+	/// Transfer an item from the `from` account to the `to` account.
+	fn transfer_item_internal(
+		&self,
+		depositor: &T::CrossAccountId,
+		from: &T::CrossAccountId,
+		to: &T::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+		nesting_budget: &dyn Budget,
+	) -> DispatchResult;
+
+	/// Burn a collection's item using a transaction.
+	///
+	/// The `transactional` attribute is needed because the inbound XCM messages
+	/// are processed in a non-transactional context.
+	/// To perform the needed logic, we use the internal pallets' functions
+	/// that are not inherently safe to use outside a transaction.
+	///
+	/// This requirement is temporary until XCM message processing becomes transactional:
+	/// https://github.com/paritytech/polkadot-sdk/issues/490
+	#[transactional]
+	fn burn_item(&self, from: T::CrossAccountId, token: TokenId, amount: u128) -> DispatchResult {
+		self.burn_item_internal(from, token, amount)
+	}
+
+	/// Burn a collection's item.
+	fn burn_item_internal(
+		&self,
+		from: T::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+	) -> DispatchResult;
+}
+
 /// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`].
 ///
 /// Used for [`CommonCollectionOperations`] implementations and flexible enough to do so.
modifiedpallets/foreign-assets/Cargo.tomldiffbeforeafterboth
--- a/pallets/foreign-assets/Cargo.toml
+++ b/pallets/foreign-assets/Cargo.toml
@@ -12,7 +12,6 @@
 frame-support = { workspace = true }
 frame-system = { workspace = true }
 log = { workspace = true }
-orml-tokens = { workspace = true }
 pallet-balances = { features = ["insecure_zero_ed"], workspace = true }
 pallet-common = { workspace = true }
 pallet-fungible = { workspace = true }
@@ -30,7 +29,6 @@
 	"frame-support/std",
 	"frame-system/std",
 	"log/std",
-	"orml-tokens/std",
 	"pallet-balances/std",
 	"pallet-common/std",
 	"pallet-fungible/std",
modifiedpallets/foreign-assets/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/foreign-assets/src/benchmarking.rs
+++ b/pallets/foreign-assets/src/benchmarking.rs
@@ -16,88 +16,36 @@
 
 #![allow(missing_docs)]
 
-use frame_benchmarking::{account, v2::*};
-use frame_support::traits::Currency;
+use frame_benchmarking::v2::*;
 use frame_system::RawOrigin;
-use sp_std::{boxed::Box, vec, vec::Vec};
-use staging_xcm::{opaque::latest::Junction::Parachain, v3::Junctions::X1, VersionedMultiLocation};
+use pallet_common::benchmarking::{create_data, create_u16_data};
+use sp_std::{boxed::Box, vec};
+use staging_xcm::prelude::*;
+use up_data_structs::{MAX_COLLECTION_NAME_LENGTH, MAX_TOKEN_PREFIX_LENGTH};
 
-use super::{Call, Config, Pallet};
-use crate::AssetMetadata;
+use super::{Call, Config, ForeignCollectionMode, Pallet};
 
-fn bounded<T: TryFrom<Vec<u8>>>(slice: &[u8]) -> T {
-	T::try_from(slice.to_vec())
-		.map_err(|_| "slice doesn't fit")
-		.unwrap()
-}
-
 #[benchmarks]
 mod benchmarks {
+
 	use super::*;
 
 	#[benchmark]
-	fn register_foreign_asset() -> Result<(), BenchmarkError> {
-		let owner: T::AccountId = account("user", 0, 1);
-		let location: VersionedMultiLocation = VersionedMultiLocation::from(X1(Parachain(1000)));
-		let metadata: AssetMetadata<
-			<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance,
-		> = AssetMetadata {
-			name: bounded(b"name"),
-			symbol: bounded(b"symbol"),
-			decimals: 18,
-			minimal_balance: 1u32.into(),
-		};
-		let mut balance: <<T as Config>::Currency as Currency<
-			<T as frame_system::Config>::AccountId,
-		>>::Balance = 4_000_000_000u32.into();
-		balance = balance * balance;
-		<T as Config>::Currency::make_free_balance_be(&owner, balance);
+	fn force_register_foreign_asset() -> Result<(), BenchmarkError> {
+		let location =
+			MultiLocation::from(X3(Parachain(1000), PalletInstance(42), GeneralIndex(1)));
+		let name = create_u16_data::<MAX_COLLECTION_NAME_LENGTH>();
+		let token_prefix = create_data::<MAX_TOKEN_PREFIX_LENGTH>();
+		let mode = ForeignCollectionMode::NFT;
 
 		#[extrinsic_call]
 		_(
 			RawOrigin::Root,
-			owner,
-			Box::new(location),
-			Box::new(metadata),
+			Box::new(location.into()),
+			name,
+			token_prefix,
+			mode,
 		);
-
-		Ok(())
-	}
-
-	#[benchmark]
-	fn update_foreign_asset() -> Result<(), BenchmarkError> {
-		let owner: T::AccountId = account("user", 0, 1);
-		let location: VersionedMultiLocation = VersionedMultiLocation::from(X1(Parachain(2000)));
-		let metadata: AssetMetadata<
-			<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance,
-		> = AssetMetadata {
-			name: bounded(b"name"),
-			symbol: bounded(b"symbol"),
-			decimals: 18,
-			minimal_balance: 1u32.into(),
-		};
-		let metadata2: AssetMetadata<
-			<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance,
-		> = AssetMetadata {
-			name: bounded(b"name2"),
-			symbol: bounded(b"symbol2"),
-			decimals: 18,
-			minimal_balance: 1u32.into(),
-		};
-		let mut balance: <<T as Config>::Currency as Currency<
-			<T as frame_system::Config>::AccountId,
-		>>::Balance = 4_000_000_000u32.into();
-		balance = balance * balance;
-		<T as Config>::Currency::make_free_balance_be(&owner, balance);
-		Pallet::<T>::register_foreign_asset(
-			RawOrigin::Root.into(),
-			owner,
-			Box::new(location.clone()),
-			Box::new(metadata),
-		)?;
-
-		#[extrinsic_call]
-		_(RawOrigin::Root, 0, Box::new(location), Box::new(metadata2));
 
 		Ok(())
 	}
deletedpallets/foreign-assets/src/impl_fungibles.rsdiffbeforeafterboth
--- a/pallets/foreign-assets/src/impl_fungibles.rs
+++ /dev/null
@@ -1,499 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// This file is part of Unique Network.
-
-// Unique Network is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Unique Network is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-
-//! Implementations for fungibles trait.
-
-use frame_support::traits::tokens::{
-	DepositConsequence, Fortitude, Precision, Preservation, Provenance, WithdrawConsequence,
-};
-use frame_system::Config as SystemConfig;
-use pallet_common::{CollectionHandle, CommonCollectionOperations};
-use pallet_fungible::FungibleHandle;
-use sp_runtime::traits::{CheckedAdd, CheckedSub};
-use up_data_structs::budget;
-
-use super::*;
-
-impl<T: Config> fungibles::Inspect<<T as SystemConfig>::AccountId> for Pallet<T>
-where
-	T: orml_tokens::Config<CurrencyId = AssetId>,
-	BalanceOf<T>: From<<T as pallet_balances::Config>::Balance>,
-	BalanceOf<T>: From<<T as orml_tokens::Config>::Balance>,
-	<T as pallet_balances::Config>::Balance: From<BalanceOf<T>>,
-	<T as orml_tokens::Config>::Balance: From<BalanceOf<T>>,
-{
-	type AssetId = AssetId;
-	type Balance = BalanceOf<T>;
-
-	fn total_issuance(asset: Self::AssetId) -> Self::Balance {
-		log::trace!(target: "fassets::impl_foreign_assets", "impl_fungible total_issuance");
-
-		match asset {
-			AssetId::NativeAssetId(NativeCurrency::Here) => {
-				<pallet_balances::Pallet<T> as fungible::Inspect<T::AccountId>>::total_issuance()
-					.into()
-			}
-			AssetId::NativeAssetId(NativeCurrency::Parent) => {
-				<orml_tokens::Pallet<T> as fungibles::Inspect<T::AccountId>>::total_issuance(
-					AssetId::NativeAssetId(NativeCurrency::Parent),
-				)
-				.into()
-			}
-			AssetId::ForeignAssetId(fid) => {
-				let target_collection_id = match <AssetBinding<T>>::get(fid) {
-					Some(v) => v,
-					None => return Zero::zero(),
-				};
-				let collection_handle = match <CollectionHandle<T>>::try_get(target_collection_id) {
-					Ok(v) => v,
-					Err(_) => return Zero::zero(),
-				};
-				let collection = FungibleHandle::cast(collection_handle);
-				Self::Balance::try_from(collection.total_supply()).unwrap_or(Zero::zero())
-			}
-		}
-	}
-
-	fn minimum_balance(asset: Self::AssetId) -> Self::Balance {
-		log::trace!(target: "fassets::impl_foreign_assets", "impl_fungible minimum_balance");
-		match asset {
-			AssetId::NativeAssetId(NativeCurrency::Here) => {
-				<pallet_balances::Pallet<T> as fungible::Inspect<T::AccountId>>::minimum_balance()
-					.into()
-			}
-			AssetId::NativeAssetId(NativeCurrency::Parent) => {
-				<orml_tokens::Pallet<T> as fungibles::Inspect<T::AccountId>>::minimum_balance(
-					AssetId::NativeAssetId(NativeCurrency::Parent),
-				)
-				.into()
-			}
-			AssetId::ForeignAssetId(fid) => AssetMetadatas::<T>::get(AssetId::ForeignAssetId(fid))
-				.map(|x| x.minimal_balance)
-				.unwrap_or_else(Zero::zero),
-		}
-	}
-
-	fn balance(asset: Self::AssetId, who: &<T as SystemConfig>::AccountId) -> Self::Balance {
-		log::trace!(target: "fassets::impl_foreign_assets", "impl_fungible balance");
-		match asset {
-			AssetId::NativeAssetId(NativeCurrency::Here) => {
-				<pallet_balances::Pallet<T> as fungible::Inspect<T::AccountId>>::balance(who).into()
-			}
-			AssetId::NativeAssetId(NativeCurrency::Parent) => {
-				<orml_tokens::Pallet<T> as fungibles::Inspect<T::AccountId>>::balance(
-					AssetId::NativeAssetId(NativeCurrency::Parent),
-					who,
-				)
-				.into()
-			}
-			AssetId::ForeignAssetId(fid) => {
-				let target_collection_id = match <AssetBinding<T>>::get(fid) {
-					Some(v) => v,
-					None => return Zero::zero(),
-				};
-				let collection_handle = match <CollectionHandle<T>>::try_get(target_collection_id) {
-					Ok(v) => v,
-					Err(_) => return Zero::zero(),
-				};
-				let collection = FungibleHandle::cast(collection_handle);
-				Self::Balance::try_from(
-					collection.balance(T::CrossAccountId::from_sub(who.clone()), TokenId(0)),
-				)
-				.unwrap_or(Zero::zero())
-			}
-		}
-	}
-
-	fn total_balance(asset: Self::AssetId, who: &<T as SystemConfig>::AccountId) -> Self::Balance {
-		Self::balance(asset, who)
-	}
-
-	fn reducible_balance(
-		asset: Self::AssetId,
-		who: &<T as SystemConfig>::AccountId,
-		preservation: Preservation,
-		fortitude: Fortitude,
-	) -> Self::Balance {
-		log::trace!(target: "fassets::impl_foreign_assets", "impl_fungible reducible_balance");
-
-		match asset {
-			AssetId::NativeAssetId(NativeCurrency::Here) => {
-				<pallet_balances::Pallet<T> as fungible::Inspect<T::AccountId>>::reducible_balance(
-					who,
-					preservation,
-					fortitude,
-				)
-				.into()
-			}
-			AssetId::NativeAssetId(NativeCurrency::Parent) => {
-				<orml_tokens::Pallet<T> as fungibles::Inspect<T::AccountId>>::reducible_balance(
-					AssetId::NativeAssetId(NativeCurrency::Parent),
-					who,
-					preservation,
-					fortitude,
-				)
-				.into()
-			}
-			_ => Self::balance(asset, who),
-		}
-	}
-
-	fn can_deposit(
-		asset: Self::AssetId,
-		who: &<T as SystemConfig>::AccountId,
-		amount: Self::Balance,
-		provenance: Provenance,
-	) -> DepositConsequence {
-		log::trace!(target: "fassets::impl_foreign_assets", "impl_fungible can_deposit");
-
-		match asset {
-			AssetId::NativeAssetId(NativeCurrency::Here) => {
-				<pallet_balances::Pallet<T> as fungible::Inspect<T::AccountId>>::can_deposit(
-					who,
-					amount.into(),
-					provenance,
-				)
-			}
-			AssetId::NativeAssetId(NativeCurrency::Parent) => {
-				<orml_tokens::Pallet<T> as fungibles::Inspect<T::AccountId>>::can_deposit(
-					AssetId::NativeAssetId(NativeCurrency::Parent),
-					who,
-					amount.into(),
-					provenance,
-				)
-			}
-			_ => {
-				if amount.is_zero() {
-					return DepositConsequence::Success;
-				}
-
-				let extential_deposit_value = T::ExistentialDeposit::get();
-				let ed_value: u128 = match extential_deposit_value.try_into() {
-					Ok(val) => val,
-					Err(_) => return DepositConsequence::CannotCreate,
-				};
-				let extential_deposit: Self::Balance = match ed_value.try_into() {
-					Ok(val) => val,
-					Err(_) => return DepositConsequence::CannotCreate,
-				};
-
-				let new_total_balance = match Self::balance(asset, who).checked_add(&amount) {
-					Some(x) => x,
-					None => return DepositConsequence::Overflow,
-				};
-
-				if new_total_balance < extential_deposit {
-					return DepositConsequence::BelowMinimum;
-				}
-
-				DepositConsequence::Success
-			}
-		}
-	}
-
-	fn can_withdraw(
-		asset: Self::AssetId,
-		who: &<T as SystemConfig>::AccountId,
-		amount: Self::Balance,
-	) -> WithdrawConsequence<Self::Balance> {
-		log::trace!(target: "fassets::impl_foreign_assets", "impl_fungible can_withdraw");
-		let value: u128 = match amount.try_into() {
-			Ok(val) => val,
-			Err(_) => return WithdrawConsequence::UnknownAsset,
-		};
-
-		match asset {
-			AssetId::NativeAssetId(NativeCurrency::Here) => {
-				let this_amount: <T as pallet_balances::Config>::Balance = match value.try_into() {
-					Ok(val) => val,
-					Err(_) => {
-						return WithdrawConsequence::UnknownAsset;
-					}
-				};
-				match <pallet_balances::Pallet<T> as fungible::Inspect<T::AccountId>>::can_withdraw(
-					who,
-					this_amount,
-				) {
-					WithdrawConsequence::BalanceLow => WithdrawConsequence::BalanceLow,
-					WithdrawConsequence::WouldDie => WithdrawConsequence::WouldDie,
-					WithdrawConsequence::UnknownAsset => WithdrawConsequence::UnknownAsset,
-					WithdrawConsequence::Underflow => WithdrawConsequence::Underflow,
-					WithdrawConsequence::Overflow => WithdrawConsequence::Overflow,
-					WithdrawConsequence::Frozen => WithdrawConsequence::Frozen,
-					WithdrawConsequence::Success => WithdrawConsequence::Success,
-					_ => WithdrawConsequence::BalanceLow,
-				}
-			}
-			AssetId::NativeAssetId(NativeCurrency::Parent) => {
-				let parent_amount: <T as orml_tokens::Config>::Balance = match value.try_into() {
-					Ok(val) => val,
-					Err(_) => {
-						return WithdrawConsequence::UnknownAsset;
-					}
-				};
-				match <orml_tokens::Pallet<T> as fungibles::Inspect<T::AccountId>>::can_withdraw(
-					AssetId::NativeAssetId(NativeCurrency::Parent),
-					who,
-					parent_amount,
-				) {
-					WithdrawConsequence::BalanceLow => WithdrawConsequence::BalanceLow,
-					WithdrawConsequence::WouldDie => WithdrawConsequence::WouldDie,
-					WithdrawConsequence::UnknownAsset => WithdrawConsequence::UnknownAsset,
-					WithdrawConsequence::Underflow => WithdrawConsequence::Underflow,
-					WithdrawConsequence::Overflow => WithdrawConsequence::Overflow,
-					WithdrawConsequence::Frozen => WithdrawConsequence::Frozen,
-					WithdrawConsequence::Success => WithdrawConsequence::Success,
-					_ => WithdrawConsequence::BalanceLow,
-				}
-			}
-			_ => match Self::balance(asset, who).checked_sub(&amount) {
-				Some(_) => WithdrawConsequence::Success,
-				None => WithdrawConsequence::BalanceLow,
-			},
-		}
-	}
-
-	fn asset_exists(asset: AssetId) -> bool {
-		match asset {
-			AssetId::NativeAssetId(_) => true,
-			AssetId::ForeignAssetId(fid) => <AssetBinding<T>>::contains_key(fid),
-		}
-	}
-}
-
-impl<T: Config> fungibles::Mutate<<T as SystemConfig>::AccountId> for Pallet<T>
-where
-	T: orml_tokens::Config<CurrencyId = AssetId>,
-	BalanceOf<T>: From<<T as pallet_balances::Config>::Balance>,
-	BalanceOf<T>: From<<T as orml_tokens::Config>::Balance>,
-	<T as pallet_balances::Config>::Balance: From<BalanceOf<T>>,
-	<T as orml_tokens::Config>::Balance: From<BalanceOf<T>>,
-	u128: From<BalanceOf<T>>,
-{
-	fn mint_into(
-		asset: Self::AssetId,
-		who: &<T as SystemConfig>::AccountId,
-		amount: Self::Balance,
-	) -> Result<BalanceOf<T>, DispatchError> {
-		//Self::do_mint(asset, who, amount, None)
-		log::trace!(target: "fassets::impl_foreign_assets", "impl_fungible mint_into {:?}", asset);
-
-		match asset {
-			AssetId::NativeAssetId(NativeCurrency::Here) => {
-				<pallet_balances::Pallet<T> as fungible::Mutate<T::AccountId>>::mint_into(
-					who,
-					amount.into(),
-				)
-				.map(Into::into)
-			}
-			AssetId::NativeAssetId(NativeCurrency::Parent) => {
-				<orml_tokens::Pallet<T> as fungibles::Mutate<T::AccountId>>::mint_into(
-					AssetId::NativeAssetId(NativeCurrency::Parent),
-					who,
-					amount.into(),
-				)
-				.map(Into::into)
-			}
-			AssetId::ForeignAssetId(fid) => {
-				let target_collection_id = match <AssetBinding<T>>::get(fid) {
-					Some(v) => v,
-					None => {
-						return Err(DispatchError::Other(
-							"Associated collection not found for asset",
-						))
-					}
-				};
-				let collection =
-					FungibleHandle::cast(<CollectionHandle<T>>::try_get(target_collection_id)?);
-				let account = T::CrossAccountId::from_sub(who.clone());
-
-				let amount_data: pallet_fungible::CreateItemData<T> =
-					(account.clone(), amount.into());
-
-				pallet_fungible::Pallet::<T>::create_item_foreign(
-					&collection,
-					&account,
-					amount_data,
-					&budget::Value::new(0),
-				)?;
-
-				Ok(amount)
-			}
-		}
-	}
-
-	fn burn_from(
-		asset: Self::AssetId,
-		who: &<T as SystemConfig>::AccountId,
-		amount: Self::Balance,
-		precision: Precision,
-		fortitude: Fortitude,
-	) -> Result<Self::Balance, DispatchError> {
-		// let f = DebitFlags { keep_alive: false, best_effort: false };
-		log::trace!(target: "fassets::impl_foreign_assets", "impl_fungible burn_from");
-
-		match asset {
-			AssetId::NativeAssetId(NativeCurrency::Here) => {
-				<pallet_balances::Pallet<T> as fungible::Mutate<T::AccountId>>::burn_from(
-					who,
-					amount.into(),
-					precision,
-					fortitude,
-				)
-				.map(Into::into)
-			}
-			AssetId::NativeAssetId(NativeCurrency::Parent) => {
-				<orml_tokens::Pallet<T> as fungibles::Mutate<T::AccountId>>::burn_from(
-					AssetId::NativeAssetId(NativeCurrency::Parent),
-					who,
-					amount.into(),
-					precision,
-					fortitude,
-				)
-				.map(Into::into)
-			}
-			AssetId::ForeignAssetId(fid) => {
-				let target_collection_id = match <AssetBinding<T>>::get(fid) {
-					Some(v) => v,
-					None => {
-						return Err(DispatchError::Other(
-							"Associated collection not found for asset",
-						))
-					}
-				};
-				let collection =
-					FungibleHandle::cast(<CollectionHandle<T>>::try_get(target_collection_id)?);
-				pallet_fungible::Pallet::<T>::burn_foreign(
-					&collection,
-					&T::CrossAccountId::from_sub(who.clone()),
-					amount.into(),
-				)?;
-
-				Ok(amount)
-			}
-		}
-	}
-
-	fn transfer(
-		asset: Self::AssetId,
-		source: &<T as SystemConfig>::AccountId,
-		dest: &<T as SystemConfig>::AccountId,
-		amount: Self::Balance,
-		preservation: Preservation,
-	) -> Result<Self::Balance, DispatchError> {
-		// let f = TransferFlags { keep_alive, best_effort: false, burn_dust: false };
-		log::trace!(target: "fassets::impl_foreign_assets", "impl_fungible transfer");
-
-		match asset {
-			AssetId::NativeAssetId(NativeCurrency::Here) => {
-				match <pallet_balances::Pallet<T> as fungible::Mutate<T::AccountId>>::transfer(
-					source,
-					dest,
-					amount.into(),
-					preservation,
-				) {
-					Ok(_) => Ok(amount),
-					Err(_) => Err(DispatchError::Other(
-						"Bad amount to relay chain value conversion",
-					)),
-				}
-			}
-			AssetId::NativeAssetId(NativeCurrency::Parent) => {
-				match <orml_tokens::Pallet<T> as fungibles::Mutate<T::AccountId>>::transfer(
-					AssetId::NativeAssetId(NativeCurrency::Parent),
-					source,
-					dest,
-					amount.into(),
-					preservation,
-				) {
-					Ok(_) => Ok(amount),
-					Err(e) => Err(e),
-				}
-			}
-			AssetId::ForeignAssetId(fid) => {
-				let target_collection_id = match <AssetBinding<T>>::get(fid) {
-					Some(v) => v,
-					None => {
-						return Err(DispatchError::Other(
-							"Associated collection not found for asset",
-						))
-					}
-				};
-				let collection =
-					FungibleHandle::cast(<CollectionHandle<T>>::try_get(target_collection_id)?);
-
-				pallet_fungible::Pallet::<T>::transfer(
-					&collection,
-					&T::CrossAccountId::from_sub(source.clone()),
-					&T::CrossAccountId::from_sub(dest.clone()),
-					amount.into(),
-					&budget::Value::new(0),
-				)
-				.map_err(|e| e.error)?;
-
-				Ok(amount)
-			}
-		}
-	}
-}
-
-#[cfg(not(debug_assertions))]
-extern "C" {
-	// This function does not exists, thus compilation will fail, if its call is
-	// not optimized away, which is only possible if it's not called at all.
-	//
-	// not(debug_assertions) is used to ensure compiler is dropping unused functions, as
-	// this option is enabled in release by defailt
-	//
-	// FIXME: maybe use build.rs, to ensure it will fail even in release with debug_assertions
-	// enabled?
-	fn unbalanced_fungible_is_called();
-}
-macro_rules! ensure_balanced {
-	() => {{
-		#[cfg(debug_assertions)]
-		panic!("unbalanced fungible methods should not be used");
-		#[cfg(not(debug_assertions))]
-		{
-			unsafe { unbalanced_fungible_is_called() };
-			unreachable!();
-		}
-	}};
-}
-
-impl<T: Config> fungibles::Unbalanced<<T as SystemConfig>::AccountId> for Pallet<T>
-where
-	T: orml_tokens::Config<CurrencyId = AssetId>,
-	BalanceOf<T>: From<<T as pallet_balances::Config>::Balance>,
-	BalanceOf<T>: From<<T as orml_tokens::Config>::Balance>,
-	<T as pallet_balances::Config>::Balance: From<BalanceOf<T>>,
-	<T as orml_tokens::Config>::Balance: From<BalanceOf<T>>,
-	u128: From<BalanceOf<T>>,
-{
-	fn handle_dust(_dust: fungibles::Dust<<T as SystemConfig>::AccountId, Self>) {
-		ensure_balanced!();
-	}
-	fn write_balance(
-		_asset: Self::AssetId,
-		_who: &<T as SystemConfig>::AccountId,
-		_amount: Self::Balance,
-	) -> Result<Option<Self::Balance>, DispatchError> {
-		ensure_balanced!();
-	}
-	fn set_total_issuance(_asset: Self::AssetId, _amount: Self::Balance) {
-		ensure_balanced!();
-	}
-}
modifiedpallets/foreign-assets/src/lib.rsdiffbeforeafterboth
--- a/pallets/foreign-assets/src/lib.rs
+++ b/pallets/foreign-assets/src/lib.rs
@@ -14,113 +14,38 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-//! # Foreign assets
-//!
-//! - [`Config`]
-//! - [`Call`]
-//! - [`Pallet`]
+//! # Foreign Assets
 //!
 //! ## Overview
 //!
-//! The foreign assests pallet provides functions for:
-//!
-//! - Local and foreign assets management. The foreign assets can be updated without runtime upgrade.
-//! - Bounds between asset and target collection for cross chain transfer and inner transfers.
-//!
-//! ## Overview
-//!
-//! Under construction
+//! The Foreign Assets is a proxy that maps XCM operations to the Unique Network's pallets logic.
 
 #![cfg_attr(not(feature = "std"), no_std)]
 #![allow(clippy::unused_unit)]
 
-use frame_support::{
-	dispatch::DispatchResult,
-	ensure,
-	pallet_prelude::*,
-	traits::{fungible, fungibles, Currency, EnsureOrigin},
-};
+use core::ops::Deref;
+
+use frame_support::{dispatch::DispatchResult, pallet_prelude::*, traits::EnsureOrigin, PalletId};
 use frame_system::pallet_prelude::*;
-use pallet_common::erc::CrossAccountId;
-use pallet_fungible::Pallet as PalletFungible;
-use scale_info::TypeInfo;
-use serde::{Deserialize, Serialize};
-use sp_runtime::{
-	traits::{One, Zero},
-	ArithmeticError,
+use pallet_common::{
+	dispatch::CollectionDispatch, erc::CrossAccountId, XcmExtensions, NATIVE_FUNGIBLE_COLLECTION_ID,
 };
-use sp_std::{boxed::Box, vec::Vec};
-use staging_xcm::{latest::MultiLocation, VersionedMultiLocation};
-// NOTE: MultiLocation is used in storages, we will need to do migration if upgrade the
-// MultiLocation to the XCM v3.
+use sp_runtime::traits::AccountIdConversion;
+use sp_std::{boxed::Box, vec, vec::Vec};
 use staging_xcm::{
 	opaque::latest::{prelude::XcmError, Weight},
-	v3::XcmContext,
+	v3::{prelude::*, MultiAsset, XcmContext},
+	VersionedAssetId,
 };
-use staging_xcm_executor::{traits::WeightTrader, Assets};
-use up_data_structs::{CollectionId, CollectionMode, CreateCollectionData, TokenId};
-
-// TODO: Move to primitives
-// Id of native currency.
-// 0 - QTZ\UNQ
-// 1 - KSM\DOT
-#[derive(
-	Clone,
-	Copy,
-	Eq,
-	PartialEq,
-	PartialOrd,
-	Ord,
-	MaxEncodedLen,
-	RuntimeDebug,
-	Encode,
-	Decode,
-	TypeInfo,
-	Serialize,
-	Deserialize,
-)]
-pub enum NativeCurrency {
-	Here = 0,
-	Parent = 1,
-}
-
-#[derive(
-	Clone,
-	Copy,
-	Eq,
-	PartialEq,
-	PartialOrd,
-	Ord,
-	MaxEncodedLen,
-	RuntimeDebug,
-	Encode,
-	Decode,
-	TypeInfo,
-	Serialize,
-	Deserialize,
-)]
-pub enum AssetId {
-	ForeignAssetId(ForeignAssetId),
-	NativeAssetId(NativeCurrency),
-}
-
-pub trait TryAsForeign<T, F> {
-	fn try_as_foreign(asset: T) -> Option<F>;
-}
-
-impl TryAsForeign<AssetId, ForeignAssetId> for AssetId {
-	fn try_as_foreign(asset: AssetId) -> Option<ForeignAssetId> {
-		match asset {
-			Self::ForeignAssetId(id) => Some(id),
-			_ => None,
-		}
-	}
-}
-
-pub type ForeignAssetId = u32;
-pub type CurrencyId = AssetId;
+use staging_xcm_executor::{
+	traits::{ConvertLocation, Error as XcmExecutorError, TransactAsset, WeightTrader},
+	Assets,
+};
+use up_data_structs::{
+	budget::ZeroBudget, CollectionId, CollectionMode, CollectionName, CollectionTokenPrefix,
+	CreateCollectionData, CreateFungibleData, CreateItemData, TokenId,
+};
 
-mod impl_fungibles;
 pub mod weights;
 
 #[cfg(feature = "runtime-benchmarks")]
@@ -128,44 +53,12 @@
 
 pub use module::*;
 pub use weights::WeightInfo;
-
-/// Type alias for currency balance.
-pub type BalanceOf<T> =
-	<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
-
-/// A mapping between ForeignAssetId and AssetMetadata.
-pub trait AssetIdMapping<ForeignAssetId, MultiLocation, AssetMetadata> {
-	/// Returns the AssetMetadata associated with a given ForeignAssetId.
-	fn get_asset_metadata(foreign_asset_id: ForeignAssetId) -> Option<AssetMetadata>;
-	/// Returns the MultiLocation associated with a given ForeignAssetId.
-	fn get_multi_location(foreign_asset_id: ForeignAssetId) -> Option<MultiLocation>;
-	/// Returns the CurrencyId associated with a given MultiLocation.
-	fn get_currency_id(multi_location: MultiLocation) -> Option<CurrencyId>;
-}
-
-pub struct XcmForeignAssetIdMapping<T>(sp_std::marker::PhantomData<T>);
-
-impl<T: Config> AssetIdMapping<ForeignAssetId, MultiLocation, AssetMetadata<BalanceOf<T>>>
-	for XcmForeignAssetIdMapping<T>
-{
-	fn get_asset_metadata(foreign_asset_id: ForeignAssetId) -> Option<AssetMetadata<BalanceOf<T>>> {
-		log::trace!(target: "fassets::asset_metadatas", "call");
-		Pallet::<T>::asset_metadatas(AssetId::ForeignAssetId(foreign_asset_id))
-	}
-
-	fn get_multi_location(foreign_asset_id: ForeignAssetId) -> Option<MultiLocation> {
-		log::trace!(target: "fassets::get_multi_location", "call");
-		Pallet::<T>::foreign_asset_locations(foreign_asset_id)
-	}
-
-	fn get_currency_id(multi_location: MultiLocation) -> Option<CurrencyId> {
-		log::trace!(target: "fassets::get_currency_id", "call");
-		Pallet::<T>::location_to_currency_ids(multi_location).map(AssetId::ForeignAssetId)
-	}
-}
 
 #[frame_support::pallet]
 pub mod module {
+	use pallet_common::CollectionIssuer;
+	use up_data_structs::CollectionDescription;
+
 	use super::*;
 
 	#[pallet::config]
@@ -173,44 +66,34 @@
 		frame_system::Config
 		+ pallet_common::Config
 		+ pallet_fungible::Config
-		+ orml_tokens::Config
 		+ pallet_balances::Config
 	{
 		/// The overarching event type.
 		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
 
-		/// Currency type for withdraw and balance storage.
-		type Currency: Currency<Self::AccountId>;
+		/// Origin for force registering of a foreign asset.
+		type ForceRegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;
 
-		/// Required origin for registering asset.
-		type RegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;
+		/// The ID of the foreign assets pallet.
+		type PalletId: Get<PalletId>;
+
+		/// Self-location of this parachain.
+		type SelfLocation: Get<MultiLocation>;
+
+		/// The converter from a MultiLocation to a CrossAccountId.
+		type LocationToAccountId: ConvertLocation<Self::CrossAccountId>;
 
 		/// Weight information for the extrinsics in this module.
 		type WeightInfo: WeightInfo;
-	}
-
-	pub type AssetName = BoundedVec<u8, ConstU32<32>>;
-	pub type AssetSymbol = BoundedVec<u8, ConstU32<7>>;
-
-	#[derive(Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)]
-	pub struct AssetMetadata<Balance> {
-		pub name: AssetName,
-		pub symbol: AssetSymbol,
-		pub decimals: u8,
-		pub minimal_balance: Balance,
 	}
 
 	#[pallet::error]
 	pub enum Error<T> {
-		/// The given location could not be used (e.g. because it cannot be expressed in the
-		/// desired version of XCM).
-		BadLocation,
-		/// MultiLocation existed
-		MultiLocationExisted,
-		/// AssetId not exists
-		AssetIdNotExists,
-		/// AssetId exists
-		AssetIdExisted,
+		/// The foreign asset is already registered.
+		ForeignAssetAlreadyRegistered,
+
+		/// The given asset ID could not be converted into the current XCM version.
+		BadForeignAssetId,
 	}
 
 	#[pallet::event]
@@ -218,64 +101,46 @@
 	pub enum Event<T: Config> {
 		/// The foreign asset registered.
 		ForeignAssetRegistered {
-			asset_id: ForeignAssetId,
-			asset_address: MultiLocation,
-			metadata: AssetMetadata<BalanceOf<T>>,
-		},
-		/// The foreign asset updated.
-		ForeignAssetUpdated {
-			asset_id: ForeignAssetId,
-			asset_address: MultiLocation,
-			metadata: AssetMetadata<BalanceOf<T>>,
-		},
-		/// The asset registered.
-		AssetRegistered {
-			asset_id: AssetId,
-			metadata: AssetMetadata<BalanceOf<T>>,
-		},
-		/// The asset updated.
-		AssetUpdated {
-			asset_id: AssetId,
-			metadata: AssetMetadata<BalanceOf<T>>,
+			collection_id: CollectionId,
+			asset_id: Box<VersionedAssetId>,
 		},
 	}
 
-	/// Next available Foreign AssetId ID.
-	///
-	/// NextForeignAssetId: ForeignAssetId
+	/// The corresponding collections of foreign assets.
 	#[pallet::storage]
-	#[pallet::getter(fn next_foreign_asset_id)]
-	pub type NextForeignAssetId<T: Config> = StorageValue<_, ForeignAssetId, ValueQuery>;
-	/// The storages for MultiLocations.
-	///
-	/// ForeignAssetLocations: map ForeignAssetId => Option<MultiLocation>
-	#[pallet::storage]
-	#[pallet::getter(fn foreign_asset_locations)]
-	pub type ForeignAssetLocations<T: Config> =
-		StorageMap<_, Twox64Concat, ForeignAssetId, staging_xcm::v3::MultiLocation, OptionQuery>;
+	#[pallet::getter(fn foreign_asset_to_collection)]
+	pub type ForeignAssetToCollection<T: Config> =
+		StorageMap<_, Twox64Concat, staging_xcm::v3::AssetId, CollectionId, OptionQuery>;
 
-	/// The storages for CurrencyIds.
-	///
-	/// LocationToCurrencyIds: map MultiLocation => Option<ForeignAssetId>
+	/// The corresponding foreign assets of collections.
 	#[pallet::storage]
-	#[pallet::getter(fn location_to_currency_ids)]
-	pub type LocationToCurrencyIds<T: Config> =
-		StorageMap<_, Twox64Concat, staging_xcm::v3::MultiLocation, ForeignAssetId, OptionQuery>;
+	#[pallet::getter(fn collection_to_foreign_asset)]
+	pub type CollectionToForeignAsset<T: Config> =
+		StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v3::AssetId, OptionQuery>;
 
-	/// The storages for AssetMetadatas.
-	///
-	/// AssetMetadatas: map AssetIds => Option<AssetMetadata>
+	/// The correponding NFT token id of reserve NFTs
 	#[pallet::storage]
-	#[pallet::getter(fn asset_metadatas)]
-	pub type AssetMetadatas<T: Config> =
-		StorageMap<_, Twox64Concat, AssetId, AssetMetadata<BalanceOf<T>>, OptionQuery>;
+	#[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)]
+	pub type ForeignReserveAssetInstanceToTokenId<T: Config> = StorageDoubleMap<
+		Hasher1 = Twox64Concat,
+		Key1 = CollectionId,
+		Hasher2 = Blake2_128Concat,
+		Key2 = staging_xcm::v3::AssetInstance,
+		Value = TokenId,
+		QueryKind = OptionQuery,
+	>;
 
-	/// The storages for assets to fungible collection binding
-	///
+	/// The correponding reserve NFT of a token ID
 	#[pallet::storage]
-	#[pallet::getter(fn asset_binding)]
-	pub type AssetBinding<T: Config> =
-		StorageMap<_, Twox64Concat, ForeignAssetId, CollectionId, OptionQuery>;
+	#[pallet::getter(fn token_id_to_foreign_reserve_asset_instance)]
+	pub type TokenIdToForeignReserveAssetInstance<T: Config> = StorageDoubleMap<
+		Hasher1 = Twox64Concat,
+		Key1 = CollectionId,
+		Hasher2 = Blake2_128Concat,
+		Key2 = TokenId,
+		Value = staging_xcm::v3::AssetInstance,
+		QueryKind = OptionQuery,
+	>;
 
 	#[pallet::pallet]
 	pub struct Pallet<T>(_);
@@ -284,196 +149,432 @@
 	impl<T: Config> Pallet<T> {
 		#[pallet::call_index(0)]
 		#[pallet::weight(<T as Config>::WeightInfo::register_foreign_asset())]
-		pub fn register_foreign_asset(
+		pub fn force_register_foreign_asset(
 			origin: OriginFor<T>,
-			owner: T::AccountId,
-			location: Box<VersionedMultiLocation>,
-			metadata: Box<AssetMetadata<BalanceOf<T>>>,
+			versioned_asset_id: Box<VersionedAssetId>,
+			name: CollectionName,
+			token_prefix: CollectionTokenPrefix,
+			mode: ForeignCollectionMode,
 		) -> DispatchResult {
-			T::RegisterOrigin::ensure_origin(origin.clone())?;
+			T::ForceRegisterOrigin::ensure_origin(origin.clone())?;
 
-			let location: MultiLocation = (*location)
+			let asset_id: AssetId = versioned_asset_id
+				.as_ref()
+				.clone()
 				.try_into()
-				.map_err(|()| Error::<T>::BadLocation)?;
+				.map_err(|()| Error::<T>::BadForeignAssetId)?;
 
-			let md = metadata.clone();
-			let name: Vec<u16> = md.name.into_iter().map(|x| x as u16).collect::<Vec<u16>>();
-			let mut description: Vec<u16> = "Foreign assets collection for "
+			ensure!(
+				!<ForeignAssetToCollection<T>>::contains_key(asset_id),
+				<Error<T>>::ForeignAssetAlreadyRegistered,
+			);
+
+			let foreign_collection_owner = Self::pallet_account();
+
+			let description: CollectionDescription = "Foreign Assets Collection"
 				.encode_utf16()
-				.collect::<Vec<u16>>();
-			description.append(&mut name.clone());
+				.collect::<Vec<_>>()
+				.try_into()
+				.expect("description length < max description length; qed");
+
+			let collection_id = T::CollectionDispatch::create(
+				foreign_collection_owner,
+				CollectionIssuer::Internals,
+				CreateCollectionData {
+					name,
+					token_prefix,
+					description,
+					mode: mode.into(),
+					..Default::default()
+				},
+			)?;
 
-			let data: CreateCollectionData<T::CrossAccountId> = CreateCollectionData {
-				name: name.try_into().unwrap(),
-				description: description.try_into().unwrap(),
-				mode: CollectionMode::Fungible(md.decimals),
-				..Default::default()
-			};
-			let owner = T::CrossAccountId::from_sub(owner);
-			let bounded_collection_id =
-				<PalletFungible<T>>::init_foreign_collection(owner.clone(), owner, data)?;
-			let foreign_asset_id =
-				Self::do_register_foreign_asset(&location, &metadata, bounded_collection_id)?;
+			<ForeignAssetToCollection<T>>::insert(asset_id, collection_id);
+			<CollectionToForeignAsset<T>>::insert(collection_id, asset_id);
 
 			Self::deposit_event(Event::<T>::ForeignAssetRegistered {
-				asset_id: foreign_asset_id,
-				asset_address: location,
-				metadata: *metadata,
+				collection_id,
+				asset_id: versioned_asset_id,
 			});
-			Ok(())
-		}
-
-		#[pallet::call_index(1)]
-		#[pallet::weight(<T as Config>::WeightInfo::update_foreign_asset())]
-		pub fn update_foreign_asset(
-			origin: OriginFor<T>,
-			foreign_asset_id: ForeignAssetId,
-			location: Box<VersionedMultiLocation>,
-			metadata: Box<AssetMetadata<BalanceOf<T>>>,
-		) -> DispatchResult {
-			T::RegisterOrigin::ensure_origin(origin)?;
-
-			let location: MultiLocation = (*location)
-				.try_into()
-				.map_err(|()| Error::<T>::BadLocation)?;
-			Self::do_update_foreign_asset(foreign_asset_id, &location, &metadata)?;
 
-			Self::deposit_event(Event::<T>::ForeignAssetUpdated {
-				asset_id: foreign_asset_id,
-				asset_address: location,
-				metadata: *metadata,
-			});
 			Ok(())
 		}
 	}
 }
 
 impl<T: Config> Pallet<T> {
-	fn get_next_foreign_asset_id() -> Result<ForeignAssetId, DispatchError> {
-		NextForeignAssetId::<T>::try_mutate(|current| -> Result<ForeignAssetId, DispatchError> {
-			let id = *current;
-			*current = current
-				.checked_add(One::one())
-				.ok_or(ArithmeticError::Overflow)?;
-			Ok(id)
-		})
+	fn pallet_account() -> T::CrossAccountId {
+		let owner: T::AccountId = T::PalletId::get().into_account_truncating();
+		T::CrossAccountId::from_sub(owner)
 	}
 
-	fn do_register_foreign_asset(
-		location: &MultiLocation,
-		metadata: &AssetMetadata<BalanceOf<T>>,
-		bounded_collection_id: CollectionId,
-	) -> Result<ForeignAssetId, DispatchError> {
-		let foreign_asset_id = Self::get_next_foreign_asset_id()?;
-		LocationToCurrencyIds::<T>::try_mutate(location, |maybe_currency_ids| -> DispatchResult {
-			ensure!(
-				maybe_currency_ids.is_none(),
-				Error::<T>::MultiLocationExisted
-			);
-			*maybe_currency_ids = Some(foreign_asset_id);
-			// *maybe_currency_ids = Some(CurrencyId::ForeignAsset(foreign_asset_id));
+	/// Converts a concrete asset ID (the asset multilocation) to a local collection on Unique Network.
+	///
+	/// The multilocation corresponds to a local collection if:
+	/// * It is `Here` location that corresponds to the native token of this parachain.
+	/// * It is `../Parachain(<Unique Network Para ID>)` that also corresponds to the native token of this parachain.
+	/// * It is `../Parachain(<Unique Network Para ID>)/GeneralIndex(<Collection ID>)` that corresponds
+	/// to the collection with the ID equal to `<Collection ID>`. The `<Collection ID>` must be in the valid range,
+	/// otherwise `None` is returned.
+	/// * It is `GeneralIndex(<Collection ID>)`. Same as the last one above.
+	///
+	/// If the multilocation doesn't match the patterns listed above,
+	/// or the `<Collection ID>` points to a foreign collection,
+	/// `None` is returned, identifying that the given multilocation doesn't correspond to a local collection.
+	fn local_asset_id_to_collection(asset_id: &AssetId) -> Option<CollectionLocality> {
+		let AssetId::Concrete(asset_location) = asset_id else {
+			return None;
+		};
 
-			ForeignAssetLocations::<T>::try_mutate(
-				foreign_asset_id,
-				|maybe_location| -> DispatchResult {
-					ensure!(maybe_location.is_none(), Error::<T>::MultiLocationExisted);
-					*maybe_location = Some(*location);
+		let self_location = T::SelfLocation::get();
 
-					AssetMetadatas::<T>::try_mutate(
-						AssetId::ForeignAssetId(foreign_asset_id),
-						|maybe_asset_metadatas| -> DispatchResult {
-							ensure!(maybe_asset_metadatas.is_none(), Error::<T>::AssetIdExisted);
-							*maybe_asset_metadatas = Some(metadata.clone());
-							Ok(())
-						},
-					)
-				},
-			)?;
+		if *asset_location == Here.into() || *asset_location == self_location {
+			return Some(CollectionLocality::Local(NATIVE_FUNGIBLE_COLLECTION_ID));
+		}
+
+		let prefix = if asset_location.parents == 0 {
+			&Here
+		} else if asset_location.parents == self_location.parents {
+			&self_location.interior
+		} else {
+			return None;
+		};
+
+		let GeneralIndex(collection_id) = asset_location.interior.match_and_split(prefix)? else {
+			return None;
+		};
 
-			AssetBinding::<T>::try_mutate(foreign_asset_id, |collection_id| -> DispatchResult {
-				*collection_id = Some(bounded_collection_id);
-				Ok(())
-			})
-		})?;
+		let collection_id = CollectionId((*collection_id).try_into().ok()?);
 
-		Ok(foreign_asset_id)
+		Self::collection_to_foreign_asset(collection_id)
+			.is_none()
+			.then_some(CollectionLocality::Local(collection_id))
+	}
+
+	/// Converts an asset ID to a Unique Network's collection locality (either foreign or a local one).
+	///
+	/// The function will check if the asset's reserve location has the corresponding
+	/// foreign collection on Unique Network,
+	/// and will return the "foreign" locality containing the collection ID if found.
+	///
+	/// If no corresponding foreign collection is found, the function will check
+	/// if the asset's reserve location corresponds to a local collection.
+	/// If the local collection is found, the "local" locality with the collection ID is returned.
+	///
+	/// If all of the above have failed, the `AssetIdConversionFailed` error will be returned.
+	fn asset_to_collection(asset_id: &AssetId) -> Result<CollectionLocality, XcmError> {
+		Self::foreign_asset_to_collection(asset_id)
+			.map(CollectionLocality::Foreign)
+			.or_else(|| Self::local_asset_id_to_collection(asset_id))
+			.ok_or_else(|| XcmExecutorError::AssetIdConversionFailed.into())
 	}
 
-	fn do_update_foreign_asset(
-		foreign_asset_id: ForeignAssetId,
-		location: &MultiLocation,
-		metadata: &AssetMetadata<BalanceOf<T>>,
+	/// Converts an XCM asset instance of local collection to the Unique Network's token ID.
+	///
+	/// The asset instance corresponds to the Unique Network's token ID if it is in the following format:
+	/// `AssetInstance::Index(<token ID>)`.
+	///
+	/// If the asset instance is not in the valid format or the `<token ID>` can't fit into the valid token ID,
+	/// `None` will be returned.
+	///
+	/// Note: this function can return `Some` containing the token ID of a non-existing NFT.
+	/// It returns `None` when it failed to convert the `asset_instance` to a local ID.
+	fn local_asset_instance_to_token_id(asset_instance: &AssetInstance) -> Option<TokenId> {
+		match asset_instance {
+			AssetInstance::Index(token_id) => Some(TokenId((*token_id).try_into().ok()?)),
+			_ => None,
+		}
+	}
+
+	/// Obtains the token ID of the `asset_instance` in the collection.
+	///
+	/// Note: this function can return `Some` containing the token ID of a non-existing NFT.
+	/// It returns `None` when it failed to convert the `asset_instance` to a local ID.
+	fn asset_instance_to_token_id(
+		collection_locality: CollectionLocality,
+		asset_instance: &AssetInstance,
+	) -> Option<TokenId> {
+		match collection_locality {
+			CollectionLocality::Local(_) => Self::local_asset_instance_to_token_id(asset_instance),
+			CollectionLocality::Foreign(collection_id) => {
+				Self::foreign_reserve_asset_instance_to_token_id(collection_id, asset_instance)
+			}
+		}
+	}
+
+	/// Creates a foreign item in the the collection.
+	fn create_foreign_asset_instance(
+		xcm_ext: &dyn XcmExtensions<T>,
+		collection_id: CollectionId,
+		asset_instance: &AssetInstance,
+		to: T::CrossAccountId,
 	) -> DispatchResult {
-		ForeignAssetLocations::<T>::try_mutate(
-			foreign_asset_id,
-			|maybe_multi_locations| -> DispatchResult {
-				let old_multi_locations = maybe_multi_locations
-					.as_mut()
-					.ok_or(Error::<T>::AssetIdNotExists)?;
+		let derivative_token_id = xcm_ext.create_item(
+			&Self::pallet_account(),
+			to,
+			CreateItemData::NFT(Default::default()),
+			&ZeroBudget,
+		)?;
+
+		<ForeignReserveAssetInstanceToTokenId<T>>::insert(
+			collection_id,
+			asset_instance,
+			derivative_token_id,
+		);
+
+		<TokenIdToForeignReserveAssetInstance<T>>::insert(
+			collection_id,
+			derivative_token_id,
+			asset_instance,
+		);
+
+		Ok(())
+	}
+
+	/// Deposits an asset instance to the `to` account.
+	///
+	/// Either transfers an existing item from the pallet's account
+	/// or creates a foreign item.
+	fn deposit_asset_instance(
+		xcm_ext: &dyn XcmExtensions<T>,
+		collection_locality: CollectionLocality,
+		asset_instance: &AssetInstance,
+		to: T::CrossAccountId,
+	) -> XcmResult {
+		let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance);
+
+		let deposit_result = match (collection_locality, token_id) {
+			(_, Some(token_id)) => {
+				let depositor = &Self::pallet_account();
+				let from = depositor;
+				let amount = 1;
+
+				xcm_ext.transfer_item(depositor, from, &to, token_id, amount, &ZeroBudget)
+			}
+			(CollectionLocality::Foreign(collection_id), None) => {
+				Self::create_foreign_asset_instance(xcm_ext, collection_id, asset_instance, to)
+			}
+			(CollectionLocality::Local(_), None) => {
+				return Err(XcmExecutorError::InstanceConversionFailed.into());
+			}
+		};
+
+		deposit_result
+			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item deposit failed"))
+	}
 
-				AssetMetadatas::<T>::try_mutate(
-					AssetId::ForeignAssetId(foreign_asset_id),
-					|maybe_asset_metadatas| -> DispatchResult {
-						ensure!(
-							maybe_asset_metadatas.is_some(),
-							Error::<T>::AssetIdNotExists
-						);
+	/// Withdraws an asset instance from the `from` account.
+	///
+	/// Transfers the asset instance to the pallet's account.
+	fn withdraw_asset_instance(
+		xcm_ext: &dyn XcmExtensions<T>,
+		collection_locality: CollectionLocality,
+		asset_instance: &AssetInstance,
+		from: T::CrossAccountId,
+	) -> XcmResult {
+		let token_id = Self::asset_instance_to_token_id(collection_locality, asset_instance)
+			.ok_or(XcmExecutorError::InstanceConversionFailed)?;
+
+		let depositor = &from;
+		let to = Self::pallet_account();
+		let amount = 1;
+		xcm_ext
+			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)
+			.map_err(|_| XcmError::FailedToTransactAsset("non-fungible item withdraw failed"))?;
+
+		Ok(())
+	}
+}
+
+impl<T: Config> TransactAsset for Pallet<T> {
+	fn can_check_in(
+		_origin: &MultiLocation,
+		_what: &MultiAsset,
+		_context: &XcmContext,
+	) -> XcmResult {
+		Err(XcmError::Unimplemented)
+	}
+
+	fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}
+
+	fn can_check_out(
+		_dest: &MultiLocation,
+		_what: &MultiAsset,
+		_context: &XcmContext,
+	) -> XcmResult {
+		Err(XcmError::Unimplemented)
+	}
+
+	fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}
+
+	fn deposit_asset(
+		what: &MultiAsset,
+		to: &MultiLocation,
+		_context: Option<&XcmContext>,
+	) -> XcmResult {
+		let to = T::LocationToAccountId::convert_location(to)
+			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;
+
+		let collection_locality = Self::asset_to_collection(&what.id)?;
+		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)
+			.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;
+
+		let collection = dispatch.as_dyn();
+		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::Unimplemented)?;
 
-						// modify location
-						if location != old_multi_locations {
-							LocationToCurrencyIds::<T>::remove(*old_multi_locations);
-							LocationToCurrencyIds::<T>::try_mutate(
-								location,
-								|maybe_currency_ids| -> DispatchResult {
-									ensure!(
-										maybe_currency_ids.is_none(),
-										Error::<T>::MultiLocationExisted
-									);
-									// *maybe_currency_ids = Some(CurrencyId::ForeignAsset(foreign_asset_id));
-									*maybe_currency_ids = Some(foreign_asset_id);
-									Ok(())
-								},
-							)?;
-						}
-						*maybe_asset_metadatas = Some(metadata.clone());
-						*old_multi_locations = *location;
-						Ok(())
-					},
+		match what.fun {
+			Fungibility::Fungible(amount) => xcm_ext
+				.create_item(
+					&Self::pallet_account(),
+					to,
+					CreateItemData::Fungible(CreateFungibleData { value: amount }),
+					&ZeroBudget,
 				)
-			},
-		)
+				.map(|_| ())
+				.map_err(|_| XcmError::FailedToTransactAsset("fungible item deposit failed")),
+
+			Fungibility::NonFungible(asset_instance) => {
+				Self::deposit_asset_instance(xcm_ext, collection_locality, &asset_instance, to)
+			}
+		}
+	}
+
+	fn withdraw_asset(
+		what: &MultiAsset,
+		from: &MultiLocation,
+		_maybe_context: Option<&XcmContext>,
+	) -> Result<staging_xcm_executor::Assets, XcmError> {
+		let from = T::LocationToAccountId::convert_location(from)
+			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;
+
+		let collection_locality = Self::asset_to_collection(&what.id)?;
+		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)
+			.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;
+
+		let collection = dispatch.as_dyn();
+		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;
+
+		match what.fun {
+			Fungibility::Fungible(amount) => xcm_ext
+				.burn_item(from, TokenId::default(), amount)
+				.map_err(|_| XcmError::FailedToTransactAsset("fungible item withdraw failed"))?,
+
+			Fungibility::NonFungible(asset_instance) => {
+				Self::withdraw_asset_instance(xcm_ext, collection_locality, &asset_instance, from)?;
+			}
+		}
+
+		Ok(what.clone().into())
+	}
+
+	fn internal_transfer_asset(
+		what: &MultiAsset,
+		from: &MultiLocation,
+		to: &MultiLocation,
+		_context: &XcmContext,
+	) -> Result<staging_xcm_executor::Assets, XcmError> {
+		let from = T::LocationToAccountId::convert_location(from)
+			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;
+
+		let to = T::LocationToAccountId::convert_location(to)
+			.ok_or(XcmExecutorError::AccountIdConversionFailed)?;
+
+		let collection_locality = Self::asset_to_collection(&what.id)?;
+
+		let dispatch = T::CollectionDispatch::dispatch(*collection_locality)
+			.map_err(|_| XcmExecutorError::AssetIdConversionFailed)?;
+		let collection = dispatch.as_dyn();
+		let xcm_ext = collection.xcm_extensions().ok_or(XcmError::NoPermission)?;
+
+		let depositor = &from;
+
+		let token_id;
+		let amount;
+		let map_error: fn(DispatchError) -> XcmError;
+
+		match what.fun {
+			Fungibility::Fungible(fungible_amount) => {
+				token_id = TokenId::default();
+				amount = fungible_amount;
+				map_error = |_| XcmError::FailedToTransactAsset("fungible item transfer failed");
+			}
+
+			Fungibility::NonFungible(asset_instance) => {
+				token_id = Self::asset_instance_to_token_id(collection_locality, &asset_instance)
+					.ok_or(XcmExecutorError::InstanceConversionFailed)?;
+
+				amount = 1;
+				map_error = |_| XcmError::FailedToTransactAsset("non-fungible item transfer failed")
+			}
+		}
+
+		xcm_ext
+			.transfer_item(depositor, &from, &to, token_id, amount, &ZeroBudget)
+			.map_err(map_error)?;
+
+		Ok(what.clone().into())
 	}
 }
 
-pub use frame_support::{
-	traits::{
-		fungibles::Balanced, tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT,
-	},
-	weights::{WeightToFee, WeightToFeePolynomial},
-};
+#[derive(Clone, Copy)]
+pub enum CollectionLocality {
+	Local(CollectionId),
+	Foreign(CollectionId),
+}
+
+impl Deref for CollectionLocality {
+	type Target = CollectionId;
 
-pub struct FreeForAll<
-	WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
-	AssetId: Get<MultiLocation>,
-	AccountId,
-	Currency: CurrencyT<AccountId>,
-	OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
->(
-	Weight,
-	Currency::Balance,
-	PhantomData<(WeightToFee, AssetId, AccountId, Currency, OnUnbalanced)>,
-);
+	fn deref(&self) -> &Self::Target {
+		match self {
+			Self::Local(id) => id,
+			Self::Foreign(id) => id,
+		}
+	}
+}
 
-impl<
-		WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
-		AssetId: Get<MultiLocation>,
-		AccountId,
-		Currency: CurrencyT<AccountId>,
-		OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
-	> WeightTrader for FreeForAll<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>
+pub struct CurrencyIdConvert<T: Config>(PhantomData<T>);
+impl<T: Config> sp_runtime::traits::Convert<CollectionId, Option<MultiLocation>>
+	for CurrencyIdConvert<T>
 {
+	fn convert(collection_id: CollectionId) -> Option<MultiLocation> {
+		if collection_id == NATIVE_FUNGIBLE_COLLECTION_ID {
+			Some(T::SelfLocation::get())
+		} else {
+			<Pallet<T>>::collection_to_foreign_asset(collection_id)
+				.and_then(|asset_id| match asset_id {
+					AssetId::Concrete(location) => Some(location),
+					_ => None,
+				})
+				.or_else(|| {
+					T::SelfLocation::get()
+						.pushed_with_interior(GeneralIndex(collection_id.0.into()))
+						.ok()
+				})
+		}
+	}
+}
+
+#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]
+pub enum ForeignCollectionMode {
+	NFT,
+	Fungible(u8),
+}
+
+impl From<ForeignCollectionMode> for CollectionMode {
+	fn from(value: ForeignCollectionMode) -> Self {
+		match value {
+			ForeignCollectionMode::NFT => Self::NFT,
+			ForeignCollectionMode::Fungible(decimals) => Self::Fungible(decimals),
+		}
+	}
+}
+
+pub struct FreeForAll;
+
+impl WeightTrader for FreeForAll {
 	fn new() -> Self {
-		Self(Weight::default(), Zero::zero(), PhantomData)
+		Self
 	}
 
 	fn buy_weight(
@@ -484,17 +585,5 @@
 	) -> Result<Assets, XcmError> {
 		log::trace!(target: "fassets::weight", "buy_weight weight: {:?}, payment: {:?}", weight, payment);
 		Ok(payment)
-	}
-}
-impl<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced> Drop
-	for FreeForAll<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>
-where
-	WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
-	AssetId: Get<MultiLocation>,
-	Currency: CurrencyT<AccountId>,
-	OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
-{
-	fn drop(&mut self) {
-		OnUnbalanced::on_unbalanced(Currency::issue(self.1));
 	}
 }
modifiedpallets/fungible/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/fungible/src/benchmarking.rs
+++ b/pallets/fungible/src/benchmarking.rs
@@ -15,7 +15,9 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 use frame_benchmarking::{account, v2::*};
-use pallet_common::{bench_init, benchmarking::create_collection_raw};
+use pallet_common::{
+	bench_init, benchmarking::create_collection_raw, CollectionIssuer, Pallet as PalletCommon,
+};
 use sp_std::prelude::*;
 use up_data_structs::{budget::Unlimited, CollectionMode, MAX_ITEMS_PER_BATCH};
 
@@ -30,7 +32,9 @@
 	create_collection_raw(
 		owner,
 		CollectionMode::Fungible(0),
-		|owner: T::CrossAccountId, data| <Pallet<T>>::init_collection(owner.clone(), owner, data),
+		|owner: T::CrossAccountId, data| {
+			<PalletCommon<T>>::init_collection(owner.clone(), CollectionIssuer::User(owner), data)
+		},
 		FungibleHandle::cast,
 	)
 }
modifiedpallets/fungible/src/common.rsdiffbeforeafterboth
--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -19,7 +19,7 @@
 use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight};
 use pallet_common::{
 	weights::WeightInfo as _, with_weight, CommonCollectionOperations, CommonWeightInfo,
-	Error as CommonError, RefungibleExtensions, SelfWeightOf as PalletCommonWeightOf,
+	Error as CommonError, SelfWeightOf as PalletCommonWeightOf, XcmExtensions,
 };
 use sp_runtime::{ArithmeticError, DispatchError};
 use sp_std::{vec, vec::Vec};
@@ -114,7 +114,7 @@
 				<Pallet<T>>::create_item(self, &sender, (to, fungible_data.value), nesting_budget),
 				<CommonWeights<T>>::create_item(&data),
 			),
-			_ => fail!(<Error<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
+			_ => fail!(<CommonError<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
 		}
 	}
 
@@ -133,7 +133,7 @@
 						.checked_add(data.value)
 						.ok_or(ArithmeticError::Overflow)?;
 				}
-				_ => fail!(<Error<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
+				_ => fail!(<CommonError<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
 			}
 		}
 
@@ -152,7 +152,7 @@
 		let weight = <CommonWeights<T>>::create_multiple_items_ex(&data);
 		let data = match data {
 			up_data_structs::CreateItemExData::Fungible(f) => f,
-			_ => fail!(<Error<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
+			_ => fail!(<CommonError<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
 		};
 
 		with_weight(
@@ -426,10 +426,6 @@
 			return 0;
 		}
 		<Allowance<T>>::get((self.id, sender, spender))
-	}
-
-	fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>> {
-		None
 	}
 
 	fn total_pieces(&self, token: TokenId) -> Option<u128> {
@@ -439,6 +435,10 @@
 		<TotalSupply<T>>::try_get(self.id).ok()
 	}
 
+	fn xcm_extensions(&self) -> Option<&dyn XcmExtensions<T>> {
+		Some(self)
+	}
+
 	fn set_allowance_for_all(
 		&self,
 		_owner: T::CrossAccountId,
@@ -457,3 +457,57 @@
 		fail!(<Error<T>>::FungibleTokensAreAlwaysValid)
 	}
 }
+
+impl<T: Config> XcmExtensions<T> for FungibleHandle<T> {
+	fn create_item_internal(
+		&self,
+		depositor: &<T>::CrossAccountId,
+		to: <T>::CrossAccountId,
+		data: CreateItemData,
+		nesting_budget: &dyn Budget,
+	) -> Result<TokenId, sp_runtime::DispatchError> {
+		match &data {
+			up_data_structs::CreateItemData::Fungible(fungible_data) => {
+				<Pallet<T>>::create_multiple_items(
+					self,
+					depositor,
+					[(to, fungible_data.value)].into_iter().collect(),
+					nesting_budget,
+				)?
+			}
+			_ => fail!(<CommonError<T>>::NotFungibleDataUsedToMintFungibleCollectionToken),
+		}
+
+		Ok(TokenId::default())
+	}
+
+	fn transfer_item_internal(
+		&self,
+		depositor: &<T>::CrossAccountId,
+		from: &<T>::CrossAccountId,
+		to: &<T>::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+		nesting_budget: &dyn Budget,
+	) -> sp_runtime::DispatchResult {
+		ensure!(
+			token == TokenId::default(),
+			<CommonError<T>>::FungibleItemsHaveNoId
+		);
+
+		<Pallet<T>>::transfer_internal(self, depositor, from, to, amount, nesting_budget)
+			.map(|_| ())
+			.map_err(|post_info| post_info.error)
+	}
+
+	fn burn_item_internal(
+		&self,
+		from: <T>::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+	) -> sp_runtime::DispatchResult {
+		<Self as CommonCollectionOperations<T>>::burn_item(self, from, token, amount)
+			.map(|_| ())
+			.map_err(|post_info| post_info.error)
+	}
+}
modifiedpallets/fungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -95,8 +95,8 @@
 use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};
 use sp_std::{collections::btree_map::BTreeMap, vec::Vec};
 use up_data_structs::{
-	budget::Budget, mapping::TokenAddressMapping, AccessMode, CollectionId, CreateCollectionData,
-	Property, PropertyKey, TokenId,
+	budget::Budget, mapping::TokenAddressMapping, AccessMode, CollectionId, Property, PropertyKey,
+	TokenId,
 };
 use weights::WeightInfo;
 
@@ -121,8 +121,6 @@
 
 	#[pallet::error]
 	pub enum Error<T> {
-		/// Not Fungible item data used to mint in Fungible collection.
-		NotFungibleDataUsedToMintFungibleCollectionToken,
 		/// Tried to set data for fungible item.
 		FungibleItemsDontHaveData,
 		/// Fungible token does not support nesting.
@@ -212,24 +210,6 @@
 
 /// Pallet implementation for fungible assets
 impl<T: Config> Pallet<T> {
-	/// Initializes the collection. Returns [CollectionId] on success, [DispatchError] otherwise.
-	pub fn init_collection(
-		owner: T::CrossAccountId,
-		payer: T::CrossAccountId,
-		data: CreateCollectionData<T::CrossAccountId>,
-	) -> Result<CollectionId, DispatchError> {
-		<PalletCommon<T>>::init_collection(owner, payer, data)
-	}
-
-	/// Initializes the collection with ForeignCollection flag. Returns [CollectionId] on success, [DispatchError] otherwise.
-	pub fn init_foreign_collection(
-		owner: T::CrossAccountId,
-		payer: T::CrossAccountId,
-		data: CreateCollectionData<T::CrossAccountId>,
-	) -> Result<CollectionId, DispatchError> {
-		<PalletCommon<T>>::init_foreign_collection(owner, payer, data)
-	}
-
 	/// Destroys a collection.
 	pub fn destroy_collection(
 		collection: FungibleHandle<T>,
@@ -293,54 +273,11 @@
 		let balance = <Balance<T>>::get((collection.id, owner))
 			.checked_sub(amount)
 			.ok_or(<CommonError<T>>::TokenValueTooLow)?;
-
-		// Foreign collection check
-		ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);
 
 		if collection.permissions.access() == AccessMode::AllowList {
 			collection.check_allowlist(owner)?;
-		}
-
-		// =========
-
-		if balance == 0 {
-			<Balance<T>>::remove((collection.id, owner));
-			<PalletStructure<T>>::unnest_if_nested(owner, collection.id, TokenId::default());
-		} else {
-			<Balance<T>>::insert((collection.id, owner), balance);
 		}
-		<TotalSupply<T>>::insert(collection.id, total_supply);
-
-		<PalletEvm<T>>::deposit_log(
-			ERC20Events::Transfer {
-				from: *owner.as_eth(),
-				to: H160::default(),
-				value: amount.into(),
-			}
-			.to_log(collection_id_to_address(collection.id)),
-		);
-		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(
-			collection.id,
-			TokenId::default(),
-			owner.clone(),
-			amount,
-		));
-		Ok(())
-	}
 
-	/// Burns the specified amount of the token.
-	pub fn burn_foreign(
-		collection: &FungibleHandle<T>,
-		owner: &T::CrossAccountId,
-		amount: u128,
-	) -> DispatchResult {
-		let total_supply = <TotalSupply<T>>::get(collection.id)
-			.checked_sub(amount)
-			.ok_or(<CommonError<T>>::TokenValueTooLow)?;
-
-		let balance = <Balance<T>>::get((collection.id, owner))
-			.checked_sub(amount)
-			.ok_or(<CommonError<T>>::TokenValueTooLow)?;
 		// =========
 
 		if balance == 0 {
@@ -468,14 +405,25 @@
 	}
 
 	/// Minting tokens for multiple IDs.
-	/// It is a utility function used in [`create_multiple_items`][`Pallet::create_multiple_items`]
-	/// and [`create_multiple_items_foreign`][`Pallet::create_multiple_items_foreign`]
-	pub fn create_multiple_items_common(
+	/// See [`create_item`][`Pallet::create_item`] for more details.
+	pub fn create_multiple_items(
 		collection: &FungibleHandle<T>,
-		sender: &T::CrossAccountId,
+		depositor: &T::CrossAccountId,
 		data: BTreeMap<T::CrossAccountId, u128>,
 		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
+		if !collection.is_owner_or_admin(depositor) {
+			ensure!(
+				collection.permissions.mint_mode(),
+				<CommonError<T>>::PublicMintingNotAllowed
+			);
+			collection.check_allowlist(depositor)?;
+
+			for (owner, _) in data.iter() {
+				collection.check_allowlist(owner)?;
+			}
+		}
+
 		let total_supply = data
 			.values()
 			.copied()
@@ -486,7 +434,7 @@
 
 		for (to, _) in data.iter() {
 			<PalletStructure<T>>::check_nesting(
-				sender,
+				depositor,
 				to,
 				collection.id,
 				TokenId::default(),
@@ -532,44 +480,7 @@
 
 		Ok(())
 	}
-
-	/// Minting tokens for multiple IDs.
-	/// See [`create_item`][`Pallet::create_item`] for more details.
-	pub fn create_multiple_items(
-		collection: &FungibleHandle<T>,
-		sender: &T::CrossAccountId,
-		data: BTreeMap<T::CrossAccountId, u128>,
-		nesting_budget: &dyn Budget,
-	) -> DispatchResult {
-		// Foreign collection check
-		ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);
 
-		if !collection.is_owner_or_admin(sender) {
-			ensure!(
-				collection.permissions.mint_mode(),
-				<CommonError<T>>::PublicMintingNotAllowed
-			);
-			collection.check_allowlist(sender)?;
-
-			for (owner, _) in data.iter() {
-				collection.check_allowlist(owner)?;
-			}
-		}
-
-		Self::create_multiple_items_common(collection, sender, data, nesting_budget)
-	}
-
-	/// Minting tokens for multiple IDs.
-	/// See [`create_item_foreign`][`Pallet::create_item_foreign`] for more details.
-	pub fn create_multiple_items_foreign(
-		collection: &FungibleHandle<T>,
-		sender: &T::CrossAccountId,
-		data: BTreeMap<T::CrossAccountId, u128>,
-		nesting_budget: &dyn Budget,
-	) -> DispatchResult {
-		Self::create_multiple_items_common(collection, sender, data, nesting_budget)
-	}
-
 	fn set_allowance_unchecked(
 		collection: &FungibleHandle<T>,
 		owner: &T::CrossAccountId,
@@ -795,24 +706,6 @@
 		nesting_budget: &dyn Budget,
 	) -> DispatchResult {
 		Self::create_multiple_items(
-			collection,
-			sender,
-			[(data.0, data.1)].into_iter().collect(),
-			nesting_budget,
-		)
-	}
-
-	/// Creates fungible token.
-	///
-	/// - `data`: Contains user who will become the owners of the tokens and amount
-	///   of tokens he will receive.
-	pub fn create_item_foreign(
-		collection: &FungibleHandle<T>,
-		sender: &T::CrossAccountId,
-		data: CreateItemData<T>,
-		nesting_budget: &dyn Budget,
-	) -> DispatchResult {
-		Self::create_multiple_items_foreign(
 			collection,
 			sender,
 			[(data.0, data.1)].into_iter().collect(),
modifiedpallets/nonfungible/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/benchmarking.rs
+++ b/pallets/nonfungible/src/benchmarking.rs
@@ -18,6 +18,7 @@
 use pallet_common::{
 	bench_init,
 	benchmarking::{create_collection_raw, property_key, property_value},
+	CollectionIssuer, Pallet as PalletCommon,
 };
 use sp_std::prelude::*;
 use up_data_structs::{
@@ -56,7 +57,9 @@
 	create_collection_raw(
 		owner,
 		CollectionMode::NFT,
-		|owner: T::CrossAccountId, data| <Pallet<T>>::init_collection(owner.clone(), owner, data),
+		|owner: T::CrossAccountId, data| {
+			<PalletCommon<T>>::init_collection(owner.clone(), CollectionIssuer::User(owner), data)
+		},
 		NonfungibleHandle::cast,
 	)
 }
modifiedpallets/nonfungible/src/common.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -19,8 +19,8 @@
 use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight};
 use pallet_common::{
 	weights::WeightInfo as _, with_weight, write_token_properties_total_weight,
-	CommonCollectionOperations, CommonWeightInfo, RefungibleExtensions,
-	SelfWeightOf as PalletCommonWeightOf,
+	CommonCollectionOperations, CommonWeightInfo, SelfWeightOf as PalletCommonWeightOf,
+	XcmExtensions,
 };
 use pallet_structure::Pallet as PalletStructure;
 use sp_runtime::DispatchError;
@@ -533,10 +533,6 @@
 		} else {
 			0
 		}
-	}
-
-	fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>> {
-		None
 	}
 
 	fn total_pieces(&self, token: TokenId) -> Option<u128> {
@@ -547,6 +543,10 @@
 		}
 	}
 
+	fn xcm_extensions(&self) -> Option<&dyn XcmExtensions<T>> {
+		Some(self)
+	}
+
 	fn set_allowance_for_all(
 		&self,
 		owner: T::CrossAccountId,
@@ -570,3 +570,53 @@
 		)
 	}
 }
+
+impl<T: Config> XcmExtensions<T> for NonfungibleHandle<T> {
+	fn token_has_children(&self, token: TokenId) -> bool {
+		<Pallet<T>>::token_has_children(self.id, token)
+	}
+
+	fn create_item_internal(
+		&self,
+		depositor: &<T>::CrossAccountId,
+		to: <T>::CrossAccountId,
+		data: up_data_structs::CreateItemData,
+		nesting_budget: &dyn Budget,
+	) -> Result<TokenId, sp_runtime::DispatchError> {
+		<Pallet<T>>::create_multiple_items(
+			self,
+			depositor,
+			vec![map_create_data::<T>(data, &to)?],
+			nesting_budget,
+		)?;
+
+		Ok(self.last_token_id())
+	}
+
+	fn transfer_item_internal(
+		&self,
+		depositor: &<T>::CrossAccountId,
+		from: &<T>::CrossAccountId,
+		to: &<T>::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+		nesting_budget: &dyn Budget,
+	) -> sp_runtime::DispatchResult {
+		ensure!(amount == 1, <Error<T>>::NonfungibleItemsHaveNoAmount);
+
+		<Pallet<T>>::transfer_internal(self, depositor, from, to, token, nesting_budget)
+			.map(|_| ())
+			.map_err(|post_info| post_info.error)
+	}
+
+	fn burn_item_internal(
+		&self,
+		from: T::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+	) -> sp_runtime::DispatchResult {
+		ensure!(amount == 1, <Error<T>>::NonfungibleItemsHaveNoAmount);
+
+		<Pallet<T>>::burn(self, &from, token)
+	}
+}
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -117,8 +117,8 @@
 use sp_std::{collections::btree_map::BTreeMap, vec, vec::Vec};
 use up_data_structs::{
 	budget::Budget, mapping::TokenAddressMapping, AccessMode, AuxPropertyValue, CollectionId,
-	CreateCollectionData, CreateNftExData, CustomDataLimit, PropertiesPermissionMap, Property,
-	PropertyKey, PropertyKeyPermission, PropertyScope, PropertyValue, TokenChild, TokenId,
+	CreateNftExData, CustomDataLimit, PropertiesPermissionMap, Property, PropertyKey,
+	PropertyKeyPermission, PropertyScope, PropertyValue, TokenChild, TokenId,
 	TokenProperties as TokenPropertiesT,
 };
 use weights::WeightInfo;
@@ -383,19 +383,6 @@
 
 // unchecked calls skips any permission checks
 impl<T: Config> Pallet<T> {
-	/// Create NFT collection
-	///
-	/// `init_collection` will take non-refundable deposit for collection creation.
-	///
-	/// - `data`: Contains settings for collection limits and permissions.
-	pub fn init_collection(
-		owner: T::CrossAccountId,
-		payer: T::CrossAccountId,
-		data: CreateCollectionData<T::CrossAccountId>,
-	) -> Result<CollectionId, DispatchError> {
-		<PalletCommon<T>>::init_collection(owner, payer, data)
-	}
-
 	/// Destroy NFT collection
 	///
 	/// `destroy_collection` will throw error if collection contains any tokens.
modifiedpallets/refungible/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/refungible/src/benchmarking.rs
+++ b/pallets/refungible/src/benchmarking.rs
@@ -20,6 +20,7 @@
 use pallet_common::{
 	bench_init,
 	benchmarking::{create_collection_raw, property_key, property_value},
+	CollectionIssuer, Pallet as PalletCommon,
 };
 use sp_std::prelude::*;
 use up_data_structs::{
@@ -61,7 +62,9 @@
 	create_collection_raw(
 		owner,
 		CollectionMode::ReFungible,
-		|owner: T::CrossAccountId, data| <Pallet<T>>::init_collection(owner.clone(), owner, data),
+		|owner: T::CrossAccountId, data| {
+			<PalletCommon<T>>::init_collection(owner.clone(), CollectionIssuer::User(owner), data)
+		},
 		RefungibleHandle::cast,
 	)
 }
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -103,7 +103,7 @@
 use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};
 use sp_std::{collections::btree_map::BTreeMap, vec, vec::Vec};
 use up_data_structs::{
-	budget::Budget, mapping::TokenAddressMapping, AccessMode, CollectionId, CreateCollectionData,
+	budget::Budget, mapping::TokenAddressMapping, AccessMode, CollectionId,
 	CreateRefungibleExMultipleOwners, PropertiesPermissionMap, Property, PropertyKey,
 	PropertyKeyPermission, PropertyScope, PropertyValue, TokenId, TokenOwnerError,
 	TokenProperties as TokenPropertiesT, MAX_REFUNGIBLE_PIECES,
@@ -296,19 +296,6 @@
 
 // unchecked calls skips any permission checks
 impl<T: Config> Pallet<T> {
-	/// Create RFT collection
-	///
-	/// `init_collection` will take non-refundable deposit for collection creation.
-	///
-	/// - `data`: Contains settings for collection limits and permissions.
-	pub fn init_collection(
-		owner: T::CrossAccountId,
-		payer: T::CrossAccountId,
-		data: CreateCollectionData<T::CrossAccountId>,
-	) -> Result<CollectionId, DispatchError> {
-		<PalletCommon<T>>::init_collection(owner, payer, data)
-	}
-
 	/// Destroy RFT collection
 	///
 	/// `destroy_collection` will throw error if collection contains any tokens.
modifiedpallets/structure/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/structure/src/benchmarking.rs
+++ b/pallets/structure/src/benchmarking.rs
@@ -16,7 +16,7 @@
 
 use frame_benchmarking::v2::{account, benchmarks, BenchmarkError};
 use frame_support::traits::{fungible::Balanced, tokens::Precision, Get};
-use pallet_common::Config as CommonConfig;
+use pallet_common::{CollectionIssuer, Config as CommonConfig};
 use pallet_evm::account::CrossAccountId;
 use sp_std::vec;
 use up_data_structs::{
@@ -44,7 +44,7 @@
 		.unwrap();
 		T::CollectionDispatch::create(
 			caller_cross.clone(),
-			caller_cross.clone(),
+			CollectionIssuer::User(caller_cross.clone()),
 			CreateCollectionData {
 				mode: CollectionMode::NFT,
 				..Default::default()
modifiedpallets/unique/src/eth/mod.rsdiffbeforeafterboth
--- a/pallets/unique/src/eth/mod.rs
+++ b/pallets/unique/src/eth/mod.rs
@@ -26,7 +26,7 @@
 	dispatch::CollectionDispatch,
 	erc::{static_property::key, CollectionHelpersEvents},
 	eth::{self, collection_id_to_address, map_eth_to_id},
-	CollectionById, CollectionHandle, Pallet as PalletCommon,
+	CollectionById, CollectionHandle, CollectionIssuer, Pallet as PalletCommon,
 };
 use pallet_evm::{account::CrossAccountId, OnMethodCall, PrecompileHandle, PrecompileResult};
 use pallet_evm_coder_substrate::{
@@ -110,8 +110,12 @@
 	let collection_helpers_address =
 		T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());
 
-	let collection_id = T::CollectionDispatch::create(caller, collection_helpers_address, data)
-		.map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;
+	let collection_id = T::CollectionDispatch::create(
+		caller,
+		CollectionIssuer::User(collection_helpers_address),
+		data,
+	)
+	.map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;
 	let address = pallet_common::eth::collection_id_to_address(collection_id);
 	Ok(address)
 }
@@ -240,8 +244,12 @@
 		let collection_helpers_address =
 			T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());
 
-		let collection_id = T::CollectionDispatch::create(caller, collection_helpers_address, data)
-			.map_err(dispatch_to_evm::<T>)?;
+		let collection_id = T::CollectionDispatch::create(
+			caller,
+			CollectionIssuer::User(collection_helpers_address),
+			data,
+		)
+		.map_err(dispatch_to_evm::<T>)?;
 
 		let address = pallet_common::eth::collection_id_to_address(collection_id);
 		Ok(address)
@@ -274,8 +282,12 @@
 		check_sent_amount_equals_collection_creation_price::<T>(value)?;
 		let collection_helpers_address =
 			T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());
-		let collection_id = T::CollectionDispatch::create(caller, collection_helpers_address, data)
-			.map_err(dispatch_to_evm::<T>)?;
+		let collection_id = T::CollectionDispatch::create(
+			caller,
+			CollectionIssuer::User(collection_helpers_address),
+			data,
+		)
+		.map_err(dispatch_to_evm::<T>)?;
 
 		let address = pallet_common::eth::collection_id_to_address(collection_id);
 		Ok(address)
modifiedpallets/unique/src/eth/stubs/CollectionHelpers.soldiffbeforeafterboth
--- a/pallets/unique/src/eth/stubs/CollectionHelpers.sol
+++ b/pallets/unique/src/eth/stubs/CollectionHelpers.sol
@@ -199,7 +199,7 @@
 type CollectionFlags is uint8;
 
 library CollectionFlagsLib {
-	/// Tokens in foreign collections can be transferred, but not burnt
+	/// A collection of foreign assets
 	CollectionFlags constant foreignField = CollectionFlags.wrap(128);
 	/// Supports ERC721Metadata
 	CollectionFlags constant erc721metadataField = CollectionFlags.wrap(64);
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -93,7 +93,8 @@
 	use frame_system::{ensure_root, ensure_signed};
 	use pallet_common::{
 		dispatch::{dispatch_tx, CollectionDispatch},
-		CollectionHandle, CommonWeightInfo, Pallet as PalletCommon, RefungibleExtensionsWeightInfo,
+		CollectionHandle, CollectionIssuer, CommonWeightInfo, Pallet as PalletCommon,
+		RefungibleExtensionsWeightInfo,
 	};
 	use pallet_evm::account::CrossAccountId;
 	use pallet_structure::weights::WeightInfo as StructureWeightInfo;
@@ -401,7 +402,11 @@
 
 			// =========
 			let sender = T::CrossAccountId::from_sub(sender);
-			let _id = T::CollectionDispatch::create(sender.clone(), sender, data)?;
+			let _id = T::CollectionDispatch::create(
+				sender.clone(),
+				CollectionIssuer::User(sender),
+				data,
+			)?;
 
 			Ok(())
 		}
modifiedprimitives/data-structs/src/budget.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/budget.rs
+++ b/primitives/data-structs/src/budget.rs
@@ -36,3 +36,10 @@
 		true
 	}
 }
+
+pub struct ZeroBudget;
+impl Budget for ZeroBudget {
+	fn consume_custom(&self, _calls: u32) -> bool {
+		false
+	}
+}
modifiedprimitives/data-structs/src/lib.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -378,7 +378,7 @@
 #[derive(AbiCoderFlags, Bitfields, Clone, Copy, PartialEq, Eq, Debug, Default)]
 #[bondrewd(enforce_bytes = 1)]
 pub struct CollectionFlags {
-	/// Tokens in foreign collections can be transferred, but not burnt
+	/// A collection of foreign assets
 	#[bondrewd(bits = "0..1")]
 	pub foreign: bool,
 	/// Supports ERC721Metadata
modifiedruntime/common/config/orml.rsdiffbeforeafterboth
--- a/runtime/common/config/orml.rs
+++ b/runtime/common/config/orml.rs
@@ -14,31 +14,21 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-use frame_support::{
-	parameter_types,
-	traits::{Contains, Everything},
-};
+use frame_support::{parameter_types, traits::Everything};
 use frame_system::EnsureSigned;
 use orml_traits::{location::AbsoluteReserveProvider, parameter_type_with_key};
-use pallet_foreign_assets::{CurrencyId, NativeCurrency};
+use pallet_foreign_assets::CurrencyIdConvert;
 use sp_runtime::traits::Convert;
-use sp_std::{vec, vec::Vec};
 use staging_xcm::latest::{Junction::*, Junctions::*, MultiLocation, Weight};
 use staging_xcm_executor::XcmExecutor;
 use up_common::{
 	constants::*,
 	types::{AccountId, Balance},
 };
+use up_data_structs::CollectionId;
 
 use crate::{
-	runtime_common::config::{
-		pallets::TreasuryAccountId,
-		substrate::{MaxLocks, MaxReserves},
-		xcm::{
-			xcm_assets::CurrencyIdConvert, SelfLocation, UniversalLocation, Weigher,
-			XcmExecutorConfig,
-		},
-	},
+	runtime_common::config::xcm::{SelfLocation, UniversalLocation, Weigher, XcmExecutorConfig},
 	RelayChainBlockNumberProvider, Runtime, RuntimeEvent,
 };
 
@@ -59,29 +49,6 @@
 	};
 }
 
-parameter_type_with_key! {
-	pub ExistentialDeposits: |currency_id: CurrencyId| -> Balance {
-		match currency_id {
-			CurrencyId::NativeAssetId(symbol) => match symbol {
-				NativeCurrency::Here => 0,
-				NativeCurrency::Parent=> 0,
-			},
-			_ => 100_000
-		}
-	};
-}
-
-pub fn get_all_module_accounts() -> Vec<AccountId> {
-	vec![TreasuryAccountId::get()]
-}
-
-pub struct DustRemovalWhitelist;
-impl Contains<AccountId> for DustRemovalWhitelist {
-	fn contains(a: &AccountId) -> bool {
-		get_all_module_accounts().contains(a)
-	}
-}
-
 pub struct AccountIdToMultiLocation;
 impl Convert<AccountId, MultiLocation> for AccountIdToMultiLocation {
 	fn convert(account: AccountId) -> MultiLocation {
@@ -91,18 +58,6 @@
 		})
 		.into()
 	}
-}
-
-pub struct CurrencyHooks;
-impl orml_traits::currency::MutationHooks<AccountId, CurrencyId, Balance> for CurrencyHooks {
-	type OnDust = orml_tokens::TransferDust<Runtime, TreasuryAccountId>;
-	type OnSlash = ();
-	type PreTransfer = ();
-	type PostTransfer = ();
-	type PreDeposit = ();
-	type PostDeposit = ();
-	type OnNewTokenAccount = ();
-	type OnKilledTokenAccount = ();
 }
 
 impl orml_vesting::Config for Runtime {
@@ -113,29 +68,13 @@
 	type WeightInfo = ();
 	type MaxVestingSchedules = MaxVestingSchedules;
 	type BlockNumberProvider = RelayChainBlockNumberProvider<Runtime>;
-}
-
-impl orml_tokens::Config for Runtime {
-	type RuntimeEvent = RuntimeEvent;
-	type Balance = Balance;
-	type Amount = Amount;
-	type CurrencyId = CurrencyId;
-	type WeightInfo = ();
-	type ExistentialDeposits = ExistentialDeposits;
-	type CurrencyHooks = CurrencyHooks;
-	type MaxLocks = MaxLocks;
-	type MaxReserves = MaxReserves;
-	// TODO: Add all module accounts
-	type DustRemovalWhitelist = DustRemovalWhitelist;
-	/// The id type for named reserves.
-	type ReserveIdentifier = ();
 }
 
 impl orml_xtokens::Config for Runtime {
 	type RuntimeEvent = RuntimeEvent;
 	type Balance = Balance;
-	type CurrencyId = CurrencyId;
-	type CurrencyIdConvert = CurrencyIdConvert;
+	type CurrencyId = CollectionId;
+	type CurrencyIdConvert = CurrencyIdConvert<Self>;
 	type AccountIdToMultiLocation = AccountIdToMultiLocation;
 	type SelfLocation = SelfLocation;
 	type XcmExecutor = XcmExecutor<XcmExecutorConfig<Self>>;
modifiedruntime/common/config/pallets/foreign_asset.rsdiffbeforeafterboth
--- a/runtime/common/config/pallets/foreign_asset.rs
+++ b/runtime/common/config/pallets/foreign_asset.rs
@@ -1,10 +1,52 @@
-use up_common::types::AccountId;
+use frame_support::{parameter_types, PalletId};
+#[cfg(not(feature = "governance"))]
+use frame_system::EnsureRoot;
+use pallet_evm::account::CrossAccountId;
+use sp_core::H160;
+use staging_xcm::prelude::*;
+use staging_xcm_builder::AccountKey20Aliases;
+
+#[cfg(feature = "governance")]
+use crate::runtime_common::config::governance;
+use crate::{
+	runtime_common::config::{
+		ethereum::CrossAccountId as ConfigCrossAccountId,
+		xcm::{LocationToAccountId, SelfLocation},
+	},
+	RelayNetwork, Runtime, RuntimeEvent,
+};
+
+parameter_types! {
+	pub ForeignAssetPalletId: PalletId = PalletId(*b"frgnasts");
+}
+
+pub struct LocationToCrossAccountId;
+impl staging_xcm_executor::traits::ConvertLocation<ConfigCrossAccountId>
+	for LocationToCrossAccountId
+{
+	fn convert_location(location: &MultiLocation) -> Option<ConfigCrossAccountId> {
+		LocationToAccountId::convert_location(location)
+			.map(ConfigCrossAccountId::from_sub)
+			.or_else(|| {
+				let eth_address =
+					AccountKey20Aliases::<RelayNetwork, H160>::convert_location(location)?;
 
-use crate::{Balances, Runtime, RuntimeEvent};
+				Some(ConfigCrossAccountId::from_eth(eth_address))
+			})
+	}
+}
 
 impl pallet_foreign_assets::Config for Runtime {
 	type RuntimeEvent = RuntimeEvent;
-	type Currency = Balances;
-	type RegisterOrigin = frame_system::EnsureRoot<AccountId>;
+
+	#[cfg(feature = "governance")]
+	type ForceRegisterOrigin = governance::RootOrTechnicalCommitteeMember;
+
+	#[cfg(not(feature = "governance"))]
+	type ForceRegisterOrigin = EnsureRoot<Self::AccountId>;
+
+	type PalletId = ForeignAssetPalletId;
+	type SelfLocation = SelfLocation;
+	type LocationToAccountId = LocationToCrossAccountId;
 	type WeightInfo = pallet_foreign_assets::weights::SubstrateWeight<Self>;
 }
addedruntime/common/config/xcm.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/config/xcm.rs
@@ -0,0 +1,254 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+use cumulus_primitives_core::ParaId;
+use frame_support::{
+	parameter_types,
+	traits::{ConstU32, Everything, Get, Nothing, ProcessMessageError},
+};
+use frame_system::EnsureRoot;
+use orml_traits::location::AbsoluteReserveProvider;
+use orml_xcm_support::MultiNativeAsset;
+use pallet_foreign_assets::FreeForAll;
+use pallet_xcm::XcmPassthrough;
+use polkadot_parachain_primitives::primitives::Sibling;
+use polkadot_runtime_common::xcm_sender::NoPriceForMessageDelivery;
+use sp_std::marker::PhantomData;
+use staging_xcm::{
+	latest::{prelude::*, MultiLocation, Weight},
+	v3::Instruction,
+};
+use staging_xcm_builder::{
+	AccountId32Aliases, EnsureXcmOrigin, FixedWeightBounds, ParentIsPreset, RelayChainAsNative,
+	SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative,
+	SignedToAccountId32, SovereignSignedViaLocation,
+};
+use staging_xcm_executor::{
+	traits::{Properties, ShouldExecute},
+	XcmExecutor,
+};
+use up_common::types::AccountId;
+
+#[cfg(feature = "governance")]
+use crate::runtime_common::config::governance;
+use crate::{
+	xcm_barrier::Barrier, AllPalletsWithSystem, Balances, ForeignAssets, ParachainInfo,
+	ParachainSystem, PolkadotXcm, RelayNetwork, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin,
+	XcmpQueue,
+};
+
+parameter_types! {
+	pub const RelayLocation: MultiLocation = MultiLocation::parent();
+	pub RelayOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into();
+	pub UniversalLocation: InteriorMultiLocation = (
+		GlobalConsensus(crate::RelayNetwork::get()),
+		Parachain(ParachainInfo::get().into()),
+	).into();
+	pub SelfLocation: MultiLocation = MultiLocation::new(1, X1(Parachain(ParachainInfo::get().into())));
+
+	// One XCM operation is 1_000_000 weight - almost certainly a conservative estimate.
+	pub UnitWeightCost: Weight = Weight::from_parts(1_000_000, 1000); // ?
+	pub const MaxInstructions: u32 = 100;
+}
+
+/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used
+/// when determining ownership of accounts for asset transacting and when attempting to use XCM
+/// `Transact` in order to determine the dispatch Origin.
+pub type LocationToAccountId = (
+	// The parent (Relay-chain) origin converts to the default `AccountId`.
+	ParentIsPreset<AccountId>,
+	// Sibling parachain origins convert to AccountId via the `ParaId::into`.
+	SiblingParachainConvertsVia<Sibling, AccountId>,
+	// Straight up local `AccountId32` origins just alias directly to `AccountId`.
+	AccountId32Aliases<RelayNetwork, AccountId>,
+);
+
+/// No local origins on this chain are allowed to dispatch XCM sends/executions.
+pub type LocalOriginToLocation = (SignedToAccountId32<RuntimeOrigin, AccountId, RelayNetwork>,);
+
+/// The means for routing XCM messages which are not for local execution into the right message
+/// queues.
+pub type XcmRouter = (
+	// Two routers - use UMP to communicate with the relay chain:
+	cumulus_primitives_utility::ParentAsUmp<ParachainSystem, PolkadotXcm, ()>,
+	// ..and XCMP to communicate with the sibling chains.
+	XcmpQueue,
+);
+
+/// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance,
+/// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can
+/// biases the kind of local `Origin` it will become.
+pub type XcmOriginToTransactDispatchOrigin = (
+	// Sovereign account converter; this attempts to derive an `AccountId` from the origin location
+	// using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for
+	// foreign chains who want to have a local sovereign account on this chain which they control.
+	SovereignSignedViaLocation<LocationToAccountId, RuntimeOrigin>,
+	// Native converter for Relay-chain (Parent) location; will converts to a `Relay` origin when
+	// recognised.
+	RelayChainAsNative<RelayOrigin, RuntimeOrigin>,
+	// Native converter for sibling Parachains; will convert to a `SiblingPara` origin when
+	// recognised.
+	SiblingParachainAsNative<cumulus_pallet_xcm::Origin, RuntimeOrigin>,
+	// Native signed account converter; this just converts an `AccountId32` origin into a normal
+	// `Origin::Signed` origin of the same 32-byte value.
+	SignedAccountId32AsNative<RelayNetwork, RuntimeOrigin>,
+	// Xcm origins can be represented natively under the Xcm pallet's Xcm origin.
+	XcmPassthrough<RuntimeOrigin>,
+);
+
+pub trait TryPass {
+	fn try_pass<Call>(
+		origin: &MultiLocation,
+		message: &mut [Instruction<Call>],
+	) -> Result<(), ProcessMessageError>;
+}
+
+#[impl_trait_for_tuples::impl_for_tuples(30)]
+impl TryPass for Tuple {
+	fn try_pass<Call>(
+		origin: &MultiLocation,
+		message: &mut [Instruction<Call>],
+	) -> Result<(), ProcessMessageError> {
+		for_tuples!( #(
+			Tuple::try_pass(origin, message)?;
+		)* );
+
+		Ok(())
+	}
+}
+
+/// Deny executing the XCM if it matches any of the Deny filter regardless of anything else.
+/// If it passes the Deny, and matches one of the Allow cases then it is let through.
+pub struct DenyThenTry<Deny, Allow>(PhantomData<Deny>, PhantomData<Allow>)
+where
+	Deny: TryPass,
+	Allow: ShouldExecute;
+
+impl<Deny, Allow> ShouldExecute for DenyThenTry<Deny, Allow>
+where
+	Deny: TryPass,
+	Allow: ShouldExecute,
+{
+	fn should_execute<Call>(
+		origin: &MultiLocation,
+		message: &mut [Instruction<Call>],
+		max_weight: Weight,
+		properties: &mut Properties,
+	) -> Result<(), ProcessMessageError> {
+		Deny::try_pass(origin, message)?;
+		Allow::should_execute(origin, message, max_weight, properties)
+	}
+}
+
+pub type Weigher = FixedWeightBounds<UnitWeightCost, RuntimeCall, MaxInstructions>;
+
+pub type IsReserve = MultiNativeAsset<AbsoluteReserveProvider>;
+
+pub type Trader = FreeForAll;
+
+pub struct XcmExecutorConfig<T>(PhantomData<T>);
+impl<T> staging_xcm_executor::Config for XcmExecutorConfig<T>
+where
+	T: pallet_configuration::Config,
+{
+	type RuntimeCall = RuntimeCall;
+	type XcmSender = XcmRouter;
+	// How to withdraw and deposit an asset.
+	type AssetTransactor = ForeignAssets;
+	type OriginConverter = XcmOriginToTransactDispatchOrigin;
+	type IsReserve = IsReserve;
+	type IsTeleporter = (); // Teleportation is disabled
+	type UniversalLocation = UniversalLocation;
+	type Barrier = Barrier;
+	type Weigher = Weigher;
+	type Trader = Trader;
+	type ResponseHandler = PolkadotXcm;
+	type SubscriptionService = PolkadotXcm;
+	type PalletInstancesInfo = AllPalletsWithSystem;
+	type MaxAssetsIntoHolding = ConstU32<8>;
+
+	type AssetTrap = PolkadotXcm;
+	type AssetClaims = PolkadotXcm;
+	type AssetLocker = ();
+	type AssetExchanger = ();
+	type FeeManager = ();
+	type MessageExporter = ();
+	type UniversalAliases = Nothing;
+	type CallDispatcher = RuntimeCall;
+	type SafeCallFilter = Nothing;
+	type Aliasers = Nothing;
+}
+
+#[cfg(feature = "runtime-benchmarks")]
+parameter_types! {
+	pub ReachableDest: Option<MultiLocation> = Some(Parent.into());
+}
+
+impl pallet_xcm::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type SendXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, ()>;
+	type XcmRouter = XcmRouter;
+	type ExecuteXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
+	type XcmExecuteFilter = Everything;
+	type XcmExecutor = XcmExecutor<XcmExecutorConfig<Self>>;
+	type XcmTeleportFilter = Everything;
+	type XcmReserveTransferFilter = Everything;
+	type Weigher = FixedWeightBounds<UnitWeightCost, RuntimeCall, MaxInstructions>;
+	type RuntimeOrigin = RuntimeOrigin;
+	type RuntimeCall = RuntimeCall;
+	const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
+	type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion;
+	type UniversalLocation = UniversalLocation;
+	type Currency = Balances;
+	type CurrencyMatcher = ();
+	type TrustedLockers = ();
+	type SovereignAccountOf = LocationToAccountId;
+	type MaxLockers = ConstU32<8>;
+	type WeightInfo = crate::weights::xcm::SubstrateWeight<Runtime>;
+	type AdminOrigin = EnsureRoot<AccountId>;
+	type MaxRemoteLockConsumers = ConstU32<0>;
+	type RemoteLockConsumerIdentifier = ();
+	#[cfg(feature = "runtime-benchmarks")]
+	type ReachableDest = ReachableDest;
+}
+
+impl cumulus_pallet_xcm::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type XcmExecutor = XcmExecutor<XcmExecutorConfig<Self>>;
+}
+impl cumulus_pallet_xcmp_queue::Config for Runtime {
+	type WeightInfo = ();
+	type RuntimeEvent = RuntimeEvent;
+	type XcmExecutor = XcmExecutor<XcmExecutorConfig<Self>>;
+	type ChannelInfo = ParachainSystem;
+	type VersionWrapper = PolkadotXcm;
+	type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
+
+	#[cfg(feature = "governance")]
+	type ControllerOrigin = governance::RootOrTechnicalCommitteeMember;
+
+	#[cfg(not(feature = "governance"))]
+	type ControllerOrigin = frame_system::EnsureRoot<AccountId>;
+
+	type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin;
+	type PriceForSiblingDelivery = NoPriceForMessageDelivery<ParaId>;
+}
+
+impl cumulus_pallet_dmp_queue::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type XcmExecutor = XcmExecutor<XcmExecutorConfig<Self>>;
+	type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
+}
deletedruntime/common/config/xcm/foreignassets.rsdiffbeforeafterboth
--- a/runtime/common/config/xcm/foreignassets.rs
+++ /dev/null
@@ -1,205 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// This file is part of Unique Network.
-
-// Unique Network is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Unique Network is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-
-use frame_support::{parameter_types, traits::Get};
-use orml_traits::location::AbsoluteReserveProvider;
-use orml_xcm_support::MultiNativeAsset;
-use pallet_foreign_assets::{
-	AssetId, AssetIdMapping, CurrencyId, ForeignAssetId, FreeForAll, NativeCurrency, TryAsForeign,
-	XcmForeignAssetIdMapping,
-};
-use sp_runtime::traits::{Convert, MaybeEquivalence};
-use sp_std::marker::PhantomData;
-use staging_xcm::latest::{prelude::*, MultiAsset, MultiLocation};
-use staging_xcm_builder::{ConvertedConcreteId, FungiblesAdapter, NoChecking};
-use staging_xcm_executor::traits::{JustTry, TransactAsset};
-use up_common::types::{AccountId, Balance};
-
-use super::{LocationToAccountId, RelayLocation};
-use crate::{Balances, ForeignAssets, ParachainInfo, PolkadotXcm, Runtime};
-
-parameter_types! {
-	pub CheckingAccount: AccountId = PolkadotXcm::check_account();
-}
-
-pub struct AsInnerId<ConvertAssetId>(PhantomData<(AssetId, ConvertAssetId)>);
-impl<ConvertAssetId: MaybeEquivalence<AssetId, AssetId>> MaybeEquivalence<MultiLocation, AssetId>
-	for AsInnerId<ConvertAssetId>
-{
-	fn convert(id: &MultiLocation) -> Option<AssetId> {
-		log::trace!(
-			target: "xcm::AsInnerId::Convert",
-			"AsInnerId {:?}",
-			id
-		);
-
-		let parent = MultiLocation::parent();
-		let here = MultiLocation::here();
-		let self_location = MultiLocation::new(1, X1(Parachain(ParachainInfo::get().into())));
-
-		if *id == parent {
-			return ConvertAssetId::convert(&AssetId::NativeAssetId(NativeCurrency::Parent));
-		}
-
-		if *id == here || *id == self_location {
-			return ConvertAssetId::convert(&AssetId::NativeAssetId(NativeCurrency::Here));
-		}
-
-		match XcmForeignAssetIdMapping::<Runtime>::get_currency_id(*id) {
-			Some(AssetId::ForeignAssetId(foreign_asset_id)) => {
-				ConvertAssetId::convert(&AssetId::ForeignAssetId(foreign_asset_id))
-			}
-			_ => None,
-		}
-	}
-
-	fn convert_back(asset_id: &AssetId) -> Option<MultiLocation> {
-		log::trace!(
-			target: "xcm::AsInnerId::Reverse",
-			"AsInnerId",
-		);
-
-		let parent_id =
-			ConvertAssetId::convert(&AssetId::NativeAssetId(NativeCurrency::Parent)).unwrap();
-		let here_id =
-			ConvertAssetId::convert(&AssetId::NativeAssetId(NativeCurrency::Here)).unwrap();
-
-		if *asset_id == parent_id {
-			return Some(MultiLocation::parent());
-		}
-
-		if *asset_id == here_id {
-			return Some(MultiLocation::new(
-				1,
-				X1(Parachain(ParachainInfo::get().into())),
-			));
-		}
-
-		let fid = <AssetId as TryAsForeign<AssetId, ForeignAssetId>>::try_as_foreign(*asset_id)?;
-		XcmForeignAssetIdMapping::<Runtime>::get_multi_location(fid)
-	}
-}
-
-/// Means for transacting assets besides the native currency on this chain.
-pub type FungiblesTransactor = FungiblesAdapter<
-	// Use this fungibles implementation:
-	ForeignAssets,
-	// Use this currency when it is a fungible asset matching the given location or name:
-	ConvertedConcreteId<AssetId, Balance, AsInnerId<JustTry>, JustTry>,
-	// Convert an XCM MultiLocation into a local account id:
-	LocationToAccountId,
-	// Our chain's account ID type (we can't get away without mentioning it explicitly):
-	AccountId,
-	// No Checking for teleported assets since we disallow teleports at all.
-	NoChecking,
-	// The account to use for tracking teleports.
-	CheckingAccount,
->;
-
-/// Means for transacting assets on this chain.
-pub struct AssetTransactor;
-impl TransactAsset for AssetTransactor {
-	fn can_check_in(
-		_origin: &MultiLocation,
-		_what: &MultiAsset,
-		_context: &XcmContext,
-	) -> XcmResult {
-		Err(XcmError::Unimplemented)
-	}
-
-	fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}
-
-	fn can_check_out(
-		_dest: &MultiLocation,
-		_what: &MultiAsset,
-		_context: &XcmContext,
-	) -> XcmResult {
-		Err(XcmError::Unimplemented)
-	}
-
-	fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}
-
-	fn deposit_asset(
-		what: &MultiAsset,
-		who: &MultiLocation,
-		context: Option<&XcmContext>,
-	) -> XcmResult {
-		FungiblesTransactor::deposit_asset(what, who, context)
-	}
-
-	fn withdraw_asset(
-		what: &MultiAsset,
-		who: &MultiLocation,
-		maybe_context: Option<&XcmContext>,
-	) -> Result<staging_xcm_executor::Assets, XcmError> {
-		FungiblesTransactor::withdraw_asset(what, who, maybe_context)
-	}
-
-	fn internal_transfer_asset(
-		what: &MultiAsset,
-		from: &MultiLocation,
-		to: &MultiLocation,
-		context: &XcmContext,
-	) -> Result<staging_xcm_executor::Assets, XcmError> {
-		FungiblesTransactor::internal_transfer_asset(what, from, to, context)
-	}
-}
-
-pub type IsReserve = MultiNativeAsset<AbsoluteReserveProvider>;
-
-pub type Trader<T> = FreeForAll<
-	pallet_configuration::WeightToFee<T, Balance>,
-	RelayLocation,
-	AccountId,
-	Balances,
-	(),
->;
-
-pub struct CurrencyIdConvert;
-impl Convert<AssetId, Option<MultiLocation>> for CurrencyIdConvert {
-	fn convert(id: AssetId) -> Option<MultiLocation> {
-		match id {
-			AssetId::NativeAssetId(NativeCurrency::Here) => Some(MultiLocation::new(
-				1,
-				X1(Parachain(ParachainInfo::get().into())),
-			)),
-			AssetId::NativeAssetId(NativeCurrency::Parent) => Some(MultiLocation::parent()),
-			AssetId::ForeignAssetId(foreign_asset_id) => {
-				XcmForeignAssetIdMapping::<Runtime>::get_multi_location(foreign_asset_id)
-			}
-		}
-	}
-}
-
-impl Convert<MultiLocation, Option<CurrencyId>> for CurrencyIdConvert {
-	fn convert(location: MultiLocation) -> Option<CurrencyId> {
-		if location == MultiLocation::here()
-			|| location == MultiLocation::new(1, X1(Parachain(ParachainInfo::get().into())))
-		{
-			return Some(AssetId::NativeAssetId(NativeCurrency::Here));
-		}
-
-		if location == MultiLocation::parent() {
-			return Some(AssetId::NativeAssetId(NativeCurrency::Parent));
-		}
-
-		if let Some(currency_id) = XcmForeignAssetIdMapping::<Runtime>::get_currency_id(location) {
-			return Some(currency_id);
-		}
-
-		None
-	}
-}
deletedruntime/common/config/xcm/mod.rsdiffbeforeafterboth
--- a/runtime/common/config/xcm/mod.rs
+++ /dev/null
@@ -1,259 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// This file is part of Unique Network.
-
-// Unique Network is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Unique Network is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-
-use cumulus_primitives_core::ParaId;
-use frame_support::{
-	parameter_types,
-	traits::{ConstU32, Everything, Get, Nothing, ProcessMessageError},
-};
-use frame_system::EnsureRoot;
-use pallet_xcm::XcmPassthrough;
-use polkadot_parachain_primitives::primitives::Sibling;
-use polkadot_runtime_common::xcm_sender::NoPriceForMessageDelivery;
-use sp_std::marker::PhantomData;
-use staging_xcm::{
-	latest::{prelude::*, MultiLocation, Weight},
-	v3::Instruction,
-};
-use staging_xcm_builder::{
-	AccountId32Aliases, EnsureXcmOrigin, FixedWeightBounds, ParentIsPreset, RelayChainAsNative,
-	SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative,
-	SignedToAccountId32, SovereignSignedViaLocation,
-};
-use staging_xcm_executor::{
-	traits::{Properties, ShouldExecute},
-	XcmExecutor,
-};
-use up_common::types::AccountId;
-
-use crate::{
-	xcm_barrier::Barrier, AllPalletsWithSystem, Balances, ParachainInfo, ParachainSystem,
-	PolkadotXcm, RelayNetwork, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, XcmpQueue,
-};
-
-#[cfg(feature = "foreign-assets")]
-pub mod foreignassets;
-
-#[cfg(not(feature = "foreign-assets"))]
-pub mod nativeassets;
-
-#[cfg(feature = "foreign-assets")]
-pub use foreignassets as xcm_assets;
-#[cfg(not(feature = "foreign-assets"))]
-pub use nativeassets as xcm_assets;
-use xcm_assets::{AssetTransactor, IsReserve, Trader};
-
-#[cfg(feature = "governance")]
-use crate::runtime_common::config::governance;
-
-parameter_types! {
-	pub const RelayLocation: MultiLocation = MultiLocation::parent();
-	pub RelayOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into();
-	pub UniversalLocation: InteriorMultiLocation = (
-		GlobalConsensus(crate::RelayNetwork::get()),
-		Parachain(ParachainInfo::get().into()),
-	).into();
-	pub SelfLocation: MultiLocation = MultiLocation::new(1, X1(Parachain(ParachainInfo::get().into())));
-
-	// One XCM operation is 1_000_000 weight - almost certainly a conservative estimate.
-	pub UnitWeightCost: Weight = Weight::from_parts(1_000_000, 1000); // ?
-	pub const MaxInstructions: u32 = 100;
-}
-
-/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used
-/// when determining ownership of accounts for asset transacting and when attempting to use XCM
-/// `Transact` in order to determine the dispatch Origin.
-pub type LocationToAccountId = (
-	// The parent (Relay-chain) origin converts to the default `AccountId`.
-	ParentIsPreset<AccountId>,
-	// Sibling parachain origins convert to AccountId via the `ParaId::into`.
-	SiblingParachainConvertsVia<Sibling, AccountId>,
-	// Straight up local `AccountId32` origins just alias directly to `AccountId`.
-	AccountId32Aliases<RelayNetwork, AccountId>,
-);
-
-/// No local origins on this chain are allowed to dispatch XCM sends/executions.
-pub type LocalOriginToLocation = (SignedToAccountId32<RuntimeOrigin, AccountId, RelayNetwork>,);
-
-/// The means for routing XCM messages which are not for local execution into the right message
-/// queues.
-pub type XcmRouter = (
-	// Two routers - use UMP to communicate with the relay chain:
-	cumulus_primitives_utility::ParentAsUmp<ParachainSystem, PolkadotXcm, ()>,
-	// ..and XCMP to communicate with the sibling chains.
-	XcmpQueue,
-);
-
-/// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance,
-/// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can
-/// biases the kind of local `Origin` it will become.
-pub type XcmOriginToTransactDispatchOrigin = (
-	// Sovereign account converter; this attempts to derive an `AccountId` from the origin location
-	// using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for
-	// foreign chains who want to have a local sovereign account on this chain which they control.
-	SovereignSignedViaLocation<LocationToAccountId, RuntimeOrigin>,
-	// Native converter for Relay-chain (Parent) location; will converts to a `Relay` origin when
-	// recognised.
-	RelayChainAsNative<RelayOrigin, RuntimeOrigin>,
-	// Native converter for sibling Parachains; will convert to a `SiblingPara` origin when
-	// recognised.
-	SiblingParachainAsNative<cumulus_pallet_xcm::Origin, RuntimeOrigin>,
-	// Native signed account converter; this just converts an `AccountId32` origin into a normal
-	// `Origin::Signed` origin of the same 32-byte value.
-	SignedAccountId32AsNative<RelayNetwork, RuntimeOrigin>,
-	// Xcm origins can be represented natively under the Xcm pallet's Xcm origin.
-	XcmPassthrough<RuntimeOrigin>,
-);
-
-pub trait TryPass {
-	fn try_pass<Call>(
-		origin: &MultiLocation,
-		message: &mut [Instruction<Call>],
-	) -> Result<(), ProcessMessageError>;
-}
-
-#[impl_trait_for_tuples::impl_for_tuples(30)]
-impl TryPass for Tuple {
-	fn try_pass<Call>(
-		origin: &MultiLocation,
-		message: &mut [Instruction<Call>],
-	) -> Result<(), ProcessMessageError> {
-		for_tuples!( #(
-			Tuple::try_pass(origin, message)?;
-		)* );
-
-		Ok(())
-	}
-}
-
-/// Deny executing the XCM if it matches any of the Deny filter regardless of anything else.
-/// If it passes the Deny, and matches one of the Allow cases then it is let through.
-pub struct DenyThenTry<Deny, Allow>(PhantomData<Deny>, PhantomData<Allow>)
-where
-	Deny: TryPass,
-	Allow: ShouldExecute;
-
-impl<Deny, Allow> ShouldExecute for DenyThenTry<Deny, Allow>
-where
-	Deny: TryPass,
-	Allow: ShouldExecute,
-{
-	fn should_execute<Call>(
-		origin: &MultiLocation,
-		message: &mut [Instruction<Call>],
-		max_weight: Weight,
-		properties: &mut Properties,
-	) -> Result<(), ProcessMessageError> {
-		Deny::try_pass(origin, message)?;
-		Allow::should_execute(origin, message, max_weight, properties)
-	}
-}
-
-pub type Weigher = FixedWeightBounds<UnitWeightCost, RuntimeCall, MaxInstructions>;
-
-pub struct XcmExecutorConfig<T>(PhantomData<T>);
-impl<T> staging_xcm_executor::Config for XcmExecutorConfig<T>
-where
-	T: pallet_configuration::Config,
-{
-	type RuntimeCall = RuntimeCall;
-	type XcmSender = XcmRouter;
-	// How to withdraw and deposit an asset.
-	type AssetTransactor = AssetTransactor;
-	type OriginConverter = XcmOriginToTransactDispatchOrigin;
-	type IsReserve = IsReserve;
-	type IsTeleporter = (); // Teleportation is disabled
-	type UniversalLocation = UniversalLocation;
-	type Barrier = Barrier;
-	type Weigher = Weigher;
-	type Trader = Trader<T>;
-	type ResponseHandler = PolkadotXcm;
-	type SubscriptionService = PolkadotXcm;
-	type PalletInstancesInfo = AllPalletsWithSystem;
-	type MaxAssetsIntoHolding = ConstU32<8>;
-
-	type AssetTrap = PolkadotXcm;
-	type AssetClaims = PolkadotXcm;
-	type AssetLocker = ();
-	type AssetExchanger = ();
-	type FeeManager = ();
-	type MessageExporter = ();
-	type UniversalAliases = Nothing;
-	type CallDispatcher = RuntimeCall;
-	type SafeCallFilter = Nothing;
-	type Aliasers = Nothing;
-}
-
-#[cfg(feature = "runtime-benchmarks")]
-parameter_types! {
-	pub ReachableDest: Option<MultiLocation> = Some(Parent.into());
-}
-
-impl pallet_xcm::Config for Runtime {
-	type RuntimeEvent = RuntimeEvent;
-	type SendXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, ()>;
-	type XcmRouter = XcmRouter;
-	type ExecuteXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
-	type XcmExecuteFilter = Everything;
-	type XcmExecutor = XcmExecutor<XcmExecutorConfig<Self>>;
-	type XcmTeleportFilter = Everything;
-	type XcmReserveTransferFilter = Everything;
-	type Weigher = FixedWeightBounds<UnitWeightCost, RuntimeCall, MaxInstructions>;
-	type RuntimeOrigin = RuntimeOrigin;
-	type RuntimeCall = RuntimeCall;
-	const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
-	type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion;
-	type UniversalLocation = UniversalLocation;
-	type Currency = Balances;
-	type CurrencyMatcher = ();
-	type TrustedLockers = ();
-	type SovereignAccountOf = LocationToAccountId;
-	type MaxLockers = ConstU32<8>;
-	type WeightInfo = crate::weights::xcm::SubstrateWeight<Runtime>;
-	type AdminOrigin = EnsureRoot<AccountId>;
-	type MaxRemoteLockConsumers = ConstU32<0>;
-	type RemoteLockConsumerIdentifier = ();
-	#[cfg(feature = "runtime-benchmarks")]
-	type ReachableDest = ReachableDest;
-}
-
-impl cumulus_pallet_xcm::Config for Runtime {
-	type RuntimeEvent = RuntimeEvent;
-	type XcmExecutor = XcmExecutor<XcmExecutorConfig<Self>>;
-}
-impl cumulus_pallet_xcmp_queue::Config for Runtime {
-	type WeightInfo = ();
-	type RuntimeEvent = RuntimeEvent;
-	type XcmExecutor = XcmExecutor<XcmExecutorConfig<Self>>;
-	type ChannelInfo = ParachainSystem;
-	type VersionWrapper = PolkadotXcm;
-	type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
-
-	#[cfg(feature = "governance")]
-	type ControllerOrigin = governance::RootOrTechnicalCommitteeMember;
-
-	#[cfg(not(feature = "governance"))]
-	type ControllerOrigin = frame_system::EnsureRoot<AccountId>;
-
-	type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin;
-	type PriceForSiblingDelivery = NoPriceForMessageDelivery<ParaId>;
-}
-
-impl cumulus_pallet_dmp_queue::Config for Runtime {
-	type RuntimeEvent = RuntimeEvent;
-	type XcmExecutor = XcmExecutor<XcmExecutorConfig<Self>>;
-	type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
-}
deletedruntime/common/config/xcm/nativeassets.rsdiffbeforeafterboth
--- a/runtime/common/config/xcm/nativeassets.rs
+++ /dev/null
@@ -1,148 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// This file is part of Unique Network.
-
-// Unique Network is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Unique Network is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-
-use cumulus_primitives_core::XcmContext;
-use frame_support::{
-	traits::{tokens::currency::Currency as CurrencyT, Get, OnUnbalanced as OnUnbalancedT},
-	weights::WeightToFeePolynomial,
-};
-use pallet_foreign_assets::{AssetIds, NativeCurrency};
-use sp_runtime::traits::{CheckedConversion, Convert, Zero};
-use sp_std::marker::PhantomData;
-use staging_xcm::latest::{
-	AssetId::Concrete, Error as XcmError, Fungibility::Fungible as XcmFungible, Junction::*,
-	Junctions::*, MultiAsset, MultiLocation, Weight,
-};
-use staging_xcm_builder::{CurrencyAdapter, NativeAsset};
-use staging_xcm_executor::{
-	traits::{MatchesFungible, WeightTrader},
-	Assets,
-};
-use up_common::types::{AccountId, Balance};
-
-use super::{LocationToAccountId, RelayLocation};
-use crate::{Balances, ParachainInfo};
-
-pub struct OnlySelfCurrency;
-impl<B: TryFrom<u128>> MatchesFungible<B> for OnlySelfCurrency {
-	fn matches_fungible(a: &MultiAsset) -> Option<B> {
-		let paraid = Parachain(ParachainInfo::parachain_id().into());
-		match (&a.id, &a.fun) {
-			(
-				Concrete(MultiLocation {
-					parents: 1,
-					interior: X1(loc),
-				}),
-				XcmFungible(ref amount),
-			) if paraid == *loc => CheckedConversion::checked_from(*amount),
-			(
-				Concrete(MultiLocation {
-					parents: 0,
-					interior: Here,
-				}),
-				XcmFungible(ref amount),
-			) => CheckedConversion::checked_from(*amount),
-			_ => None,
-		}
-	}
-}
-
-/// Means for transacting assets on this chain.
-pub type LocalAssetTransactor = CurrencyAdapter<
-	// Use this currency:
-	Balances,
-	// Use this currency when it is a fungible asset matching the given location or name:
-	OnlySelfCurrency,
-	// Do a simple punn to convert an AccountId32 MultiLocation into a native chain account ID:
-	LocationToAccountId,
-	// Our chain's account ID type (we can't get away without mentioning it explicitly):
-	AccountId,
-	// We don't track any teleports.
-	(),
->;
-
-pub type AssetTransactor = LocalAssetTransactor;
-
-pub type IsReserve = NativeAsset;
-
-pub struct UsingOnlySelfCurrencyComponents<
-	WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
-	AssetId: Get<MultiLocation>,
-	AccountId,
-	Currency: CurrencyT<AccountId>,
-	OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
->(
-	Weight,
-	Currency::Balance,
-	PhantomData<(WeightToFee, AssetId, AccountId, Currency, OnUnbalanced)>,
-);
-impl<
-		WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
-		AssetId: Get<MultiLocation>,
-		AccountId,
-		Currency: CurrencyT<AccountId>,
-		OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
-	> WeightTrader
-	for UsingOnlySelfCurrencyComponents<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>
-{
-	fn new() -> Self {
-		// FIXME: benchmark
-		Self(Weight::from_parts(0, 0), Zero::zero(), PhantomData)
-	}
-
-	fn buy_weight(
-		&mut self,
-		_weight: Weight,
-		payment: Assets,
-		_xcm: &XcmContext,
-	) -> Result<Assets, XcmError> {
-		Ok(payment)
-	}
-}
-impl<
-		WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
-		AssetId: Get<MultiLocation>,
-		AccountId,
-		Currency: CurrencyT<AccountId>,
-		OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
-	> Drop
-	for UsingOnlySelfCurrencyComponents<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>
-{
-	fn drop(&mut self) {
-		OnUnbalanced::on_unbalanced(Currency::issue(self.1));
-	}
-}
-
-pub type Trader<T> = UsingOnlySelfCurrencyComponents<
-	pallet_configuration::WeightToFee<T, Balance>,
-	RelayLocation,
-	AccountId,
-	Balances,
-	(),
->;
-
-pub struct CurrencyIdConvert;
-impl Convert<AssetIds, Option<MultiLocation>> for CurrencyIdConvert {
-	fn convert(id: AssetIds) -> Option<MultiLocation> {
-		match id {
-			AssetIds::NativeAssetId(NativeCurrency::Here) => Some(MultiLocation::new(
-				1,
-				X1(Parachain(ParachainInfo::get().into())),
-			)),
-			_ => None,
-		}
-	}
-}
modifiedruntime/common/construct_runtime.rsdiffbeforeafterboth
--- a/runtime/common/construct_runtime.rs
+++ b/runtime/common/construct_runtime.rs
@@ -47,7 +47,7 @@
 				Vesting: orml_vesting = 37,
 
 				XTokens: orml_xtokens = 38,
-				Tokens: orml_tokens = 39,
+				// [REMOVED] Tokens: orml_tokens = 39,
 				// Contracts: pallet_contracts::{Pallet, Call, Storage, Event<T>} = 38,
 
 				#[cfg(feature = "governance")]
modifiedruntime/common/dispatch.rsdiffbeforeafterboth
--- a/runtime/common/dispatch.rs
+++ b/runtime/common/dispatch.rs
@@ -17,11 +17,9 @@
 use frame_support::{dispatch::DispatchResult, ensure, fail};
 use pallet_balances_adapter::NativeFungibleHandle;
 pub use pallet_common::dispatch::CollectionDispatch;
-#[cfg(not(feature = "refungible"))]
-use pallet_common::unsupported;
 use pallet_common::{
-	erc::CommonEvmHandler, eth::map_eth_to_id, CollectionById, CollectionHandle,
-	CommonCollectionOperations,
+	erc::CommonEvmHandler, eth::map_eth_to_id, CollectionById, CollectionHandle, CollectionIssuer,
+	CommonCollectionOperations, Pallet as PalletCommon,
 };
 use pallet_evm::{PrecompileHandle, PrecompileResult};
 use pallet_fungible::{FungibleHandle, Pallet as PalletFungible};
@@ -70,27 +68,25 @@
 
 	fn create(
 		sender: T::CrossAccountId,
-		payer: T::CrossAccountId,
+		issuer: CollectionIssuer<T::CrossAccountId>,
 		data: CreateCollectionData<T::CrossAccountId>,
 	) -> Result<CollectionId, DispatchError> {
-		let id = match data.mode {
-			CollectionMode::NFT => <PalletNonfungible<T>>::init_collection(sender, payer, data)?,
+		match data.mode {
 			CollectionMode::Fungible(decimal_points) => {
 				// check params
 				ensure!(
 					decimal_points <= MAX_DECIMAL_POINTS,
 					pallet_unique::Error::<T>::CollectionDecimalPointLimitExceeded
 				);
-				<PalletFungible<T>>::init_collection(sender, payer, data)?
 			}
-
-			#[cfg(feature = "refungible")]
-			CollectionMode::ReFungible => <PalletRefungible<T>>::init_collection(sender, payer, data)?,
 
 			#[cfg(not(feature = "refungible"))]
 			CollectionMode::ReFungible => return unsupported!(T),
+
+			_ => {}
 		};
-		Ok(id)
+
+		<PalletCommon<T>>::init_collection(sender, issuer, data)
 	}
 
 	fn destroy(sender: T::CrossAccountId, collection_id: CollectionId) -> DispatchResult {
modifiedruntime/opal/Cargo.tomldiffbeforeafterboth
--- a/runtime/opal/Cargo.toml
+++ b/runtime/opal/Cargo.toml
@@ -151,7 +151,6 @@
 	'up-rpc/std',
 	'up-sponsorship/std',
 
-	"orml-tokens/std",
 	"orml-traits/std",
 	"orml-vesting/std",
 	"orml-xcm-support/std",
@@ -179,7 +178,6 @@
 	'frame-system/try-runtime',
 	'frame-try-runtime',
 	'frame-try-runtime?/try-runtime',
-	'orml-tokens/try-runtime',
 	'orml-vesting/try-runtime',
 	'orml-xtokens/try-runtime',
 	'pallet-app-promotion/try-runtime',
@@ -254,7 +252,6 @@
 frame-support = { workspace = true }
 frame-system = { workspace = true }
 frame-system-rpc-runtime-api = { workspace = true }
-orml-tokens = { workspace = true }
 orml-traits = { workspace = true }
 orml-vesting = { workspace = true }
 orml-xcm-support = { workspace = true }
modifiedruntime/quartz/Cargo.tomldiffbeforeafterboth
--- a/runtime/quartz/Cargo.toml
+++ b/runtime/quartz/Cargo.toml
@@ -150,7 +150,6 @@
 	'up-rpc/std',
 	'up-sponsorship/std',
 
-	"orml-tokens/std",
 	"orml-traits/std",
 	"orml-vesting/std",
 	"orml-xcm-support/std",
@@ -175,7 +174,6 @@
 	'frame-support/try-runtime',
 	'frame-system/try-runtime',
 	'frame-try-runtime',
-	'orml-tokens/try-runtime',
 	'orml-vesting/try-runtime',
 	'orml-xtokens/try-runtime',
 	'pallet-app-promotion/try-runtime',
@@ -242,7 +240,6 @@
 frame-support = { workspace = true }
 frame-system = { workspace = true }
 frame-system-rpc-runtime-api = { workspace = true }
-orml-tokens = { workspace = true }
 orml-traits = { workspace = true }
 orml-vesting = { workspace = true }
 orml-xcm-support = { workspace = true }
modifiedruntime/tests/src/tests.rsdiffbeforeafterboth
--- a/runtime/tests/src/tests.rs
+++ b/runtime/tests/src/tests.rs
@@ -2357,7 +2357,8 @@
 #[test]
 fn total_number_collections_bound_neg() {
 	new_test_ext().execute_with(|| {
-		let origin1 = RuntimeOrigin::signed(1);
+		let user = 1;
+		let origin1 = RuntimeOrigin::signed(user);
 
 		for i in 1..=COLLECTION_NUMBER_LIMIT {
 			create_test_collection(&CollectionMode::NFT, CollectionId(i));
@@ -2376,6 +2377,7 @@
 		};
 
 		// 11-th collection in chain. Expects error
+		add_balance(user, CollectionCreationPrice::get() as u64 + 1);
 		assert_noop!(
 			Unique::create_collection_ex(origin1, data),
 			CommonError::<Test>::TotalCollectionsLimitExceeded
modifiedruntime/unique/Cargo.tomldiffbeforeafterboth
--- a/runtime/unique/Cargo.toml
+++ b/runtime/unique/Cargo.toml
@@ -147,7 +147,6 @@
 	'up-rpc/std',
 	'up-sponsorship/std',
 
-	"orml-tokens/std",
 	"orml-traits/std",
 	"orml-vesting/std",
 	"orml-xcm-support/std",
@@ -173,7 +172,6 @@
 	'frame-support/try-runtime',
 	'frame-system/try-runtime',
 	'frame-try-runtime',
-	'orml-tokens/try-runtime',
 	'orml-vesting/try-runtime',
 	'orml-xtokens/try-runtime',
 	'pallet-app-promotion/try-runtime',
@@ -245,7 +243,6 @@
 frame-support = { workspace = true }
 frame-system = { workspace = true }
 frame-system-rpc-runtime-api = { workspace = true }
-orml-tokens = { workspace = true }
 orml-traits = { workspace = true }
 orml-vesting = { workspace = true }
 orml-xcm-support = { workspace = true }