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
before · pallets/unique/src/lib.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//! # Unique Pallet18//!19//! A pallet governing Unique transactions.20//!21//! - [`Config`]22//! - [`Call`]23//! - [`Pallet`]24//!25//! ## Overview26//!27//! The Unique pallet's purpose is to be the primary interface between28//! external users and the inner structure of the Unique chains.29//!30//! It also contains an implementation of [`CollectionHelpers`][`eth`],31//! an Ethereum contract dealing with collection operations.32//!33//! ## Interface34//!35//! ### Dispatchables36//!37//! - `create_collection` - Create a collection of tokens. **Deprecated**, use `create_collection_ex`.38//! - `create_collection_ex` - Create a collection of tokens with explicit parameters.39//! - `destroy_collection` - Destroy a collection if no tokens exist within.40//! - `add_to_allow_list` - Add an address to allow list.41//! - `remove_from_allow_list` - Remove an address from allow list.42//! - `change_collection_owner` - Change the owner of the collection.43//! - `add_collection_admin` - Add an admin to a collection.44//! - `remove_collection_admin` - Remove admin of a collection.45//! - `set_collection_sponsor` - Invite a new collection sponsor.46//! - `confirm_sponsorship` - Confirm own sponsorship of a collection, becoming the sponsor.47//! - `remove_collection_sponsor` - Remove a sponsor from a collection.48//! - `create_item` - Create an item within a collection.49//! - `create_multiple_items` - Create multiple items within a collection.50//! - `set_collection_properties` - Add or change collection properties.51//! - `delete_collection_properties` - Delete specified collection properties.52//! - `set_token_properties` - Add or change token properties.53//! - `delete_token_properties` - Delete token properties.54//! - `set_token_property_permissions` - Add or change token property permissions of a collection.55//! - `create_multiple_items_ex` - Create multiple items within a collection with explicitly specified initial parameters.56//! - `set_transfers_enabled_flag` - Completely allow or disallow transfers for a particular collection.57//! - `burn_item` - Destroy an item.58//! - `burn_from` - Destroy an item on behalf of the owner as a non-owner account.59//! - `transfer` - Change ownership of the token.60//! - `transfer_from` - Change ownership of the token on behalf of the owner as a non-owner account.61//! - `approve` - Allow a non-permissioned address to transfer or burn an item.62//! - `set_collection_limits` - Set specific limits of a collection.63//! - `set_collection_permissions` - Set specific permissions of a collection.64//! - `repartition` - Re-partition a refungible token, while owning all of its parts.6566#![recursion_limit = "1024"]67#![cfg_attr(not(feature = "std"), no_std)]68#![allow(69	clippy::too_many_arguments,70	clippy::unnecessary_mut_passed,71	clippy::unused_unit72)]7374extern crate alloc;7576use frame_support::pallet_prelude::*;77use frame_system::pallet_prelude::*;78pub use pallet::*;79pub mod eth;8081#[cfg(feature = "runtime-benchmarks")]82pub mod benchmarking;83pub mod weights;8485#[frame_support::pallet]86pub mod pallet {87	use frame_support::{88		dispatch::{DispatchErrorWithPostInfo, DispatchResult, PostDispatchInfo},89		ensure, fail,90		storage::Key,91		BoundedVec,92	};93	use frame_system::{ensure_root, ensure_signed};94	use pallet_common::{95		dispatch::{dispatch_tx, CollectionDispatch},96		CollectionHandle, CommonWeightInfo, Pallet as PalletCommon, RefungibleExtensionsWeightInfo,97	};98	use pallet_evm::account::CrossAccountId;99	use pallet_structure::weights::WeightInfo as StructureWeightInfo;100	use scale_info::TypeInfo;101	use sp_std::{vec, vec::Vec};102	use up_data_structs::{103		budget, CollectionId, CollectionLimits, CollectionMode, CollectionPermissions,104		CreateCollectionData, CreateItemData, CreateItemExData, Property, PropertyKey,105		PropertyKeyPermission, TokenId, COLLECTION_ADMINS_LIMIT, MAX_COLLECTION_DESCRIPTION_LENGTH,106		MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_PROPERTIES_SIZE, MAX_PROPERTIES_PER_ITEM,107		MAX_PROPERTY_KEY_LENGTH, MAX_PROPERTY_VALUE_LENGTH, MAX_TOKEN_PREFIX_LENGTH,108		MAX_TOKEN_PROPERTIES_SIZE,109	};110	use weights::WeightInfo;111112	use super::*;113114	/// Errors for the common Unique transactions.115	#[pallet::error]116	pub enum Error<T> {117		/// Decimal_points parameter must be lower than [`up_data_structs::MAX_DECIMAL_POINTS`].118		CollectionDecimalPointLimitExceeded,119		/// Length of items properties must be greater than 0.120		EmptyArgument,121		/// Repertition is only supported by refungible collection.122		RepartitionCalledOnNonRefungibleCollection,123	}124125	/// Configuration trait of this pallet.126	#[pallet::config]127	pub trait Config: frame_system::Config + pallet_common::Config + Sized + TypeInfo {128		/// Weight information for extrinsics in this pallet.129		type WeightInfo: WeightInfo;130131		/// Weight information for common pallet operations.132		type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;133134		type StructureWeightInfo: StructureWeightInfo;135136		/// Weight info information for extra refungible pallet operations.137		type RefungibleExtensionsWeightInfo: RefungibleExtensionsWeightInfo;138	}139140	#[pallet::pallet]141	pub struct Pallet<T>(_);142143	pub type SelfWeightOf<T> = <T as Config>::WeightInfo;144145	// # Used definitions146	//147	// ## User control levels148	//149	// chain-controlled - key is uncontrolled by user150	//                    i.e autoincrementing index151	//                    can use non-cryptographic hash152	// real - key is controlled by user153	//        but it is hard to generate enough colliding values, i.e owner of signed txs154	//        can use non-cryptographic hash155	// controlled - key is completly controlled by users156	//              i.e maps with mutable keys157	//              should use cryptographic hash158	//159	// ## User control level downgrade reasons160	//161	// ?1 - chain-controlled -> controlled162	//      collections/tokens can be destroyed, resulting in massive holes163	// ?2 - chain-controlled -> controlled164	//      same as ?1, but can be only added, resulting in easier exploitation165	// ?3 - real -> controlled166	//      no confirmation required, so addresses can be easily generated167168	//#region Private members169	/// Used for migrations170	#[pallet::storage]171	pub type ChainVersion<T> = StorageValue<_, u64, ValueQuery>;172	//#endregion173174	//#region Tokens transfer sponosoring rate limit baskets175	/// (Collection id (controlled?2), who created (real))176	/// TODO: Off chain worker should remove from this map when collection gets removed177	#[pallet::storage]178	#[pallet::getter(fn create_item_busket)]179	pub type CreateItemBasket<T: Config> = StorageMap<180		Hasher = Blake2_128Concat,181		Key = (CollectionId, T::AccountId),182		Value = BlockNumberFor<T>,183		QueryKind = OptionQuery,184	>;185	/// Collection id (controlled?2), token id (controlled?2)186	#[pallet::storage]187	#[pallet::getter(fn nft_transfer_basket)]188	pub type NftTransferBasket<T: Config> = StorageDoubleMap<189		Hasher1 = Blake2_128Concat,190		Key1 = CollectionId,191		Hasher2 = Blake2_128Concat,192		Key2 = TokenId,193		Value = BlockNumberFor<T>,194		QueryKind = OptionQuery,195	>;196	/// Collection id (controlled?2), owning user (real)197	#[pallet::storage]198	#[pallet::getter(fn fungible_transfer_basket)]199	pub type FungibleTransferBasket<T: Config> = StorageDoubleMap<200		Hasher1 = Blake2_128Concat,201		Key1 = CollectionId,202		Hasher2 = Twox64Concat,203		Key2 = T::AccountId,204		Value = BlockNumberFor<T>,205		QueryKind = OptionQuery,206	>;207	/// Collection id (controlled?2), token id (controlled?2)208	#[pallet::storage]209	#[pallet::getter(fn refungible_transfer_basket)]210	pub type ReFungibleTransferBasket<T: Config> = StorageNMap<211		Key = (212			Key<Blake2_128Concat, CollectionId>,213			Key<Blake2_128Concat, TokenId>,214			Key<Twox64Concat, T::AccountId>,215		),216		Value = BlockNumberFor<T>,217		QueryKind = OptionQuery,218	>;219	//#endregion220221	/// Last sponsoring of token property setting // todo:doc rephrase this and the following222	#[pallet::storage]223	#[pallet::getter(fn token_property_basket)]224	pub type TokenPropertyBasket<T: Config> = StorageDoubleMap<225		Hasher1 = Blake2_128Concat,226		Key1 = CollectionId,227		Hasher2 = Blake2_128Concat,228		Key2 = TokenId,229		Value = BlockNumberFor<T>,230		QueryKind = OptionQuery,231	>;232233	/// Last sponsoring of NFT approval in a collection234	#[pallet::storage]235	#[pallet::getter(fn nft_approve_basket)]236	pub type NftApproveBasket<T: Config> = StorageDoubleMap<237		Hasher1 = Blake2_128Concat,238		Key1 = CollectionId,239		Hasher2 = Blake2_128Concat,240		Key2 = TokenId,241		Value = BlockNumberFor<T>,242		QueryKind = OptionQuery,243	>;244	/// Last sponsoring of fungible tokens approval in a collection245	#[pallet::storage]246	#[pallet::getter(fn fungible_approve_basket)]247	pub type FungibleApproveBasket<T: Config> = StorageDoubleMap<248		Hasher1 = Blake2_128Concat,249		Key1 = CollectionId,250		Hasher2 = Twox64Concat,251		Key2 = T::AccountId,252		Value = BlockNumberFor<T>,253		QueryKind = OptionQuery,254	>;255	/// Last sponsoring of RFT approval in a collection256	#[pallet::storage]257	#[pallet::getter(fn refungible_approve_basket)]258	pub type RefungibleApproveBasket<T: Config> = StorageNMap<259		Key = (260			Key<Blake2_128Concat, CollectionId>,261			Key<Blake2_128Concat, TokenId>,262			Key<Twox64Concat, T::AccountId>,263		),264		Value = BlockNumberFor<T>,265		QueryKind = OptionQuery,266	>;267268	#[pallet::extra_constants]269	impl<T: Config> Pallet<T> {270		/// A maximum number of levels of depth in the token nesting tree.271		fn nesting_budget() -> u32 {272			5273		}274275		/// Maximal length of a collection name.276		fn max_collection_name_length() -> u32 {277			MAX_COLLECTION_NAME_LENGTH278		}279280		/// Maximal length of a collection description.281		fn max_collection_description_length() -> u32 {282			MAX_COLLECTION_DESCRIPTION_LENGTH283		}284285		/// Maximal length of a token prefix.286		fn max_token_prefix_length() -> u32 {287			MAX_TOKEN_PREFIX_LENGTH288		}289290		/// Maximum admins per collection.291		fn collection_admins_limit() -> u32 {292			COLLECTION_ADMINS_LIMIT293		}294295		/// Maximal length of a property key.296		fn max_property_key_length() -> u32 {297			MAX_PROPERTY_KEY_LENGTH298		}299300		/// Maximal length of a property value.301		fn max_property_value_length() -> u32 {302			MAX_PROPERTY_VALUE_LENGTH303		}304305		/// A maximum number of token properties.306		fn max_properties_per_item() -> u32 {307			MAX_PROPERTIES_PER_ITEM308		}309310		/// Maximum size for all collection properties.311		fn max_collection_properties_size() -> u32 {312			MAX_COLLECTION_PROPERTIES_SIZE313		}314315		/// Maximum size of all token properties.316		fn max_token_properties_size() -> u32 {317			MAX_TOKEN_PROPERTIES_SIZE318		}319320		/// Default NFT collection limit.321		fn nft_default_collection_limits() -> CollectionLimits {322			CollectionLimits::with_default_limits(CollectionMode::NFT)323		}324325		/// Default RFT collection limit.326		fn rft_default_collection_limits() -> CollectionLimits {327			CollectionLimits::with_default_limits(CollectionMode::ReFungible)328		}329330		/// Default FT collection limit.331		fn ft_default_collection_limits() -> CollectionLimits {332			CollectionLimits::with_default_limits(CollectionMode::Fungible(0))333		}334	}335336	/// Type alias to Pallet, to be used by construct_runtime.337	#[pallet::call]338	impl<T: Config> Pallet<T> {339		/// Create a collection of tokens.340		///341		/// Each Token may have multiple properties encoded as an array of bytes342		/// of certain length. The initial owner of the collection is set343		/// to the address that signed the transaction and can be changed later.344		///345		/// Prefer the more advanced [`create_collection_ex`][`Pallet::create_collection_ex`] instead.346		///347		/// # Permissions348		///349		/// * Anyone - becomes the owner of the new collection.350		///351		/// # Arguments352		///353		/// * `collection_name`: Wide-character string with collection name354		/// (limit [`MAX_COLLECTION_NAME_LENGTH`]).355		/// * `collection_description`: Wide-character string with collection description356		/// (limit [`MAX_COLLECTION_DESCRIPTION_LENGTH`]).357		/// * `token_prefix`: Byte string containing the token prefix to mark a collection358		/// to which a token belongs (limit [`MAX_TOKEN_PREFIX_LENGTH`]).359		/// * `mode`: Type of items stored in the collection and type dependent data.360		///361		/// returns collection ID362		///363		/// Deprecated: `create_collection_ex` is more up-to-date and advanced, prefer it instead.364		#[pallet::call_index(0)]365		#[pallet::weight(<SelfWeightOf<T>>::create_collection())]366		pub fn create_collection(367			origin: OriginFor<T>,368			collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,369			collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,370			token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,371			mode: CollectionMode,372		) -> DispatchResult {373			let data: CreateCollectionData<T::CrossAccountId> = CreateCollectionData {374				name: collection_name,375				description: collection_description,376				token_prefix,377				mode,378				..Default::default()379			};380			Self::create_collection_ex(origin, data)381		}382383		/// Create a collection with explicit parameters.384		///385		/// Prefer it to the deprecated [`create_collection`][`Pallet::create_collection`] method.386		///387		/// # Permissions388		///389		/// * Anyone - becomes the owner of the new collection.390		///391		/// # Arguments392		///393		/// * `data`: Explicit data of a collection used for its creation.394		#[pallet::call_index(1)]395		#[pallet::weight(<SelfWeightOf<T>>::create_collection())]396		pub fn create_collection_ex(397			origin: OriginFor<T>,398			data: CreateCollectionData<T::CrossAccountId>,399		) -> DispatchResult {400			let sender = ensure_signed(origin)?;401402			// =========403			let sender = T::CrossAccountId::from_sub(sender);404			let _id = T::CollectionDispatch::create(sender.clone(), sender, data)?;405406			Ok(())407		}408409		/// Destroy a collection if no tokens exist within.410		///411		/// # Permissions412		///413		/// * Collection owner414		///415		/// # Arguments416		///417		/// * `collection_id`: Collection to destroy.418		#[pallet::call_index(2)]419		#[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]420		pub fn destroy_collection(421			origin: OriginFor<T>,422			collection_id: CollectionId,423		) -> DispatchResult {424			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);425426			Self::destroy_collection_internal(sender, collection_id)427		}428429		/// Add an address to allow list.430		///431		/// # Permissions432		///433		/// * Collection owner434		/// * Collection admin435		///436		/// # Arguments437		///438		/// * `collection_id`: ID of the modified collection.439		/// * `address`: ID of the address to be added to the allowlist.440		#[pallet::call_index(3)]441		#[pallet::weight(<SelfWeightOf<T>>::add_to_allow_list())]442		pub fn add_to_allow_list(443			origin: OriginFor<T>,444			collection_id: CollectionId,445			address: T::CrossAccountId,446		) -> DispatchResult {447			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {448				fail!(<pallet_common::Error<T>>::UnsupportedOperation);449			}450451			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);452			let collection = <CollectionHandle<T>>::try_get(collection_id)?;453			collection.check_is_internal()?;454455			<PalletCommon<T>>::toggle_allowlist(&collection, &sender, &address, true)?;456457			Ok(())458		}459460		/// Remove an address from allow list.461		///462		/// # Permissions463		///464		/// * Collection owner465		/// * Collection admin466		///467		/// # Arguments468		///469		/// * `collection_id`: ID of the modified collection.470		/// * `address`: ID of the address to be removed from the allowlist.471		#[pallet::call_index(4)]472		#[pallet::weight(<SelfWeightOf<T>>::remove_from_allow_list())]473		pub fn remove_from_allow_list(474			origin: OriginFor<T>,475			collection_id: CollectionId,476			address: T::CrossAccountId,477		) -> DispatchResult {478			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {479				fail!(<pallet_common::Error<T>>::UnsupportedOperation);480			}481482			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);483			let collection = <CollectionHandle<T>>::try_get(collection_id)?;484			collection.check_is_internal()?;485486			<PalletCommon<T>>::toggle_allowlist(&collection, &sender, &address, false)?;487488			Ok(())489		}490491		/// Change the owner of the collection.492		///493		/// # Permissions494		///495		/// * Collection owner496		///497		/// # Arguments498		///499		/// * `collection_id`: ID of the modified collection.500		/// * `new_owner`: ID of the account that will become the owner.501		#[pallet::call_index(5)]502		#[pallet::weight(<SelfWeightOf<T>>::change_collection_owner())]503		pub fn change_collection_owner(504			origin: OriginFor<T>,505			collection_id: CollectionId,506			new_owner: T::AccountId,507		) -> DispatchResult {508			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {509				fail!(<pallet_common::Error<T>>::UnsupportedOperation);510			}511			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);512			let new_owner = T::CrossAccountId::from_sub(new_owner);513			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;514			target_collection.change_owner(sender, new_owner)515		}516517		/// Add an admin to a collection.518		///519		/// NFT Collection can be controlled by multiple admin addresses520		/// (some which can also be servers, for example). Admins can issue521		/// and burn NFTs, as well as add and remove other admins,522		/// but cannot change NFT or Collection ownership.523		///524		/// # Permissions525		///526		/// * Collection owner527		/// * Collection admin528		///529		/// # Arguments530		///531		/// * `collection_id`: ID of the Collection to add an admin for.532		/// * `new_admin`: Address of new admin to add.533		#[pallet::call_index(6)]534		#[pallet::weight(<SelfWeightOf<T>>::add_collection_admin())]535		pub fn add_collection_admin(536			origin: OriginFor<T>,537			collection_id: CollectionId,538			new_admin_id: T::CrossAccountId,539		) -> DispatchResult {540			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {541				fail!(<pallet_common::Error<T>>::UnsupportedOperation);542			}543			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);544			let collection = <CollectionHandle<T>>::try_get(collection_id)?;545			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)546		}547548		/// Remove admin of a collection.549		///550		/// An admin address can remove itself. List of admins may become empty,551		/// in which case only Collection Owner will be able to add an Admin.552		///553		/// # Permissions554		///555		/// * Collection owner556		/// * Collection admin557		///558		/// # Arguments559		///560		/// * `collection_id`: ID of the collection to remove the admin for.561		/// * `account_id`: Address of the admin to remove.562		#[pallet::call_index(7)]563		#[pallet::weight(<SelfWeightOf<T>>::remove_collection_admin())]564		pub fn remove_collection_admin(565			origin: OriginFor<T>,566			collection_id: CollectionId,567			account_id: T::CrossAccountId,568		) -> DispatchResult {569			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {570				fail!(<pallet_common::Error<T>>::UnsupportedOperation);571			}572			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);573			let collection = <CollectionHandle<T>>::try_get(collection_id)?;574			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)575		}576577		/// Set (invite) a new collection sponsor.578		///579		/// If successful, confirmation from the sponsor-to-be will be pending.580		///581		/// # Permissions582		///583		/// * Collection owner584		/// * Collection admin585		///586		/// # Arguments587		///588		/// * `collection_id`: ID of the modified collection.589		/// * `new_sponsor`: ID of the account of the sponsor-to-be.590		#[pallet::call_index(8)]591		#[pallet::weight(<SelfWeightOf<T>>::set_collection_sponsor())]592		pub fn set_collection_sponsor(593			origin: OriginFor<T>,594			collection_id: CollectionId,595			new_sponsor: T::AccountId,596		) -> DispatchResult {597			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {598				fail!(<pallet_common::Error<T>>::UnsupportedOperation);599			}600			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);601			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;602			target_collection.set_sponsor(&sender, new_sponsor.clone())603		}604605		/// Confirm own sponsorship of a collection, becoming the sponsor.606		///607		/// An invitation must be pending, see [`set_collection_sponsor`][`Pallet::set_collection_sponsor`].608		/// Sponsor can pay the fees of a transaction instead of the sender,609		/// but only within specified limits.610		///611		/// # Permissions612		///613		/// * Sponsor-to-be614		///615		/// # Arguments616		///617		/// * `collection_id`: ID of the collection with the pending sponsor.618		#[pallet::call_index(9)]619		#[pallet::weight(<SelfWeightOf<T>>::confirm_sponsorship())]620		pub fn confirm_sponsorship(621			origin: OriginFor<T>,622			collection_id: CollectionId,623		) -> DispatchResult {624			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {625				fail!(<pallet_common::Error<T>>::UnsupportedOperation);626			}627			let sender = ensure_signed(origin)?;628			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;629			target_collection.confirm_sponsorship(&sender)630		}631632		/// Remove a collection's a sponsor, making everyone pay for their own transactions.633		///634		/// # Permissions635		///636		/// * Collection owner637		///638		/// # Arguments639		///640		/// * `collection_id`: ID of the collection with the sponsor to remove.641		#[pallet::call_index(10)]642		#[pallet::weight(<SelfWeightOf<T>>::remove_collection_sponsor())]643		pub fn remove_collection_sponsor(644			origin: OriginFor<T>,645			collection_id: CollectionId,646		) -> DispatchResult {647			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {648				fail!(<pallet_common::Error<T>>::UnsupportedOperation);649			}650			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);651			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;652			target_collection.remove_sponsor(&sender)653		}654655		/// Mint an item within a collection.656		///657		/// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].658		///659		/// # Permissions660		///661		/// * Collection owner662		/// * Collection admin663		/// * Anyone if664		///     * Allow List is enabled, and665		///     * Address is added to allow list, and666		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])667		///668		/// # Arguments669		///670		/// * `collection_id`: ID of the collection to which an item would belong.671		/// * `owner`: Address of the initial owner of the item.672		/// * `data`: Token data describing the item to store on chain.673		#[pallet::call_index(11)]674		#[pallet::weight(T::CommonWeightInfo::create_item(data) + <Pallet<T>>::nesting_budget_predispatch_weight())]675		pub fn create_item(676			origin: OriginFor<T>,677			collection_id: CollectionId,678			owner: T::CrossAccountId,679			data: CreateItemData,680		) -> DispatchResultWithPostInfo {681			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);682			let budget = Self::structure_nesting_budget();683684			Self::refund_nesting_budget(685				dispatch_tx::<T, _>(collection_id, |d| {686					d.create_item(sender, owner, data, &budget)687				}),688				budget,689			)690		}691692		/// Create multiple items within a collection.693		///694		/// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].695		///696		/// # Permissions697		///698		/// * Collection owner699		/// * Collection admin700		/// * Anyone if701		///     * Allow List is enabled, and702		///     * Address is added to the allow list, and703		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])704		///705		/// # Arguments706		///707		/// * `collection_id`: ID of the collection to which the tokens would belong.708		/// * `owner`: Address of the initial owner of the tokens.709		/// * `items_data`: Vector of data describing each item to be created.710		#[pallet::call_index(12)]711		#[pallet::weight(T::CommonWeightInfo::create_multiple_items(items_data) + <Pallet<T>>::nesting_budget_predispatch_weight())]712		pub fn create_multiple_items(713			origin: OriginFor<T>,714			collection_id: CollectionId,715			owner: T::CrossAccountId,716			items_data: Vec<CreateItemData>,717		) -> DispatchResultWithPostInfo {718			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);719			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);720			let budget = Self::structure_nesting_budget();721722			Self::refund_nesting_budget(723				dispatch_tx::<T, _>(collection_id, |d| {724					d.create_multiple_items(sender, owner, items_data, &budget)725				}),726				budget,727			)728		}729730		/// Add or change collection properties.731		///732		/// # Permissions733		///734		/// * Collection owner735		/// * Collection admin736		///737		/// # Arguments738		///739		/// * `collection_id`: ID of the modified collection.740		/// * `properties`: Vector of key-value pairs stored as the collection's metadata.741		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.742		#[pallet::call_index(13)]743		#[pallet::weight(T::CommonWeightInfo::set_collection_properties(properties.len() as u32))]744		pub fn set_collection_properties(745			origin: OriginFor<T>,746			collection_id: CollectionId,747			properties: Vec<Property>,748		) -> DispatchResultWithPostInfo {749			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);750751			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);752753			dispatch_tx::<T, _>(collection_id, |d| {754				d.set_collection_properties(sender, properties)755			})756		}757758		/// Delete specified collection properties.759		///760		/// # Permissions761		///762		/// * Collection Owner763		/// * Collection Admin764		///765		/// # Arguments766		///767		/// * `collection_id`: ID of the modified collection.768		/// * `property_keys`: Vector of keys of the properties to be deleted.769		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.770		#[pallet::call_index(14)]771		#[pallet::weight(T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32))]772		pub fn delete_collection_properties(773			origin: OriginFor<T>,774			collection_id: CollectionId,775			property_keys: Vec<PropertyKey>,776		) -> DispatchResultWithPostInfo {777			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);778779			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);780781			dispatch_tx::<T, _>(collection_id, |d| {782				d.delete_collection_properties(&sender, property_keys)783			})784		}785786		/// Add or change token properties according to collection's permissions.787		/// Currently properties only work with NFTs.788		///789		/// # Permissions790		///791		/// * Depends on collection's token property permissions and specified property mutability:792		/// 	* Collection owner793		/// 	* Collection admin794		/// 	* Token owner795		///796		/// See [`set_token_property_permissions`][`Pallet::set_token_property_permissions`].797		///798		/// # Arguments799		///800		/// * `collection_id: ID of the collection to which the token belongs.801		/// * `token_id`: ID of the modified token.802		/// * `properties`: Vector of key-value pairs stored as the token's metadata.803		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.804		#[pallet::call_index(15)]805		#[pallet::weight(T::CommonWeightInfo::set_token_properties(properties.len() as u32) + <Pallet<T>>::nesting_budget_predispatch_weight())]806		pub fn set_token_properties(807			origin: OriginFor<T>,808			collection_id: CollectionId,809			token_id: TokenId,810			properties: Vec<Property>,811		) -> DispatchResultWithPostInfo {812			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);813814			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);815			let budget = Self::structure_nesting_budget();816817			Self::refund_nesting_budget(818				dispatch_tx::<T, _>(collection_id, |d| {819					d.set_token_properties(sender, token_id, properties, &budget)820				}),821				budget,822			)823		}824825		/// Delete specified token properties. Currently properties only work with NFTs.826		///827		/// # Permissions828		///829		/// * Depends on collection's token property permissions and specified property mutability:830		/// 	* Collection owner831		/// 	* Collection admin832		/// 	* Token owner833		///834		/// # Arguments835		///836		/// * `collection_id`: ID of the collection to which the token belongs.837		/// * `token_id`: ID of the modified token.838		/// * `property_keys`: Vector of keys of the properties to be deleted.839		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.840		#[pallet::call_index(16)]841		#[pallet::weight(T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32) + <Pallet<T>>::nesting_budget_predispatch_weight())]842		pub fn delete_token_properties(843			origin: OriginFor<T>,844			collection_id: CollectionId,845			token_id: TokenId,846			property_keys: Vec<PropertyKey>,847		) -> DispatchResultWithPostInfo {848			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);849850			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);851			let budget = Self::structure_nesting_budget();852853			Self::refund_nesting_budget(854				dispatch_tx::<T, _>(collection_id, |d| {855					d.delete_token_properties(sender, token_id, property_keys, &budget)856				}),857				budget,858			)859		}860861		/// Add or change token property permissions of a collection.862		///863		/// Without a permission for a particular key, a property with that key864		/// cannot be created in a token.865		///866		/// # Permissions867		///868		/// * Collection owner869		/// * Collection admin870		///871		/// # Arguments872		///873		/// * `collection_id`: ID of the modified collection.874		/// * `property_permissions`: Vector of permissions for property keys.875		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.876		#[pallet::call_index(17)]877		#[pallet::weight(T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32))]878		pub fn set_token_property_permissions(879			origin: OriginFor<T>,880			collection_id: CollectionId,881			property_permissions: Vec<PropertyKeyPermission>,882		) -> DispatchResultWithPostInfo {883			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);884885			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);886887			dispatch_tx::<T, _>(collection_id, |d| {888				d.set_token_property_permissions(&sender, property_permissions)889			})890		}891892		/// Create multiple items within a collection with explicitly specified initial parameters.893		///894		/// # Permissions895		///896		/// * Collection owner897		/// * Collection admin898		/// * Anyone if899		///     * Allow List is enabled, and900		///     * Address is added to allow list, and901		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])902		///903		/// # Arguments904		///905		/// * `collection_id`: ID of the collection to which the tokens would belong.906		/// * `data`: Explicit item creation data.907		#[pallet::call_index(18)]908		#[pallet::weight(T::CommonWeightInfo::create_multiple_items_ex(data) + <Pallet<T>>::nesting_budget_predispatch_weight())]909		pub fn create_multiple_items_ex(910			origin: OriginFor<T>,911			collection_id: CollectionId,912			data: CreateItemExData<T::CrossAccountId>,913		) -> DispatchResultWithPostInfo {914			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);915			let budget = Self::structure_nesting_budget();916917			Self::refund_nesting_budget(918				dispatch_tx::<T, _>(collection_id, |d| {919					d.create_multiple_items_ex(sender, data, &budget)920				}),921				budget,922			)923		}924925		/// Completely allow or disallow transfers for a particular collection.926		///927		/// # Permissions928		///929		/// * Collection owner930		///931		/// # Arguments932		///933		/// * `collection_id`: ID of the collection.934		/// * `value`: New value of the flag, are transfers allowed?935		#[pallet::call_index(19)]936		#[pallet::weight(<SelfWeightOf<T>>::set_transfers_enabled_flag())]937		pub fn set_transfers_enabled_flag(938			origin: OriginFor<T>,939			collection_id: CollectionId,940			value: bool,941		) -> DispatchResult {942			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {943				fail!(<pallet_common::Error<T>>::UnsupportedOperation);944			}945			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);946			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;947			target_collection.check_is_internal()?;948			target_collection.check_is_owner(&sender)?;949950			// =========951952			target_collection.limits.transfers_enabled = Some(value);953			target_collection.save()954		}955956		/// Destroy an item.957		///958		/// # Permissions959		///960		/// * Collection owner961		/// * Collection admin962		/// * Current item owner963		///964		/// # Arguments965		///966		/// * `collection_id`: ID of the collection to which the item belongs.967		/// * `item_id`: ID of item to burn.968		/// * `value`: Number of pieces of the item to destroy.969		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.970		///     * Fungible Mode: The desired number of pieces to burn.971		///     * Re-Fungible Mode: The desired number of pieces to burn.972		#[pallet::call_index(20)]973		#[pallet::weight(T::CommonWeightInfo::burn_item())]974		pub fn burn_item(975			origin: OriginFor<T>,976			collection_id: CollectionId,977			item_id: TokenId,978			value: u128,979		) -> DispatchResultWithPostInfo {980			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);981982			let post_info =983				dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;984			if value == 1 {985				<NftTransferBasket<T>>::remove(collection_id, item_id);986				<NftApproveBasket<T>>::remove(collection_id, item_id);987			}988			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?989			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());990			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));991			Ok(post_info)992		}993994		/// Destroy a token on behalf of the owner as a non-owner account.995		///996		/// See also: [`approve`][`Pallet::approve`].997		///998		/// After this method executes, one approval is removed from the total so that999		/// the approved address will not be able to transfer this item again from this owner.1000		///1001		/// # Permissions1002		///1003		/// * Collection owner1004		/// * Collection admin1005		/// * Current token owner1006		/// * Address approved by current item owner1007		///1008		/// # Arguments1009		///1010		/// * `from`: The owner of the burning item.1011		/// * `collection_id`: ID of the collection to which the item belongs.1012		/// * `item_id`: ID of item to burn.1013		/// * `value`: Number of pieces to burn.1014		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1015		///     * Fungible Mode: The desired number of pieces to burn.1016		///     * Re-Fungible Mode: The desired number of pieces to burn.1017		#[pallet::call_index(21)]1018		#[pallet::weight(T::CommonWeightInfo::burn_from() + <Pallet<T>>::nesting_budget_predispatch_weight())]1019		pub fn burn_from(1020			origin: OriginFor<T>,1021			collection_id: CollectionId,1022			from: T::CrossAccountId,1023			item_id: TokenId,1024			value: u128,1025		) -> DispatchResultWithPostInfo {1026			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1027			let budget = Self::structure_nesting_budget();10281029			Self::refund_nesting_budget(1030				dispatch_tx::<T, _>(collection_id, |d| {1031					d.burn_from(sender, from, item_id, value, &budget)1032				}),1033				budget,1034			)1035		}10361037		/// Change ownership of the token.1038		///1039		/// # Permissions1040		///1041		/// * Collection owner1042		/// * Collection admin1043		/// * Current token owner1044		///1045		/// # Arguments1046		///1047		/// * `recipient`: Address of token recipient.1048		/// * `collection_id`: ID of the collection the item belongs to.1049		/// * `item_id`: ID of the item.1050		///     * Non-Fungible Mode: Required.1051		///     * Fungible Mode: Ignored.1052		///     * Re-Fungible Mode: Required.1053		///1054		/// * `value`: Amount to transfer.1055		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1056		///     * Fungible Mode: The desired number of pieces to transfer.1057		///     * Re-Fungible Mode: The desired number of pieces to transfer.1058		#[pallet::call_index(22)]1059		#[pallet::weight(T::CommonWeightInfo::transfer() + <Pallet<T>>::nesting_budget_predispatch_weight())]1060		pub fn transfer(1061			origin: OriginFor<T>,1062			recipient: T::CrossAccountId,1063			collection_id: CollectionId,1064			item_id: TokenId,1065			value: u128,1066		) -> DispatchResultWithPostInfo {1067			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1068			let budget = Self::structure_nesting_budget();10691070			Self::refund_nesting_budget(1071				dispatch_tx::<T, _>(collection_id, |d| {1072					d.transfer(sender, recipient, item_id, value, &budget)1073				}),1074				budget,1075			)1076		}10771078		/// Allow a non-permissioned address to transfer or burn an item.1079		///1080		/// # Permissions1081		///1082		/// * Collection owner1083		/// * Collection admin1084		/// * Current item owner1085		///1086		/// # Arguments1087		///1088		/// * `spender`: Account to be approved to make specific transactions on non-owned tokens.1089		/// * `collection_id`: ID of the collection the item belongs to.1090		/// * `item_id`: ID of the item transactions on which are now approved.1091		/// * `amount`: Number of pieces of the item approved for a transaction (maximum of 1 for NFTs).1092		/// Set to 0 to revoke the approval.1093		#[pallet::call_index(23)]1094		#[pallet::weight(T::CommonWeightInfo::approve())]1095		pub fn approve(1096			origin: OriginFor<T>,1097			spender: T::CrossAccountId,1098			collection_id: CollectionId,1099			item_id: TokenId,1100			amount: u128,1101		) -> DispatchResultWithPostInfo {1102			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);11031104			dispatch_tx::<T, _>(collection_id, |d| {1105				d.approve(sender, spender, item_id, amount)1106			})1107		}11081109		/// Allow a non-permissioned address to transfer or burn an item from owner's eth mirror.1110		///1111		/// # Permissions1112		///1113		/// * Collection owner1114		/// * Collection admin1115		/// * Current item owner1116		///1117		/// # Arguments1118		///1119		/// * `from`: Owner's account eth mirror1120		/// * `to`: Account to be approved to make specific transactions on non-owned tokens.1121		/// * `collection_id`: ID of the collection the item belongs to.1122		/// * `item_id`: ID of the item transactions on which are now approved.1123		/// * `amount`: Number of pieces of the item approved for a transaction (maximum of 1 for NFTs).1124		/// Set to 0 to revoke the approval.1125		#[pallet::call_index(24)]1126		#[pallet::weight(T::CommonWeightInfo::approve_from())]1127		pub fn approve_from(1128			origin: OriginFor<T>,1129			from: T::CrossAccountId,1130			to: T::CrossAccountId,1131			collection_id: CollectionId,1132			item_id: TokenId,1133			amount: u128,1134		) -> DispatchResultWithPostInfo {1135			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);11361137			dispatch_tx::<T, _>(collection_id, |d| {1138				d.approve_from(sender, from, to, item_id, amount)1139			})1140		}11411142		/// Change ownership of an item on behalf of the owner as a non-owner account.1143		///1144		/// See the [`approve`][`Pallet::approve`] method for additional information.1145		///1146		/// After this method executes, one approval is removed from the total so that1147		/// the approved address will not be able to transfer this item again from this owner.1148		///1149		/// # Permissions1150		///1151		/// * Collection owner1152		/// * Collection admin1153		/// * Current item owner1154		/// * Address approved by current item owner1155		///1156		/// # Arguments1157		///1158		/// * `from`: Address that currently owns the token.1159		/// * `recipient`: Address of the new token-owner-to-be.1160		/// * `collection_id`: ID of the collection the item.1161		/// * `item_id`: ID of the item to be transferred.1162		/// * `value`: Amount to transfer.1163		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1164		///     * Fungible Mode: The desired number of pieces to transfer.1165		///     * Re-Fungible Mode: The desired number of pieces to transfer.1166		#[pallet::call_index(25)]1167		#[pallet::weight(T::CommonWeightInfo::transfer_from() + <Pallet<T>>::nesting_budget_predispatch_weight())]1168		pub fn transfer_from(1169			origin: OriginFor<T>,1170			from: T::CrossAccountId,1171			recipient: T::CrossAccountId,1172			collection_id: CollectionId,1173			item_id: TokenId,1174			value: u128,1175		) -> DispatchResultWithPostInfo {1176			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1177			let budget = Self::structure_nesting_budget();11781179			Self::refund_nesting_budget(1180				dispatch_tx::<T, _>(collection_id, |d| {1181					d.transfer_from(sender, from, recipient, item_id, value, &budget)1182				}),1183				budget,1184			)1185		}11861187		/// Set specific limits of a collection. Empty, or None fields mean chain default.1188		///1189		/// # Permissions1190		///1191		/// * Collection owner1192		/// * Collection admin1193		///1194		/// # Arguments1195		///1196		/// * `collection_id`: ID of the modified collection.1197		/// * `new_limit`: New limits of the collection. Fields that are not set (None)1198		/// will not overwrite the old ones.1199		#[pallet::call_index(26)]1200		#[pallet::weight(<SelfWeightOf<T>>::set_collection_limits())]1201		pub fn set_collection_limits(1202			origin: OriginFor<T>,1203			collection_id: CollectionId,1204			new_limit: CollectionLimits,1205		) -> DispatchResult {1206			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {1207				fail!(<pallet_common::Error<T>>::UnsupportedOperation);1208			}1209			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1210			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1211			<PalletCommon<T>>::update_limits(&sender, &mut target_collection, new_limit)1212		}12131214		/// Set specific permissions of a collection. Empty, or None fields mean chain default.1215		///1216		/// # Permissions1217		///1218		/// * Collection owner1219		/// * Collection admin1220		///1221		/// # Arguments1222		///1223		/// * `collection_id`: ID of the modified collection.1224		/// * `new_permission`: New permissions of the collection. Fields that are not set (None)1225		/// will not overwrite the old ones.1226		#[pallet::call_index(27)]1227		#[pallet::weight(<SelfWeightOf<T>>::set_collection_limits())]1228		pub fn set_collection_permissions(1229			origin: OriginFor<T>,1230			collection_id: CollectionId,1231			new_permission: CollectionPermissions,1232		) -> DispatchResult {1233			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {1234				fail!(<pallet_common::Error<T>>::UnsupportedOperation);1235			}1236			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1237			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1238			<PalletCommon<T>>::update_permissions(&sender, &mut target_collection, new_permission)1239		}12401241		/// Re-partition a refungible token, while owning all of its parts/pieces.1242		///1243		/// # Permissions1244		///1245		/// * Token owner (must own every part)1246		///1247		/// # Arguments1248		///1249		/// * `collection_id`: ID of the collection the RFT belongs to.1250		/// * `token_id`: ID of the RFT.1251		/// * `amount`: New number of parts/pieces into which the token shall be partitioned.1252		#[pallet::call_index(28)]1253		#[pallet::weight(T::RefungibleExtensionsWeightInfo::repartition())]1254		pub fn repartition(1255			origin: OriginFor<T>,1256			collection_id: CollectionId,1257			token_id: TokenId,1258			amount: u128,1259		) -> DispatchResultWithPostInfo {1260			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1261			dispatch_tx::<T, _>(collection_id, |d| {1262				if let Some(refungible_extensions) = d.refungible_extensions() {1263					refungible_extensions.repartition(&sender, token_id, amount)1264				} else {1265					fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection)1266				}1267			})1268		}12691270		/// Sets or unsets the approval of a given operator.1271		///1272		/// The `operator` is allowed to transfer all tokens of the `owner` on their behalf.1273		///1274		/// # Arguments1275		///1276		/// * `owner`: Token owner1277		/// * `operator`: Operator1278		/// * `approve`: Should operator status be granted or revoked?1279		#[pallet::call_index(29)]1280		#[pallet::weight(T::CommonWeightInfo::set_allowance_for_all())]1281		pub fn set_allowance_for_all(1282			origin: OriginFor<T>,1283			collection_id: CollectionId,1284			operator: T::CrossAccountId,1285			approve: bool,1286		) -> DispatchResultWithPostInfo {1287			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1288			dispatch_tx::<T, _>(collection_id, |d| {1289				d.set_allowance_for_all(sender, operator, approve)1290			})1291		}12921293		/// Repairs a collection if the data was somehow corrupted.1294		///1295		/// # Arguments1296		///1297		/// * `collection_id`: ID of the collection to repair.1298		#[pallet::call_index(30)]1299		#[pallet::weight(<SelfWeightOf<T>>::force_repair_collection())]1300		pub fn force_repair_collection(1301			origin: OriginFor<T>,1302			collection_id: CollectionId,1303		) -> DispatchResult {1304			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {1305				fail!(<pallet_common::Error<T>>::UnsupportedOperation);1306			}1307			ensure_root(origin)?;1308			<PalletCommon<T>>::repair_collection(collection_id)1309		}13101311		/// Repairs a token if the data was somehow corrupted.1312		///1313		/// # Arguments1314		///1315		/// * `collection_id`: ID of the collection the item belongs to.1316		/// * `item_id`: ID of the item.1317		#[pallet::call_index(31)]1318		#[pallet::weight(T::CommonWeightInfo::force_repair_item())]1319		pub fn force_repair_item(1320			origin: OriginFor<T>,1321			collection_id: CollectionId,1322			item_id: TokenId,1323		) -> DispatchResultWithPostInfo {1324			ensure_root(origin)?;1325			dispatch_tx::<T, _>(collection_id, |d| d.repair_item(item_id))1326		}1327	}13281329	impl<T: Config> Pallet<T> {1330		/// Force set `sponsor` for `collection`.1331		///1332		/// Differs from [`set_collection_sponsor`][`Pallet::set_collection_sponsor`] in that confirmation1333		/// from the `sponsor` is not required.1334		///1335		/// # Arguments1336		///1337		/// * `sponsor`: ID of the account of the sponsor-to-be.1338		/// * `collection_id`: ID of the modified collection.1339		pub fn force_set_sponsor(1340			sponsor: T::AccountId,1341			collection_id: CollectionId,1342		) -> DispatchResult {1343			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1344			target_collection.force_set_sponsor(sponsor)1345		}13461347		/// Force remove `sponsor` for `collection`.1348		///1349		/// Differs from `remove_sponsor` in that1350		/// it doesn't require consent from the `owner` of the collection.1351		///1352		/// # Arguments1353		///1354		/// * `collection_id`: ID of the modified collection.1355		pub fn force_remove_collection_sponsor(collection_id: CollectionId) -> DispatchResult {1356			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1357			target_collection.force_remove_sponsor()1358		}13591360		#[inline(always)]1361		pub(crate) fn destroy_collection_internal(1362			sender: T::CrossAccountId,1363			collection_id: CollectionId,1364		) -> DispatchResult {1365			T::CollectionDispatch::destroy(sender, collection_id)?;13661367			// TODO: basket cleanup should be moved elsewhere1368			// Maybe runtime dispatch.rs should perform it?13691370			let _ = <NftTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1371			let _ = <FungibleTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1372			let _ = <ReFungibleTransferBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);13731374			let _ = <NftApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1375			let _ = <FungibleApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1376			let _ = <RefungibleApproveBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);13771378			Ok(())1379		}13801381		fn structure_nesting_budget() -> budget::Value {1382			budget::Value::new(Self::nesting_budget())1383		}13841385		fn nesting_budget_predispatch_weight() -> Weight {1386			T::StructureWeightInfo::find_parent().saturating_mul(Self::nesting_budget() as u64)1387		}13881389		pub fn refund_nesting_budget(1390			mut result: DispatchResultWithPostInfo,1391			budget: budget::Value,1392		) -> DispatchResultWithPostInfo {1393			let refund_amount = budget.refund_amount();1394			let consumed = Self::nesting_budget() - refund_amount;13951396			match &mut result {1397				Ok(PostDispatchInfo {1398					actual_weight: Some(weight),1399					..1400				})1401				| Err(DispatchErrorWithPostInfo {1402					post_info: PostDispatchInfo {1403						actual_weight: Some(weight),1404						..1405					},1406					..1407				}) => {1408					*weight += T::StructureWeightInfo::find_parent().saturating_mul(consumed as u64)1409				}1410				_ => {}1411			}14121413			result1414		}1415	}1416}
after · pallets/unique/src/lib.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//! # Unique Pallet18//!19//! A pallet governing Unique transactions.20//!21//! - [`Config`]22//! - [`Call`]23//! - [`Pallet`]24//!25//! ## Overview26//!27//! The Unique pallet's purpose is to be the primary interface between28//! external users and the inner structure of the Unique chains.29//!30//! It also contains an implementation of [`CollectionHelpers`][`eth`],31//! an Ethereum contract dealing with collection operations.32//!33//! ## Interface34//!35//! ### Dispatchables36//!37//! - `create_collection` - Create a collection of tokens. **Deprecated**, use `create_collection_ex`.38//! - `create_collection_ex` - Create a collection of tokens with explicit parameters.39//! - `destroy_collection` - Destroy a collection if no tokens exist within.40//! - `add_to_allow_list` - Add an address to allow list.41//! - `remove_from_allow_list` - Remove an address from allow list.42//! - `change_collection_owner` - Change the owner of the collection.43//! - `add_collection_admin` - Add an admin to a collection.44//! - `remove_collection_admin` - Remove admin of a collection.45//! - `set_collection_sponsor` - Invite a new collection sponsor.46//! - `confirm_sponsorship` - Confirm own sponsorship of a collection, becoming the sponsor.47//! - `remove_collection_sponsor` - Remove a sponsor from a collection.48//! - `create_item` - Create an item within a collection.49//! - `create_multiple_items` - Create multiple items within a collection.50//! - `set_collection_properties` - Add or change collection properties.51//! - `delete_collection_properties` - Delete specified collection properties.52//! - `set_token_properties` - Add or change token properties.53//! - `delete_token_properties` - Delete token properties.54//! - `set_token_property_permissions` - Add or change token property permissions of a collection.55//! - `create_multiple_items_ex` - Create multiple items within a collection with explicitly specified initial parameters.56//! - `set_transfers_enabled_flag` - Completely allow or disallow transfers for a particular collection.57//! - `burn_item` - Destroy an item.58//! - `burn_from` - Destroy an item on behalf of the owner as a non-owner account.59//! - `transfer` - Change ownership of the token.60//! - `transfer_from` - Change ownership of the token on behalf of the owner as a non-owner account.61//! - `approve` - Allow a non-permissioned address to transfer or burn an item.62//! - `set_collection_limits` - Set specific limits of a collection.63//! - `set_collection_permissions` - Set specific permissions of a collection.64//! - `repartition` - Re-partition a refungible token, while owning all of its parts.6566#![recursion_limit = "1024"]67#![cfg_attr(not(feature = "std"), no_std)]68#![allow(69	clippy::too_many_arguments,70	clippy::unnecessary_mut_passed,71	clippy::unused_unit72)]7374extern crate alloc;7576use frame_support::pallet_prelude::*;77use frame_system::pallet_prelude::*;78pub use pallet::*;79pub mod eth;8081#[cfg(feature = "runtime-benchmarks")]82pub mod benchmarking;83pub mod weights;8485#[frame_support::pallet]86pub mod pallet {87	use frame_support::{88		dispatch::{DispatchErrorWithPostInfo, DispatchResult, PostDispatchInfo},89		ensure, fail,90		storage::Key,91		BoundedVec,92	};93	use frame_system::{ensure_root, ensure_signed};94	use pallet_common::{95		dispatch::{dispatch_tx, CollectionDispatch},96		CollectionHandle, CollectionIssuer, CommonWeightInfo, Pallet as PalletCommon,97		RefungibleExtensionsWeightInfo,98	};99	use pallet_evm::account::CrossAccountId;100	use pallet_structure::weights::WeightInfo as StructureWeightInfo;101	use scale_info::TypeInfo;102	use sp_std::{vec, vec::Vec};103	use up_data_structs::{104		budget, CollectionId, CollectionLimits, CollectionMode, CollectionPermissions,105		CreateCollectionData, CreateItemData, CreateItemExData, Property, PropertyKey,106		PropertyKeyPermission, TokenId, COLLECTION_ADMINS_LIMIT, MAX_COLLECTION_DESCRIPTION_LENGTH,107		MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_PROPERTIES_SIZE, MAX_PROPERTIES_PER_ITEM,108		MAX_PROPERTY_KEY_LENGTH, MAX_PROPERTY_VALUE_LENGTH, MAX_TOKEN_PREFIX_LENGTH,109		MAX_TOKEN_PROPERTIES_SIZE,110	};111	use weights::WeightInfo;112113	use super::*;114115	/// Errors for the common Unique transactions.116	#[pallet::error]117	pub enum Error<T> {118		/// Decimal_points parameter must be lower than [`up_data_structs::MAX_DECIMAL_POINTS`].119		CollectionDecimalPointLimitExceeded,120		/// Length of items properties must be greater than 0.121		EmptyArgument,122		/// Repertition is only supported by refungible collection.123		RepartitionCalledOnNonRefungibleCollection,124	}125126	/// Configuration trait of this pallet.127	#[pallet::config]128	pub trait Config: frame_system::Config + pallet_common::Config + Sized + TypeInfo {129		/// Weight information for extrinsics in this pallet.130		type WeightInfo: WeightInfo;131132		/// Weight information for common pallet operations.133		type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;134135		type StructureWeightInfo: StructureWeightInfo;136137		/// Weight info information for extra refungible pallet operations.138		type RefungibleExtensionsWeightInfo: RefungibleExtensionsWeightInfo;139	}140141	#[pallet::pallet]142	pub struct Pallet<T>(_);143144	pub type SelfWeightOf<T> = <T as Config>::WeightInfo;145146	// # Used definitions147	//148	// ## User control levels149	//150	// chain-controlled - key is uncontrolled by user151	//                    i.e autoincrementing index152	//                    can use non-cryptographic hash153	// real - key is controlled by user154	//        but it is hard to generate enough colliding values, i.e owner of signed txs155	//        can use non-cryptographic hash156	// controlled - key is completly controlled by users157	//              i.e maps with mutable keys158	//              should use cryptographic hash159	//160	// ## User control level downgrade reasons161	//162	// ?1 - chain-controlled -> controlled163	//      collections/tokens can be destroyed, resulting in massive holes164	// ?2 - chain-controlled -> controlled165	//      same as ?1, but can be only added, resulting in easier exploitation166	// ?3 - real -> controlled167	//      no confirmation required, so addresses can be easily generated168169	//#region Private members170	/// Used for migrations171	#[pallet::storage]172	pub type ChainVersion<T> = StorageValue<_, u64, ValueQuery>;173	//#endregion174175	//#region Tokens transfer sponosoring rate limit baskets176	/// (Collection id (controlled?2), who created (real))177	/// TODO: Off chain worker should remove from this map when collection gets removed178	#[pallet::storage]179	#[pallet::getter(fn create_item_busket)]180	pub type CreateItemBasket<T: Config> = StorageMap<181		Hasher = Blake2_128Concat,182		Key = (CollectionId, T::AccountId),183		Value = BlockNumberFor<T>,184		QueryKind = OptionQuery,185	>;186	/// Collection id (controlled?2), token id (controlled?2)187	#[pallet::storage]188	#[pallet::getter(fn nft_transfer_basket)]189	pub type NftTransferBasket<T: Config> = StorageDoubleMap<190		Hasher1 = Blake2_128Concat,191		Key1 = CollectionId,192		Hasher2 = Blake2_128Concat,193		Key2 = TokenId,194		Value = BlockNumberFor<T>,195		QueryKind = OptionQuery,196	>;197	/// Collection id (controlled?2), owning user (real)198	#[pallet::storage]199	#[pallet::getter(fn fungible_transfer_basket)]200	pub type FungibleTransferBasket<T: Config> = StorageDoubleMap<201		Hasher1 = Blake2_128Concat,202		Key1 = CollectionId,203		Hasher2 = Twox64Concat,204		Key2 = T::AccountId,205		Value = BlockNumberFor<T>,206		QueryKind = OptionQuery,207	>;208	/// Collection id (controlled?2), token id (controlled?2)209	#[pallet::storage]210	#[pallet::getter(fn refungible_transfer_basket)]211	pub type ReFungibleTransferBasket<T: Config> = StorageNMap<212		Key = (213			Key<Blake2_128Concat, CollectionId>,214			Key<Blake2_128Concat, TokenId>,215			Key<Twox64Concat, T::AccountId>,216		),217		Value = BlockNumberFor<T>,218		QueryKind = OptionQuery,219	>;220	//#endregion221222	/// Last sponsoring of token property setting // todo:doc rephrase this and the following223	#[pallet::storage]224	#[pallet::getter(fn token_property_basket)]225	pub type TokenPropertyBasket<T: Config> = StorageDoubleMap<226		Hasher1 = Blake2_128Concat,227		Key1 = CollectionId,228		Hasher2 = Blake2_128Concat,229		Key2 = TokenId,230		Value = BlockNumberFor<T>,231		QueryKind = OptionQuery,232	>;233234	/// Last sponsoring of NFT approval in a collection235	#[pallet::storage]236	#[pallet::getter(fn nft_approve_basket)]237	pub type NftApproveBasket<T: Config> = StorageDoubleMap<238		Hasher1 = Blake2_128Concat,239		Key1 = CollectionId,240		Hasher2 = Blake2_128Concat,241		Key2 = TokenId,242		Value = BlockNumberFor<T>,243		QueryKind = OptionQuery,244	>;245	/// Last sponsoring of fungible tokens approval in a collection246	#[pallet::storage]247	#[pallet::getter(fn fungible_approve_basket)]248	pub type FungibleApproveBasket<T: Config> = StorageDoubleMap<249		Hasher1 = Blake2_128Concat,250		Key1 = CollectionId,251		Hasher2 = Twox64Concat,252		Key2 = T::AccountId,253		Value = BlockNumberFor<T>,254		QueryKind = OptionQuery,255	>;256	/// Last sponsoring of RFT approval in a collection257	#[pallet::storage]258	#[pallet::getter(fn refungible_approve_basket)]259	pub type RefungibleApproveBasket<T: Config> = StorageNMap<260		Key = (261			Key<Blake2_128Concat, CollectionId>,262			Key<Blake2_128Concat, TokenId>,263			Key<Twox64Concat, T::AccountId>,264		),265		Value = BlockNumberFor<T>,266		QueryKind = OptionQuery,267	>;268269	#[pallet::extra_constants]270	impl<T: Config> Pallet<T> {271		/// A maximum number of levels of depth in the token nesting tree.272		fn nesting_budget() -> u32 {273			5274		}275276		/// Maximal length of a collection name.277		fn max_collection_name_length() -> u32 {278			MAX_COLLECTION_NAME_LENGTH279		}280281		/// Maximal length of a collection description.282		fn max_collection_description_length() -> u32 {283			MAX_COLLECTION_DESCRIPTION_LENGTH284		}285286		/// Maximal length of a token prefix.287		fn max_token_prefix_length() -> u32 {288			MAX_TOKEN_PREFIX_LENGTH289		}290291		/// Maximum admins per collection.292		fn collection_admins_limit() -> u32 {293			COLLECTION_ADMINS_LIMIT294		}295296		/// Maximal length of a property key.297		fn max_property_key_length() -> u32 {298			MAX_PROPERTY_KEY_LENGTH299		}300301		/// Maximal length of a property value.302		fn max_property_value_length() -> u32 {303			MAX_PROPERTY_VALUE_LENGTH304		}305306		/// A maximum number of token properties.307		fn max_properties_per_item() -> u32 {308			MAX_PROPERTIES_PER_ITEM309		}310311		/// Maximum size for all collection properties.312		fn max_collection_properties_size() -> u32 {313			MAX_COLLECTION_PROPERTIES_SIZE314		}315316		/// Maximum size of all token properties.317		fn max_token_properties_size() -> u32 {318			MAX_TOKEN_PROPERTIES_SIZE319		}320321		/// Default NFT collection limit.322		fn nft_default_collection_limits() -> CollectionLimits {323			CollectionLimits::with_default_limits(CollectionMode::NFT)324		}325326		/// Default RFT collection limit.327		fn rft_default_collection_limits() -> CollectionLimits {328			CollectionLimits::with_default_limits(CollectionMode::ReFungible)329		}330331		/// Default FT collection limit.332		fn ft_default_collection_limits() -> CollectionLimits {333			CollectionLimits::with_default_limits(CollectionMode::Fungible(0))334		}335	}336337	/// Type alias to Pallet, to be used by construct_runtime.338	#[pallet::call]339	impl<T: Config> Pallet<T> {340		/// Create a collection of tokens.341		///342		/// Each Token may have multiple properties encoded as an array of bytes343		/// of certain length. The initial owner of the collection is set344		/// to the address that signed the transaction and can be changed later.345		///346		/// Prefer the more advanced [`create_collection_ex`][`Pallet::create_collection_ex`] instead.347		///348		/// # Permissions349		///350		/// * Anyone - becomes the owner of the new collection.351		///352		/// # Arguments353		///354		/// * `collection_name`: Wide-character string with collection name355		/// (limit [`MAX_COLLECTION_NAME_LENGTH`]).356		/// * `collection_description`: Wide-character string with collection description357		/// (limit [`MAX_COLLECTION_DESCRIPTION_LENGTH`]).358		/// * `token_prefix`: Byte string containing the token prefix to mark a collection359		/// to which a token belongs (limit [`MAX_TOKEN_PREFIX_LENGTH`]).360		/// * `mode`: Type of items stored in the collection and type dependent data.361		///362		/// returns collection ID363		///364		/// Deprecated: `create_collection_ex` is more up-to-date and advanced, prefer it instead.365		#[pallet::call_index(0)]366		#[pallet::weight(<SelfWeightOf<T>>::create_collection())]367		pub fn create_collection(368			origin: OriginFor<T>,369			collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,370			collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,371			token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,372			mode: CollectionMode,373		) -> DispatchResult {374			let data: CreateCollectionData<T::CrossAccountId> = CreateCollectionData {375				name: collection_name,376				description: collection_description,377				token_prefix,378				mode,379				..Default::default()380			};381			Self::create_collection_ex(origin, data)382		}383384		/// Create a collection with explicit parameters.385		///386		/// Prefer it to the deprecated [`create_collection`][`Pallet::create_collection`] method.387		///388		/// # Permissions389		///390		/// * Anyone - becomes the owner of the new collection.391		///392		/// # Arguments393		///394		/// * `data`: Explicit data of a collection used for its creation.395		#[pallet::call_index(1)]396		#[pallet::weight(<SelfWeightOf<T>>::create_collection())]397		pub fn create_collection_ex(398			origin: OriginFor<T>,399			data: CreateCollectionData<T::CrossAccountId>,400		) -> DispatchResult {401			let sender = ensure_signed(origin)?;402403			// =========404			let sender = T::CrossAccountId::from_sub(sender);405			let _id = T::CollectionDispatch::create(406				sender.clone(),407				CollectionIssuer::User(sender),408				data,409			)?;410411			Ok(())412		}413414		/// Destroy a collection if no tokens exist within.415		///416		/// # Permissions417		///418		/// * Collection owner419		///420		/// # Arguments421		///422		/// * `collection_id`: Collection to destroy.423		#[pallet::call_index(2)]424		#[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]425		pub fn destroy_collection(426			origin: OriginFor<T>,427			collection_id: CollectionId,428		) -> DispatchResult {429			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);430431			Self::destroy_collection_internal(sender, collection_id)432		}433434		/// Add an address to allow list.435		///436		/// # Permissions437		///438		/// * Collection owner439		/// * Collection admin440		///441		/// # Arguments442		///443		/// * `collection_id`: ID of the modified collection.444		/// * `address`: ID of the address to be added to the allowlist.445		#[pallet::call_index(3)]446		#[pallet::weight(<SelfWeightOf<T>>::add_to_allow_list())]447		pub fn add_to_allow_list(448			origin: OriginFor<T>,449			collection_id: CollectionId,450			address: T::CrossAccountId,451		) -> DispatchResult {452			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {453				fail!(<pallet_common::Error<T>>::UnsupportedOperation);454			}455456			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);457			let collection = <CollectionHandle<T>>::try_get(collection_id)?;458			collection.check_is_internal()?;459460			<PalletCommon<T>>::toggle_allowlist(&collection, &sender, &address, true)?;461462			Ok(())463		}464465		/// Remove an address from allow list.466		///467		/// # Permissions468		///469		/// * Collection owner470		/// * Collection admin471		///472		/// # Arguments473		///474		/// * `collection_id`: ID of the modified collection.475		/// * `address`: ID of the address to be removed from the allowlist.476		#[pallet::call_index(4)]477		#[pallet::weight(<SelfWeightOf<T>>::remove_from_allow_list())]478		pub fn remove_from_allow_list(479			origin: OriginFor<T>,480			collection_id: CollectionId,481			address: T::CrossAccountId,482		) -> DispatchResult {483			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {484				fail!(<pallet_common::Error<T>>::UnsupportedOperation);485			}486487			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);488			let collection = <CollectionHandle<T>>::try_get(collection_id)?;489			collection.check_is_internal()?;490491			<PalletCommon<T>>::toggle_allowlist(&collection, &sender, &address, false)?;492493			Ok(())494		}495496		/// Change the owner of the collection.497		///498		/// # Permissions499		///500		/// * Collection owner501		///502		/// # Arguments503		///504		/// * `collection_id`: ID of the modified collection.505		/// * `new_owner`: ID of the account that will become the owner.506		#[pallet::call_index(5)]507		#[pallet::weight(<SelfWeightOf<T>>::change_collection_owner())]508		pub fn change_collection_owner(509			origin: OriginFor<T>,510			collection_id: CollectionId,511			new_owner: T::AccountId,512		) -> DispatchResult {513			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {514				fail!(<pallet_common::Error<T>>::UnsupportedOperation);515			}516			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);517			let new_owner = T::CrossAccountId::from_sub(new_owner);518			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;519			target_collection.change_owner(sender, new_owner)520		}521522		/// Add an admin to a collection.523		///524		/// NFT Collection can be controlled by multiple admin addresses525		/// (some which can also be servers, for example). Admins can issue526		/// and burn NFTs, as well as add and remove other admins,527		/// but cannot change NFT or Collection ownership.528		///529		/// # Permissions530		///531		/// * Collection owner532		/// * Collection admin533		///534		/// # Arguments535		///536		/// * `collection_id`: ID of the Collection to add an admin for.537		/// * `new_admin`: Address of new admin to add.538		#[pallet::call_index(6)]539		#[pallet::weight(<SelfWeightOf<T>>::add_collection_admin())]540		pub fn add_collection_admin(541			origin: OriginFor<T>,542			collection_id: CollectionId,543			new_admin_id: T::CrossAccountId,544		) -> DispatchResult {545			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {546				fail!(<pallet_common::Error<T>>::UnsupportedOperation);547			}548			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);549			let collection = <CollectionHandle<T>>::try_get(collection_id)?;550			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)551		}552553		/// Remove admin of a collection.554		///555		/// An admin address can remove itself. List of admins may become empty,556		/// in which case only Collection Owner will be able to add an Admin.557		///558		/// # Permissions559		///560		/// * Collection owner561		/// * Collection admin562		///563		/// # Arguments564		///565		/// * `collection_id`: ID of the collection to remove the admin for.566		/// * `account_id`: Address of the admin to remove.567		#[pallet::call_index(7)]568		#[pallet::weight(<SelfWeightOf<T>>::remove_collection_admin())]569		pub fn remove_collection_admin(570			origin: OriginFor<T>,571			collection_id: CollectionId,572			account_id: T::CrossAccountId,573		) -> DispatchResult {574			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {575				fail!(<pallet_common::Error<T>>::UnsupportedOperation);576			}577			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);578			let collection = <CollectionHandle<T>>::try_get(collection_id)?;579			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)580		}581582		/// Set (invite) a new collection sponsor.583		///584		/// If successful, confirmation from the sponsor-to-be will be pending.585		///586		/// # Permissions587		///588		/// * Collection owner589		/// * Collection admin590		///591		/// # Arguments592		///593		/// * `collection_id`: ID of the modified collection.594		/// * `new_sponsor`: ID of the account of the sponsor-to-be.595		#[pallet::call_index(8)]596		#[pallet::weight(<SelfWeightOf<T>>::set_collection_sponsor())]597		pub fn set_collection_sponsor(598			origin: OriginFor<T>,599			collection_id: CollectionId,600			new_sponsor: T::AccountId,601		) -> DispatchResult {602			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {603				fail!(<pallet_common::Error<T>>::UnsupportedOperation);604			}605			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);606			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;607			target_collection.set_sponsor(&sender, new_sponsor.clone())608		}609610		/// Confirm own sponsorship of a collection, becoming the sponsor.611		///612		/// An invitation must be pending, see [`set_collection_sponsor`][`Pallet::set_collection_sponsor`].613		/// Sponsor can pay the fees of a transaction instead of the sender,614		/// but only within specified limits.615		///616		/// # Permissions617		///618		/// * Sponsor-to-be619		///620		/// # Arguments621		///622		/// * `collection_id`: ID of the collection with the pending sponsor.623		#[pallet::call_index(9)]624		#[pallet::weight(<SelfWeightOf<T>>::confirm_sponsorship())]625		pub fn confirm_sponsorship(626			origin: OriginFor<T>,627			collection_id: CollectionId,628		) -> DispatchResult {629			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {630				fail!(<pallet_common::Error<T>>::UnsupportedOperation);631			}632			let sender = ensure_signed(origin)?;633			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;634			target_collection.confirm_sponsorship(&sender)635		}636637		/// Remove a collection's a sponsor, making everyone pay for their own transactions.638		///639		/// # Permissions640		///641		/// * Collection owner642		///643		/// # Arguments644		///645		/// * `collection_id`: ID of the collection with the sponsor to remove.646		#[pallet::call_index(10)]647		#[pallet::weight(<SelfWeightOf<T>>::remove_collection_sponsor())]648		pub fn remove_collection_sponsor(649			origin: OriginFor<T>,650			collection_id: CollectionId,651		) -> DispatchResult {652			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {653				fail!(<pallet_common::Error<T>>::UnsupportedOperation);654			}655			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);656			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;657			target_collection.remove_sponsor(&sender)658		}659660		/// Mint an item within a collection.661		///662		/// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].663		///664		/// # Permissions665		///666		/// * Collection owner667		/// * Collection admin668		/// * Anyone if669		///     * Allow List is enabled, and670		///     * Address is added to allow list, and671		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])672		///673		/// # Arguments674		///675		/// * `collection_id`: ID of the collection to which an item would belong.676		/// * `owner`: Address of the initial owner of the item.677		/// * `data`: Token data describing the item to store on chain.678		#[pallet::call_index(11)]679		#[pallet::weight(T::CommonWeightInfo::create_item(data) + <Pallet<T>>::nesting_budget_predispatch_weight())]680		pub fn create_item(681			origin: OriginFor<T>,682			collection_id: CollectionId,683			owner: T::CrossAccountId,684			data: CreateItemData,685		) -> DispatchResultWithPostInfo {686			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);687			let budget = Self::structure_nesting_budget();688689			Self::refund_nesting_budget(690				dispatch_tx::<T, _>(collection_id, |d| {691					d.create_item(sender, owner, data, &budget)692				}),693				budget,694			)695		}696697		/// Create multiple items within a collection.698		///699		/// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].700		///701		/// # Permissions702		///703		/// * Collection owner704		/// * Collection admin705		/// * Anyone if706		///     * Allow List is enabled, and707		///     * Address is added to the allow list, and708		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])709		///710		/// # Arguments711		///712		/// * `collection_id`: ID of the collection to which the tokens would belong.713		/// * `owner`: Address of the initial owner of the tokens.714		/// * `items_data`: Vector of data describing each item to be created.715		#[pallet::call_index(12)]716		#[pallet::weight(T::CommonWeightInfo::create_multiple_items(items_data) + <Pallet<T>>::nesting_budget_predispatch_weight())]717		pub fn create_multiple_items(718			origin: OriginFor<T>,719			collection_id: CollectionId,720			owner: T::CrossAccountId,721			items_data: Vec<CreateItemData>,722		) -> DispatchResultWithPostInfo {723			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);724			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);725			let budget = Self::structure_nesting_budget();726727			Self::refund_nesting_budget(728				dispatch_tx::<T, _>(collection_id, |d| {729					d.create_multiple_items(sender, owner, items_data, &budget)730				}),731				budget,732			)733		}734735		/// Add or change collection properties.736		///737		/// # Permissions738		///739		/// * Collection owner740		/// * Collection admin741		///742		/// # Arguments743		///744		/// * `collection_id`: ID of the modified collection.745		/// * `properties`: Vector of key-value pairs stored as the collection's metadata.746		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.747		#[pallet::call_index(13)]748		#[pallet::weight(T::CommonWeightInfo::set_collection_properties(properties.len() as u32))]749		pub fn set_collection_properties(750			origin: OriginFor<T>,751			collection_id: CollectionId,752			properties: Vec<Property>,753		) -> DispatchResultWithPostInfo {754			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);755756			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);757758			dispatch_tx::<T, _>(collection_id, |d| {759				d.set_collection_properties(sender, properties)760			})761		}762763		/// Delete specified collection properties.764		///765		/// # Permissions766		///767		/// * Collection Owner768		/// * Collection Admin769		///770		/// # Arguments771		///772		/// * `collection_id`: ID of the modified collection.773		/// * `property_keys`: Vector of keys of the properties to be deleted.774		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.775		#[pallet::call_index(14)]776		#[pallet::weight(T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32))]777		pub fn delete_collection_properties(778			origin: OriginFor<T>,779			collection_id: CollectionId,780			property_keys: Vec<PropertyKey>,781		) -> DispatchResultWithPostInfo {782			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);783784			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);785786			dispatch_tx::<T, _>(collection_id, |d| {787				d.delete_collection_properties(&sender, property_keys)788			})789		}790791		/// Add or change token properties according to collection's permissions.792		/// Currently properties only work with NFTs.793		///794		/// # Permissions795		///796		/// * Depends on collection's token property permissions and specified property mutability:797		/// 	* Collection owner798		/// 	* Collection admin799		/// 	* Token owner800		///801		/// See [`set_token_property_permissions`][`Pallet::set_token_property_permissions`].802		///803		/// # Arguments804		///805		/// * `collection_id: ID of the collection to which the token belongs.806		/// * `token_id`: ID of the modified token.807		/// * `properties`: Vector of key-value pairs stored as the token's metadata.808		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.809		#[pallet::call_index(15)]810		#[pallet::weight(T::CommonWeightInfo::set_token_properties(properties.len() as u32) + <Pallet<T>>::nesting_budget_predispatch_weight())]811		pub fn set_token_properties(812			origin: OriginFor<T>,813			collection_id: CollectionId,814			token_id: TokenId,815			properties: Vec<Property>,816		) -> DispatchResultWithPostInfo {817			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);818819			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);820			let budget = Self::structure_nesting_budget();821822			Self::refund_nesting_budget(823				dispatch_tx::<T, _>(collection_id, |d| {824					d.set_token_properties(sender, token_id, properties, &budget)825				}),826				budget,827			)828		}829830		/// Delete specified token properties. Currently properties only work with NFTs.831		///832		/// # Permissions833		///834		/// * Depends on collection's token property permissions and specified property mutability:835		/// 	* Collection owner836		/// 	* Collection admin837		/// 	* Token owner838		///839		/// # Arguments840		///841		/// * `collection_id`: ID of the collection to which the token belongs.842		/// * `token_id`: ID of the modified token.843		/// * `property_keys`: Vector of keys of the properties to be deleted.844		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.845		#[pallet::call_index(16)]846		#[pallet::weight(T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32) + <Pallet<T>>::nesting_budget_predispatch_weight())]847		pub fn delete_token_properties(848			origin: OriginFor<T>,849			collection_id: CollectionId,850			token_id: TokenId,851			property_keys: Vec<PropertyKey>,852		) -> DispatchResultWithPostInfo {853			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);854855			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);856			let budget = Self::structure_nesting_budget();857858			Self::refund_nesting_budget(859				dispatch_tx::<T, _>(collection_id, |d| {860					d.delete_token_properties(sender, token_id, property_keys, &budget)861				}),862				budget,863			)864		}865866		/// Add or change token property permissions of a collection.867		///868		/// Without a permission for a particular key, a property with that key869		/// cannot be created in a token.870		///871		/// # Permissions872		///873		/// * Collection owner874		/// * Collection admin875		///876		/// # Arguments877		///878		/// * `collection_id`: ID of the modified collection.879		/// * `property_permissions`: Vector of permissions for property keys.880		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.881		#[pallet::call_index(17)]882		#[pallet::weight(T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32))]883		pub fn set_token_property_permissions(884			origin: OriginFor<T>,885			collection_id: CollectionId,886			property_permissions: Vec<PropertyKeyPermission>,887		) -> DispatchResultWithPostInfo {888			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);889890			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);891892			dispatch_tx::<T, _>(collection_id, |d| {893				d.set_token_property_permissions(&sender, property_permissions)894			})895		}896897		/// Create multiple items within a collection with explicitly specified initial parameters.898		///899		/// # Permissions900		///901		/// * Collection owner902		/// * Collection admin903		/// * Anyone if904		///     * Allow List is enabled, and905		///     * Address is added to allow list, and906		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])907		///908		/// # Arguments909		///910		/// * `collection_id`: ID of the collection to which the tokens would belong.911		/// * `data`: Explicit item creation data.912		#[pallet::call_index(18)]913		#[pallet::weight(T::CommonWeightInfo::create_multiple_items_ex(data) + <Pallet<T>>::nesting_budget_predispatch_weight())]914		pub fn create_multiple_items_ex(915			origin: OriginFor<T>,916			collection_id: CollectionId,917			data: CreateItemExData<T::CrossAccountId>,918		) -> DispatchResultWithPostInfo {919			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);920			let budget = Self::structure_nesting_budget();921922			Self::refund_nesting_budget(923				dispatch_tx::<T, _>(collection_id, |d| {924					d.create_multiple_items_ex(sender, data, &budget)925				}),926				budget,927			)928		}929930		/// Completely allow or disallow transfers for a particular collection.931		///932		/// # Permissions933		///934		/// * Collection owner935		///936		/// # Arguments937		///938		/// * `collection_id`: ID of the collection.939		/// * `value`: New value of the flag, are transfers allowed?940		#[pallet::call_index(19)]941		#[pallet::weight(<SelfWeightOf<T>>::set_transfers_enabled_flag())]942		pub fn set_transfers_enabled_flag(943			origin: OriginFor<T>,944			collection_id: CollectionId,945			value: bool,946		) -> DispatchResult {947			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {948				fail!(<pallet_common::Error<T>>::UnsupportedOperation);949			}950			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);951			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;952			target_collection.check_is_internal()?;953			target_collection.check_is_owner(&sender)?;954955			// =========956957			target_collection.limits.transfers_enabled = Some(value);958			target_collection.save()959		}960961		/// Destroy an item.962		///963		/// # Permissions964		///965		/// * Collection owner966		/// * Collection admin967		/// * Current item owner968		///969		/// # Arguments970		///971		/// * `collection_id`: ID of the collection to which the item belongs.972		/// * `item_id`: ID of item to burn.973		/// * `value`: Number of pieces of the item to destroy.974		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.975		///     * Fungible Mode: The desired number of pieces to burn.976		///     * Re-Fungible Mode: The desired number of pieces to burn.977		#[pallet::call_index(20)]978		#[pallet::weight(T::CommonWeightInfo::burn_item())]979		pub fn burn_item(980			origin: OriginFor<T>,981			collection_id: CollectionId,982			item_id: TokenId,983			value: u128,984		) -> DispatchResultWithPostInfo {985			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);986987			let post_info =988				dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;989			if value == 1 {990				<NftTransferBasket<T>>::remove(collection_id, item_id);991				<NftApproveBasket<T>>::remove(collection_id, item_id);992			}993			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?994			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());995			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));996			Ok(post_info)997		}998999		/// Destroy a token on behalf of the owner as a non-owner account.1000		///1001		/// See also: [`approve`][`Pallet::approve`].1002		///1003		/// After this method executes, one approval is removed from the total so that1004		/// the approved address will not be able to transfer this item again from this owner.1005		///1006		/// # Permissions1007		///1008		/// * Collection owner1009		/// * Collection admin1010		/// * Current token owner1011		/// * Address approved by current item owner1012		///1013		/// # Arguments1014		///1015		/// * `from`: The owner of the burning item.1016		/// * `collection_id`: ID of the collection to which the item belongs.1017		/// * `item_id`: ID of item to burn.1018		/// * `value`: Number of pieces to burn.1019		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1020		///     * Fungible Mode: The desired number of pieces to burn.1021		///     * Re-Fungible Mode: The desired number of pieces to burn.1022		#[pallet::call_index(21)]1023		#[pallet::weight(T::CommonWeightInfo::burn_from() + <Pallet<T>>::nesting_budget_predispatch_weight())]1024		pub fn burn_from(1025			origin: OriginFor<T>,1026			collection_id: CollectionId,1027			from: T::CrossAccountId,1028			item_id: TokenId,1029			value: u128,1030		) -> DispatchResultWithPostInfo {1031			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1032			let budget = Self::structure_nesting_budget();10331034			Self::refund_nesting_budget(1035				dispatch_tx::<T, _>(collection_id, |d| {1036					d.burn_from(sender, from, item_id, value, &budget)1037				}),1038				budget,1039			)1040		}10411042		/// Change ownership of the token.1043		///1044		/// # Permissions1045		///1046		/// * Collection owner1047		/// * Collection admin1048		/// * Current token owner1049		///1050		/// # Arguments1051		///1052		/// * `recipient`: Address of token recipient.1053		/// * `collection_id`: ID of the collection the item belongs to.1054		/// * `item_id`: ID of the item.1055		///     * Non-Fungible Mode: Required.1056		///     * Fungible Mode: Ignored.1057		///     * Re-Fungible Mode: Required.1058		///1059		/// * `value`: Amount to transfer.1060		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1061		///     * Fungible Mode: The desired number of pieces to transfer.1062		///     * Re-Fungible Mode: The desired number of pieces to transfer.1063		#[pallet::call_index(22)]1064		#[pallet::weight(T::CommonWeightInfo::transfer() + <Pallet<T>>::nesting_budget_predispatch_weight())]1065		pub fn transfer(1066			origin: OriginFor<T>,1067			recipient: T::CrossAccountId,1068			collection_id: CollectionId,1069			item_id: TokenId,1070			value: u128,1071		) -> DispatchResultWithPostInfo {1072			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1073			let budget = Self::structure_nesting_budget();10741075			Self::refund_nesting_budget(1076				dispatch_tx::<T, _>(collection_id, |d| {1077					d.transfer(sender, recipient, item_id, value, &budget)1078				}),1079				budget,1080			)1081		}10821083		/// Allow a non-permissioned address to transfer or burn an item.1084		///1085		/// # Permissions1086		///1087		/// * Collection owner1088		/// * Collection admin1089		/// * Current item owner1090		///1091		/// # Arguments1092		///1093		/// * `spender`: Account to be approved to make specific transactions on non-owned tokens.1094		/// * `collection_id`: ID of the collection the item belongs to.1095		/// * `item_id`: ID of the item transactions on which are now approved.1096		/// * `amount`: Number of pieces of the item approved for a transaction (maximum of 1 for NFTs).1097		/// Set to 0 to revoke the approval.1098		#[pallet::call_index(23)]1099		#[pallet::weight(T::CommonWeightInfo::approve())]1100		pub fn approve(1101			origin: OriginFor<T>,1102			spender: T::CrossAccountId,1103			collection_id: CollectionId,1104			item_id: TokenId,1105			amount: u128,1106		) -> DispatchResultWithPostInfo {1107			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);11081109			dispatch_tx::<T, _>(collection_id, |d| {1110				d.approve(sender, spender, item_id, amount)1111			})1112		}11131114		/// Allow a non-permissioned address to transfer or burn an item from owner's eth mirror.1115		///1116		/// # Permissions1117		///1118		/// * Collection owner1119		/// * Collection admin1120		/// * Current item owner1121		///1122		/// # Arguments1123		///1124		/// * `from`: Owner's account eth mirror1125		/// * `to`: Account to be approved to make specific transactions on non-owned tokens.1126		/// * `collection_id`: ID of the collection the item belongs to.1127		/// * `item_id`: ID of the item transactions on which are now approved.1128		/// * `amount`: Number of pieces of the item approved for a transaction (maximum of 1 for NFTs).1129		/// Set to 0 to revoke the approval.1130		#[pallet::call_index(24)]1131		#[pallet::weight(T::CommonWeightInfo::approve_from())]1132		pub fn approve_from(1133			origin: OriginFor<T>,1134			from: T::CrossAccountId,1135			to: T::CrossAccountId,1136			collection_id: CollectionId,1137			item_id: TokenId,1138			amount: u128,1139		) -> DispatchResultWithPostInfo {1140			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);11411142			dispatch_tx::<T, _>(collection_id, |d| {1143				d.approve_from(sender, from, to, item_id, amount)1144			})1145		}11461147		/// Change ownership of an item on behalf of the owner as a non-owner account.1148		///1149		/// See the [`approve`][`Pallet::approve`] method for additional information.1150		///1151		/// After this method executes, one approval is removed from the total so that1152		/// the approved address will not be able to transfer this item again from this owner.1153		///1154		/// # Permissions1155		///1156		/// * Collection owner1157		/// * Collection admin1158		/// * Current item owner1159		/// * Address approved by current item owner1160		///1161		/// # Arguments1162		///1163		/// * `from`: Address that currently owns the token.1164		/// * `recipient`: Address of the new token-owner-to-be.1165		/// * `collection_id`: ID of the collection the item.1166		/// * `item_id`: ID of the item to be transferred.1167		/// * `value`: Amount to transfer.1168		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1169		///     * Fungible Mode: The desired number of pieces to transfer.1170		///     * Re-Fungible Mode: The desired number of pieces to transfer.1171		#[pallet::call_index(25)]1172		#[pallet::weight(T::CommonWeightInfo::transfer_from() + <Pallet<T>>::nesting_budget_predispatch_weight())]1173		pub fn transfer_from(1174			origin: OriginFor<T>,1175			from: T::CrossAccountId,1176			recipient: T::CrossAccountId,1177			collection_id: CollectionId,1178			item_id: TokenId,1179			value: u128,1180		) -> DispatchResultWithPostInfo {1181			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1182			let budget = Self::structure_nesting_budget();11831184			Self::refund_nesting_budget(1185				dispatch_tx::<T, _>(collection_id, |d| {1186					d.transfer_from(sender, from, recipient, item_id, value, &budget)1187				}),1188				budget,1189			)1190		}11911192		/// Set specific limits of a collection. Empty, or None fields mean chain default.1193		///1194		/// # Permissions1195		///1196		/// * Collection owner1197		/// * Collection admin1198		///1199		/// # Arguments1200		///1201		/// * `collection_id`: ID of the modified collection.1202		/// * `new_limit`: New limits of the collection. Fields that are not set (None)1203		/// will not overwrite the old ones.1204		#[pallet::call_index(26)]1205		#[pallet::weight(<SelfWeightOf<T>>::set_collection_limits())]1206		pub fn set_collection_limits(1207			origin: OriginFor<T>,1208			collection_id: CollectionId,1209			new_limit: CollectionLimits,1210		) -> DispatchResult {1211			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {1212				fail!(<pallet_common::Error<T>>::UnsupportedOperation);1213			}1214			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1215			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1216			<PalletCommon<T>>::update_limits(&sender, &mut target_collection, new_limit)1217		}12181219		/// Set specific permissions of a collection. Empty, or None fields mean chain default.1220		///1221		/// # Permissions1222		///1223		/// * Collection owner1224		/// * Collection admin1225		///1226		/// # Arguments1227		///1228		/// * `collection_id`: ID of the modified collection.1229		/// * `new_permission`: New permissions of the collection. Fields that are not set (None)1230		/// will not overwrite the old ones.1231		#[pallet::call_index(27)]1232		#[pallet::weight(<SelfWeightOf<T>>::set_collection_limits())]1233		pub fn set_collection_permissions(1234			origin: OriginFor<T>,1235			collection_id: CollectionId,1236			new_permission: CollectionPermissions,1237		) -> DispatchResult {1238			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {1239				fail!(<pallet_common::Error<T>>::UnsupportedOperation);1240			}1241			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1242			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1243			<PalletCommon<T>>::update_permissions(&sender, &mut target_collection, new_permission)1244		}12451246		/// Re-partition a refungible token, while owning all of its parts/pieces.1247		///1248		/// # Permissions1249		///1250		/// * Token owner (must own every part)1251		///1252		/// # Arguments1253		///1254		/// * `collection_id`: ID of the collection the RFT belongs to.1255		/// * `token_id`: ID of the RFT.1256		/// * `amount`: New number of parts/pieces into which the token shall be partitioned.1257		#[pallet::call_index(28)]1258		#[pallet::weight(T::RefungibleExtensionsWeightInfo::repartition())]1259		pub fn repartition(1260			origin: OriginFor<T>,1261			collection_id: CollectionId,1262			token_id: TokenId,1263			amount: u128,1264		) -> DispatchResultWithPostInfo {1265			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1266			dispatch_tx::<T, _>(collection_id, |d| {1267				if let Some(refungible_extensions) = d.refungible_extensions() {1268					refungible_extensions.repartition(&sender, token_id, amount)1269				} else {1270					fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection)1271				}1272			})1273		}12741275		/// Sets or unsets the approval of a given operator.1276		///1277		/// The `operator` is allowed to transfer all tokens of the `owner` on their behalf.1278		///1279		/// # Arguments1280		///1281		/// * `owner`: Token owner1282		/// * `operator`: Operator1283		/// * `approve`: Should operator status be granted or revoked?1284		#[pallet::call_index(29)]1285		#[pallet::weight(T::CommonWeightInfo::set_allowance_for_all())]1286		pub fn set_allowance_for_all(1287			origin: OriginFor<T>,1288			collection_id: CollectionId,1289			operator: T::CrossAccountId,1290			approve: bool,1291		) -> DispatchResultWithPostInfo {1292			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1293			dispatch_tx::<T, _>(collection_id, |d| {1294				d.set_allowance_for_all(sender, operator, approve)1295			})1296		}12971298		/// Repairs a collection if the data was somehow corrupted.1299		///1300		/// # Arguments1301		///1302		/// * `collection_id`: ID of the collection to repair.1303		#[pallet::call_index(30)]1304		#[pallet::weight(<SelfWeightOf<T>>::force_repair_collection())]1305		pub fn force_repair_collection(1306			origin: OriginFor<T>,1307			collection_id: CollectionId,1308		) -> DispatchResult {1309			if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {1310				fail!(<pallet_common::Error<T>>::UnsupportedOperation);1311			}1312			ensure_root(origin)?;1313			<PalletCommon<T>>::repair_collection(collection_id)1314		}13151316		/// Repairs a token if the data was somehow corrupted.1317		///1318		/// # Arguments1319		///1320		/// * `collection_id`: ID of the collection the item belongs to.1321		/// * `item_id`: ID of the item.1322		#[pallet::call_index(31)]1323		#[pallet::weight(T::CommonWeightInfo::force_repair_item())]1324		pub fn force_repair_item(1325			origin: OriginFor<T>,1326			collection_id: CollectionId,1327			item_id: TokenId,1328		) -> DispatchResultWithPostInfo {1329			ensure_root(origin)?;1330			dispatch_tx::<T, _>(collection_id, |d| d.repair_item(item_id))1331		}1332	}13331334	impl<T: Config> Pallet<T> {1335		/// Force set `sponsor` for `collection`.1336		///1337		/// Differs from [`set_collection_sponsor`][`Pallet::set_collection_sponsor`] in that confirmation1338		/// from the `sponsor` is not required.1339		///1340		/// # Arguments1341		///1342		/// * `sponsor`: ID of the account of the sponsor-to-be.1343		/// * `collection_id`: ID of the modified collection.1344		pub fn force_set_sponsor(1345			sponsor: T::AccountId,1346			collection_id: CollectionId,1347		) -> DispatchResult {1348			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1349			target_collection.force_set_sponsor(sponsor)1350		}13511352		/// Force remove `sponsor` for `collection`.1353		///1354		/// Differs from `remove_sponsor` in that1355		/// it doesn't require consent from the `owner` of the collection.1356		///1357		/// # Arguments1358		///1359		/// * `collection_id`: ID of the modified collection.1360		pub fn force_remove_collection_sponsor(collection_id: CollectionId) -> DispatchResult {1361			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1362			target_collection.force_remove_sponsor()1363		}13641365		#[inline(always)]1366		pub(crate) fn destroy_collection_internal(1367			sender: T::CrossAccountId,1368			collection_id: CollectionId,1369		) -> DispatchResult {1370			T::CollectionDispatch::destroy(sender, collection_id)?;13711372			// TODO: basket cleanup should be moved elsewhere1373			// Maybe runtime dispatch.rs should perform it?13741375			let _ = <NftTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1376			let _ = <FungibleTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1377			let _ = <ReFungibleTransferBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);13781379			let _ = <NftApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1380			let _ = <FungibleApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1381			let _ = <RefungibleApproveBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);13821383			Ok(())1384		}13851386		fn structure_nesting_budget() -> budget::Value {1387			budget::Value::new(Self::nesting_budget())1388		}13891390		fn nesting_budget_predispatch_weight() -> Weight {1391			T::StructureWeightInfo::find_parent().saturating_mul(Self::nesting_budget() as u64)1392		}13931394		pub fn refund_nesting_budget(1395			mut result: DispatchResultWithPostInfo,1396			budget: budget::Value,1397		) -> DispatchResultWithPostInfo {1398			let refund_amount = budget.refund_amount();1399			let consumed = Self::nesting_budget() - refund_amount;14001401			match &mut result {1402				Ok(PostDispatchInfo {1403					actual_weight: Some(weight),1404					..1405				})1406				| Err(DispatchErrorWithPostInfo {1407					post_info: PostDispatchInfo {1408						actual_weight: Some(weight),1409						..1410					},1411					..1412				}) => {1413					*weight += T::StructureWeightInfo::find_parent().saturating_mul(consumed as u64)1414				}1415				_ => {}1416			}14171418			result1419		}1420	}1421}
modifiedprimitives/data-structs/src/budget.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/budget.rs
+++ b/primitives/data-structs/src/budget.rs
@@ -36,3 +36,10 @@
 		true
 	}
 }
+
+pub struct ZeroBudget;
+impl Budget for ZeroBudget {
+	fn consume_custom(&self, _calls: u32) -> bool {
+		false
+	}
+}
modifiedprimitives/data-structs/src/lib.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -378,7 +378,7 @@
 #[derive(AbiCoderFlags, Bitfields, Clone, Copy, PartialEq, Eq, Debug, Default)]
 #[bondrewd(enforce_bytes = 1)]
 pub struct CollectionFlags {
-	/// Tokens in foreign collections can be transferred, but not burnt
+	/// A collection of foreign assets
 	#[bondrewd(bits = "0..1")]
 	pub foreign: bool,
 	/// Supports ERC721Metadata
modifiedruntime/common/config/orml.rsdiffbeforeafterboth
--- a/runtime/common/config/orml.rs
+++ b/runtime/common/config/orml.rs
@@ -14,31 +14,21 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-use frame_support::{
-	parameter_types,
-	traits::{Contains, Everything},
-};
+use frame_support::{parameter_types, traits::Everything};
 use frame_system::EnsureSigned;
 use orml_traits::{location::AbsoluteReserveProvider, parameter_type_with_key};
-use pallet_foreign_assets::{CurrencyId, NativeCurrency};
+use pallet_foreign_assets::CurrencyIdConvert;
 use sp_runtime::traits::Convert;
-use sp_std::{vec, vec::Vec};
 use staging_xcm::latest::{Junction::*, Junctions::*, MultiLocation, Weight};
 use staging_xcm_executor::XcmExecutor;
 use up_common::{
 	constants::*,
 	types::{AccountId, Balance},
 };
+use up_data_structs::CollectionId;
 
 use crate::{
-	runtime_common::config::{
-		pallets::TreasuryAccountId,
-		substrate::{MaxLocks, MaxReserves},
-		xcm::{
-			xcm_assets::CurrencyIdConvert, SelfLocation, UniversalLocation, Weigher,
-			XcmExecutorConfig,
-		},
-	},
+	runtime_common::config::xcm::{SelfLocation, UniversalLocation, Weigher, XcmExecutorConfig},
 	RelayChainBlockNumberProvider, Runtime, RuntimeEvent,
 };
 
@@ -59,29 +49,6 @@
 	};
 }
 
-parameter_type_with_key! {
-	pub ExistentialDeposits: |currency_id: CurrencyId| -> Balance {
-		match currency_id {
-			CurrencyId::NativeAssetId(symbol) => match symbol {
-				NativeCurrency::Here => 0,
-				NativeCurrency::Parent=> 0,
-			},
-			_ => 100_000
-		}
-	};
-}
-
-pub fn get_all_module_accounts() -> Vec<AccountId> {
-	vec![TreasuryAccountId::get()]
-}
-
-pub struct DustRemovalWhitelist;
-impl Contains<AccountId> for DustRemovalWhitelist {
-	fn contains(a: &AccountId) -> bool {
-		get_all_module_accounts().contains(a)
-	}
-}
-
 pub struct AccountIdToMultiLocation;
 impl Convert<AccountId, MultiLocation> for AccountIdToMultiLocation {
 	fn convert(account: AccountId) -> MultiLocation {
@@ -91,18 +58,6 @@
 		})
 		.into()
 	}
-}
-
-pub struct CurrencyHooks;
-impl orml_traits::currency::MutationHooks<AccountId, CurrencyId, Balance> for CurrencyHooks {
-	type OnDust = orml_tokens::TransferDust<Runtime, TreasuryAccountId>;
-	type OnSlash = ();
-	type PreTransfer = ();
-	type PostTransfer = ();
-	type PreDeposit = ();
-	type PostDeposit = ();
-	type OnNewTokenAccount = ();
-	type OnKilledTokenAccount = ();
 }
 
 impl orml_vesting::Config for Runtime {
@@ -113,29 +68,13 @@
 	type WeightInfo = ();
 	type MaxVestingSchedules = MaxVestingSchedules;
 	type BlockNumberProvider = RelayChainBlockNumberProvider<Runtime>;
-}
-
-impl orml_tokens::Config for Runtime {
-	type RuntimeEvent = RuntimeEvent;
-	type Balance = Balance;
-	type Amount = Amount;
-	type CurrencyId = CurrencyId;
-	type WeightInfo = ();
-	type ExistentialDeposits = ExistentialDeposits;
-	type CurrencyHooks = CurrencyHooks;
-	type MaxLocks = MaxLocks;
-	type MaxReserves = MaxReserves;
-	// TODO: Add all module accounts
-	type DustRemovalWhitelist = DustRemovalWhitelist;
-	/// The id type for named reserves.
-	type ReserveIdentifier = ();
 }
 
 impl orml_xtokens::Config for Runtime {
 	type RuntimeEvent = RuntimeEvent;
 	type Balance = Balance;
-	type CurrencyId = CurrencyId;
-	type CurrencyIdConvert = CurrencyIdConvert;
+	type CurrencyId = CollectionId;
+	type CurrencyIdConvert = CurrencyIdConvert<Self>;
 	type AccountIdToMultiLocation = AccountIdToMultiLocation;
 	type SelfLocation = SelfLocation;
 	type XcmExecutor = XcmExecutor<XcmExecutorConfig<Self>>;
modifiedruntime/common/config/pallets/foreign_asset.rsdiffbeforeafterboth
--- a/runtime/common/config/pallets/foreign_asset.rs
+++ b/runtime/common/config/pallets/foreign_asset.rs
@@ -1,10 +1,52 @@
-use up_common::types::AccountId;
+use frame_support::{parameter_types, PalletId};
+#[cfg(not(feature = "governance"))]
+use frame_system::EnsureRoot;
+use pallet_evm::account::CrossAccountId;
+use sp_core::H160;
+use staging_xcm::prelude::*;
+use staging_xcm_builder::AccountKey20Aliases;
+
+#[cfg(feature = "governance")]
+use crate::runtime_common::config::governance;
+use crate::{
+	runtime_common::config::{
+		ethereum::CrossAccountId as ConfigCrossAccountId,
+		xcm::{LocationToAccountId, SelfLocation},
+	},
+	RelayNetwork, Runtime, RuntimeEvent,
+};
+
+parameter_types! {
+	pub ForeignAssetPalletId: PalletId = PalletId(*b"frgnasts");
+}
+
+pub struct LocationToCrossAccountId;
+impl staging_xcm_executor::traits::ConvertLocation<ConfigCrossAccountId>
+	for LocationToCrossAccountId
+{
+	fn convert_location(location: &MultiLocation) -> Option<ConfigCrossAccountId> {
+		LocationToAccountId::convert_location(location)
+			.map(ConfigCrossAccountId::from_sub)
+			.or_else(|| {
+				let eth_address =
+					AccountKey20Aliases::<RelayNetwork, H160>::convert_location(location)?;
 
-use crate::{Balances, Runtime, RuntimeEvent};
+				Some(ConfigCrossAccountId::from_eth(eth_address))
+			})
+	}
+}
 
 impl pallet_foreign_assets::Config for Runtime {
 	type RuntimeEvent = RuntimeEvent;
-	type Currency = Balances;
-	type RegisterOrigin = frame_system::EnsureRoot<AccountId>;
+
+	#[cfg(feature = "governance")]
+	type ForceRegisterOrigin = governance::RootOrTechnicalCommitteeMember;
+
+	#[cfg(not(feature = "governance"))]
+	type ForceRegisterOrigin = EnsureRoot<Self::AccountId>;
+
+	type PalletId = ForeignAssetPalletId;
+	type SelfLocation = SelfLocation;
+	type LocationToAccountId = LocationToCrossAccountId;
 	type WeightInfo = pallet_foreign_assets::weights::SubstrateWeight<Self>;
 }
addedruntime/common/config/xcm.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/config/xcm.rs
@@ -0,0 +1,254 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+use cumulus_primitives_core::ParaId;
+use frame_support::{
+	parameter_types,
+	traits::{ConstU32, Everything, Get, Nothing, ProcessMessageError},
+};
+use frame_system::EnsureRoot;
+use orml_traits::location::AbsoluteReserveProvider;
+use orml_xcm_support::MultiNativeAsset;
+use pallet_foreign_assets::FreeForAll;
+use pallet_xcm::XcmPassthrough;
+use polkadot_parachain_primitives::primitives::Sibling;
+use polkadot_runtime_common::xcm_sender::NoPriceForMessageDelivery;
+use sp_std::marker::PhantomData;
+use staging_xcm::{
+	latest::{prelude::*, MultiLocation, Weight},
+	v3::Instruction,
+};
+use staging_xcm_builder::{
+	AccountId32Aliases, EnsureXcmOrigin, FixedWeightBounds, ParentIsPreset, RelayChainAsNative,
+	SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative,
+	SignedToAccountId32, SovereignSignedViaLocation,
+};
+use staging_xcm_executor::{
+	traits::{Properties, ShouldExecute},
+	XcmExecutor,
+};
+use up_common::types::AccountId;
+
+#[cfg(feature = "governance")]
+use crate::runtime_common::config::governance;
+use crate::{
+	xcm_barrier::Barrier, AllPalletsWithSystem, Balances, ForeignAssets, ParachainInfo,
+	ParachainSystem, PolkadotXcm, RelayNetwork, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin,
+	XcmpQueue,
+};
+
+parameter_types! {
+	pub const RelayLocation: MultiLocation = MultiLocation::parent();
+	pub RelayOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into();
+	pub UniversalLocation: InteriorMultiLocation = (
+		GlobalConsensus(crate::RelayNetwork::get()),
+		Parachain(ParachainInfo::get().into()),
+	).into();
+	pub SelfLocation: MultiLocation = MultiLocation::new(1, X1(Parachain(ParachainInfo::get().into())));
+
+	// One XCM operation is 1_000_000 weight - almost certainly a conservative estimate.
+	pub UnitWeightCost: Weight = Weight::from_parts(1_000_000, 1000); // ?
+	pub const MaxInstructions: u32 = 100;
+}
+
+/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used
+/// when determining ownership of accounts for asset transacting and when attempting to use XCM
+/// `Transact` in order to determine the dispatch Origin.
+pub type LocationToAccountId = (
+	// The parent (Relay-chain) origin converts to the default `AccountId`.
+	ParentIsPreset<AccountId>,
+	// Sibling parachain origins convert to AccountId via the `ParaId::into`.
+	SiblingParachainConvertsVia<Sibling, AccountId>,
+	// Straight up local `AccountId32` origins just alias directly to `AccountId`.
+	AccountId32Aliases<RelayNetwork, AccountId>,
+);
+
+/// No local origins on this chain are allowed to dispatch XCM sends/executions.
+pub type LocalOriginToLocation = (SignedToAccountId32<RuntimeOrigin, AccountId, RelayNetwork>,);
+
+/// The means for routing XCM messages which are not for local execution into the right message
+/// queues.
+pub type XcmRouter = (
+	// Two routers - use UMP to communicate with the relay chain:
+	cumulus_primitives_utility::ParentAsUmp<ParachainSystem, PolkadotXcm, ()>,
+	// ..and XCMP to communicate with the sibling chains.
+	XcmpQueue,
+);
+
+/// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance,
+/// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can
+/// biases the kind of local `Origin` it will become.
+pub type XcmOriginToTransactDispatchOrigin = (
+	// Sovereign account converter; this attempts to derive an `AccountId` from the origin location
+	// using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for
+	// foreign chains who want to have a local sovereign account on this chain which they control.
+	SovereignSignedViaLocation<LocationToAccountId, RuntimeOrigin>,
+	// Native converter for Relay-chain (Parent) location; will converts to a `Relay` origin when
+	// recognised.
+	RelayChainAsNative<RelayOrigin, RuntimeOrigin>,
+	// Native converter for sibling Parachains; will convert to a `SiblingPara` origin when
+	// recognised.
+	SiblingParachainAsNative<cumulus_pallet_xcm::Origin, RuntimeOrigin>,
+	// Native signed account converter; this just converts an `AccountId32` origin into a normal
+	// `Origin::Signed` origin of the same 32-byte value.
+	SignedAccountId32AsNative<RelayNetwork, RuntimeOrigin>,
+	// Xcm origins can be represented natively under the Xcm pallet's Xcm origin.
+	XcmPassthrough<RuntimeOrigin>,
+);
+
+pub trait TryPass {
+	fn try_pass<Call>(
+		origin: &MultiLocation,
+		message: &mut [Instruction<Call>],
+	) -> Result<(), ProcessMessageError>;
+}
+
+#[impl_trait_for_tuples::impl_for_tuples(30)]
+impl TryPass for Tuple {
+	fn try_pass<Call>(
+		origin: &MultiLocation,
+		message: &mut [Instruction<Call>],
+	) -> Result<(), ProcessMessageError> {
+		for_tuples!( #(
+			Tuple::try_pass(origin, message)?;
+		)* );
+
+		Ok(())
+	}
+}
+
+/// Deny executing the XCM if it matches any of the Deny filter regardless of anything else.
+/// If it passes the Deny, and matches one of the Allow cases then it is let through.
+pub struct DenyThenTry<Deny, Allow>(PhantomData<Deny>, PhantomData<Allow>)
+where
+	Deny: TryPass,
+	Allow: ShouldExecute;
+
+impl<Deny, Allow> ShouldExecute for DenyThenTry<Deny, Allow>
+where
+	Deny: TryPass,
+	Allow: ShouldExecute,
+{
+	fn should_execute<Call>(
+		origin: &MultiLocation,
+		message: &mut [Instruction<Call>],
+		max_weight: Weight,
+		properties: &mut Properties,
+	) -> Result<(), ProcessMessageError> {
+		Deny::try_pass(origin, message)?;
+		Allow::should_execute(origin, message, max_weight, properties)
+	}
+}
+
+pub type Weigher = FixedWeightBounds<UnitWeightCost, RuntimeCall, MaxInstructions>;
+
+pub type IsReserve = MultiNativeAsset<AbsoluteReserveProvider>;
+
+pub type Trader = FreeForAll;
+
+pub struct XcmExecutorConfig<T>(PhantomData<T>);
+impl<T> staging_xcm_executor::Config for XcmExecutorConfig<T>
+where
+	T: pallet_configuration::Config,
+{
+	type RuntimeCall = RuntimeCall;
+	type XcmSender = XcmRouter;
+	// How to withdraw and deposit an asset.
+	type AssetTransactor = ForeignAssets;
+	type OriginConverter = XcmOriginToTransactDispatchOrigin;
+	type IsReserve = IsReserve;
+	type IsTeleporter = (); // Teleportation is disabled
+	type UniversalLocation = UniversalLocation;
+	type Barrier = Barrier;
+	type Weigher = Weigher;
+	type Trader = Trader;
+	type ResponseHandler = PolkadotXcm;
+	type SubscriptionService = PolkadotXcm;
+	type PalletInstancesInfo = AllPalletsWithSystem;
+	type MaxAssetsIntoHolding = ConstU32<8>;
+
+	type AssetTrap = PolkadotXcm;
+	type AssetClaims = PolkadotXcm;
+	type AssetLocker = ();
+	type AssetExchanger = ();
+	type FeeManager = ();
+	type MessageExporter = ();
+	type UniversalAliases = Nothing;
+	type CallDispatcher = RuntimeCall;
+	type SafeCallFilter = Nothing;
+	type Aliasers = Nothing;
+}
+
+#[cfg(feature = "runtime-benchmarks")]
+parameter_types! {
+	pub ReachableDest: Option<MultiLocation> = Some(Parent.into());
+}
+
+impl pallet_xcm::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type SendXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, ()>;
+	type XcmRouter = XcmRouter;
+	type ExecuteXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
+	type XcmExecuteFilter = Everything;
+	type XcmExecutor = XcmExecutor<XcmExecutorConfig<Self>>;
+	type XcmTeleportFilter = Everything;
+	type XcmReserveTransferFilter = Everything;
+	type Weigher = FixedWeightBounds<UnitWeightCost, RuntimeCall, MaxInstructions>;
+	type RuntimeOrigin = RuntimeOrigin;
+	type RuntimeCall = RuntimeCall;
+	const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
+	type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion;
+	type UniversalLocation = UniversalLocation;
+	type Currency = Balances;
+	type CurrencyMatcher = ();
+	type TrustedLockers = ();
+	type SovereignAccountOf = LocationToAccountId;
+	type MaxLockers = ConstU32<8>;
+	type WeightInfo = crate::weights::xcm::SubstrateWeight<Runtime>;
+	type AdminOrigin = EnsureRoot<AccountId>;
+	type MaxRemoteLockConsumers = ConstU32<0>;
+	type RemoteLockConsumerIdentifier = ();
+	#[cfg(feature = "runtime-benchmarks")]
+	type ReachableDest = ReachableDest;
+}
+
+impl cumulus_pallet_xcm::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type XcmExecutor = XcmExecutor<XcmExecutorConfig<Self>>;
+}
+impl cumulus_pallet_xcmp_queue::Config for Runtime {
+	type WeightInfo = ();
+	type RuntimeEvent = RuntimeEvent;
+	type XcmExecutor = XcmExecutor<XcmExecutorConfig<Self>>;
+	type ChannelInfo = ParachainSystem;
+	type VersionWrapper = PolkadotXcm;
+	type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
+
+	#[cfg(feature = "governance")]
+	type ControllerOrigin = governance::RootOrTechnicalCommitteeMember;
+
+	#[cfg(not(feature = "governance"))]
+	type ControllerOrigin = frame_system::EnsureRoot<AccountId>;
+
+	type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin;
+	type PriceForSiblingDelivery = NoPriceForMessageDelivery<ParaId>;
+}
+
+impl cumulus_pallet_dmp_queue::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type XcmExecutor = XcmExecutor<XcmExecutorConfig<Self>>;
+	type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
+}
deletedruntime/common/config/xcm/foreignassets.rsdiffbeforeafterboth
--- a/runtime/common/config/xcm/foreignassets.rs
+++ /dev/null
@@ -1,205 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// This file is part of Unique Network.
-
-// Unique Network is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Unique Network is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-
-use frame_support::{parameter_types, traits::Get};
-use orml_traits::location::AbsoluteReserveProvider;
-use orml_xcm_support::MultiNativeAsset;
-use pallet_foreign_assets::{
-	AssetId, AssetIdMapping, CurrencyId, ForeignAssetId, FreeForAll, NativeCurrency, TryAsForeign,
-	XcmForeignAssetIdMapping,
-};
-use sp_runtime::traits::{Convert, MaybeEquivalence};
-use sp_std::marker::PhantomData;
-use staging_xcm::latest::{prelude::*, MultiAsset, MultiLocation};
-use staging_xcm_builder::{ConvertedConcreteId, FungiblesAdapter, NoChecking};
-use staging_xcm_executor::traits::{JustTry, TransactAsset};
-use up_common::types::{AccountId, Balance};
-
-use super::{LocationToAccountId, RelayLocation};
-use crate::{Balances, ForeignAssets, ParachainInfo, PolkadotXcm, Runtime};
-
-parameter_types! {
-	pub CheckingAccount: AccountId = PolkadotXcm::check_account();
-}
-
-pub struct AsInnerId<ConvertAssetId>(PhantomData<(AssetId, ConvertAssetId)>);
-impl<ConvertAssetId: MaybeEquivalence<AssetId, AssetId>> MaybeEquivalence<MultiLocation, AssetId>
-	for AsInnerId<ConvertAssetId>
-{
-	fn convert(id: &MultiLocation) -> Option<AssetId> {
-		log::trace!(
-			target: "xcm::AsInnerId::Convert",
-			"AsInnerId {:?}",
-			id
-		);
-
-		let parent = MultiLocation::parent();
-		let here = MultiLocation::here();
-		let self_location = MultiLocation::new(1, X1(Parachain(ParachainInfo::get().into())));
-
-		if *id == parent {
-			return ConvertAssetId::convert(&AssetId::NativeAssetId(NativeCurrency::Parent));
-		}
-
-		if *id == here || *id == self_location {
-			return ConvertAssetId::convert(&AssetId::NativeAssetId(NativeCurrency::Here));
-		}
-
-		match XcmForeignAssetIdMapping::<Runtime>::get_currency_id(*id) {
-			Some(AssetId::ForeignAssetId(foreign_asset_id)) => {
-				ConvertAssetId::convert(&AssetId::ForeignAssetId(foreign_asset_id))
-			}
-			_ => None,
-		}
-	}
-
-	fn convert_back(asset_id: &AssetId) -> Option<MultiLocation> {
-		log::trace!(
-			target: "xcm::AsInnerId::Reverse",
-			"AsInnerId",
-		);
-
-		let parent_id =
-			ConvertAssetId::convert(&AssetId::NativeAssetId(NativeCurrency::Parent)).unwrap();
-		let here_id =
-			ConvertAssetId::convert(&AssetId::NativeAssetId(NativeCurrency::Here)).unwrap();
-
-		if *asset_id == parent_id {
-			return Some(MultiLocation::parent());
-		}
-
-		if *asset_id == here_id {
-			return Some(MultiLocation::new(
-				1,
-				X1(Parachain(ParachainInfo::get().into())),
-			));
-		}
-
-		let fid = <AssetId as TryAsForeign<AssetId, ForeignAssetId>>::try_as_foreign(*asset_id)?;
-		XcmForeignAssetIdMapping::<Runtime>::get_multi_location(fid)
-	}
-}
-
-/// Means for transacting assets besides the native currency on this chain.
-pub type FungiblesTransactor = FungiblesAdapter<
-	// Use this fungibles implementation:
-	ForeignAssets,
-	// Use this currency when it is a fungible asset matching the given location or name:
-	ConvertedConcreteId<AssetId, Balance, AsInnerId<JustTry>, JustTry>,
-	// Convert an XCM MultiLocation into a local account id:
-	LocationToAccountId,
-	// Our chain's account ID type (we can't get away without mentioning it explicitly):
-	AccountId,
-	// No Checking for teleported assets since we disallow teleports at all.
-	NoChecking,
-	// The account to use for tracking teleports.
-	CheckingAccount,
->;
-
-/// Means for transacting assets on this chain.
-pub struct AssetTransactor;
-impl TransactAsset for AssetTransactor {
-	fn can_check_in(
-		_origin: &MultiLocation,
-		_what: &MultiAsset,
-		_context: &XcmContext,
-	) -> XcmResult {
-		Err(XcmError::Unimplemented)
-	}
-
-	fn check_in(_origin: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}
-
-	fn can_check_out(
-		_dest: &MultiLocation,
-		_what: &MultiAsset,
-		_context: &XcmContext,
-	) -> XcmResult {
-		Err(XcmError::Unimplemented)
-	}
-
-	fn check_out(_dest: &MultiLocation, _what: &MultiAsset, _context: &XcmContext) {}
-
-	fn deposit_asset(
-		what: &MultiAsset,
-		who: &MultiLocation,
-		context: Option<&XcmContext>,
-	) -> XcmResult {
-		FungiblesTransactor::deposit_asset(what, who, context)
-	}
-
-	fn withdraw_asset(
-		what: &MultiAsset,
-		who: &MultiLocation,
-		maybe_context: Option<&XcmContext>,
-	) -> Result<staging_xcm_executor::Assets, XcmError> {
-		FungiblesTransactor::withdraw_asset(what, who, maybe_context)
-	}
-
-	fn internal_transfer_asset(
-		what: &MultiAsset,
-		from: &MultiLocation,
-		to: &MultiLocation,
-		context: &XcmContext,
-	) -> Result<staging_xcm_executor::Assets, XcmError> {
-		FungiblesTransactor::internal_transfer_asset(what, from, to, context)
-	}
-}
-
-pub type IsReserve = MultiNativeAsset<AbsoluteReserveProvider>;
-
-pub type Trader<T> = FreeForAll<
-	pallet_configuration::WeightToFee<T, Balance>,
-	RelayLocation,
-	AccountId,
-	Balances,
-	(),
->;
-
-pub struct CurrencyIdConvert;
-impl Convert<AssetId, Option<MultiLocation>> for CurrencyIdConvert {
-	fn convert(id: AssetId) -> Option<MultiLocation> {
-		match id {
-			AssetId::NativeAssetId(NativeCurrency::Here) => Some(MultiLocation::new(
-				1,
-				X1(Parachain(ParachainInfo::get().into())),
-			)),
-			AssetId::NativeAssetId(NativeCurrency::Parent) => Some(MultiLocation::parent()),
-			AssetId::ForeignAssetId(foreign_asset_id) => {
-				XcmForeignAssetIdMapping::<Runtime>::get_multi_location(foreign_asset_id)
-			}
-		}
-	}
-}
-
-impl Convert<MultiLocation, Option<CurrencyId>> for CurrencyIdConvert {
-	fn convert(location: MultiLocation) -> Option<CurrencyId> {
-		if location == MultiLocation::here()
-			|| location == MultiLocation::new(1, X1(Parachain(ParachainInfo::get().into())))
-		{
-			return Some(AssetId::NativeAssetId(NativeCurrency::Here));
-		}
-
-		if location == MultiLocation::parent() {
-			return Some(AssetId::NativeAssetId(NativeCurrency::Parent));
-		}
-
-		if let Some(currency_id) = XcmForeignAssetIdMapping::<Runtime>::get_currency_id(location) {
-			return Some(currency_id);
-		}
-
-		None
-	}
-}
deletedruntime/common/config/xcm/mod.rsdiffbeforeafterboth
--- a/runtime/common/config/xcm/mod.rs
+++ /dev/null
@@ -1,259 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// This file is part of Unique Network.
-
-// Unique Network is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Unique Network is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-
-use cumulus_primitives_core::ParaId;
-use frame_support::{
-	parameter_types,
-	traits::{ConstU32, Everything, Get, Nothing, ProcessMessageError},
-};
-use frame_system::EnsureRoot;
-use pallet_xcm::XcmPassthrough;
-use polkadot_parachain_primitives::primitives::Sibling;
-use polkadot_runtime_common::xcm_sender::NoPriceForMessageDelivery;
-use sp_std::marker::PhantomData;
-use staging_xcm::{
-	latest::{prelude::*, MultiLocation, Weight},
-	v3::Instruction,
-};
-use staging_xcm_builder::{
-	AccountId32Aliases, EnsureXcmOrigin, FixedWeightBounds, ParentIsPreset, RelayChainAsNative,
-	SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative,
-	SignedToAccountId32, SovereignSignedViaLocation,
-};
-use staging_xcm_executor::{
-	traits::{Properties, ShouldExecute},
-	XcmExecutor,
-};
-use up_common::types::AccountId;
-
-use crate::{
-	xcm_barrier::Barrier, AllPalletsWithSystem, Balances, ParachainInfo, ParachainSystem,
-	PolkadotXcm, RelayNetwork, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, XcmpQueue,
-};
-
-#[cfg(feature = "foreign-assets")]
-pub mod foreignassets;
-
-#[cfg(not(feature = "foreign-assets"))]
-pub mod nativeassets;
-
-#[cfg(feature = "foreign-assets")]
-pub use foreignassets as xcm_assets;
-#[cfg(not(feature = "foreign-assets"))]
-pub use nativeassets as xcm_assets;
-use xcm_assets::{AssetTransactor, IsReserve, Trader};
-
-#[cfg(feature = "governance")]
-use crate::runtime_common::config::governance;
-
-parameter_types! {
-	pub const RelayLocation: MultiLocation = MultiLocation::parent();
-	pub RelayOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into();
-	pub UniversalLocation: InteriorMultiLocation = (
-		GlobalConsensus(crate::RelayNetwork::get()),
-		Parachain(ParachainInfo::get().into()),
-	).into();
-	pub SelfLocation: MultiLocation = MultiLocation::new(1, X1(Parachain(ParachainInfo::get().into())));
-
-	// One XCM operation is 1_000_000 weight - almost certainly a conservative estimate.
-	pub UnitWeightCost: Weight = Weight::from_parts(1_000_000, 1000); // ?
-	pub const MaxInstructions: u32 = 100;
-}
-
-/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used
-/// when determining ownership of accounts for asset transacting and when attempting to use XCM
-/// `Transact` in order to determine the dispatch Origin.
-pub type LocationToAccountId = (
-	// The parent (Relay-chain) origin converts to the default `AccountId`.
-	ParentIsPreset<AccountId>,
-	// Sibling parachain origins convert to AccountId via the `ParaId::into`.
-	SiblingParachainConvertsVia<Sibling, AccountId>,
-	// Straight up local `AccountId32` origins just alias directly to `AccountId`.
-	AccountId32Aliases<RelayNetwork, AccountId>,
-);
-
-/// No local origins on this chain are allowed to dispatch XCM sends/executions.
-pub type LocalOriginToLocation = (SignedToAccountId32<RuntimeOrigin, AccountId, RelayNetwork>,);
-
-/// The means for routing XCM messages which are not for local execution into the right message
-/// queues.
-pub type XcmRouter = (
-	// Two routers - use UMP to communicate with the relay chain:
-	cumulus_primitives_utility::ParentAsUmp<ParachainSystem, PolkadotXcm, ()>,
-	// ..and XCMP to communicate with the sibling chains.
-	XcmpQueue,
-);
-
-/// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance,
-/// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can
-/// biases the kind of local `Origin` it will become.
-pub type XcmOriginToTransactDispatchOrigin = (
-	// Sovereign account converter; this attempts to derive an `AccountId` from the origin location
-	// using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for
-	// foreign chains who want to have a local sovereign account on this chain which they control.
-	SovereignSignedViaLocation<LocationToAccountId, RuntimeOrigin>,
-	// Native converter for Relay-chain (Parent) location; will converts to a `Relay` origin when
-	// recognised.
-	RelayChainAsNative<RelayOrigin, RuntimeOrigin>,
-	// Native converter for sibling Parachains; will convert to a `SiblingPara` origin when
-	// recognised.
-	SiblingParachainAsNative<cumulus_pallet_xcm::Origin, RuntimeOrigin>,
-	// Native signed account converter; this just converts an `AccountId32` origin into a normal
-	// `Origin::Signed` origin of the same 32-byte value.
-	SignedAccountId32AsNative<RelayNetwork, RuntimeOrigin>,
-	// Xcm origins can be represented natively under the Xcm pallet's Xcm origin.
-	XcmPassthrough<RuntimeOrigin>,
-);
-
-pub trait TryPass {
-	fn try_pass<Call>(
-		origin: &MultiLocation,
-		message: &mut [Instruction<Call>],
-	) -> Result<(), ProcessMessageError>;
-}
-
-#[impl_trait_for_tuples::impl_for_tuples(30)]
-impl TryPass for Tuple {
-	fn try_pass<Call>(
-		origin: &MultiLocation,
-		message: &mut [Instruction<Call>],
-	) -> Result<(), ProcessMessageError> {
-		for_tuples!( #(
-			Tuple::try_pass(origin, message)?;
-		)* );
-
-		Ok(())
-	}
-}
-
-/// Deny executing the XCM if it matches any of the Deny filter regardless of anything else.
-/// If it passes the Deny, and matches one of the Allow cases then it is let through.
-pub struct DenyThenTry<Deny, Allow>(PhantomData<Deny>, PhantomData<Allow>)
-where
-	Deny: TryPass,
-	Allow: ShouldExecute;
-
-impl<Deny, Allow> ShouldExecute for DenyThenTry<Deny, Allow>
-where
-	Deny: TryPass,
-	Allow: ShouldExecute,
-{
-	fn should_execute<Call>(
-		origin: &MultiLocation,
-		message: &mut [Instruction<Call>],
-		max_weight: Weight,
-		properties: &mut Properties,
-	) -> Result<(), ProcessMessageError> {
-		Deny::try_pass(origin, message)?;
-		Allow::should_execute(origin, message, max_weight, properties)
-	}
-}
-
-pub type Weigher = FixedWeightBounds<UnitWeightCost, RuntimeCall, MaxInstructions>;
-
-pub struct XcmExecutorConfig<T>(PhantomData<T>);
-impl<T> staging_xcm_executor::Config for XcmExecutorConfig<T>
-where
-	T: pallet_configuration::Config,
-{
-	type RuntimeCall = RuntimeCall;
-	type XcmSender = XcmRouter;
-	// How to withdraw and deposit an asset.
-	type AssetTransactor = AssetTransactor;
-	type OriginConverter = XcmOriginToTransactDispatchOrigin;
-	type IsReserve = IsReserve;
-	type IsTeleporter = (); // Teleportation is disabled
-	type UniversalLocation = UniversalLocation;
-	type Barrier = Barrier;
-	type Weigher = Weigher;
-	type Trader = Trader<T>;
-	type ResponseHandler = PolkadotXcm;
-	type SubscriptionService = PolkadotXcm;
-	type PalletInstancesInfo = AllPalletsWithSystem;
-	type MaxAssetsIntoHolding = ConstU32<8>;
-
-	type AssetTrap = PolkadotXcm;
-	type AssetClaims = PolkadotXcm;
-	type AssetLocker = ();
-	type AssetExchanger = ();
-	type FeeManager = ();
-	type MessageExporter = ();
-	type UniversalAliases = Nothing;
-	type CallDispatcher = RuntimeCall;
-	type SafeCallFilter = Nothing;
-	type Aliasers = Nothing;
-}
-
-#[cfg(feature = "runtime-benchmarks")]
-parameter_types! {
-	pub ReachableDest: Option<MultiLocation> = Some(Parent.into());
-}
-
-impl pallet_xcm::Config for Runtime {
-	type RuntimeEvent = RuntimeEvent;
-	type SendXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, ()>;
-	type XcmRouter = XcmRouter;
-	type ExecuteXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
-	type XcmExecuteFilter = Everything;
-	type XcmExecutor = XcmExecutor<XcmExecutorConfig<Self>>;
-	type XcmTeleportFilter = Everything;
-	type XcmReserveTransferFilter = Everything;
-	type Weigher = FixedWeightBounds<UnitWeightCost, RuntimeCall, MaxInstructions>;
-	type RuntimeOrigin = RuntimeOrigin;
-	type RuntimeCall = RuntimeCall;
-	const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
-	type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion;
-	type UniversalLocation = UniversalLocation;
-	type Currency = Balances;
-	type CurrencyMatcher = ();
-	type TrustedLockers = ();
-	type SovereignAccountOf = LocationToAccountId;
-	type MaxLockers = ConstU32<8>;
-	type WeightInfo = crate::weights::xcm::SubstrateWeight<Runtime>;
-	type AdminOrigin = EnsureRoot<AccountId>;
-	type MaxRemoteLockConsumers = ConstU32<0>;
-	type RemoteLockConsumerIdentifier = ();
-	#[cfg(feature = "runtime-benchmarks")]
-	type ReachableDest = ReachableDest;
-}
-
-impl cumulus_pallet_xcm::Config for Runtime {
-	type RuntimeEvent = RuntimeEvent;
-	type XcmExecutor = XcmExecutor<XcmExecutorConfig<Self>>;
-}
-impl cumulus_pallet_xcmp_queue::Config for Runtime {
-	type WeightInfo = ();
-	type RuntimeEvent = RuntimeEvent;
-	type XcmExecutor = XcmExecutor<XcmExecutorConfig<Self>>;
-	type ChannelInfo = ParachainSystem;
-	type VersionWrapper = PolkadotXcm;
-	type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
-
-	#[cfg(feature = "governance")]
-	type ControllerOrigin = governance::RootOrTechnicalCommitteeMember;
-
-	#[cfg(not(feature = "governance"))]
-	type ControllerOrigin = frame_system::EnsureRoot<AccountId>;
-
-	type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin;
-	type PriceForSiblingDelivery = NoPriceForMessageDelivery<ParaId>;
-}
-
-impl cumulus_pallet_dmp_queue::Config for Runtime {
-	type RuntimeEvent = RuntimeEvent;
-	type XcmExecutor = XcmExecutor<XcmExecutorConfig<Self>>;
-	type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
-}
deletedruntime/common/config/xcm/nativeassets.rsdiffbeforeafterboth
--- a/runtime/common/config/xcm/nativeassets.rs
+++ /dev/null
@@ -1,148 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// This file is part of Unique Network.
-
-// Unique Network is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Unique Network is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-
-use cumulus_primitives_core::XcmContext;
-use frame_support::{
-	traits::{tokens::currency::Currency as CurrencyT, Get, OnUnbalanced as OnUnbalancedT},
-	weights::WeightToFeePolynomial,
-};
-use pallet_foreign_assets::{AssetIds, NativeCurrency};
-use sp_runtime::traits::{CheckedConversion, Convert, Zero};
-use sp_std::marker::PhantomData;
-use staging_xcm::latest::{
-	AssetId::Concrete, Error as XcmError, Fungibility::Fungible as XcmFungible, Junction::*,
-	Junctions::*, MultiAsset, MultiLocation, Weight,
-};
-use staging_xcm_builder::{CurrencyAdapter, NativeAsset};
-use staging_xcm_executor::{
-	traits::{MatchesFungible, WeightTrader},
-	Assets,
-};
-use up_common::types::{AccountId, Balance};
-
-use super::{LocationToAccountId, RelayLocation};
-use crate::{Balances, ParachainInfo};
-
-pub struct OnlySelfCurrency;
-impl<B: TryFrom<u128>> MatchesFungible<B> for OnlySelfCurrency {
-	fn matches_fungible(a: &MultiAsset) -> Option<B> {
-		let paraid = Parachain(ParachainInfo::parachain_id().into());
-		match (&a.id, &a.fun) {
-			(
-				Concrete(MultiLocation {
-					parents: 1,
-					interior: X1(loc),
-				}),
-				XcmFungible(ref amount),
-			) if paraid == *loc => CheckedConversion::checked_from(*amount),
-			(
-				Concrete(MultiLocation {
-					parents: 0,
-					interior: Here,
-				}),
-				XcmFungible(ref amount),
-			) => CheckedConversion::checked_from(*amount),
-			_ => None,
-		}
-	}
-}
-
-/// Means for transacting assets on this chain.
-pub type LocalAssetTransactor = CurrencyAdapter<
-	// Use this currency:
-	Balances,
-	// Use this currency when it is a fungible asset matching the given location or name:
-	OnlySelfCurrency,
-	// Do a simple punn to convert an AccountId32 MultiLocation into a native chain account ID:
-	LocationToAccountId,
-	// Our chain's account ID type (we can't get away without mentioning it explicitly):
-	AccountId,
-	// We don't track any teleports.
-	(),
->;
-
-pub type AssetTransactor = LocalAssetTransactor;
-
-pub type IsReserve = NativeAsset;
-
-pub struct UsingOnlySelfCurrencyComponents<
-	WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
-	AssetId: Get<MultiLocation>,
-	AccountId,
-	Currency: CurrencyT<AccountId>,
-	OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
->(
-	Weight,
-	Currency::Balance,
-	PhantomData<(WeightToFee, AssetId, AccountId, Currency, OnUnbalanced)>,
-);
-impl<
-		WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
-		AssetId: Get<MultiLocation>,
-		AccountId,
-		Currency: CurrencyT<AccountId>,
-		OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
-	> WeightTrader
-	for UsingOnlySelfCurrencyComponents<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>
-{
-	fn new() -> Self {
-		// FIXME: benchmark
-		Self(Weight::from_parts(0, 0), Zero::zero(), PhantomData)
-	}
-
-	fn buy_weight(
-		&mut self,
-		_weight: Weight,
-		payment: Assets,
-		_xcm: &XcmContext,
-	) -> Result<Assets, XcmError> {
-		Ok(payment)
-	}
-}
-impl<
-		WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
-		AssetId: Get<MultiLocation>,
-		AccountId,
-		Currency: CurrencyT<AccountId>,
-		OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
-	> Drop
-	for UsingOnlySelfCurrencyComponents<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>
-{
-	fn drop(&mut self) {
-		OnUnbalanced::on_unbalanced(Currency::issue(self.1));
-	}
-}
-
-pub type Trader<T> = UsingOnlySelfCurrencyComponents<
-	pallet_configuration::WeightToFee<T, Balance>,
-	RelayLocation,
-	AccountId,
-	Balances,
-	(),
->;
-
-pub struct CurrencyIdConvert;
-impl Convert<AssetIds, Option<MultiLocation>> for CurrencyIdConvert {
-	fn convert(id: AssetIds) -> Option<MultiLocation> {
-		match id {
-			AssetIds::NativeAssetId(NativeCurrency::Here) => Some(MultiLocation::new(
-				1,
-				X1(Parachain(ParachainInfo::get().into())),
-			)),
-			_ => None,
-		}
-	}
-}
modifiedruntime/common/construct_runtime.rsdiffbeforeafterboth
--- a/runtime/common/construct_runtime.rs
+++ b/runtime/common/construct_runtime.rs
@@ -47,7 +47,7 @@
 				Vesting: orml_vesting = 37,
 
 				XTokens: orml_xtokens = 38,
-				Tokens: orml_tokens = 39,
+				// [REMOVED] Tokens: orml_tokens = 39,
 				// Contracts: pallet_contracts::{Pallet, Call, Storage, Event<T>} = 38,
 
 				#[cfg(feature = "governance")]
modifiedruntime/common/dispatch.rsdiffbeforeafterboth
--- a/runtime/common/dispatch.rs
+++ b/runtime/common/dispatch.rs
@@ -17,11 +17,9 @@
 use frame_support::{dispatch::DispatchResult, ensure, fail};
 use pallet_balances_adapter::NativeFungibleHandle;
 pub use pallet_common::dispatch::CollectionDispatch;
-#[cfg(not(feature = "refungible"))]
-use pallet_common::unsupported;
 use pallet_common::{
-	erc::CommonEvmHandler, eth::map_eth_to_id, CollectionById, CollectionHandle,
-	CommonCollectionOperations,
+	erc::CommonEvmHandler, eth::map_eth_to_id, CollectionById, CollectionHandle, CollectionIssuer,
+	CommonCollectionOperations, Pallet as PalletCommon,
 };
 use pallet_evm::{PrecompileHandle, PrecompileResult};
 use pallet_fungible::{FungibleHandle, Pallet as PalletFungible};
@@ -70,27 +68,25 @@
 
 	fn create(
 		sender: T::CrossAccountId,
-		payer: T::CrossAccountId,
+		issuer: CollectionIssuer<T::CrossAccountId>,
 		data: CreateCollectionData<T::CrossAccountId>,
 	) -> Result<CollectionId, DispatchError> {
-		let id = match data.mode {
-			CollectionMode::NFT => <PalletNonfungible<T>>::init_collection(sender, payer, data)?,
+		match data.mode {
 			CollectionMode::Fungible(decimal_points) => {
 				// check params
 				ensure!(
 					decimal_points <= MAX_DECIMAL_POINTS,
 					pallet_unique::Error::<T>::CollectionDecimalPointLimitExceeded
 				);
-				<PalletFungible<T>>::init_collection(sender, payer, data)?
 			}
-
-			#[cfg(feature = "refungible")]
-			CollectionMode::ReFungible => <PalletRefungible<T>>::init_collection(sender, payer, data)?,
 
 			#[cfg(not(feature = "refungible"))]
 			CollectionMode::ReFungible => return unsupported!(T),
+
+			_ => {}
 		};
-		Ok(id)
+
+		<PalletCommon<T>>::init_collection(sender, issuer, data)
 	}
 
 	fn destroy(sender: T::CrossAccountId, collection_id: CollectionId) -> DispatchResult {
modifiedruntime/opal/Cargo.tomldiffbeforeafterboth
--- a/runtime/opal/Cargo.toml
+++ b/runtime/opal/Cargo.toml
@@ -151,7 +151,6 @@
 	'up-rpc/std',
 	'up-sponsorship/std',
 
-	"orml-tokens/std",
 	"orml-traits/std",
 	"orml-vesting/std",
 	"orml-xcm-support/std",
@@ -179,7 +178,6 @@
 	'frame-system/try-runtime',
 	'frame-try-runtime',
 	'frame-try-runtime?/try-runtime',
-	'orml-tokens/try-runtime',
 	'orml-vesting/try-runtime',
 	'orml-xtokens/try-runtime',
 	'pallet-app-promotion/try-runtime',
@@ -254,7 +252,6 @@
 frame-support = { workspace = true }
 frame-system = { workspace = true }
 frame-system-rpc-runtime-api = { workspace = true }
-orml-tokens = { workspace = true }
 orml-traits = { workspace = true }
 orml-vesting = { workspace = true }
 orml-xcm-support = { workspace = true }
modifiedruntime/quartz/Cargo.tomldiffbeforeafterboth
--- a/runtime/quartz/Cargo.toml
+++ b/runtime/quartz/Cargo.toml
@@ -150,7 +150,6 @@
 	'up-rpc/std',
 	'up-sponsorship/std',
 
-	"orml-tokens/std",
 	"orml-traits/std",
 	"orml-vesting/std",
 	"orml-xcm-support/std",
@@ -175,7 +174,6 @@
 	'frame-support/try-runtime',
 	'frame-system/try-runtime',
 	'frame-try-runtime',
-	'orml-tokens/try-runtime',
 	'orml-vesting/try-runtime',
 	'orml-xtokens/try-runtime',
 	'pallet-app-promotion/try-runtime',
@@ -242,7 +240,6 @@
 frame-support = { workspace = true }
 frame-system = { workspace = true }
 frame-system-rpc-runtime-api = { workspace = true }
-orml-tokens = { workspace = true }
 orml-traits = { workspace = true }
 orml-vesting = { workspace = true }
 orml-xcm-support = { workspace = true }
modifiedruntime/tests/src/tests.rsdiffbeforeafterboth
--- a/runtime/tests/src/tests.rs
+++ b/runtime/tests/src/tests.rs
@@ -2357,7 +2357,8 @@
 #[test]
 fn total_number_collections_bound_neg() {
 	new_test_ext().execute_with(|| {
-		let origin1 = RuntimeOrigin::signed(1);
+		let user = 1;
+		let origin1 = RuntimeOrigin::signed(user);
 
 		for i in 1..=COLLECTION_NUMBER_LIMIT {
 			create_test_collection(&CollectionMode::NFT, CollectionId(i));
@@ -2376,6 +2377,7 @@
 		};
 
 		// 11-th collection in chain. Expects error
+		add_balance(user, CollectionCreationPrice::get() as u64 + 1);
 		assert_noop!(
 			Unique::create_collection_ex(origin1, data),
 			CommonError::<Test>::TotalCollectionsLimitExceeded
modifiedruntime/unique/Cargo.tomldiffbeforeafterboth
--- a/runtime/unique/Cargo.toml
+++ b/runtime/unique/Cargo.toml
@@ -147,7 +147,6 @@
 	'up-rpc/std',
 	'up-sponsorship/std',
 
-	"orml-tokens/std",
 	"orml-traits/std",
 	"orml-vesting/std",
 	"orml-xcm-support/std",
@@ -173,7 +172,6 @@
 	'frame-support/try-runtime',
 	'frame-system/try-runtime',
 	'frame-try-runtime',
-	'orml-tokens/try-runtime',
 	'orml-vesting/try-runtime',
 	'orml-xtokens/try-runtime',
 	'pallet-app-promotion/try-runtime',
@@ -245,7 +243,6 @@
 frame-support = { workspace = true }
 frame-system = { workspace = true }
 frame-system-rpc-runtime-api = { workspace = true }
-orml-tokens = { workspace = true }
 orml-traits = { workspace = true }
 orml-vesting = { workspace = true }
 orml-xcm-support = { workspace = true }