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
--- a/js-packages/tests/xcm/xcmUnique.test.ts
+++ b/js-packages/tests/xcm/xcmUnique.test.ts
@@ -20,7 +20,7 @@
 import {Event} from '@unique/playgrounds/unique.dev.js';
 import {hexToString, nToBigInt} from '@polkadot/util';
 import {ACALA_CHAIN, ASTAR_CHAIN, MOONBEAM_CHAIN, POLKADEX_CHAIN, SAFE_XCM_VERSION, STATEMINT_CHAIN, UNIQUE_CHAIN, expectFailedToTransact, expectUntrustedReserveLocationFail, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types.js';
-
+import {XcmTestHelper} from './xcm.types.js';
 
 const STATEMINT_PALLET_INSTANCE = 50;
 
@@ -50,6 +50,8 @@
 const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;
 const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;
 
+const testHelper = new XcmTestHelper('unique');
+
 describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => {
   let alice: IKeyringPair;
   let bob: IKeyringPair;
@@ -68,6 +70,8 @@
   let balanceBobRelayTokenBefore: bigint;
   let balanceBobRelayTokenAfter: bigint;
 
+  let usdtCollectionId: number;
+  let relayCollectionId: number;
 
   before(async () => {
     await usingPlaygrounds(async (helper, privateKey) => {
@@ -76,6 +80,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) => {
@@ -85,21 +91,25 @@
     });
 
     await usingStatemintPlaygrounds(statemintUrl, 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,
@@ -107,6 +117,8 @@
         USDT_ASSET_AMOUNT,
       );
 
+      const sovereignFundingAmount = 3_500_000_000n;
+
       // funding parachain sovereing account on Statemint.
       // The sovereign account should be created before any action
       // (the assets pallet on Statemint check if the sovereign account exists)
@@ -117,31 +129,30 @@
 
     await usingPlaygrounds(async (helper) => {
       const location = {
-        V2: {
-          parents: 1,
-          interior: {X3: [
-            {
-              Parachain: STATEMINT_CHAIN,
-            },
-            {
-              PalletInstance: STATEMINT_PALLET_INSTANCE,
-            },
-            {
-              GeneralIndex: USDT_ASSET_ID,
-            },
-          ]},
-        },
+        parents: 1,
+        interior: {X3: [
+          {
+            Parachain: STATEMINT_CHAIN,
+          },
+          {
+            PalletInstance: STATEMINT_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 Unique');
+      }
+
       balanceUniqueBefore = await helper.balance.getSubstrate(alice.address);
+      usdtCollectionId = await helper.foreignAssets.foreignCollectionId(assetId);
     });
 
 
@@ -259,8 +270,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});
 
     balanceUniqueAfter = await helper.balance.getSubstrate(alice.address);
 
@@ -299,15 +309,11 @@
     const relayFee = 400_000_000_000_000n;
     const currencies: [any, bigint][] = [
       [
-        {
-          ForeignAssetId: 0,
-        },
+        usdtCollectionId,
         TRANSFER_AMOUNT,
       ],
       [
-        {
-          NativeAssetId: 'Parent',
-        },
+        relayCollectionId,
         relayFee,
       ],
     ];
@@ -332,7 +338,7 @@
 
   itSub('Should connect and send Relay token to Unique', 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 = {
@@ -380,7 +386,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 wndFeeOnUnique = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;
     const wndDiffOnUnique = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;
@@ -420,9 +426,7 @@
 
     const currencies: any = [
       [
-        {
-          NativeAssetId: 'Parent',
-        },
+        relayCollectionId,
         TRANSFER_AMOUNT_RELAY,
       ],
     ];
@@ -1275,9 +1279,7 @@
   });
 
   itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {
-    const currencyId = {
-      NativeAssetId: 'Here',
-    };
+    const currencyId = 0;
     const dest = {
       V2: {
         parents: 1,
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
after · runtime/tests/src/tests.rs
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/>.1617// Tests to be written here18use frame_support::{assert_err, assert_noop, assert_ok};19use pallet_common::Error as CommonError;20use pallet_evm::account::CrossAccountId;21use pallet_unique::Error as UniqueError;22use sp_std::convert::TryInto;23use up_data_structs::{24	AccessMode, CollectionId, CollectionMode, CollectionPermissions,25	CollectionPropertiesPermissionsVec, CollectionPropertiesVec, CreateCollectionData,26	CreateFungibleData, CreateItemData, CreateNftData, CreateReFungibleData, Property,27	PropertyKeyPermission, PropertyPermission, TokenId, COLLECTION_ADMINS_LIMIT,28	COLLECTION_NUMBER_LIMIT, MAX_DECIMAL_POINTS, MAX_TOKEN_OWNERSHIP,29};3031use crate::{32	new_test_ext, CollectionCreationPrice, RuntimeOrigin, Test, TestCrossAccountId, Unique,33};3435fn add_balance(user: u64, value: u64) {36	const DONOR_USER: u64 = 999;37	assert_ok!(<pallet_balances::Pallet<Test>>::force_set_balance(38		RuntimeOrigin::root(),39		DONOR_USER,40		value,41	));42	assert_ok!(<pallet_balances::Pallet<Test>>::force_transfer(43		RuntimeOrigin::root(),44		DONOR_USER,45		user,46		value47	));48}4950fn default_nft_data() -> CreateNftData {51	CreateNftData {52		properties: vec![Property {53			key: b"test-prop".to_vec().try_into().unwrap(),54			value: b"test-nft-prop".to_vec().try_into().unwrap(),55		}]56		.try_into()57		.unwrap(),58	}59}6061fn default_fungible_data() -> CreateFungibleData {62	CreateFungibleData { value: 5 }63}6465fn default_re_fungible_data() -> CreateReFungibleData {66	CreateReFungibleData {67		pieces: 1023,68		properties: vec![Property {69			key: b"test-prop".to_vec().try_into().unwrap(),70			value: b"test-nft-prop".to_vec().try_into().unwrap(),71		}]72		.try_into()73		.unwrap(),74	}75}7677fn create_test_collection_for_owner(78	mode: &CollectionMode,79	owner: u64,80	id: CollectionId,81) -> CollectionId {82	add_balance(owner, CollectionCreationPrice::get() as u64 + 1);8384	let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();85	let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();86	let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();87	let token_property_permissions: CollectionPropertiesPermissionsVec =88		vec![PropertyKeyPermission {89			key: b"test-prop".to_vec().try_into().unwrap(),90			permission: PropertyPermission {91				mutable: true,92				collection_admin: false,93				token_owner: true,94			},95		}]96		.try_into()97		.unwrap();98	let properties: CollectionPropertiesVec = vec![Property {99		key: b"test-collection-prop".to_vec().try_into().unwrap(),100		value: b"test-collection-value".to_vec().try_into().unwrap(),101	}]102	.try_into()103	.unwrap();104105	let data = CreateCollectionData {106		name: col_name1.try_into().unwrap(),107		description: col_desc1.try_into().unwrap(),108		token_prefix: token_prefix1.try_into().unwrap(),109		mode: mode.clone(),110		token_property_permissions: token_property_permissions.clone(),111		properties: properties.clone(),112		..Default::default()113	};114115	let origin1 = RuntimeOrigin::signed(owner);116	assert_ok!(Unique::create_collection_ex(origin1, data));117118	let saved_col_name: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();119	let saved_description: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();120	let saved_prefix: Vec<u8> = b"token_prefix1\0".to_vec();121	assert_eq!(122		<pallet_common::CollectionById<Test>>::get(id)123			.unwrap()124			.owner,125		owner126	);127	assert_eq!(128		<pallet_common::CollectionById<Test>>::get(id).unwrap().name,129		saved_col_name130	);131	assert_eq!(132		<pallet_common::CollectionById<Test>>::get(id).unwrap().mode,133		*mode134	);135	assert_eq!(136		<pallet_common::CollectionById<Test>>::get(id)137			.unwrap()138			.description,139		saved_description140	);141	assert_eq!(142		<pallet_common::CollectionById<Test>>::get(id)143			.unwrap()144			.token_prefix,145		saved_prefix146	);147	assert_eq!(148		get_collection_property_permissions(id).as_slice(),149		token_property_permissions.as_slice()150	);151	assert_eq!(152		get_collection_properties(id).as_slice(),153		properties.as_slice()154	);155	id156}157158fn get_collection_property_permissions(collection_id: CollectionId) -> Vec<PropertyKeyPermission> {159	<pallet_common::Pallet<Test>>::property_permissions(collection_id)160		.into_iter()161		.map(|(key, permission)| PropertyKeyPermission { key, permission })162		.collect()163}164165fn get_collection_properties(collection_id: CollectionId) -> Vec<Property> {166	<pallet_common::Pallet<Test>>::collection_properties(collection_id)167		.into_iter()168		.map(|(key, value)| Property { key, value })169		.collect()170}171172fn get_token_properties(collection_id: CollectionId, token_id: TokenId) -> Vec<Property> {173	<pallet_nonfungible::Pallet<Test>>::token_properties((collection_id, token_id))174		.unwrap_or_default()175		.into_iter()176		.map(|(key, value)| Property { key, value })177		.collect()178}179180fn create_test_collection(mode: &CollectionMode, id: CollectionId) -> CollectionId {181	create_test_collection_for_owner(&mode, 1, id)182}183184fn create_test_item(collection_id: CollectionId, data: &CreateItemData) {185	let origin1 = RuntimeOrigin::signed(1);186	assert_ok!(Unique::create_item(187		origin1,188		collection_id,189		account(1),190		data.clone()191	));192}193194fn account(sub: u64) -> TestCrossAccountId {195	TestCrossAccountId::from_sub(sub)196}197198// Use cases tests region199// #region200201#[test]202fn check_not_sufficient_founds() {203	new_test_ext().execute_with(|| {204		let acc: u64 = 1;205		<pallet_balances::Pallet<Test>>::force_set_balance(RuntimeOrigin::root(), acc, 0).unwrap();206207		let name: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();208		let description: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();209		let token_prefix: Vec<u8> = b"token_prefix1\0".to_vec();210211		let data = CreateCollectionData {212			name: name.try_into().unwrap(),213			description: description.try_into().unwrap(),214			token_prefix: token_prefix.try_into().unwrap(),215			mode: CollectionMode::NFT,216			..Default::default()217		};218219		let result = Unique::create_collection_ex(RuntimeOrigin::signed(acc), data);220		assert_err!(result, <CommonError<Test>>::NotSufficientFounds);221	});222}223224#[test]225fn create_fungible_collection_fails_with_large_decimal_numbers() {226	new_test_ext().execute_with(|| {227		let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();228		let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();229		let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();230231		let data = CreateCollectionData {232			name: col_name1.try_into().unwrap(),233			description: col_desc1.try_into().unwrap(),234			token_prefix: token_prefix1.try_into().unwrap(),235			mode: CollectionMode::Fungible(MAX_DECIMAL_POINTS + 1),236			..Default::default()237		};238239		let origin1 = RuntimeOrigin::signed(1);240		assert_noop!(241			Unique::create_collection_ex(origin1, data),242			UniqueError::<Test>::CollectionDecimalPointLimitExceeded243		);244	});245}246247#[test]248fn create_nft_item() {249	new_test_ext().execute_with(|| {250		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));251252		let data = default_nft_data();253		create_test_item(collection_id, &data.clone().into());254255		assert_eq!(256			get_token_properties(collection_id, TokenId(1)).as_slice(),257			data.properties.as_slice(),258		);259	});260}261262// Use cases tests region263// #region264#[test]265fn create_nft_multiple_items() {266	new_test_ext().execute_with(|| {267		create_test_collection(&CollectionMode::NFT, CollectionId(1));268269		let origin1 = RuntimeOrigin::signed(1);270271		let items_data = vec![default_nft_data(), default_nft_data(), default_nft_data()];272273		assert_ok!(Unique::create_multiple_items(274			origin1,275			CollectionId(1),276			account(1),277			items_data278				.clone()279				.into_iter()280				.map(|d| { d.into() })281				.collect()282		));283		for (index, data) in items_data.into_iter().enumerate() {284			assert_eq!(285				get_token_properties(CollectionId(1), TokenId(index as u32 + 1)).as_slice(),286				data.properties.as_slice()287			);288		}289	});290}291292#[test]293fn create_refungible_item() {294	new_test_ext().execute_with(|| {295		let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1));296297		let data = default_re_fungible_data();298		create_test_item(collection_id, &data.clone().into());299		let balance =300			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1)));301		assert_eq!(balance, 1023);302	});303}304305#[test]306fn create_multiple_refungible_items() {307	new_test_ext().execute_with(|| {308		create_test_collection(&CollectionMode::ReFungible, CollectionId(1));309310		let origin1 = RuntimeOrigin::signed(1);311312		let items_data = vec![313			default_re_fungible_data(),314			default_re_fungible_data(),315			default_re_fungible_data(),316		];317318		assert_ok!(Unique::create_multiple_items(319			origin1,320			CollectionId(1),321			account(1),322			items_data323				.clone()324				.into_iter()325				.map(|d| { d.into() })326				.collect()327		));328		for (index, _data) in items_data.into_iter().enumerate() {329			let balance = <pallet_refungible::Balance<Test>>::get((330				CollectionId(1),331				TokenId((index + 1) as u32),332				account(1),333			));334			assert_eq!(balance, 1023);335		}336	});337}338339#[test]340fn create_fungible_item() {341	new_test_ext().execute_with(|| {342		let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1));343344		let data = default_fungible_data();345		create_test_item(collection_id, &data.into());346347		assert_eq!(348			<pallet_fungible::Balance<Test>>::get((collection_id, account(1))),349			5350		);351	});352}353354//#[test]355// fn create_multiple_fungible_items() {356//     new_test_ext().execute_with(|| {357//         default_limits();358359//         create_test_collection(&CollectionMode::Fungible(3), CollectionId(1));360361//         let origin1 = RuntimeOrigin::signed(1);362363//         let items_data = vec![default_fungible_data(), default_fungible_data(), default_fungible_data()];364365//         assert_ok!(Unique::create_multiple_items(366//             origin1.clone(),367//             1,368//             1,369//             items_data.clone().into_iter().map(|d| { d.into() }).collect()370//         ));371372//         for (index, _) in items_data.iter().enumerate() {373//             assert_eq!(Unique::fungible_item_id(1, (index + 1) as TokenId).value, 5);374//         }375//         assert_eq!(Unique::balance_count(1, 1), 3000);376//         assert_eq!(Unique::address_tokens(1, 1), [1, 2, 3]);377//     });378// }379380#[test]381fn transfer_fungible_item() {382	new_test_ext().execute_with(|| {383		let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1));384385		let origin1 = RuntimeOrigin::signed(1);386		let origin2 = RuntimeOrigin::signed(2);387388		let data = default_fungible_data();389		create_test_item(collection_id, &data.into());390391		assert_eq!(392			<pallet_fungible::Balance<Test>>::get((CollectionId(1), account(1))),393			5394		);395396		// change owner scenario397		assert_ok!(Unique::transfer(398			origin1,399			account(2),400			CollectionId(1),401			TokenId(0),402			5403		));404		assert_eq!(405			<pallet_fungible::Balance<Test>>::get((CollectionId(1), account(1))),406			0407		);408409		// split item scenario410		assert_ok!(Unique::transfer(411			origin2.clone(),412			account(3),413			CollectionId(1),414			TokenId(0),415			3416		));417418		// split item and new owner has account scenario419		assert_ok!(Unique::transfer(420			origin2,421			account(3),422			CollectionId(1),423			TokenId(0),424			1425		));426		assert_eq!(427			<pallet_fungible::Balance<Test>>::get((CollectionId(1), account(2))),428			1429		);430		assert_eq!(431			<pallet_fungible::Balance<Test>>::get((CollectionId(1), account(3))),432			4433		);434	});435}436437#[test]438fn transfer_refungible_item() {439	new_test_ext().execute_with(|| {440		let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1));441442		// Create RFT 1 in 1023 pieces for account 1443		let data = default_re_fungible_data();444		create_test_item(collection_id, &data.clone().into());445		assert_eq!(446			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),447			1448		);449		assert_eq!(450			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1))),451			1023452		);453		assert_eq!(454			<pallet_refungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),455			true456		);457458		// Account 1 transfers all 1023 pieces of RFT 1 to account 2459		let origin1 = RuntimeOrigin::signed(1);460		let origin2 = RuntimeOrigin::signed(2);461		assert_ok!(Unique::transfer(462			origin1,463			account(2),464			CollectionId(1),465			TokenId(1),466			1023467		));468		assert_eq!(469			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(2))),470			1023471		);472		assert_eq!(473			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),474			0475		);476		assert_eq!(477			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(2))),478			1479		);480		assert_eq!(481			<pallet_refungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),482			false483		);484		assert_eq!(485			<pallet_refungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),486			true487		);488489		// Account 2 transfers 500 pieces of RFT 1 to account 3490		assert_ok!(Unique::transfer(491			origin2.clone(),492			account(3),493			CollectionId(1),494			TokenId(1),495			500496		));497		assert_eq!(498			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(2))),499			523500		);501		assert_eq!(502			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(3))),503			500504		);505		assert_eq!(506			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(2))),507			1508		);509		assert_eq!(510			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(3))),511			1512		);513		assert_eq!(514			<pallet_refungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),515			true516		);517		assert_eq!(518			<pallet_refungible::Owned<Test>>::get((collection_id, account(3), TokenId(1))),519			true520		);521522		// Account 2 transfers 200 more pieces of RFT 1 to account 3 with pre-existing balance523		assert_ok!(Unique::transfer(524			origin2,525			account(3),526			CollectionId(1),527			TokenId(1),528			200529		));530		assert_eq!(531			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(2))),532			323533		);534		assert_eq!(535			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(3))),536			700537		);538		assert_eq!(539			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(2))),540			1541		);542		assert_eq!(543			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(3))),544			1545		);546		assert_eq!(547			<pallet_refungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),548			true549		);550		assert_eq!(551			<pallet_refungible::Owned<Test>>::get((collection_id, account(3), TokenId(1))),552			true553		);554	});555}556557#[test]558fn transfer_nft_item() {559	new_test_ext().execute_with(|| {560		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));561562		let data = default_nft_data();563		create_test_item(collection_id, &data.into());564		assert_eq!(565			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),566			1567		);568		assert_eq!(569			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),570			true571		);572573		let origin1 = RuntimeOrigin::signed(1);574		// default scenario575		assert_ok!(Unique::transfer(576			origin1,577			account(2),578			CollectionId(1),579			TokenId(1),580			1581		));582		assert_eq!(583			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),584			0585		);586		assert_eq!(587			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(2))),588			1589		);590		assert_eq!(591			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),592			false593		);594		assert_eq!(595			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),596			true597		);598	});599}600601#[test]602fn transfer_nft_item_wrong_value() {603	new_test_ext().execute_with(|| {604		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));605606		let data = default_nft_data();607		create_test_item(collection_id, &data.into());608		assert_eq!(609			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),610			1611		);612		assert_eq!(613			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),614			true615		);616617		let origin1 = RuntimeOrigin::signed(1);618619		assert_noop!(620			Unique::transfer(origin1, account(2), CollectionId(1), TokenId(1), 2)621				.map_err(|e| e.error),622			<pallet_nonfungible::Error::<Test>>::NonfungibleItemsHaveNoAmount623		);624	});625}626627#[test]628fn transfer_nft_item_zero_value() {629	new_test_ext().execute_with(|| {630		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));631632		let data = default_nft_data();633		create_test_item(collection_id, &data.into());634		assert_eq!(635			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),636			1637		);638		assert_eq!(639			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),640			true641		);642643		let origin1 = RuntimeOrigin::signed(1);644645		// Transferring 0 amount works on NFT...646		assert_ok!(Unique::transfer(647			origin1,648			account(2),649			CollectionId(1),650			TokenId(1),651			0652		));653		// ... and results in no transfer654		assert_eq!(655			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),656			1657		);658		assert_eq!(659			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),660			true661		);662	});663}664665#[test]666fn nft_approve_and_transfer_from() {667	new_test_ext().execute_with(|| {668		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));669670		let data = default_nft_data();671		create_test_item(collection_id, &data.into());672673		let origin1 = RuntimeOrigin::signed(1);674		let origin2 = RuntimeOrigin::signed(2);675676		assert_eq!(677			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),678			1679		);680		assert_eq!(681			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),682			true683		);684685		// neg transfer_from686		assert_noop!(687			Unique::transfer_from(688				origin2.clone(),689				account(1),690				account(2),691				CollectionId(1),692				TokenId(1),693				1694			)695			.map_err(|e| e.error),696			CommonError::<Test>::ApprovedValueTooLow697		);698699		// do approve700		assert_ok!(Unique::approve(701			origin1,702			account(2),703			CollectionId(1),704			TokenId(1),705			1706		));707		assert_eq!(708			<pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),709			account(2)710		);711712		assert_ok!(Unique::transfer_from(713			origin2,714			account(1),715			account(3),716			CollectionId(1),717			TokenId(1),718			1719		));720		assert!(721			<pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).is_none()722		);723	});724}725726#[test]727fn nft_approve_and_transfer_from_allow_list() {728	new_test_ext().execute_with(|| {729		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));730731		let origin1 = RuntimeOrigin::signed(1);732		let origin2 = RuntimeOrigin::signed(2);733734		// Create NFT 1 for account 1735		let data = default_nft_data();736		create_test_item(collection_id, &data.clone().into());737		assert_eq!(738			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),739			1740		);741		assert_eq!(742			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),743			true744		);745746		// Allow allow-list users to mint and add accounts 1, 2, and 3 to allow-list747		assert_ok!(Unique::set_collection_permissions(748			origin1.clone(),749			CollectionId(1),750			CollectionPermissions {751				mint_mode: Some(true),752				access: Some(AccessMode::AllowList),753				nesting: None,754			}755		));756		assert_ok!(Unique::add_to_allow_list(757			origin1.clone(),758			CollectionId(1),759			account(1)760		));761		assert_ok!(Unique::add_to_allow_list(762			origin1.clone(),763			CollectionId(1),764			account(2)765		));766		assert_ok!(Unique::add_to_allow_list(767			origin1.clone(),768			CollectionId(1),769			account(3)770		));771772		// Account 1 approves account 2 for NFT 1773		assert_ok!(Unique::approve(774			origin1.clone(),775			account(2),776			CollectionId(1),777			TokenId(1),778			1779		));780		assert_eq!(781			<pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),782			account(2)783		);784785		// Account 2 transfers NFT 1 from account 1 to account 3786		assert_ok!(Unique::transfer_from(787			origin2,788			account(1),789			account(3),790			CollectionId(1),791			TokenId(1),792			1793		));794		assert!(795			<pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).is_none()796		);797	});798}799800#[test]801fn refungible_approve_and_transfer_from() {802	new_test_ext().execute_with(|| {803		let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1));804805		let origin1 = RuntimeOrigin::signed(1);806		let origin2 = RuntimeOrigin::signed(2);807808		// Create RFT 1 in 1023 pieces for account 1809		let data = default_re_fungible_data();810		create_test_item(collection_id, &data.into());811812		assert_eq!(813			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),814			1815		);816		assert_eq!(817			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1))),818			1023819		);820		assert_eq!(821			<pallet_refungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),822			true823		);824825		// Allow public minting, enable allow-list and add accounts 1, 2, 3 to allow-list826		assert_ok!(Unique::set_collection_permissions(827			origin1.clone(),828			CollectionId(1),829			CollectionPermissions {830				mint_mode: Some(true),831				access: Some(AccessMode::AllowList),832				nesting: None,833			}834		));835		assert_ok!(Unique::add_to_allow_list(836			origin1.clone(),837			CollectionId(1),838			account(1)839		));840		assert_ok!(Unique::add_to_allow_list(841			origin1.clone(),842			CollectionId(1),843			account(2)844		));845		assert_ok!(Unique::add_to_allow_list(846			origin1.clone(),847			CollectionId(1),848			account(3)849		));850851		// Account 1 approves account 2 for 1023 pieces of RFT 1852		assert_ok!(Unique::approve(853			origin1,854			account(2),855			CollectionId(1),856			TokenId(1),857			1023858		));859		assert_eq!(860			<pallet_refungible::Allowance<Test>>::get((861				CollectionId(1),862				TokenId(1),863				account(1),864				account(2)865			)),866			1023867		);868869		// Account 2 transfers 100 pieces of RFT 1 from account 1 to account 3870		assert_ok!(Unique::transfer_from(871			origin2,872			account(1),873			account(3),874			CollectionId(1),875			TokenId(1),876			100877		));878		assert_eq!(879			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),880			1881		);882		assert_eq!(883			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(3))),884			1885		);886		assert_eq!(887			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1))),888			923889		);890		assert_eq!(891			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(3))),892			100893		);894		assert_eq!(895			<pallet_refungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),896			true897		);898		assert_eq!(899			<pallet_refungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),900			true901		);902		assert_eq!(903			<pallet_refungible::Allowance<Test>>::get((904				CollectionId(1),905				TokenId(1),906				account(1),907				account(2)908			)),909			923910		);911	});912}913914#[test]915fn fungible_approve_and_transfer_from() {916	new_test_ext().execute_with(|| {917		let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1));918919		let data = default_fungible_data();920		create_test_item(collection_id, &data.into());921922		let origin1 = RuntimeOrigin::signed(1);923		let origin2 = RuntimeOrigin::signed(2);924925		assert_ok!(Unique::set_collection_permissions(926			origin1.clone(),927			CollectionId(1),928			CollectionPermissions {929				mint_mode: Some(true),930				access: Some(AccessMode::AllowList),931				nesting: None,932			}933		));934		assert_ok!(Unique::add_to_allow_list(935			origin1.clone(),936			CollectionId(1),937			account(1)938		));939		assert_ok!(Unique::add_to_allow_list(940			origin1.clone(),941			CollectionId(1),942			account(2)943		));944		assert_ok!(Unique::add_to_allow_list(945			origin1.clone(),946			CollectionId(1),947			account(3)948		));949950		// do approve951		assert_ok!(Unique::approve(952			origin1.clone(),953			account(2),954			CollectionId(1),955			TokenId(0),956			5957		));958		assert_eq!(959			<pallet_fungible::Allowance<Test>>::get((CollectionId(1), account(1), account(2))),960			5961		);962		assert_ok!(Unique::approve(963			origin1,964			account(3),965			CollectionId(1),966			TokenId(0),967			5968		));969		assert_eq!(970			<pallet_fungible::Allowance<Test>>::get((CollectionId(1), account(1), account(2))),971			5972		);973		assert_eq!(974			<pallet_fungible::Allowance<Test>>::get((CollectionId(1), account(1), account(3))),975			5976		);977978		assert_ok!(Unique::transfer_from(979			origin2.clone(),980			account(1),981			account(3),982			CollectionId(1),983			TokenId(0),984			4985		));986987		assert_eq!(988			<pallet_fungible::Allowance<Test>>::get((CollectionId(1), account(1), account(2))),989			1990		);991992		assert_noop!(993			Unique::transfer_from(994				origin2,995				account(1),996				account(3),997				CollectionId(1),998				TokenId(0),999				41000			)1001			.map_err(|e| e.error),1002			CommonError::<Test>::ApprovedValueTooLow1003		);1004	});1005}10061007#[test]1008fn change_collection_owner() {1009	new_test_ext().execute_with(|| {1010		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));10111012		let origin1 = RuntimeOrigin::signed(1);1013		assert_ok!(Unique::change_collection_owner(origin1, collection_id, 2));1014		assert_eq!(1015			<pallet_common::CollectionById<Test>>::get(collection_id)1016				.unwrap()1017				.owner,1018			21019		);1020	});1021}10221023#[test]1024fn destroy_collection() {1025	new_test_ext().execute_with(|| {1026		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));10271028		let origin1 = RuntimeOrigin::signed(1);1029		assert_ok!(Unique::destroy_collection(origin1, collection_id));1030	});1031}10321033#[test]1034fn burn_nft_item() {1035	new_test_ext().execute_with(|| {1036		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));10371038		let origin1 = RuntimeOrigin::signed(1);10391040		let data = default_nft_data();1041		create_test_item(collection_id, &data.into());10421043		// check balance (collection with id = 1, user id = 1)1044		assert_eq!(1045			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),1046			11047		);10481049		// burn item1050		assert_ok!(Unique::burn_item(1051			origin1.clone(),1052			collection_id,1053			TokenId(1),1054			11055		));1056		assert_eq!(1057			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),1058			01059		);1060	});1061}10621063#[test]1064fn burn_same_nft_item_twice() {1065	new_test_ext().execute_with(|| {1066		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));10671068		let origin1 = RuntimeOrigin::signed(1);10691070		let data = default_nft_data();1071		create_test_item(collection_id, &data.into());10721073		// check balance (collection with id = 1, user id = 1)1074		assert_eq!(1075			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),1076			11077		);10781079		// burn item1080		assert_ok!(Unique::burn_item(1081			origin1.clone(),1082			collection_id,1083			TokenId(1),1084			11085		));10861087		// burn item again1088		assert_noop!(1089			Unique::burn_item(origin1, collection_id, TokenId(1), 1).map_err(|e| e.error),1090			CommonError::<Test>::TokenNotFound1091		);10921093		assert_eq!(1094			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),1095			01096		);1097	});1098}10991100#[test]1101fn burn_fungible_item() {1102	new_test_ext().execute_with(|| {1103		let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1));11041105		let origin1 = RuntimeOrigin::signed(1);1106		assert_ok!(Unique::add_collection_admin(1107			origin1.clone(),1108			collection_id,1109			account(2)1110		));11111112		let data = default_fungible_data();1113		create_test_item(collection_id, &data.into());11141115		// check balance (collection with id = 1, user id = 1)1116		assert_eq!(1117			<pallet_fungible::Balance<Test>>::get((collection_id, account(1))),1118			51119		);11201121		// burn item1122		assert_ok!(Unique::burn_item(1123			origin1.clone(),1124			CollectionId(1),1125			TokenId(0),1126			51127		));1128		assert_noop!(1129			Unique::burn_item(origin1, CollectionId(1), TokenId(0), 5).map_err(|e| e.error),1130			CommonError::<Test>::TokenValueTooLow1131		);11321133		assert_eq!(1134			<pallet_fungible::Balance<Test>>::get((collection_id, account(1))),1135			01136		);1137	});1138}11391140#[test]1141fn burn_fungible_item_with_token_id() {1142	new_test_ext().execute_with(|| {1143		let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1));11441145		let origin1 = RuntimeOrigin::signed(1);1146		assert_ok!(Unique::add_collection_admin(1147			origin1.clone(),1148			collection_id,1149			account(2)1150		));11511152		let data = default_fungible_data();1153		create_test_item(collection_id, &data.into());11541155		// check balance (collection with id = 1, user id = 1)1156		assert_eq!(1157			<pallet_fungible::Balance<Test>>::get((collection_id, account(1))),1158			51159		);11601161		// Try to burn item using Token ID1162		assert_noop!(1163			Unique::burn_item(origin1, CollectionId(1), TokenId(1), 5).map_err(|e| e.error),1164			<CommonError::<Test>>::FungibleItemsHaveNoId1165		);1166	});1167}1168#[test]1169fn burn_refungible_item() {1170	new_test_ext().execute_with(|| {1171		let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1));1172		let origin1 = RuntimeOrigin::signed(1);11731174		assert_ok!(Unique::set_collection_permissions(1175			origin1.clone(),1176			collection_id,1177			CollectionPermissions {1178				mint_mode: Some(true),1179				access: Some(AccessMode::AllowList),1180				nesting: None,1181			}1182		));1183		assert_ok!(Unique::add_to_allow_list(1184			origin1.clone(),1185			collection_id,1186			account(1)1187		));11881189		assert_ok!(Unique::add_collection_admin(1190			origin1.clone(),1191			collection_id,1192			account(2)1193		));11941195		let data = default_re_fungible_data();1196		create_test_item(collection_id, &data.into());11971198		// check balance (collection with id = 1, user id = 2)1199		assert_eq!(1200			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),1201			11202		);1203		assert_eq!(1204			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1))),1205			10231206		);12071208		// burn item1209		assert_ok!(Unique::burn_item(1210			origin1.clone(),1211			collection_id,1212			TokenId(1),1213			10231214		));1215		assert_noop!(1216			Unique::burn_item(origin1, collection_id, TokenId(1), 1023).map_err(|e| e.error),1217			CommonError::<Test>::TokenValueTooLow1218		);12191220		assert_eq!(1221			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1))),1222			01223		);1224	});1225}12261227#[test]1228fn add_collection_admin() {1229	new_test_ext().execute_with(|| {1230		let collection1_id =1231			create_test_collection_for_owner(&CollectionMode::NFT, 1, CollectionId(1));1232		let origin1 = RuntimeOrigin::signed(1);12331234		// Add collection admins1235		assert_ok!(Unique::add_collection_admin(1236			origin1.clone(),1237			collection1_id,1238			account(2)1239		));1240		assert_ok!(Unique::add_collection_admin(1241			origin1,1242			collection1_id,1243			account(3)1244		));12451246		// Owner is not an admin by default1247		assert_eq!(1248			<pallet_common::IsAdmin<Test>>::get((CollectionId(1), account(1))),1249			false1250		);1251		assert!(<pallet_common::IsAdmin<Test>>::get((1252			CollectionId(1),1253			account(2)1254		)));1255		assert!(<pallet_common::IsAdmin<Test>>::get((1256			CollectionId(1),1257			account(3)1258		)));1259	});1260}12611262#[test]1263fn remove_collection_admin() {1264	new_test_ext().execute_with(|| {1265		let collection1_id =1266			create_test_collection_for_owner(&CollectionMode::NFT, 1, CollectionId(1));1267		let origin1 = RuntimeOrigin::signed(1);12681269		// Add collection admins 2 and 31270		assert_ok!(Unique::add_collection_admin(1271			origin1.clone(),1272			collection1_id,1273			account(2)1274		));1275		assert_ok!(Unique::add_collection_admin(1276			origin1.clone(),1277			collection1_id,1278			account(3)1279		));12801281		assert!(<pallet_common::IsAdmin<Test>>::get((1282			CollectionId(1),1283			account(2)1284		)));1285		assert!(<pallet_common::IsAdmin<Test>>::get((1286			CollectionId(1),1287			account(3)1288		)));12891290		// remove admin 31291		assert_ok!(Unique::remove_collection_admin(1292			origin1,1293			CollectionId(1),1294			account(3)1295		));12961297		// 2 is still admin, 3 is not an admin anymore1298		assert!(<pallet_common::IsAdmin<Test>>::get((1299			CollectionId(1),1300			account(2)1301		)));1302		assert_eq!(1303			<pallet_common::IsAdmin<Test>>::get((CollectionId(1), account(3))),1304			false1305		);1306	});1307}13081309#[test]1310fn balance_of() {1311	new_test_ext().execute_with(|| {1312		let nft_collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1313		let fungible_collection_id =1314			create_test_collection(&CollectionMode::Fungible(3), CollectionId(2));1315		let re_fungible_collection_id =1316			create_test_collection(&CollectionMode::ReFungible, CollectionId(3));13171318		// check balance before1319		assert_eq!(1320			<pallet_nonfungible::AccountBalance<Test>>::get((nft_collection_id, account(1))),1321			01322		);1323		assert_eq!(1324			<pallet_fungible::Balance<Test>>::get((fungible_collection_id, account(1))),1325			01326		);1327		assert_eq!(1328			<pallet_refungible::AccountBalance<Test>>::get((re_fungible_collection_id, account(1))),1329			01330		);13311332		let nft_data = default_nft_data();1333		create_test_item(nft_collection_id, &nft_data.into());13341335		let fungible_data = default_fungible_data();1336		create_test_item(fungible_collection_id, &fungible_data.into());13371338		let re_fungible_data = default_re_fungible_data();1339		create_test_item(re_fungible_collection_id, &re_fungible_data.into());13401341		// check balance (collection with id = 1, user id = 1)1342		assert_eq!(1343			<pallet_nonfungible::AccountBalance<Test>>::get((nft_collection_id, account(1))),1344			11345		);1346		assert_eq!(1347			<pallet_fungible::Balance<Test>>::get((fungible_collection_id, account(1))),1348			51349		);1350		assert_eq!(1351			<pallet_refungible::AccountBalance<Test>>::get((re_fungible_collection_id, account(1))),1352			11353		);13541355		assert_eq!(1356			<pallet_nonfungible::Owned<Test>>::get((nft_collection_id, account(1), TokenId(1))),1357			true1358		);1359		assert_eq!(1360			<pallet_refungible::Owned<Test>>::get((1361				re_fungible_collection_id,1362				account(1),1363				TokenId(1)1364			)),1365			true1366		);1367	});1368}13691370#[test]1371fn approve() {1372	new_test_ext().execute_with(|| {1373		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));13741375		let data = default_nft_data();1376		create_test_item(collection_id, &data.into());13771378		let origin1 = RuntimeOrigin::signed(1);13791380		// approve1381		assert_ok!(Unique::approve(1382			origin1,1383			account(2),1384			CollectionId(1),1385			TokenId(1),1386			11387		));1388		assert_eq!(1389			<pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),1390			account(2)1391		);1392	});1393}13941395#[test]1396fn transfer_from() {1397	new_test_ext().execute_with(|| {1398		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1399		let origin1 = RuntimeOrigin::signed(1);1400		let origin2 = RuntimeOrigin::signed(2);14011402		let data = default_nft_data();1403		create_test_item(collection_id, &data.into());14041405		// approve1406		assert_ok!(Unique::approve(1407			origin1.clone(),1408			account(2),1409			CollectionId(1),1410			TokenId(1),1411			11412		));1413		assert_eq!(1414			<pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),1415			account(2)1416		);14171418		assert_ok!(Unique::set_collection_permissions(1419			origin1.clone(),1420			CollectionId(1),1421			CollectionPermissions {1422				mint_mode: Some(true),1423				access: Some(AccessMode::AllowList),1424				nesting: None,1425			}1426		));1427		assert_ok!(Unique::add_to_allow_list(1428			origin1.clone(),1429			CollectionId(1),1430			account(1)1431		));1432		assert_ok!(Unique::add_to_allow_list(1433			origin1.clone(),1434			CollectionId(1),1435			account(2)1436		));1437		assert_ok!(Unique::add_to_allow_list(1438			origin1,1439			CollectionId(1),1440			account(3)1441		));14421443		assert_ok!(Unique::transfer_from(1444			origin2,1445			account(1),1446			account(2),1447			CollectionId(1),1448			TokenId(1),1449			11450		));14511452		// after transfer1453		assert_eq!(1454			<pallet_nonfungible::AccountBalance<Test>>::get((CollectionId(1), account(1))),1455			01456		);1457		assert_eq!(1458			<pallet_nonfungible::AccountBalance<Test>>::get((CollectionId(1), account(2))),1459			11460		);1461	});1462}14631464// #endregion14651466// Coverage tests region1467// #region14681469#[test]1470fn owner_can_add_address_to_allow_list() {1471	new_test_ext().execute_with(|| {1472		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));14731474		let origin1 = RuntimeOrigin::signed(1);1475		assert_ok!(Unique::add_to_allow_list(1476			origin1,1477			collection_id,1478			account(2)1479		));1480		assert!(<pallet_common::Allowlist<Test>>::get((1481			collection_id,1482			account(2)1483		)));1484	});1485}14861487#[test]1488fn admin_can_add_address_to_allow_list() {1489	new_test_ext().execute_with(|| {1490		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1491		let origin1 = RuntimeOrigin::signed(1);1492		let origin2 = RuntimeOrigin::signed(2);14931494		assert_ok!(Unique::add_collection_admin(1495			origin1,1496			collection_id,1497			account(2)1498		));1499		assert_ok!(Unique::add_to_allow_list(1500			origin2,1501			collection_id,1502			account(3)1503		));1504		assert!(<pallet_common::Allowlist<Test>>::get((1505			collection_id,1506			account(3)1507		)));1508	});1509}15101511#[test]1512fn nonprivileged_user_cannot_add_address_to_allow_list() {1513	new_test_ext().execute_with(|| {1514		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));15151516		let origin2 = RuntimeOrigin::signed(2);1517		assert_noop!(1518			Unique::add_to_allow_list(origin2, collection_id, account(3)),1519			CommonError::<Test>::NoPermission1520		);1521	});1522}15231524#[test]1525fn nobody_can_add_address_to_allow_list_of_nonexisting_collection() {1526	new_test_ext().execute_with(|| {1527		let origin1 = RuntimeOrigin::signed(1);15281529		assert_noop!(1530			Unique::add_to_allow_list(origin1, CollectionId(1), account(2)),1531			CommonError::<Test>::CollectionNotFound1532		);1533	});1534}15351536#[test]1537fn nobody_can_add_address_to_allow_list_of_deleted_collection() {1538	new_test_ext().execute_with(|| {1539		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));15401541		let origin1 = RuntimeOrigin::signed(1);1542		assert_ok!(Unique::destroy_collection(origin1.clone(), collection_id));1543		assert_noop!(1544			Unique::add_to_allow_list(origin1, collection_id, account(2)),1545			CommonError::<Test>::CollectionNotFound1546		);1547	});1548}15491550// If address is already added to allow list, nothing happens1551#[test]1552fn address_is_already_added_to_allow_list() {1553	new_test_ext().execute_with(|| {1554		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1555		let origin1 = RuntimeOrigin::signed(1);15561557		assert_ok!(Unique::add_to_allow_list(1558			origin1.clone(),1559			collection_id,1560			account(2)1561		));1562		assert_ok!(Unique::add_to_allow_list(1563			origin1,1564			collection_id,1565			account(2)1566		));1567		assert!(<pallet_common::Allowlist<Test>>::get((1568			collection_id,1569			account(2)1570		)));1571	});1572}15731574#[test]1575fn owner_can_remove_address_from_allow_list() {1576	new_test_ext().execute_with(|| {1577		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));15781579		let origin1 = RuntimeOrigin::signed(1);1580		assert_ok!(Unique::add_to_allow_list(1581			origin1.clone(),1582			collection_id,1583			account(2)1584		));1585		assert_ok!(Unique::remove_from_allow_list(1586			origin1,1587			collection_id,1588			account(2)1589		));1590		assert_eq!(1591			<pallet_common::Allowlist<Test>>::get((collection_id, account(2))),1592			false1593		);1594	});1595}15961597#[test]1598fn admin_can_remove_address_from_allow_list() {1599	new_test_ext().execute_with(|| {1600		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1601		let origin1 = RuntimeOrigin::signed(1);1602		let origin2 = RuntimeOrigin::signed(2);16031604		// Owner adds admin1605		assert_ok!(Unique::add_collection_admin(1606			origin1.clone(),1607			collection_id,1608			account(2)1609		));16101611		// Owner adds address 3 to allow list1612		assert_ok!(Unique::add_to_allow_list(1613			origin1,1614			collection_id,1615			account(3)1616		));16171618		// Admin removes address 3 from allow list1619		assert_ok!(Unique::remove_from_allow_list(1620			origin2,1621			collection_id,1622			account(3)1623		));1624		assert_eq!(1625			<pallet_common::Allowlist<Test>>::get((collection_id, account(3))),1626			false1627		);1628	});1629}16301631#[test]1632fn nonprivileged_user_cannot_remove_address_from_allow_list() {1633	new_test_ext().execute_with(|| {1634		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1635		let origin1 = RuntimeOrigin::signed(1);1636		let origin2 = RuntimeOrigin::signed(2);16371638		assert_ok!(Unique::add_to_allow_list(1639			origin1,1640			collection_id,1641			account(2)1642		));1643		assert_noop!(1644			Unique::remove_from_allow_list(origin2, collection_id, account(2)),1645			CommonError::<Test>::NoPermission1646		);1647		assert!(<pallet_common::Allowlist<Test>>::get((1648			collection_id,1649			account(2)1650		)));1651	});1652}16531654#[test]1655fn nobody_can_remove_address_from_allow_list_of_nonexisting_collection() {1656	new_test_ext().execute_with(|| {1657		let origin1 = RuntimeOrigin::signed(1);16581659		assert_noop!(1660			Unique::remove_from_allow_list(origin1, CollectionId(1), account(2)),1661			CommonError::<Test>::CollectionNotFound1662		);1663	});1664}16651666#[test]1667fn nobody_can_remove_address_from_allow_list_of_deleted_collection() {1668	new_test_ext().execute_with(|| {1669		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1670		let origin1 = RuntimeOrigin::signed(1);1671		let origin2 = RuntimeOrigin::signed(2);16721673		// Add account 2 to allow list1674		assert_ok!(Unique::add_to_allow_list(1675			origin1.clone(),1676			collection_id,1677			account(2)1678		));16791680		// Account 2 is in collection allow-list1681		assert!(<pallet_common::Allowlist<Test>>::get((1682			collection_id,1683			account(2)1684		)));16851686		// Destroy collection1687		assert_ok!(Unique::destroy_collection(origin1, collection_id));16881689		// Attempt to remove account 2 from collection allow-list => error1690		assert_noop!(1691			Unique::remove_from_allow_list(origin2, collection_id, account(2)),1692			CommonError::<Test>::CollectionNotFound1693		);16941695		// Account 2 is not found in collection allow-list anyway1696		assert_eq!(1697			<pallet_common::Allowlist<Test>>::get((collection_id, account(2))),1698			false1699		);1700	});1701}17021703// If address is already removed from allow list, nothing happens1704#[test]1705fn address_is_already_removed_from_allow_list() {1706	new_test_ext().execute_with(|| {1707		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1708		let origin1 = RuntimeOrigin::signed(1);17091710		assert_ok!(Unique::add_to_allow_list(1711			origin1.clone(),1712			collection_id,1713			account(2)1714		));1715		assert_ok!(Unique::remove_from_allow_list(1716			origin1.clone(),1717			collection_id,1718			account(2)1719		));1720		assert_eq!(1721			<pallet_common::Allowlist<Test>>::get((collection_id, account(2))),1722			false1723		);1724		assert_ok!(Unique::remove_from_allow_list(1725			origin1,1726			collection_id,1727			account(2)1728		));1729		assert_eq!(1730			<pallet_common::Allowlist<Test>>::get((collection_id, account(2))),1731			false1732		);1733	});1734}17351736// If Public Access mode is set to AllowList, tokens can’t be transferred from a non-allowlisted address with transfer or transferFrom (2 tests)1737#[test]1738fn allow_list_test_1() {1739	new_test_ext().execute_with(|| {1740		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));17411742		let origin1 = RuntimeOrigin::signed(1);1743		assert_ok!(Unique::add_collection_admin(1744			origin1.clone(),1745			collection_id,1746			account(1)1747		));17481749		let data = default_nft_data();1750		create_test_item(collection_id, &data.into());17511752		assert_ok!(Unique::set_collection_permissions(1753			origin1.clone(),1754			collection_id,1755			CollectionPermissions {1756				mint_mode: None,1757				access: Some(AccessMode::AllowList),1758				nesting: None,1759			}1760		));1761		assert_ok!(Unique::add_to_allow_list(1762			origin1.clone(),1763			collection_id,1764			account(2)1765		));17661767		assert_noop!(1768			Unique::transfer(origin1, account(3), CollectionId(1), TokenId(1), 1)1769				.map_err(|e| e.error),1770			CommonError::<Test>::AddressNotInAllowlist1771		);1772	});1773}17741775#[test]1776fn allow_list_test_2() {1777	new_test_ext().execute_with(|| {1778		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1779		let origin1 = RuntimeOrigin::signed(1);17801781		let data = default_nft_data();1782		create_test_item(collection_id, &data.into());17831784		assert_ok!(Unique::set_collection_permissions(1785			origin1.clone(),1786			collection_id,1787			CollectionPermissions {1788				mint_mode: None,1789				access: Some(AccessMode::AllowList),1790				nesting: None,1791			}1792		));1793		assert_ok!(Unique::add_to_allow_list(1794			origin1.clone(),1795			collection_id,1796			account(1)1797		));1798		assert_ok!(Unique::add_to_allow_list(1799			origin1.clone(),1800			collection_id,1801			account(2)1802		));18031804		// do approve1805		assert_ok!(Unique::approve(1806			origin1.clone(),1807			account(1),1808			collection_id,1809			TokenId(1),1810			11811		));1812		assert_eq!(1813			<pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),1814			account(1)1815		);18161817		assert_ok!(Unique::remove_from_allow_list(1818			origin1.clone(),1819			collection_id,1820			account(1)1821		));18221823		assert_noop!(1824			Unique::transfer_from(1825				origin1,1826				account(1),1827				account(3),1828				CollectionId(1),1829				TokenId(1),1830				11831			)1832			.map_err(|e| e.error),1833			CommonError::<Test>::AddressNotInAllowlist1834		);1835	});1836}18371838// If Public Access mode is set to AllowList, tokens can’t be transferred to a non-allowlisted address with transfer or transferFrom (2 tests)1839#[test]1840fn allow_list_test_3() {1841	new_test_ext().execute_with(|| {1842		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));18431844		let origin1 = RuntimeOrigin::signed(1);18451846		let data = default_nft_data();1847		create_test_item(collection_id, &data.into());18481849		assert_ok!(Unique::set_collection_permissions(1850			origin1.clone(),1851			collection_id,1852			CollectionPermissions {1853				mint_mode: None,1854				access: Some(AccessMode::AllowList),1855				nesting: None,1856			}1857		));1858		assert_ok!(Unique::add_to_allow_list(1859			origin1.clone(),1860			collection_id,1861			account(1)1862		));18631864		assert_noop!(1865			Unique::transfer(origin1, account(3), collection_id, TokenId(1), 1)1866				.map_err(|e| e.error),1867			CommonError::<Test>::AddressNotInAllowlist1868		);1869	});1870}18711872#[test]1873fn allow_list_test_4() {1874	new_test_ext().execute_with(|| {1875		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));18761877		let origin1 = RuntimeOrigin::signed(1);18781879		let data = default_nft_data();1880		create_test_item(collection_id, &data.into());18811882		assert_ok!(Unique::set_collection_permissions(1883			origin1.clone(),1884			collection_id,1885			CollectionPermissions {1886				mint_mode: None,1887				access: Some(AccessMode::AllowList),1888				nesting: None,1889			}1890		));1891		assert_ok!(Unique::add_to_allow_list(1892			origin1.clone(),1893			collection_id,1894			account(1)1895		));1896		assert_ok!(Unique::add_to_allow_list(1897			origin1.clone(),1898			collection_id,1899			account(2)1900		));19011902		// do approve1903		assert_ok!(Unique::approve(1904			origin1.clone(),1905			account(1),1906			collection_id,1907			TokenId(1),1908			11909		));1910		assert_eq!(1911			<pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),1912			account(1)1913		);19141915		assert_ok!(Unique::remove_from_allow_list(1916			origin1.clone(),1917			collection_id,1918			account(2)1919		));19201921		assert_noop!(1922			Unique::transfer_from(1923				origin1,1924				account(1),1925				account(3),1926				collection_id,1927				TokenId(1),1928				11929			)1930			.map_err(|e| e.error),1931			CommonError::<Test>::AddressNotInAllowlist1932		);1933	});1934}19351936// If Public Access mode is set to AllowList, tokens can’t be destroyed by a non-allowlisted address (even if it owned them before enabling AllowList mode)1937#[test]1938fn allow_list_test_5() {1939	new_test_ext().execute_with(|| {1940		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));19411942		let origin1 = RuntimeOrigin::signed(1);19431944		let data = default_nft_data();1945		create_test_item(collection_id, &data.into());19461947		assert_ok!(Unique::set_collection_permissions(1948			origin1.clone(),1949			collection_id,1950			CollectionPermissions {1951				mint_mode: None,1952				access: Some(AccessMode::AllowList),1953				nesting: None,1954			}1955		));1956		assert_noop!(1957			Unique::burn_item(origin1.clone(), CollectionId(1), TokenId(1), 1).map_err(|e| e.error),1958			CommonError::<Test>::AddressNotInAllowlist1959		);1960	});1961}19621963// If Public Access mode is set to AllowList, token transfers can’t be Approved by a non-allowlisted address (see Approve method).1964#[test]1965fn allow_list_test_6() {1966	new_test_ext().execute_with(|| {1967		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));19681969		let origin1 = RuntimeOrigin::signed(1);19701971		let data = default_nft_data();1972		create_test_item(collection_id, &data.into());19731974		assert_ok!(Unique::set_collection_permissions(1975			origin1.clone(),1976			collection_id,1977			CollectionPermissions {1978				mint_mode: None,1979				access: Some(AccessMode::AllowList),1980				nesting: None,1981			}1982		));19831984		// do approve1985		assert_noop!(1986			Unique::approve(origin1, account(1), CollectionId(1), TokenId(1), 1)1987				.map_err(|e| e.error),1988			CommonError::<Test>::AddressNotInAllowlist1989		);1990	});1991}19921993// If Public Access mode is set to AllowList, tokens can be transferred from a allowlisted address with transfer or transferFrom (2 tests) and1994//          tokens can be transferred from a allowlisted address with transfer or transferFrom (2 tests)1995#[test]1996fn allow_list_test_7() {1997	new_test_ext().execute_with(|| {1998		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));19992000		let data = default_nft_data();2001		create_test_item(collection_id, &data.into());20022003		let origin1 = RuntimeOrigin::signed(1);20042005		assert_ok!(Unique::set_collection_permissions(2006			origin1.clone(),2007			collection_id,2008			CollectionPermissions {2009				mint_mode: None,2010				access: Some(AccessMode::AllowList),2011				nesting: None,2012			}2013		));2014		assert_ok!(Unique::add_to_allow_list(2015			origin1.clone(),2016			collection_id,2017			account(1)2018		));2019		assert_ok!(Unique::add_to_allow_list(2020			origin1.clone(),2021			collection_id,2022			account(2)2023		));20242025		assert_ok!(Unique::transfer(2026			origin1,2027			account(2),2028			CollectionId(1),2029			TokenId(1),2030			12031		));2032	});2033}20342035#[test]2036fn allow_list_test_8() {2037	new_test_ext().execute_with(|| {2038		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));20392040		// Create NFT for account 12041		let data = default_nft_data();2042		create_test_item(collection_id, &data.into());20432044		let origin1 = RuntimeOrigin::signed(1);20452046		// Toggle Allow List mode and add accounts 1 and 22047		assert_ok!(Unique::set_collection_permissions(2048			origin1.clone(),2049			collection_id,2050			CollectionPermissions {2051				mint_mode: None,2052				access: Some(AccessMode::AllowList),2053				nesting: None,2054			}2055		));2056		assert_ok!(Unique::add_to_allow_list(2057			origin1.clone(),2058			collection_id,2059			account(1)2060		));2061		assert_ok!(Unique::add_to_allow_list(2062			origin1.clone(),2063			collection_id,2064			account(2)2065		));20662067		// Sself-approve account 1 for NFT 12068		assert_ok!(Unique::approve(2069			origin1.clone(),2070			account(1),2071			CollectionId(1),2072			TokenId(1),2073			12074		));2075		assert_eq!(2076			<pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),2077			account(1)2078		);20792080		// Transfer from 1 to 22081		assert_ok!(Unique::transfer_from(2082			origin1,2083			account(1),2084			account(2),2085			CollectionId(1),2086			TokenId(1),2087			12088		));2089	});2090}20912092// If Public Access mode is set to AllowList, and Mint Permission is set to false, tokens can be created by owner.2093#[test]2094fn allow_list_test_9() {2095	new_test_ext().execute_with(|| {2096		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));2097		let origin1 = RuntimeOrigin::signed(1);20982099		assert_ok!(Unique::set_collection_permissions(2100			origin1.clone(),2101			collection_id,2102			CollectionPermissions {2103				mint_mode: Some(false),2104				access: Some(AccessMode::AllowList),2105				nesting: None,2106			}2107		));21082109		let data = default_nft_data();2110		create_test_item(collection_id, &data.into());2111	});2112}21132114// If Public Access mode is set to AllowList, and Mint Permission is set to false, tokens can be created by admin.2115#[test]2116fn allow_list_test_10() {2117	new_test_ext().execute_with(|| {2118		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));21192120		let origin1 = RuntimeOrigin::signed(1);2121		let origin2 = RuntimeOrigin::signed(2);21222123		assert_ok!(Unique::set_collection_permissions(2124			origin1.clone(),2125			collection_id,2126			CollectionPermissions {2127				mint_mode: Some(false),2128				access: Some(AccessMode::AllowList),2129				nesting: None,2130			}2131		));21322133		assert_ok!(Unique::add_collection_admin(2134			origin1,2135			collection_id,2136			account(2)2137		));21382139		assert_ok!(Unique::create_item(2140			origin2,2141			collection_id,2142			account(2),2143			default_nft_data().into()2144		));2145	});2146}21472148// If Public Access mode is set to AllowList, and Mint Permission is set to false, tokens cannot be created by non-privileged and allow listed address.2149#[test]2150fn allow_list_test_11() {2151	new_test_ext().execute_with(|| {2152		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));21532154		let origin1 = RuntimeOrigin::signed(1);2155		let origin2 = RuntimeOrigin::signed(2);21562157		assert_ok!(Unique::set_collection_permissions(2158			origin1.clone(),2159			collection_id,2160			CollectionPermissions {2161				mint_mode: Some(false),2162				access: Some(AccessMode::AllowList),2163				nesting: None,2164			}2165		));2166		assert_ok!(Unique::add_to_allow_list(2167			origin1,2168			collection_id,2169			account(2)2170		));21712172		assert_noop!(2173			Unique::create_item(2174				origin2,2175				CollectionId(1),2176				account(2),2177				default_nft_data().into()2178			)2179			.map_err(|e| e.error),2180			CommonError::<Test>::PublicMintingNotAllowed2181		);2182	});2183}21842185// If Public Access mode is set to AllowList, and Mint Permission is set to false, tokens cannot be created by non-privileged and non-allow listed address.2186#[test]2187fn allow_list_test_12() {2188	new_test_ext().execute_with(|| {2189		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));21902191		let origin1 = RuntimeOrigin::signed(1);2192		let origin2 = RuntimeOrigin::signed(2);21932194		assert_ok!(Unique::set_collection_permissions(2195			origin1.clone(),2196			collection_id,2197			CollectionPermissions {2198				mint_mode: Some(false),2199				access: Some(AccessMode::AllowList),2200				nesting: None,2201			}2202		));22032204		assert_noop!(2205			Unique::create_item(2206				origin2,2207				CollectionId(1),2208				account(2),2209				default_nft_data().into()2210			)2211			.map_err(|e| e.error),2212			CommonError::<Test>::PublicMintingNotAllowed2213		);2214	});2215}22162217// If Public Access mode is set to AllowList, and Mint Permission is set to true, tokens can be created by owner.2218#[test]2219fn allow_list_test_13() {2220	new_test_ext().execute_with(|| {2221		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));22222223		let origin1 = RuntimeOrigin::signed(1);22242225		assert_ok!(Unique::set_collection_permissions(2226			origin1.clone(),2227			collection_id,2228			CollectionPermissions {2229				mint_mode: Some(true),2230				access: Some(AccessMode::AllowList),2231				nesting: None,2232			}2233		));22342235		let data = default_nft_data();2236		create_test_item(collection_id, &data.into());2237	});2238}22392240// If Public Access mode is set to AllowList, and Mint Permission is set to true, tokens can be created by admin.2241#[test]2242fn allow_list_test_14() {2243	new_test_ext().execute_with(|| {2244		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));22452246		let origin1 = RuntimeOrigin::signed(1);2247		let origin2 = RuntimeOrigin::signed(2);22482249		assert_ok!(Unique::set_collection_permissions(2250			origin1.clone(),2251			collection_id,2252			CollectionPermissions {2253				mint_mode: Some(true),2254				access: Some(AccessMode::AllowList),2255				nesting: None,2256			}2257		));22582259		assert_ok!(Unique::add_collection_admin(2260			origin1,2261			collection_id,2262			account(2)2263		));22642265		assert_ok!(Unique::create_item(2266			origin2,2267			collection_id,2268			account(2),2269			default_nft_data().into()2270		));2271	});2272}22732274// If Public Access mode is set to AllowList, and Mint Permission is set to true, tokens cannot be created by non-privileged and non-allow listed address.2275#[test]2276fn allow_list_test_15() {2277	new_test_ext().execute_with(|| {2278		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));22792280		let origin1 = RuntimeOrigin::signed(1);2281		let origin2 = RuntimeOrigin::signed(2);22822283		assert_ok!(Unique::set_collection_permissions(2284			origin1.clone(),2285			collection_id,2286			CollectionPermissions {2287				mint_mode: Some(true),2288				access: Some(AccessMode::AllowList),2289				nesting: None,2290			}2291		));22922293		assert_noop!(2294			Unique::create_item(2295				origin2,2296				collection_id,2297				account(2),2298				default_nft_data().into()2299			)2300			.map_err(|e| e.error),2301			CommonError::<Test>::AddressNotInAllowlist2302		);2303	});2304}23052306// If Public Access mode is set to AllowList, and Mint Permission is set to true, tokens can be created by non-privileged and allow listed address.2307#[test]2308fn allow_list_test_16() {2309	new_test_ext().execute_with(|| {2310		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));23112312		let origin1 = RuntimeOrigin::signed(1);2313		let origin2 = RuntimeOrigin::signed(2);23142315		assert_ok!(Unique::set_collection_permissions(2316			origin1.clone(),2317			collection_id,2318			CollectionPermissions {2319				mint_mode: Some(true),2320				access: Some(AccessMode::AllowList),2321				nesting: None,2322			}2323		));2324		assert_ok!(Unique::add_to_allow_list(2325			origin1,2326			collection_id,2327			account(2)2328		));23292330		assert_ok!(Unique::create_item(2331			origin2,2332			collection_id,2333			account(2),2334			default_nft_data().into()2335		));2336	});2337}23382339// Total number of collections. Positive test2340#[test]2341fn total_number_collections_bound() {2342	new_test_ext().execute_with(|| {2343		create_test_collection(&CollectionMode::NFT, CollectionId(1));2344	});2345}23462347#[test]2348fn create_max_collections() {2349	new_test_ext().execute_with(|| {2350		for i in 1..COLLECTION_NUMBER_LIMIT {2351			create_test_collection(&CollectionMode::NFT, CollectionId(i));2352		}2353	});2354}23552356// Total number of collections. Negative test2357#[test]2358fn total_number_collections_bound_neg() {2359	new_test_ext().execute_with(|| {2360		let user = 1;2361		let origin1 = RuntimeOrigin::signed(user);23622363		for i in 1..=COLLECTION_NUMBER_LIMIT {2364			create_test_collection(&CollectionMode::NFT, CollectionId(i));2365		}23662367		let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();2368		let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();2369		let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();23702371		let data = CreateCollectionData {2372			name: col_name1.try_into().unwrap(),2373			description: col_desc1.try_into().unwrap(),2374			token_prefix: token_prefix1.try_into().unwrap(),2375			mode: CollectionMode::NFT,2376			..Default::default()2377		};23782379		// 11-th collection in chain. Expects error2380		add_balance(user, CollectionCreationPrice::get() as u64 + 1);2381		assert_noop!(2382			Unique::create_collection_ex(origin1, data),2383			CommonError::<Test>::TotalCollectionsLimitExceeded2384		);2385	});2386}23872388// Owned tokens by a single address. Positive test2389#[test]2390fn owned_tokens_bound() {2391	new_test_ext().execute_with(|| {2392		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));23932394		let data = default_nft_data();2395		create_test_item(collection_id, &data.clone().into());2396		create_test_item(collection_id, &data.into());2397	});2398}23992400// Owned tokens by a single address. Negotive test2401#[test]2402fn owned_tokens_bound_neg() {2403	new_test_ext().execute_with(|| {2404		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));24052406		let origin1 = RuntimeOrigin::signed(1);24072408		for _ in 1..=MAX_TOKEN_OWNERSHIP {2409			let data = default_nft_data();2410			create_test_item(collection_id, &data.clone().into());2411		}24122413		let data = default_nft_data();2414		assert_noop!(2415			Unique::create_item(origin1, CollectionId(1), account(1), data.into())2416				.map_err(|e| e.error),2417			CommonError::<Test>::AccountTokenLimitExceeded2418		);2419	});2420}24212422// Number of collection admins. Positive test2423#[test]2424fn collection_admins_bound() {2425	new_test_ext().execute_with(|| {2426		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));24272428		let origin1 = RuntimeOrigin::signed(1);24292430		assert_ok!(Unique::add_collection_admin(2431			origin1.clone(),2432			collection_id,2433			account(2)2434		));2435		assert_ok!(Unique::add_collection_admin(2436			origin1,2437			collection_id,2438			account(3)2439		));2440	});2441}24422443// Number of collection admins. Negotive test2444#[test]2445fn collection_admins_bound_neg() {2446	new_test_ext().execute_with(|| {2447		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));24482449		let origin1 = RuntimeOrigin::signed(1);24502451		for i in 0..COLLECTION_ADMINS_LIMIT {2452			assert_ok!(Unique::add_collection_admin(2453				origin1.clone(),2454				collection_id,2455				account((2 + i).into())2456			));2457		}2458		assert_noop!(2459			Unique::add_collection_admin(2460				origin1,2461				collection_id,2462				account((3 + COLLECTION_ADMINS_LIMIT).into())2463			),2464			CommonError::<Test>::CollectionAdminCountExceeded2465		);2466	});2467}2468// #endregion24692470#[test]2471fn collection_transfer_flag_works() {2472	new_test_ext().execute_with(|| {2473		let origin1 = RuntimeOrigin::signed(1);24742475		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));2476		assert_ok!(Unique::set_transfers_enabled_flag(2477			origin1,2478			collection_id,2479			true2480		));24812482		let data = default_nft_data();2483		create_test_item(collection_id, &data.into());2484		assert_eq!(2485			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),2486			12487		);2488		assert_eq!(2489			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),2490			true2491		);24922493		let origin1 = RuntimeOrigin::signed(1);24942495		// default scenario2496		assert_ok!(Unique::transfer(2497			origin1,2498			account(2),2499			collection_id,2500			TokenId(1),2501			12502		));2503		assert_eq!(2504			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),2505			false2506		);2507		assert_eq!(2508			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),2509			true2510		);2511		assert_eq!(2512			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),2513			02514		);2515		assert_eq!(2516			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(2))),2517			12518		);2519	});2520}25212522#[test]2523fn collection_transfer_flag_works_neg() {2524	new_test_ext().execute_with(|| {2525		let origin1 = RuntimeOrigin::signed(1);25262527		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));2528		assert_ok!(Unique::set_transfers_enabled_flag(2529			origin1,2530			collection_id,2531			false2532		));25332534		let data = default_nft_data();2535		create_test_item(collection_id, &data.into());2536		assert_eq!(2537			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),2538			12539		);2540		assert_eq!(2541			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),2542			true2543		);25442545		let origin1 = RuntimeOrigin::signed(1);25462547		// default scenario2548		assert_noop!(2549			Unique::transfer(origin1, account(2), CollectionId(1), TokenId(1), 1)2550				.map_err(|e| e.error),2551			CommonError::<Test>::TransferNotAllowed2552		);2553		assert_eq!(2554			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),2555			12556		);2557		assert_eq!(2558			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(2))),2559			02560		);2561		assert_eq!(2562			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),2563			true2564		);2565		assert_eq!(2566			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),2567			false2568		);2569	});2570}25712572#[test]2573fn collection_sponsoring() {2574	new_test_ext().execute_with(|| {2575		// default_limits();2576		let user1 = 1_u64;2577		let user2 = 777_u64;2578		let origin1 = RuntimeOrigin::signed(user1);2579		let origin2 = RuntimeOrigin::signed(user2);2580		let account2 = account(user2);25812582		let collection_id =2583			create_test_collection_for_owner(&CollectionMode::NFT, user1, CollectionId(1));2584		assert_ok!(Unique::set_collection_sponsor(2585			origin1.clone(),2586			collection_id,2587			user12588		));2589		assert_ok!(Unique::confirm_sponsorship(origin1.clone(), collection_id));25902591		// Expect error while have no permissions2592		assert!(Unique::create_item(2593			origin2.clone(),2594			collection_id,2595			account2.clone(),2596			default_nft_data().into()2597		)2598		.is_err());25992600		assert_ok!(Unique::set_collection_permissions(2601			origin1.clone(),2602			collection_id,2603			CollectionPermissions {2604				mint_mode: Some(true),2605				access: Some(AccessMode::AllowList),2606				nesting: None,2607			}2608		));2609		assert_ok!(Unique::add_to_allow_list(2610			origin1.clone(),2611			collection_id,2612			account2.clone()2613		));26142615		assert_ok!(Unique::create_item(2616			origin2,2617			collection_id,2618			account2,2619			default_nft_data().into()2620		));2621	});2622}26232624mod check_token_permissions {2625	use pallet_common::LazyValue;26262627	use super::*;26282629	fn test(2630		i: usize,2631		test_case: &pallet_common::tests::TestCase,2632		check_token_existence: &mut LazyValue<bool>,2633	) {2634		let collection_admin = test_case.collection_admin;2635		let mut is_collection_admin = LazyValue::new(|| test_case.is_collection_admin);2636		let token_owner = test_case.token_owner;2637		let mut is_token_owner = LazyValue::new(|| Ok(test_case.is_token_owner));2638		let is_no_permission = test_case.no_permission;26392640		let result = pallet_common::tests::check_token_permissions::<Test>(2641			collection_admin,2642			token_owner,2643			&mut is_collection_admin,2644			&mut is_token_owner,2645			check_token_existence,2646		);26472648		if is_no_permission {2649			assert!(2650				result.is_err(),2651				"{i}: {test_case:?}, token_exist: {}",2652				check_token_existence.value()2653			);2654			assert_err!(result, pallet_common::Error::<Test>::NoPermission,);2655		} else if check_token_existence.has_value() && !check_token_existence.value() {2656			assert!(2657				result.is_err(),2658				"{i}: {test_case:?}, token_exist: {}",2659				check_token_existence.value()2660			);2661			assert_err!(result, pallet_common::Error::<Test>::TokenNotFound,);2662		}2663	}26642665	#[test]2666	fn no_permission_only() {2667		new_test_ext().execute_with(|| {2668			let mut check_token_existence = LazyValue::new(|| true);2669			for (i, row) in pallet_common::tests::TABLE.iter().enumerate() {2670				test(i, row, &mut check_token_existence);2671			}2672		});2673	}26742675	#[test]2676	fn no_permission_and_token_not_found() {2677		new_test_ext().execute_with(|| {2678			for (i, row) in pallet_common::tests::TABLE.iter().enumerate() {2679				// This is inside the loop to keep track of whether the lambda was called2680				let mut check_token_existence = LazyValue::new(|| false);2681				test(i, row, &mut check_token_existence);2682			}2683		});2684	}2685}
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 }