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

difftreelog

Merge branch 'develop' into CI-41-forckless-update-parachain-relay-nodata

Alex2022-08-12parents: #b1ec0e6 #e4268af.patch.diff
in: master

155 files changed

modified.docker/Dockerfile-parachaindiffbeforeafterboth
--- a/.docker/Dockerfile-parachain
+++ b/.docker/Dockerfile-parachain
@@ -9,7 +9,6 @@
 ENV PATH="/cargo-home/bin:$PATH"
 
 
-
 RUN apt-get update && \
     apt-get install -y curl cmake pkg-config libssl-dev git clang && \
     apt-get clean && \
@@ -23,11 +22,11 @@
     rustup target list --installed && \
     rustup show
 RUN rustup target add wasm32-unknown-unknown --toolchain $RUST_TOOLCHAIN
-
 
 RUN mkdir /unique_parachain
 WORKDIR /unique_parachain
 
+
 # ===== BUILD ======
 FROM rust-builder as builder-unique
 
@@ -42,6 +41,7 @@
 RUN git clone $REPO_URL -b $BRANCH && \
     cargo build --features=$FEATURE --$PROFILE
 
+
 # ===== BUILD POLKADOT =====
 FROM rust-builder as builder-polkadot
 
@@ -60,7 +60,6 @@
 FROM ubuntu:20.04
 
 RUN apt-get -y update && \
-      apt-get -y upgrade && \
       apt-get -y install curl git && \
       curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash && \
       export NVM_DIR="$HOME/.nvm" && \
added.docker/docker-compose-master.yamldiffbeforeafterboth
--- /dev/null
+++ b/.docker/docker-compose-master.yaml
@@ -0,0 +1,19 @@
+version: "3.5"
+
+services:
+  blockchain_nodes:
+    build:
+      context: ../
+      dockerfile: .docker/Dockerfile-parachain
+    image: blockchain_nodes
+    container_name: blockchain_nodes
+    expose:
+      - 9944
+      - 9933
+    ports:
+      - 127.0.0.1:9944:9944
+      - 127.0.0.1:9933:9933
+    logging:
+      options:
+        max-size: "1m"
+        max-file: "3"
added.docker/docker-compose.tmp-master.j2diffbeforeafterboth
--- /dev/null
+++ b/.docker/docker-compose.tmp-master.j2
@@ -0,0 +1,11 @@
+version: "3.5"
+
+services:
+  blockchain_nodes:
+    build:
+      args:
+        - "RUST_TOOLCHAIN={{ RUST_TOOLCHAIN }}"
+        - "BRANCH={{ BRANCH }}"
+        - "REPO_URL={{ REPO_URL }}"
+        - "FEATURE={{ FEATURE }}"
+        - "POLKADOT_BUILD_BRANCH={{ POLKADOT_BUILD_BRANCH }}"
modified.gitattributesdiffbeforeafterboth
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,2 +1,3 @@
 * text=auto
 *.sh text eol=lf
+*.ts linguist-detectable=false
\ No newline at end of file
added.github/workflows/build-test-master.ymldiffbeforeafterboth
--- /dev/null
+++ b/.github/workflows/build-test-master.yml
@@ -0,0 +1,122 @@
+name: MASTER - Build & Test
+
+# Controls when the action will run.
+on:
+ # Triggers the workflow on push or pull request events but only for the master branch
+  pull_request:
+    branches:
+      - master
+    types:
+      - opened
+      - reopened
+      - synchronize #commit(s) pushed to the pull request
+
+  # 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:
+
+  build:
+    # The type of runner that the job will run on
+    runs-on: self-hosted-ci
+
+
+    name: Build Container, Spin it Up an test
+
+    continue-on-error: true         #Do not stop testing of matrix runs failed.
+
+    strategy:
+      matrix:
+        include:
+          - network: "Opal"
+            features: " "
+          - network: "Quartz"
+            features: "--features=quartz-runtime"
+          - network: "Unique"
+            features: "--features=unique-runtime"
+
+    steps:
+      - name: Skip if pull request is in Draft
+        # `if: github.event.pull_request.draft == true` should be kept here, at
+        # the step level, rather than at the job level. The latter is not
+        # recommended because when the PR is moved from "Draft" to "Ready to
+        # review" the workflow will immediately be passing (since it was skipped),
+        # even though it hasn't actually ran, since it takes a few seconds for
+        # the workflow to start. This is also disclosed in:
+        # https://github.community/t/dont-run-actions-on-draft-pull-requests/16817/17
+        # That scenario would open an opportunity for the check to be bypassed:
+        # 1. Get your PR approved
+        # 2. Move it to Draft
+        # 3. Push whatever commits you want
+        # 4. Move it to "Ready for review"; now the workflow is passing (it was
+        #    skipped) and "Check reviews" is also passing (it won't be updated
+        #    until the workflow is finished)
+        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
+        with:
+          ref: ${{ github.head_ref }}
+
+      - name: Read .env file
+        uses: xom9ikk/dotenv@v1.0.2
+
+      - name: Generate ENV related extend file for docker-compose
+        uses: cuchi/jinja2-action@v1.2.0
+        with:
+          template: .docker/docker-compose.tmp-master.j2
+          output_file: .docker/docker-compose.${{ matrix.network }}.yml
+          variables: |
+            REPO_URL=${{ github.server_url }}/${{ github.repository }}.git
+            RUST_TOOLCHAIN=${{ env.RUST_TOOLCHAIN }}
+            POLKADOT_BUILD_BRANCH=${{ env.POLKADOT_BUILD_BRANCH }}
+            FEATURE=${{ matrix.features }}
+            BRANCH=${{ github.head_ref }}
+
+      - name: Show build configuration
+        run: cat .docker/docker-compose.${{ matrix.network }}.yml
+
+      - name: Build the stack
+        run: docker-compose -f ".docker/docker-compose-master.yml" -f ".docker/docker-compose.${{ matrix.network }}.yml" up -d --build
+
+      - uses: actions/setup-node@v3
+        with:
+          node-version: 16
+
+      - name: Run tests
+        run: |
+          cd tests
+          yarn install
+          yarn add mochawesome
+          echo "Ready to start tests"
+          node scripts/readyness.js
+          NOW=$(date +%s) && yarn test --reporter mochawesome --reporter-options reportFilename=test-${NOW}
+        env:
+          RPC_URL: http://127.0.0.1:9933/
+
+      - name: Test Report
+        uses: phoenix-actions/test-reporting@v8
+        id: test-report
+        if: success() || failure()    # run this step even if previous step failed
+        with:
+          name: Tests ${{ matrix.network }}            # Name of the check run which will be created
+          path: tests/mochawesome-report/test-*.json    # Path to test results
+          reporter: mochawesome-json
+          fail-on-error: 'false'
+
+      - name: Read output variables
+        run: |
+          echo "url is ${{ steps.test-report.outputs.runHtmlUrl }}"
+
+      - name: Stop running containers
+        if: always()                   # run this step always
+        run: docker-compose -f ".docker/docker-compose-master.yml" -f ".docker/docker-compose.${{ matrix.network }}.yml" down
modified.github/workflows/node_build_test.ymldiffbeforeafterboth
--- a/.github/workflows/node_build_test.yml
+++ b/.github/workflows/node_build_test.yml
@@ -32,7 +32,7 @@
       matrix:
         include:
           - network: "Opal"
-            features: "--features=opal-runtime"
+            features: " "
           - network: "Quartz"
             features: "--features=quartz-runtime"
           - network: "Unique"
@@ -57,6 +57,9 @@
         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
         with:
@@ -74,7 +77,7 @@
             REPO_URL=${{ github.server_url }}/${{ github.repository }}.git
             RUST_TOOLCHAIN=${{ env.RUST_TOOLCHAIN }}
             POLKADOT_BUILD_BRANCH=${{ env.POLKADOT_BUILD_BRANCH }}
-            FEATURE='${{ matrix.features }}'
+            FEATURE=${{ matrix.features }}
             BRANCH=${{ github.head_ref }}
 
       - name: Show build configuration
@@ -100,6 +103,7 @@
 
       - name: Test Report
         uses: phoenix-actions/test-reporting@v8
+        id: test-report
         if: success() || failure()    # run this step even if previous step failed
         with:
           name: Tests ${{ matrix.network }}            # Name of the check run which will be created
modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5291,6 +5291,7 @@
  "cumulus-primitives-timestamp",
  "cumulus-primitives-utility",
  "derivative",
+ "evm-coder",
  "fp-evm-mapping",
  "fp-rpc",
  "fp-self-contained",
@@ -5352,9 +5353,10 @@
  "sp-transaction-pool",
  "sp-version",
  "substrate-wasm-builder",
- "unique-runtime-common",
+ "up-common",
  "up-data-structs",
  "up-rpc",
+ "up-sponsorship",
  "xcm",
  "xcm-builder",
  "xcm-executor",
@@ -8605,6 +8607,7 @@
  "cumulus-primitives-timestamp",
  "cumulus-primitives-utility",
  "derivative",
+ "evm-coder",
  "fp-evm-mapping",
  "fp-rpc",
  "fp-self-contained",
@@ -8666,9 +8669,10 @@
  "sp-transaction-pool",
  "sp-version",
  "substrate-wasm-builder",
- "unique-runtime-common",
+ "up-common",
  "up-data-structs",
  "up-rpc",
+ "up-sponsorship",
  "xcm",
  "xcm-builder",
  "xcm-executor",
@@ -11881,6 +11885,7 @@
 name = "tests"
 version = "0.1.0"
 dependencies = [
+ "evm-coder",
  "fp-evm-mapping",
  "frame-support",
  "frame-system",
@@ -11902,8 +11907,8 @@
  "sp-io",
  "sp-runtime",
  "sp-std",
- "unique-runtime-common",
  "up-data-structs",
+ "up-sponsorship",
 ]
 
 [[package]]
@@ -12387,7 +12392,6 @@
  "sp-core",
  "sp-rpc",
  "sp-runtime",
- "unique-runtime-common",
  "up-data-structs",
  "up-rpc",
 ]
@@ -12541,7 +12545,7 @@
  "try-runtime-cli",
  "unique-rpc",
  "unique-runtime",
- "unique-runtime-common",
+ "up-common",
  "up-data-structs",
  "up-rpc",
 ]
@@ -12590,7 +12594,7 @@
  "substrate-frame-rpc-system",
  "tokio 0.2.25",
  "uc-rpc",
- "unique-runtime-common",
+ "up-common",
  "up-data-structs",
  "up-rpc",
 ]
@@ -12608,6 +12612,7 @@
  "cumulus-primitives-timestamp",
  "cumulus-primitives-utility",
  "derivative",
+ "evm-coder",
  "fp-evm-mapping",
  "fp-rpc",
  "fp-self-contained",
@@ -12669,41 +12674,16 @@
  "sp-transaction-pool",
  "sp-version",
  "substrate-wasm-builder",
- "unique-runtime-common",
+ "up-common",
  "up-data-structs",
  "up-rpc",
+ "up-sponsorship",
  "xcm",
  "xcm-builder",
  "xcm-executor",
 ]
 
 [[package]]
-name = "unique-runtime-common"
-version = "0.9.24"
-dependencies = [
- "evm-coder",
- "fp-rpc",
- "frame-support",
- "frame-system",
- "pallet-common",
- "pallet-evm",
- "pallet-fungible",
- "pallet-nonfungible",
- "pallet-refungible",
- "pallet-unique",
- "pallet-unique-scheduler",
- "parity-scale-codec 3.1.5",
- "rmrk-rpc",
- "scale-info",
- "sp-consensus-aura",
- "sp-core",
- "sp-runtime",
- "sp-std",
- "up-data-structs",
- "up-sponsorship",
-]
-
-[[package]]
 name = "universal-hash"
 version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -12732,6 +12712,19 @@
 checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
 
 [[package]]
+name = "up-common"
+version = "0.9.24"
+dependencies = [
+ "fp-rpc",
+ "frame-support",
+ "pallet-evm",
+ "sp-consensus-aura",
+ "sp-core",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
 name = "up-data-structs"
 version = "0.2.1"
 dependencies = [
modifiedREADME.mddiffbeforeafterboth
--- a/README.md
+++ b/README.md
@@ -23,8 +23,8 @@
 
 Wider Unique Ecosystem (most of it was developed during Hackusama):
 
--   [SubstraPunks Game hosted on IPFS](https://github.com/usetech-llc/substrapunks)
--   [Unique Wallet and UI](https://uniqueapps.usetech.com/#/nft)
+-   [SubstraPunks Game hosted on IPFS](https://github.com/UniqueNetwork/substrapunks)
+-   [Unique Wallet and UI](https://wallet.unique.network)
 -   [NFT Asset for Unity Framework](https://github.com/usetech-llc/nft_unity)
 
 Please see our [walk-through instructions](doc/hackusama_walk_through.md) to try everything out!
modifiedclient/rpc/Cargo.tomldiffbeforeafterboth
--- a/client/rpc/Cargo.toml
+++ b/client/rpc/Cargo.toml
@@ -5,7 +5,6 @@
 edition = "2021"
 
 [dependencies]
-unique-runtime-common = { default-features = false, path = "../../runtime/common" }
 pallet-common = { default-features = false, path = '../../pallets/common' }
 up-data-structs = { default-features = false, path = '../../primitives/data-structs' }
 up-rpc = { path = "../../primitives/rpc" }
modifiednode/cli/Cargo.tomldiffbeforeafterboth
--- a/node/cli/Cargo.toml
+++ b/node/cli/Cargo.toml
@@ -253,9 +253,8 @@
 ################################################################################
 # Local dependencies
 
-[dependencies.unique-runtime-common]
-default-features = false
-path = "../../runtime/common"
+[dependencies.up-common]
+path = "../../primitives/common"
 
 [dependencies.unique-runtime]
 path = '../../runtime/unique'
modifiednode/cli/src/chain_spec.rsdiffbeforeafterboth
--- a/node/cli/src/chain_spec.rs
+++ b/node/cli/src/chain_spec.rs
@@ -23,7 +23,7 @@
 use serde::{Deserialize, Serialize};
 use serde_json::map::Map;
 
-use unique_runtime_common::types::*;
+use up_common::types::opaque::*;
 
 #[cfg(feature = "unique-runtime")]
 pub use unique_runtime as default_runtime;
modifiednode/cli/src/command.rsdiffbeforeafterboth
--- a/node/cli/src/command.rs
+++ b/node/cli/src/command.rs
@@ -64,7 +64,7 @@
 use sp_runtime::traits::{AccountIdConversion, Block as BlockT};
 use std::{io::Write, net::SocketAddr, time::Duration};
 
-use unique_runtime_common::types::Block;
+use up_common::types::opaque::Block;
 
 macro_rules! no_runtime_err {
 	($chain_name:expr) => {
modifiednode/cli/src/service.rsdiffbeforeafterboth
--- a/node/cli/src/service.rs
+++ b/node/cli/src/service.rs
@@ -63,7 +63,7 @@
 use fc_rpc_core::types::FilterPool;
 use fc_mapping_sync::{MappingSyncWorker, SyncStrategy};
 
-use unique_runtime_common::types::{AuraId, RuntimeInstance, AccountId, Balance, Index, Hash, Block};
+use up_common::types::opaque::{AuraId, RuntimeInstance, AccountId, Balance, Index, Hash, Block};
 
 // RMRK
 use up_data_structs::{
modifiednode/rpc/Cargo.tomldiffbeforeafterboth
--- a/node/rpc/Cargo.toml
+++ b/node/rpc/Cargo.toml
@@ -49,7 +49,7 @@
 fc-mapping-sync = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" }
 
 pallet-common = { default-features = false, path = "../../pallets/common" }
-unique-runtime-common = { default-features = false, path = "../../runtime/common" }
+up-common = { path = "../../primitives/common" }
 pallet-unique = { path = "../../pallets/unique" }
 uc-rpc = { path = "../../client/rpc" }
 up-rpc = { path = "../../primitives/rpc" }
modifiednode/rpc/src/lib.rsdiffbeforeafterboth
--- a/node/rpc/src/lib.rs
+++ b/node/rpc/src/lib.rs
@@ -40,9 +40,8 @@
 use sc_service::TransactionPool;
 use std::{collections::BTreeMap, sync::Arc};
 
-use unique_runtime_common::types::{
-	Hash, AccountId, RuntimeInstance, Index, Block, BlockNumber, Balance,
-};
+use up_common::types::opaque::{Hash, AccountId, RuntimeInstance, Index, Block, BlockNumber, Balance};
+
 // RMRK
 use up_data_structs::{
 	RmrkCollectionInfo, RmrkInstanceInfo, RmrkResourceInfo, RmrkPropertyInfo, RmrkBaseInfo,
modifiedpallets/common/src/erc.rsdiffbeforeafterboth
--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -25,7 +25,8 @@
 use pallet_evm_coder_substrate::dispatch_to_evm;
 use sp_std::vec::Vec;
 use up_data_structs::{
-	Property, SponsoringRateLimit, OwnerRestrictedSet, AccessMode, CollectionPermissions,
+	AccessMode, CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property,
+	SponsoringRateLimit,
 };
 use alloc::format;
 
@@ -408,6 +409,28 @@
 
 		save(self)
 	}
+
+	/// Check that account is the owner or admin of the collection
+	///
+	/// @param user account to verify
+	/// @return "true" if account is the owner or admin
+	fn verify_owner_or_admin(&self, user: address) -> Result<bool> {
+		Ok(check_is_owner_or_admin(user, self)
+			.map(|_| true)
+			.unwrap_or(false))
+	}
+
+	/// Returns collection type
+	///
+	/// @return `Fungible` or `NFT` or `ReFungible`
+	fn unique_collection_type(&mut self) -> Result<string> {
+		let mode = match self.collection.mode {
+			CollectionMode::Fungible(_) => "Fungible",
+			CollectionMode::NFT => "NFT",
+			CollectionMode::ReFungible => "ReFungible",
+		};
+		Ok(mode.into())
+	}
 }
 
 fn check_is_owner_or_admin<T: Config>(
@@ -462,6 +485,11 @@
 		pub fn suffix() -> up_data_structs::PropertyKey {
 			property_key_from_bytes(b"suffix").expect(EXPECT_CONVERT_ERROR)
 		}
+
+		/// Key "parentNft".
+		pub fn parent_nft() -> up_data_structs::PropertyKey {
+			property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)
+		}
 	}
 
 	/// Values.
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -535,7 +535,8 @@
 
 		/// Can't transfer tokens to ethereum zero address
 		AddressIsZero,
-		/// Target collection doesn't support this operation
+
+		/// The oprtation is not supported
 		UnsupportedOperation,
 
 		/// Insufficient funds to perform an action
@@ -1112,6 +1113,26 @@
 		sender: &T::CrossAccountId,
 		property_permission: PropertyKeyPermission,
 	) -> DispatchResult {
+		Self::set_scoped_property_permission(
+			collection,
+			sender,
+			PropertyScope::None,
+			property_permission,
+		)
+	}
+
+	/// Set collection property permission with scope.
+	///
+	/// * `collection` - Collection handler.
+	/// * `sender` - The owner or administrator of the collection.
+	/// * `scope` - Property scope.
+	/// * `property_permission` - Property permission.
+	pub fn set_scoped_property_permission(
+		collection: &CollectionHandle<T>,
+		sender: &T::CrossAccountId,
+		scope: PropertyScope,
+		property_permission: PropertyKeyPermission,
+	) -> DispatchResult {
 		collection.check_is_owner_or_admin(sender)?;
 
 		let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);
@@ -1125,7 +1146,11 @@
 
 		CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {
 			let property_permission = property_permission.clone();
-			permissions.try_set(property_permission.key, property_permission.permission)
+			permissions.try_scoped_set(
+				scope,
+				property_permission.key,
+				property_permission.permission,
+			)
 		})
 		.map_err(<Error<T>>::from)?;
 
@@ -1148,8 +1173,29 @@
 		sender: &T::CrossAccountId,
 		property_permissions: Vec<PropertyKeyPermission>,
 	) -> DispatchResult {
+		Self::set_scoped_token_property_permissions(
+			collection,
+			sender,
+			PropertyScope::None,
+			property_permissions,
+		)
+	}
+
+	/// Set token property permission with scope.
+	///
+	/// * `collection` - Collection handler.
+	/// * `sender` - The owner or administrator of the collection.
+	/// * `scope` - Property scope.
+	/// * `property_permissions` - Property permissions.
+	#[transactional]
+	pub fn set_scoped_token_property_permissions(
+		collection: &CollectionHandle<T>,
+		sender: &T::CrossAccountId,
+		scope: PropertyScope,
+		property_permissions: Vec<PropertyKeyPermission>,
+	) -> DispatchResult {
 		for prop_pemission in property_permissions {
-			Self::set_property_permission(collection, sender, prop_pemission)?;
+			Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;
 		}
 
 		Ok(())
@@ -1353,8 +1399,8 @@
 /// Indicates unsupported methods by returning [Error::UnsupportedOperation].
 #[macro_export]
 macro_rules! unsupported {
-	() => {
-		Err(<Error<T>>::UnsupportedOperation.into())
+	($runtime:path) => {
+		Err($crate::Error::<$runtime>::UnsupportedOperation.into())
 	};
 }
 
@@ -1429,6 +1475,9 @@
 			.saturating_mul(max_selfs.max(1) as u64)
 			.saturating_add(Self::burn_recursively_breadth_raw(max_breadth))
 	}
+
+	/// The price of retrieving token owner
+	fn token_owner() -> Weight;
 }
 
 /// Weight info extension trait for refungible pallet.
@@ -1567,7 +1616,7 @@
 	///
 	/// * `sender` - Must be either the owner of the token or its admin.
 	/// * `token_id` - The token for which the properties are being set.
-	/// * `properties` - Properties to be set.
+	/// * `property_permissions` - Property permissions to be set.
 	/// * `budget` - Budget for setting properties.
 	fn set_token_property_permissions(
 		&self,
modifiedpallets/fungible/src/common.rsdiffbeforeafterboth
--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -103,6 +103,10 @@
 		// Fungible tokens can't have children
 		0
 	}
+
+	fn token_owner() -> Weight {
+		0
+	}
 }
 
 /// Implementation of `CommonCollectionOperations` for `FungibleHandle`. It wraps FungibleHandle Pallete
modifiedpallets/fungible/src/stubs/UniqueFungible.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/fungible/src/stubs/UniqueFungible.soldiffbeforeafterboth
--- a/pallets/fungible/src/stubs/UniqueFungible.sol
+++ b/pallets/fungible/src/stubs/UniqueFungible.sol
@@ -31,19 +31,7 @@
 	);
 }
 
-// Selector: 79cc6790
-contract ERC20UniqueExtensions is Dummy, ERC165 {
-	// Selector: burnFrom(address,uint256) 79cc6790
-	function burnFrom(address from, uint256 amount) public returns (bool) {
-		require(false, stub_error);
-		from;
-		amount;
-		dummy = 0;
-		return false;
-	}
-}
-
-// Selector: 7d9262e6
+// Selector: 6cf113cd
 contract Collection is Dummy, ERC165 {
 	// Set collection property.
 	//
@@ -268,6 +256,42 @@
 		mode;
 		dummy = 0;
 	}
+
+	// Check that account is the owner or admin of the collection
+	//
+	// @param user account to verify
+	// @return "true" if account is the owner or admin
+	//
+	// Selector: verifyOwnerOrAdmin(address) c2282493
+	function verifyOwnerOrAdmin(address user) public view returns (bool) {
+		require(false, stub_error);
+		user;
+		dummy;
+		return false;
+	}
+
+	// Returns collection type
+	//
+	// @return `Fungible` or `NFT` or `ReFungible`
+	//
+	// Selector: uniqueCollectionType() d34b55b8
+	function uniqueCollectionType() public returns (string memory) {
+		require(false, stub_error);
+		dummy = 0;
+		return "";
+	}
+}
+
+// Selector: 79cc6790
+contract ERC20UniqueExtensions is Dummy, ERC165 {
+	// Selector: burnFrom(address,uint256) 79cc6790
+	function burnFrom(address from, uint256 amount) public returns (bool) {
+		require(false, stub_error);
+		from;
+		amount;
+		dummy = 0;
+		return false;
+	}
 }
 
 // Selector: 942e8b22
modifiedpallets/nonfungible/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/benchmarking.rs
+++ b/pallets/nonfungible/src/benchmarking.rs
@@ -17,11 +17,14 @@
 use super::*;
 use crate::{Pallet, Config, NonfungibleHandle};
 
-use sp_std::prelude::*;
-use pallet_common::benchmarking::{create_collection_raw, property_key, property_value};
 use frame_benchmarking::{benchmarks, account};
+use pallet_common::{
+	bench_init,
+	benchmarking::{create_collection_raw, property_key, property_value},
+	CommonCollectionOperations,
+};
+use sp_std::prelude::*;
 use up_data_structs::{CollectionMode, MAX_ITEMS_PER_BATCH, MAX_PROPERTIES_PER_ITEM, budget::Unlimited};
-use pallet_common::bench_init;
 
 const SEED: u32 = 1;
 
@@ -208,4 +211,13 @@
 		<Pallet<T>>::set_token_properties(&collection, &owner, item, props.into_iter(), false, &Unlimited)?;
 		let to_delete = (0..b).map(|k| property_key(k as usize)).collect::<Vec<_>>();
 	}: {<Pallet<T>>::delete_token_properties(&collection, &owner, item, to_delete.into_iter(), &Unlimited)?}
+
+	token_owner {
+		bench_init!{
+			owner: sub; collection: collection(owner);
+			owner: cross_from_sub;
+		};
+		let item = create_max_item(&collection, &owner, owner.clone())?;
+
+	}: {collection.token_owner(item)}
 }
modifiedpallets/nonfungible/src/common.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -118,6 +118,10 @@
 		<SelfWeightOf<T>>::burn_recursively_breadth_plus_self_plus_self_per_each_raw(amount)
 			.saturating_sub(Self::burn_recursively_self_raw().saturating_mul(amount as u64 + 1))
 	}
+
+	fn token_owner() -> Weight {
+		<SelfWeightOf<T>>::token_owner()
+	}
 }
 
 fn map_create_data<T: Config>(
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -784,6 +784,23 @@
 		<PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)
 	}
 
+	/// Set property permissions for the token with scope.
+	///
+	/// Sender should be the owner or admin of token's collection.
+	pub fn set_scoped_token_property_permissions(
+		collection: &CollectionHandle<T>,
+		sender: &T::CrossAccountId,
+		scope: PropertyScope,
+		property_permissions: Vec<PropertyKeyPermission>,
+	) -> DispatchResult {
+		<PalletCommon<T>>::set_scoped_token_property_permissions(
+			collection,
+			sender,
+			scope,
+			property_permissions,
+		)
+	}
+
 	/// Set property permissions for the collection.
 	///
 	/// Sender should be the owner or admin of the collection.
modifiedpallets/nonfungible/src/stubs/UniqueNFT.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth
--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -373,49 +373,7 @@
 	}
 }
 
-// Selector: 780e9d63
-contract ERC721Enumerable is Dummy, ERC165 {
-	// @notice Enumerate valid NFTs
-	// @param index A counter less than `totalSupply()`
-	// @return The token identifier for the `index`th NFT,
-	//  (sort order not specified)
-	//
-	// Selector: tokenByIndex(uint256) 4f6ccce7
-	function tokenByIndex(uint256 index) public view returns (uint256) {
-		require(false, stub_error);
-		index;
-		dummy;
-		return 0;
-	}
-
-	// @dev Not implemented
-	//
-	// Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59
-	function tokenOfOwnerByIndex(address owner, uint256 index)
-		public
-		view
-		returns (uint256)
-	{
-		require(false, stub_error);
-		owner;
-		index;
-		dummy;
-		return 0;
-	}
-
-	// @notice Count NFTs tracked by this contract
-	// @return A count of valid NFTs tracked by this contract, where each one of
-	//  them has an assigned and queryable owner not equal to the zero address
-	//
-	// Selector: totalSupply() 18160ddd
-	function totalSupply() public view returns (uint256) {
-		require(false, stub_error);
-		dummy;
-		return 0;
-	}
-}
-
-// Selector: 7d9262e6
+// Selector: 6cf113cd
 contract Collection is Dummy, ERC165 {
 	// Set collection property.
 	//
@@ -640,6 +598,72 @@
 		mode;
 		dummy = 0;
 	}
+
+	// Check that account is the owner or admin of the collection
+	//
+	// @param user account to verify
+	// @return "true" if account is the owner or admin
+	//
+	// Selector: verifyOwnerOrAdmin(address) c2282493
+	function verifyOwnerOrAdmin(address user) public view returns (bool) {
+		require(false, stub_error);
+		user;
+		dummy;
+		return false;
+	}
+
+	// Returns collection type
+	//
+	// @return `Fungible` or `NFT` or `ReFungible`
+	//
+	// Selector: uniqueCollectionType() d34b55b8
+	function uniqueCollectionType() public returns (string memory) {
+		require(false, stub_error);
+		dummy = 0;
+		return "";
+	}
+}
+
+// Selector: 780e9d63
+contract ERC721Enumerable is Dummy, ERC165 {
+	// @notice Enumerate valid NFTs
+	// @param index A counter less than `totalSupply()`
+	// @return The token identifier for the `index`th NFT,
+	//  (sort order not specified)
+	//
+	// Selector: tokenByIndex(uint256) 4f6ccce7
+	function tokenByIndex(uint256 index) public view returns (uint256) {
+		require(false, stub_error);
+		index;
+		dummy;
+		return 0;
+	}
+
+	// @dev Not implemented
+	//
+	// Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59
+	function tokenOfOwnerByIndex(address owner, uint256 index)
+		public
+		view
+		returns (uint256)
+	{
+		require(false, stub_error);
+		owner;
+		index;
+		dummy;
+		return 0;
+	}
+
+	// @notice Count NFTs tracked by this contract
+	// @return A count of valid NFTs tracked by this contract, where each one of
+	//  them has an assigned and queryable owner not equal to the zero address
+	//
+	// Selector: totalSupply() 18160ddd
+	function totalSupply() public view returns (uint256) {
+		require(false, stub_error);
+		dummy;
+		return 0;
+	}
 }
 
 // Selector: d74d154f
modifiedpallets/nonfungible/src/weights.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/weights.rs
+++ b/pallets/nonfungible/src/weights.rs
@@ -3,7 +3,7 @@
 //! Autogenerated weights for pallet_nonfungible
 //!
 //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
-//! DATE: 2022-07-20, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! DATE: 2022-08-01, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]`
 //! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024
 
 // Executed Command:
@@ -46,6 +46,7 @@
 	fn set_token_property_permissions(b: u32, ) -> Weight;
 	fn set_token_properties(b: u32, ) -> Weight;
 	fn delete_token_properties(b: u32, ) -> Weight;
+	fn token_owner() -> Weight;
 }
 
 /// Weights for pallet_nonfungible using the Substrate node and recommended hardware.
@@ -56,7 +57,7 @@
 	// Storage: Nonfungible TokenData (r:0 w:1)
 	// Storage: Nonfungible Owned (r:0 w:1)
 	fn create_item() -> Weight {
-		(20_328_000 as Weight)
+		(20_909_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes(4 as Weight))
 	}
@@ -65,9 +66,9 @@
 	// Storage: Nonfungible TokenData (r:0 w:4)
 	// Storage: Nonfungible Owned (r:0 w:4)
 	fn create_multiple_items(b: u32, ) -> Weight {
-		(10_134_000 as Weight)
-			// Standard Error: 3_000
-			.saturating_add((4_927_000 as Weight).saturating_mul(b as Weight))
+		(12_601_000 as Weight)
+			// Standard Error: 1_000
+			.saturating_add((4_920_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(T::DbWeight::get().reads(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(b as Weight)))
@@ -77,9 +78,9 @@
 	// Storage: Nonfungible TokenData (r:0 w:4)
 	// Storage: Nonfungible Owned (r:0 w:4)
 	fn create_multiple_items_ex(b: u32, ) -> Weight {
-		(5_710_000 as Weight)
-			// Standard Error: 4_000
-			.saturating_add((7_578_000 as Weight).saturating_mul(b as Weight))
+		(0 as Weight)
+			// Standard Error: 3_000
+			.saturating_add((7_734_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(T::DbWeight::get().reads(1 as Weight))
 			.saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
@@ -93,7 +94,7 @@
 	// Storage: Nonfungible Owned (r:0 w:1)
 	// Storage: Nonfungible TokenProperties (r:0 w:1)
 	fn burn_item() -> Weight {
-		(28_433_000 as Weight)
+		(29_746_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(5 as Weight))
 			.saturating_add(T::DbWeight::get().writes(5 as Weight))
 	}
@@ -105,7 +106,7 @@
 	// Storage: Nonfungible Owned (r:0 w:1)
 	// Storage: Nonfungible TokenProperties (r:0 w:1)
 	fn burn_recursively_self_raw() -> Weight {
-		(34_435_000 as Weight)
+		(36_077_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(5 as Weight))
 			.saturating_add(T::DbWeight::get().writes(5 as Weight))
 	}
@@ -119,8 +120,8 @@
 	// Storage: Common CollectionById (r:1 w:0)
 	fn burn_recursively_breadth_plus_self_plus_self_per_each_raw(b: u32, ) -> Weight {
 		(0 as Weight)
-			// Standard Error: 1_539_000
-			.saturating_add((304_456_000 as Weight).saturating_mul(b as Weight))
+			// Standard Error: 1_605_000
+			.saturating_add((312_391_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(T::DbWeight::get().reads(7 as Weight))
 			.saturating_add(T::DbWeight::get().reads((4 as Weight).saturating_mul(b as Weight)))
 			.saturating_add(T::DbWeight::get().writes(6 as Weight))
@@ -131,14 +132,14 @@
 	// Storage: Nonfungible Allowance (r:1 w:0)
 	// Storage: Nonfungible Owned (r:0 w:2)
 	fn transfer() -> Weight {
-		(24_376_000 as Weight)
+		(25_248_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(4 as Weight))
 			.saturating_add(T::DbWeight::get().writes(5 as Weight))
 	}
 	// Storage: Nonfungible TokenData (r:1 w:0)
 	// Storage: Nonfungible Allowance (r:1 w:1)
 	fn approve() -> Weight {
-		(15_890_000 as Weight)
+		(16_321_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
@@ -147,7 +148,7 @@
 	// Storage: Nonfungible AccountBalance (r:2 w:2)
 	// Storage: Nonfungible Owned (r:0 w:2)
 	fn transfer_from() -> Weight {
-		(28_634_000 as Weight)
+		(29_325_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(4 as Weight))
 			.saturating_add(T::DbWeight::get().writes(6 as Weight))
 	}
@@ -159,15 +160,15 @@
 	// Storage: Nonfungible Owned (r:0 w:1)
 	// Storage: Nonfungible TokenProperties (r:0 w:1)
 	fn burn_from() -> Weight {
-		(32_201_000 as Weight)
+		(33_323_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(5 as Weight))
 			.saturating_add(T::DbWeight::get().writes(6 as Weight))
 	}
 	// Storage: Common CollectionPropertyPermissions (r:1 w:1)
 	fn set_token_property_permissions(b: u32, ) -> Weight {
 		(0 as Weight)
-			// Standard Error: 57_000
-			.saturating_add((15_232_000 as Weight).saturating_mul(b as Weight))
+			// Standard Error: 62_000
+			.saturating_add((16_222_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(T::DbWeight::get().reads(1 as Weight))
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
@@ -175,8 +176,8 @@
 	// Storage: Nonfungible TokenProperties (r:1 w:1)
 	fn set_token_properties(b: u32, ) -> Weight {
 		(0 as Weight)
-			// Standard Error: 1_648_000
-			.saturating_add((288_654_000 as Weight).saturating_mul(b as Weight))
+			// Standard Error: 1_750_000
+			.saturating_add((304_476_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(T::DbWeight::get().reads(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
@@ -184,11 +185,16 @@
 	// Storage: Nonfungible TokenProperties (r:1 w:1)
 	fn delete_token_properties(b: u32, ) -> Weight {
 		(0 as Weight)
-			// Standard Error: 1_632_000
-			.saturating_add((289_190_000 as Weight).saturating_mul(b as Weight))
+			// Standard Error: 1_638_000
+			.saturating_add((294_096_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(T::DbWeight::get().reads(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
+	// Storage: Nonfungible TokenData (r:1 w:0)
+	fn token_owner() -> Weight {
+		(2_986_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(1 as Weight))
+	}
 }
 
 // For backwards compatibility and tests
@@ -198,7 +204,7 @@
 	// Storage: Nonfungible TokenData (r:0 w:1)
 	// Storage: Nonfungible Owned (r:0 w:1)
 	fn create_item() -> Weight {
-		(20_328_000 as Weight)
+		(20_909_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(4 as Weight))
 	}
@@ -207,9 +213,9 @@
 	// Storage: Nonfungible TokenData (r:0 w:4)
 	// Storage: Nonfungible Owned (r:0 w:4)
 	fn create_multiple_items(b: u32, ) -> Weight {
-		(10_134_000 as Weight)
-			// Standard Error: 3_000
-			.saturating_add((4_927_000 as Weight).saturating_mul(b as Weight))
+		(12_601_000 as Weight)
+			// Standard Error: 1_000
+			.saturating_add((4_920_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes((2 as Weight).saturating_mul(b as Weight)))
@@ -219,9 +225,9 @@
 	// Storage: Nonfungible TokenData (r:0 w:4)
 	// Storage: Nonfungible Owned (r:0 w:4)
 	fn create_multiple_items_ex(b: u32, ) -> Weight {
-		(5_710_000 as Weight)
-			// Standard Error: 4_000
-			.saturating_add((7_578_000 as Weight).saturating_mul(b as Weight))
+		(0 as Weight)
+			// Standard Error: 3_000
+			.saturating_add((7_734_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
 			.saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
@@ -235,7 +241,7 @@
 	// Storage: Nonfungible Owned (r:0 w:1)
 	// Storage: Nonfungible TokenProperties (r:0 w:1)
 	fn burn_item() -> Weight {
-		(28_433_000 as Weight)
+		(29_746_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(5 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(5 as Weight))
 	}
@@ -247,7 +253,7 @@
 	// Storage: Nonfungible Owned (r:0 w:1)
 	// Storage: Nonfungible TokenProperties (r:0 w:1)
 	fn burn_recursively_self_raw() -> Weight {
-		(34_435_000 as Weight)
+		(36_077_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(5 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(5 as Weight))
 	}
@@ -261,8 +267,8 @@
 	// Storage: Common CollectionById (r:1 w:0)
 	fn burn_recursively_breadth_plus_self_plus_self_per_each_raw(b: u32, ) -> Weight {
 		(0 as Weight)
-			// Standard Error: 1_539_000
-			.saturating_add((304_456_000 as Weight).saturating_mul(b as Weight))
+			// Standard Error: 1_605_000
+			.saturating_add((312_391_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(RocksDbWeight::get().reads(7 as Weight))
 			.saturating_add(RocksDbWeight::get().reads((4 as Weight).saturating_mul(b as Weight)))
 			.saturating_add(RocksDbWeight::get().writes(6 as Weight))
@@ -273,14 +279,14 @@
 	// Storage: Nonfungible Allowance (r:1 w:0)
 	// Storage: Nonfungible Owned (r:0 w:2)
 	fn transfer() -> Weight {
-		(24_376_000 as Weight)
+		(25_248_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(4 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(5 as Weight))
 	}
 	// Storage: Nonfungible TokenData (r:1 w:0)
 	// Storage: Nonfungible Allowance (r:1 w:1)
 	fn approve() -> Weight {
-		(15_890_000 as Weight)
+		(16_321_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}
@@ -289,7 +295,7 @@
 	// Storage: Nonfungible AccountBalance (r:2 w:2)
 	// Storage: Nonfungible Owned (r:0 w:2)
 	fn transfer_from() -> Weight {
-		(28_634_000 as Weight)
+		(29_325_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(4 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(6 as Weight))
 	}
@@ -301,15 +307,15 @@
 	// Storage: Nonfungible Owned (r:0 w:1)
 	// Storage: Nonfungible TokenProperties (r:0 w:1)
 	fn burn_from() -> Weight {
-		(32_201_000 as Weight)
+		(33_323_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(5 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(6 as Weight))
 	}
 	// Storage: Common CollectionPropertyPermissions (r:1 w:1)
 	fn set_token_property_permissions(b: u32, ) -> Weight {
 		(0 as Weight)
-			// Standard Error: 57_000
-			.saturating_add((15_232_000 as Weight).saturating_mul(b as Weight))
+			// Standard Error: 62_000
+			.saturating_add((16_222_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}
@@ -317,8 +323,8 @@
 	// Storage: Nonfungible TokenProperties (r:1 w:1)
 	fn set_token_properties(b: u32, ) -> Weight {
 		(0 as Weight)
-			// Standard Error: 1_648_000
-			.saturating_add((288_654_000 as Weight).saturating_mul(b as Weight))
+			// Standard Error: 1_750_000
+			.saturating_add((304_476_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}
@@ -326,9 +332,14 @@
 	// Storage: Nonfungible TokenProperties (r:1 w:1)
 	fn delete_token_properties(b: u32, ) -> Weight {
 		(0 as Weight)
-			// Standard Error: 1_632_000
-			.saturating_add((289_190_000 as Weight).saturating_mul(b as Weight))
+			// Standard Error: 1_638_000
+			.saturating_add((294_096_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}
+	// Storage: Nonfungible TokenData (r:1 w:0)
+	fn token_owner() -> Weight {
+		(2_986_000 as Weight)
+			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
+	}
 }
modifiedpallets/refungible/src/benchmarking.rsdiffbeforeafterboth
--- a/pallets/refungible/src/benchmarking.rs
+++ b/pallets/refungible/src/benchmarking.rs
@@ -17,25 +17,26 @@
 use super::*;
 use crate::{Pallet, Config, RefungibleHandle};
 
+use core::convert::TryInto;
+use core::iter::IntoIterator;
+use frame_benchmarking::{benchmarks, account};
+use pallet_common::{
+	bench_init,
+	benchmarking::{create_collection_raw, property_key, property_value, create_data},
+};
+use sp_core::H160;
 use sp_std::prelude::*;
-use pallet_common::benchmarking::{create_collection_raw, property_key, property_value, create_data};
-use frame_benchmarking::{benchmarks, account};
 use up_data_structs::{
 	CollectionMode, MAX_ITEMS_PER_BATCH, MAX_PROPERTIES_PER_ITEM, CUSTOM_DATA_LIMIT,
 	budget::Unlimited,
 };
-use pallet_common::bench_init;
-use core::convert::TryInto;
-use core::iter::IntoIterator;
 
 const SEED: u32 = 1;
 
 fn create_max_item_data<CrossAccountId: Ord>(
 	users: impl IntoIterator<Item = (CrossAccountId, u128)>,
 ) -> CreateRefungibleExData<CrossAccountId> {
-	let const_data = create_data::<CUSTOM_DATA_LIMIT>();
 	CreateRefungibleExData {
-		const_data,
 		users: users
 			.into_iter()
 			.collect::<BTreeMap<_, _>>()
@@ -44,6 +45,7 @@
 		properties: Default::default(),
 	}
 }
+
 fn create_max_item<T: Config>(
 	collection: &RefungibleHandle<T>,
 	sender: &T::CrossAccountId,
@@ -59,11 +61,12 @@
 ) -> Result<RefungibleHandle<T>, DispatchError> {
 	create_collection_raw(
 		owner,
-		CollectionMode::NFT,
+		CollectionMode::ReFungible,
 		<Pallet<T>>::init_collection,
 		RefungibleHandle::cast,
 	)
 }
+
 benchmarks! {
 	create_item {
 		bench_init!{
@@ -277,4 +280,21 @@
 		};
 		let item = create_max_item(&collection, &sender, [(owner.clone(), 100)])?;
 	}: {<Pallet<T>>::repartition(&collection, &owner, item, 200)?}
+
+	set_parent_nft_unchecked {
+		bench_init!{
+			owner: sub; collection: collection(owner);
+			sender: cross_from_sub(owner); owner: cross_sub;
+		};
+		let item = create_max_item(&collection, &sender, [(owner.clone(), 100)])?;
+
+	}: {<Pallet<T>>::set_parent_nft_unchecked(&collection, item, owner,  T::CrossAccountId::from_eth(H160::default()))?}
+
+	token_owner {
+		bench_init!{
+			owner: sub; collection: collection(owner);
+			sender: cross_from_sub(owner); owner: cross_sub;
+		};
+		let item = create_max_item(&collection, &sender, [(owner.clone(), 100)])?;
+	}: {<Pallet<T>>::token_owner(collection.id, item)}
 }
modifiedpallets/refungible/src/common.rsdiffbeforeafterboth
--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -148,6 +148,10 @@
 		// Refungible token can't have children
 		0
 	}
+
+	fn token_owner() -> Weight {
+		<SelfWeightOf<T>>::token_owner()
+	}
 }
 
 fn map_create_data<T: Config>(
modifiedpallets/refungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -41,8 +41,8 @@
 use sp_core::H160;
 use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec};
 use up_data_structs::{
-	CollectionId, CollectionPropertiesVec, Property, PropertyKey, PropertyKeyPermission,
-	PropertyPermission, TokenId,
+	CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey,
+	PropertyKeyPermission, PropertyPermission, TokenId,
 };
 
 use crate::{
@@ -413,7 +413,7 @@
 }
 
 /// Returns amount of pieces of `token` that `owner` have
-fn balance<T: Config>(
+pub fn balance<T: Config>(
 	collection: &RefungibleHandle<T>,
 	token: TokenId,
 	owner: &T::CrossAccountId,
@@ -424,7 +424,7 @@
 }
 
 /// Throws if `owner_balance` is lower than total amount of `token` pieces
-fn ensure_single_owner<T: Config>(
+pub fn ensure_single_owner<T: Config>(
 	collection: &RefungibleHandle<T>,
 	token: TokenId,
 	owner_balance: u128,
@@ -788,6 +788,16 @@
 			.map_err(dispatch_to_evm::<T>)?;
 		Ok(true)
 	}
+
+	/// Returns EVM address for refungible token
+	///
+	/// @param token ID of the token
+	fn token_contract_address(&self, token: uint256) -> Result<address> {
+		Ok(T::EvmTokenAddressMapping::token_to_address(
+			self.id,
+			token.try_into().map_err(|_| "token id overflow")?,
+		))
+	}
 }
 
 #[solidity_interface(
modifiedpallets/refungible/src/erc_token.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc_token.rs
+++ b/pallets/refungible/src/erc_token.rs
@@ -20,29 +20,95 @@
 //! Method implementations are mostly doing parameter conversion and calling Nonfungible Pallet methods.
 
 extern crate alloc;
+
+#[cfg(not(feature = "std"))]
+use alloc::format;
+
 use core::{
 	char::{REPLACEMENT_CHARACTER, decode_utf16},
 	convert::TryInto,
 	ops::Deref,
 };
-use evm_coder::{ToLog, execution::*, generate_stubgen, solidity_interface, types::*, weight};
+use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};
 use pallet_common::{
 	CommonWeightInfo,
-	erc::{CommonEvmHandler, PrecompileResult},
+	erc::{CommonEvmHandler, PrecompileResult, static_property::key},
+	eth::map_eth_to_id,
 };
 use pallet_evm::{account::CrossAccountId, PrecompileHandle};
 use pallet_evm_coder_substrate::{call, dispatch_to_evm, WithRecorder};
 use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};
+use sp_core::H160;
 use sp_std::vec::Vec;
-use up_data_structs::TokenId;
+use up_data_structs::{mapping::TokenAddressMapping, PropertyScope, TokenId};
 
 use crate::{
 	Allowance, Balance, common::CommonWeights, Config, Pallet, RefungibleHandle, SelfWeightOf,
-	weights::WeightInfo, TotalSupply,
+	TokenProperties, TotalSupply, weights::WeightInfo,
 };
 
 pub struct RefungibleTokenHandle<T: Config>(pub RefungibleHandle<T>, pub TokenId);
 
+#[solidity_interface(name = "ERC1633")]
+impl<T: Config> RefungibleTokenHandle<T> {
+	fn parent_token(&self) -> Result<address> {
+		self.consume_store_reads(2)?;
+		let props = <TokenProperties<T>>::get((self.id, self.1));
+		let key = key::parent_nft();
+
+		let key_scoped = PropertyScope::Eth
+			.apply(key)
+			.expect("property key shouldn't exceed length limit");
+		if let Some(value) = props.get(&key_scoped) {
+			Ok(H160::from_slice(value.as_slice()))
+		} else {
+			Ok(*T::CrossTokenAddressMapping::token_to_address(self.id, self.1).as_eth())
+		}
+	}
+
+	fn parent_token_id(&self) -> Result<uint256> {
+		self.consume_store_reads(2)?;
+		let props = <TokenProperties<T>>::get((self.id, self.1));
+		let key = key::parent_nft();
+
+		let key_scoped = PropertyScope::Eth
+			.apply(key)
+			.expect("property key shouldn't exceed length limit");
+		if let Some(value) = props.get(&key_scoped) {
+			let nft_token_address = H160::from_slice(value.as_slice());
+			let nft_token_account = T::CrossAccountId::from_eth(nft_token_address);
+			let (_, token_id) = T::CrossTokenAddressMapping::address_to_token(&nft_token_account)
+				.ok_or("parent NFT should contain NFT token address")?;
+
+			Ok(token_id.into())
+		} else {
+			Ok(self.1.into())
+		}
+	}
+}
+
+#[solidity_interface(name = "ERC1633UniqueExtensions")]
+impl<T: Config> RefungibleTokenHandle<T> {
+	#[solidity(rename_selector = "setParentNFT")]
+	#[weight(<CommonWeights<T>>::token_owner() + <SelfWeightOf<T>>::set_parent_nft_unchecked())]
+	fn set_parent_nft(
+		&mut self,
+		caller: caller,
+		collection: address,
+		nft_id: uint256,
+	) -> Result<bool> {
+		self.consume_store_reads(1)?;
+		let caller = T::CrossAccountId::from_eth(caller);
+		let nft_collection = map_eth_to_id(&collection).ok_or("collection not found")?;
+		let nft_token = nft_id.try_into()?;
+
+		<Pallet<T>>::set_parent_nft(&self.0, self.1, caller, nft_collection, nft_token)
+			.map_err(dispatch_to_evm::<T>)?;
+
+		Ok(true)
+	}
+}
+
 #[derive(ToLog)]
 pub enum ERC20Events {
 	/// @dev This event is emitted when the amount of tokens (value) is sent
@@ -120,7 +186,7 @@
 			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
 		<Pallet<T>>::transfer(self, &caller, &to, self.1, amount, &budget)
-			.map_err(|_| "transfer error")?;
+			.map_err(dispatch_to_evm::<T>)?;
 		Ok(true)
 	}
 
@@ -239,7 +305,10 @@
 	}
 }
 
-#[solidity_interface(name = "UniqueRefungibleToken", is(ERC20, ERC20UniqueExtensions,))]
+#[solidity_interface(
+	name = "UniqueRefungibleToken",
+	is(ERC20, ERC20UniqueExtensions, ERC1633, ERC1633UniqueExtensions)
+)]
 impl<T: Config> RefungibleTokenHandle<T> where T::AccountId: From<[u8; 32]> {}
 
 generate_stubgen!(gen_impl, UniqueRefungibleTokenCall<()>, true);
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -99,8 +99,12 @@
 use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};
 use pallet_evm_coder_substrate::WithRecorder;
 use pallet_common::{
-	CommonCollectionOperations, Error as CommonError, Event as CommonEvent,
-	eth::collection_id_to_address, Pallet as PalletCommon,
+	CollectionHandle, CommonCollectionOperations,
+	dispatch::CollectionDispatch,
+	erc::static_property::{key, property_value_from_bytes},
+	Error as CommonError,
+	eth::collection_id_to_address,
+	Event as CommonEvent, Pallet as PalletCommon,
 };
 use pallet_structure::Pallet as PalletStructure;
 use scale_info::TypeInfo;
@@ -108,10 +112,10 @@
 use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};
 use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};
 use up_data_structs::{
-	AccessMode, budget::Budget, CollectionId, CreateCollectionData, CustomDataLimit,
-	mapping::TokenAddressMapping, MAX_REFUNGIBLE_PIECES, MAX_ITEMS_PER_BATCH, TokenId, Property,
-	PropertyKey, PropertyKeyPermission, PropertyPermission, PropertyScope, PropertyValue,
-	TrySetProperty, CollectionPropertiesVec,
+	AccessMode, budget::Budget, CollectionId, CollectionMode, CollectionPropertiesVec,
+	CreateCollectionData, CustomDataLimit, mapping::TokenAddressMapping, MAX_ITEMS_PER_BATCH,
+	MAX_REFUNGIBLE_PIECES, Property, PropertyKey, PropertyKeyPermission, PropertyPermission,
+	PropertyScope, PropertyValue, TokenId, TrySetProperty,
 };
 use frame_support::BoundedBTreeMap;
 use derivative::Derivative;
@@ -1341,6 +1345,20 @@
 		<PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)
 	}
 
+	pub fn set_scoped_token_property_permissions(
+		collection: &RefungibleHandle<T>,
+		sender: &T::CrossAccountId,
+		scope: PropertyScope,
+		property_permissions: Vec<PropertyKeyPermission>,
+	) -> DispatchResult {
+		<PalletCommon<T>>::set_scoped_token_property_permissions(
+			collection,
+			sender,
+			scope,
+			property_permissions,
+		)
+	}
+
 	/// Returns 10 token in no particular order.
 	///
 	/// There is no direct way to get token holders in ascending order,
@@ -1362,4 +1380,68 @@
 			Some(res)
 		}
 	}
+
+	/// Sets the NFT token as a parent for the RFT token
+	///
+	/// Throws if `sender` is not the owner of the NFT token.
+	/// Throws if `sender` is not the owner of all of the RFT token pieces.
+	pub fn set_parent_nft(
+		collection: &RefungibleHandle<T>,
+		rft_token_id: TokenId,
+		sender: T::CrossAccountId,
+		nft_collection: CollectionId,
+		nft_token: TokenId,
+	) -> DispatchResult {
+		let handle = <CollectionHandle<T>>::try_get(nft_collection)?;
+		if handle.mode != CollectionMode::NFT {
+			return Err("Only NFT token could be parent to RFT".into());
+		}
+		let dispatch = T::CollectionDispatch::dispatch(handle);
+		let dispatch = dispatch.as_dyn();
+
+		let owner = dispatch.token_owner(nft_token).ok_or("owner not found")?;
+		if owner != sender {
+			return Err("Only owned token could be set as parent".into());
+		}
+
+		let nft_token_address =
+			T::CrossTokenAddressMapping::token_to_address(nft_collection, nft_token);
+
+		Self::set_parent_nft_unchecked(collection, rft_token_id, sender, nft_token_address)
+	}
+
+	/// Sets the NFT token as a parent for the RFT token
+	///
+	/// `sender` should be the owner of the NFT token.
+	/// Throws if `sender` is not the owner of all of the RFT token pieces.
+	pub fn set_parent_nft_unchecked(
+		collection: &RefungibleHandle<T>,
+		rft_token_id: TokenId,
+		sender: T::CrossAccountId,
+		nft_token_address: T::CrossAccountId,
+	) -> DispatchResult {
+		let owner_balance = <Balance<T>>::get((collection.id, rft_token_id, &sender));
+		let total_supply = <TotalSupply<T>>::get((collection.id, rft_token_id));
+		if total_supply != owner_balance {
+			return Err("token has multiple owners".into());
+		}
+
+		let parent_nft_property_key = key::parent_nft();
+
+		let parent_nft_property_value =
+			property_value_from_bytes(&nft_token_address.as_eth().to_fixed_bytes())
+				.expect("address should fit in value length limit");
+
+		<Pallet<T>>::set_scoped_token_property(
+			collection.id,
+			rft_token_id,
+			PropertyScope::Eth,
+			Property {
+				key: parent_nft_property_key,
+				value: parent_nft_property_value,
+			},
+		)?;
+
+		Ok(())
+	}
 }
modifiedpallets/refungible/src/stubs/UniqueRefungible.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth
--- a/pallets/refungible/src/stubs/UniqueRefungible.sol
+++ b/pallets/refungible/src/stubs/UniqueRefungible.sol
@@ -371,49 +371,7 @@
 	}
 }
 
-// Selector: 780e9d63
-contract ERC721Enumerable is Dummy, ERC165 {
-	// @notice Enumerate valid RFTs
-	// @param index A counter less than `totalSupply()`
-	// @return The token identifier for the `index`th NFT,
-	//  (sort order not specified)
-	//
-	// Selector: tokenByIndex(uint256) 4f6ccce7
-	function tokenByIndex(uint256 index) public view returns (uint256) {
-		require(false, stub_error);
-		index;
-		dummy;
-		return 0;
-	}
-
-	// Not implemented
-	//
-	// Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59
-	function tokenOfOwnerByIndex(address owner, uint256 index)
-		public
-		view
-		returns (uint256)
-	{
-		require(false, stub_error);
-		owner;
-		index;
-		dummy;
-		return 0;
-	}
-
-	// @notice Count RFTs tracked by this contract
-	// @return A count of valid RFTs tracked by this contract, where each one of
-	//  them has an assigned and queryable owner not equal to the zero address
-	//
-	// Selector: totalSupply() 18160ddd
-	function totalSupply() public view returns (uint256) {
-		require(false, stub_error);
-		dummy;
-		return 0;
-	}
-}
-
-// Selector: 7d9262e6
+// Selector: 6cf113cd
 contract Collection is Dummy, ERC165 {
 	// Set collection property.
 	//
@@ -638,9 +596,75 @@
 		mode;
 		dummy = 0;
 	}
+
+	// Check that account is the owner or admin of the collection
+	//
+	// @param user account to verify
+	// @return "true" if account is the owner or admin
+	//
+	// Selector: verifyOwnerOrAdmin(address) c2282493
+	function verifyOwnerOrAdmin(address user) public view returns (bool) {
+		require(false, stub_error);
+		user;
+		dummy;
+		return false;
+	}
+
+	// Returns collection type
+	//
+	// @return `Fungible` or `NFT` or `ReFungible`
+	//
+	// Selector: uniqueCollectionType() d34b55b8
+	function uniqueCollectionType() public returns (string memory) {
+		require(false, stub_error);
+		dummy = 0;
+		return "";
+	}
+}
+
+// Selector: 780e9d63
+contract ERC721Enumerable is Dummy, ERC165 {
+	// @notice Enumerate valid RFTs
+	// @param index A counter less than `totalSupply()`
+	// @return The token identifier for the `index`th NFT,
+	//  (sort order not specified)
+	//
+	// Selector: tokenByIndex(uint256) 4f6ccce7
+	function tokenByIndex(uint256 index) public view returns (uint256) {
+		require(false, stub_error);
+		index;
+		dummy;
+		return 0;
+	}
+
+	// Not implemented
+	//
+	// Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59
+	function tokenOfOwnerByIndex(address owner, uint256 index)
+		public
+		view
+		returns (uint256)
+	{
+		require(false, stub_error);
+		owner;
+		index;
+		dummy;
+		return 0;
+	}
+
+	// @notice Count RFTs tracked by this contract
+	// @return A count of valid RFTs tracked by this contract, where each one of
+	//  them has an assigned and queryable owner not equal to the zero address
+	//
+	// Selector: totalSupply() 18160ddd
+	function totalSupply() public view returns (uint256) {
+		require(false, stub_error);
+		dummy;
+		return 0;
+	}
 }
 
-// Selector: d74d154f
+// Selector: 7c3bef89
 contract ERC721UniqueExtensions is Dummy, ERC165 {
 	// @notice Transfer ownership of an RFT
 	// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
@@ -719,6 +743,18 @@
 		dummy = 0;
 		return false;
 	}
+
+	// Returns EVM address for refungible token
+	//
+	// @param token ID of the token
+	//
+	// Selector: tokenContractAddress(uint256) ab76fac6
+	function tokenContractAddress(uint256 token) public view returns (address) {
+		require(false, stub_error);
+		token;
+		dummy;
+		return 0x0000000000000000000000000000000000000000;
+	}
 }
 
 contract UniqueRefungible is
modifiedpallets/refungible/src/stubs/UniqueRefungibleToken.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/refungible/src/stubs/UniqueRefungibleToken.soldiffbeforeafterboth
--- a/pallets/refungible/src/stubs/UniqueRefungibleToken.sol
+++ b/pallets/refungible/src/stubs/UniqueRefungibleToken.sol
@@ -31,6 +31,38 @@
 	);
 }
 
+// Selector: 042f1106
+contract ERC1633UniqueExtensions is Dummy, ERC165 {
+	// Selector: setParentNFT(address,uint256) 042f1106
+	function setParentNFT(address collection, uint256 nftId)
+		public
+		returns (bool)
+	{
+		require(false, stub_error);
+		collection;
+		nftId;
+		dummy = 0;
+		return false;
+	}
+}
+
+// Selector: 5755c3f2
+contract ERC1633 is Dummy, ERC165 {
+	// Selector: parentToken() 80a54001
+	function parentToken() public view returns (address) {
+		require(false, stub_error);
+		dummy;
+		return 0x0000000000000000000000000000000000000000;
+	}
+
+	// Selector: parentTokenId() d7f083f3
+	function parentTokenId() public view returns (uint256) {
+		require(false, stub_error);
+		dummy;
+		return 0;
+	}
+}
+
 // Selector: 942e8b22
 contract ERC20 is Dummy, ERC165, ERC20Events {
 	// @return the name of the token.
@@ -178,4 +210,11 @@
 	}
 }
 
-contract UniqueRefungibleToken is Dummy, ERC165, ERC20, ERC20UniqueExtensions {}
+contract UniqueRefungibleToken is
+	Dummy,
+	ERC165,
+	ERC20,
+	ERC20UniqueExtensions,
+	ERC1633,
+	ERC1633UniqueExtensions
+{}
modifiedpallets/refungible/src/weights.rsdiffbeforeafterboth
--- a/pallets/refungible/src/weights.rs
+++ b/pallets/refungible/src/weights.rs
@@ -3,7 +3,7 @@
 //! Autogenerated weights for pallet_refungible
 //!
 //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
-//! DATE: 2022-07-20, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! DATE: 2022-08-01, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]`
 //! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024
 
 // Executed Command:
@@ -53,6 +53,8 @@
 	fn set_token_properties(b: u32, ) -> Weight;
 	fn delete_token_properties(b: u32, ) -> Weight;
 	fn repartition_item() -> Weight;
+	fn set_parent_nft_unchecked() -> Weight;
+	fn token_owner() -> Weight;
 }
 
 /// Weights for pallet_refungible using the Substrate node and recommended hardware.
@@ -65,7 +67,7 @@
 	// Storage: Refungible TokenData (r:0 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
 	fn create_item() -> Weight {
-		(21_310_000 as Weight)
+		(25_197_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes(6 as Weight))
 	}
@@ -76,9 +78,9 @@
 	// Storage: Refungible TokenData (r:0 w:4)
 	// Storage: Refungible Owned (r:0 w:4)
 	fn create_multiple_items(b: u32, ) -> Weight {
-		(9_552_000 as Weight)
+		(10_852_000 as Weight)
 			// Standard Error: 2_000
-			.saturating_add((7_056_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add((8_087_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(T::DbWeight::get().reads(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes((4 as Weight).saturating_mul(b as Weight)))
@@ -90,9 +92,9 @@
 	// Storage: Refungible TokenData (r:0 w:4)
 	// Storage: Refungible Owned (r:0 w:4)
 	fn create_multiple_items_ex_multiple_items(b: u32, ) -> Weight {
-		(4_857_000 as Weight)
+		(9_978_000 as Weight)
 			// Standard Error: 2_000
-			.saturating_add((9_838_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add((10_848_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(T::DbWeight::get().reads(1 as Weight))
 			.saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
@@ -105,9 +107,9 @@
 	// Storage: Refungible Balance (r:0 w:4)
 	// Storage: Refungible Owned (r:0 w:4)
 	fn create_multiple_items_ex_multiple_owners(b: u32, ) -> Weight {
-		(11_335_000 as Weight)
+		(15_419_000 as Weight)
 			// Standard Error: 2_000
-			.saturating_add((6_784_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add((7_813_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(T::DbWeight::get().reads(1 as Weight))
 			.saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
 			.saturating_add(T::DbWeight::get().writes(3 as Weight))
@@ -118,7 +120,7 @@
 	// Storage: Refungible AccountBalance (r:1 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
 	fn burn_item_partial() -> Weight {
-		(21_239_000 as Weight)
+		(25_578_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(3 as Weight))
 			.saturating_add(T::DbWeight::get().writes(4 as Weight))
 	}
@@ -130,13 +132,13 @@
 	// Storage: Refungible Owned (r:0 w:1)
 	// Storage: Refungible TokenProperties (r:0 w:1)
 	fn burn_item_fully() -> Weight {
-		(29_426_000 as Weight)
+		(33_593_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(4 as Weight))
 			.saturating_add(T::DbWeight::get().writes(7 as Weight))
 	}
 	// Storage: Refungible Balance (r:2 w:2)
 	fn transfer_normal() -> Weight {
-		(17_743_000 as Weight)
+		(21_049_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes(2 as Weight))
 	}
@@ -144,7 +146,7 @@
 	// Storage: Refungible AccountBalance (r:1 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
 	fn transfer_creating() -> Weight {
-		(20_699_000 as Weight)
+		(24_646_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(3 as Weight))
 			.saturating_add(T::DbWeight::get().writes(4 as Weight))
 	}
@@ -152,7 +154,7 @@
 	// Storage: Refungible AccountBalance (r:1 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
 	fn transfer_removing() -> Weight {
-		(22_833_000 as Weight)
+		(26_570_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(3 as Weight))
 			.saturating_add(T::DbWeight::get().writes(4 as Weight))
 	}
@@ -160,21 +162,21 @@
 	// Storage: Refungible AccountBalance (r:2 w:2)
 	// Storage: Refungible Owned (r:0 w:2)
 	fn transfer_creating_removing() -> Weight {
-		(24_936_000 as Weight)
+		(28_906_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(4 as Weight))
 			.saturating_add(T::DbWeight::get().writes(6 as Weight))
 	}
 	// Storage: Refungible Balance (r:1 w:0)
 	// Storage: Refungible Allowance (r:0 w:1)
 	fn approve() -> Weight {
-		(13_446_000 as Weight)
+		(16_451_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(1 as Weight))
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
 	// Storage: Refungible Allowance (r:1 w:1)
 	// Storage: Refungible Balance (r:2 w:2)
 	fn transfer_from_normal() -> Weight {
-		(24_777_000 as Weight)
+		(29_545_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(3 as Weight))
 			.saturating_add(T::DbWeight::get().writes(3 as Weight))
 	}
@@ -183,7 +185,7 @@
 	// Storage: Refungible AccountBalance (r:1 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
 	fn transfer_from_creating() -> Weight {
-		(28_483_000 as Weight)
+		(33_392_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(4 as Weight))
 			.saturating_add(T::DbWeight::get().writes(5 as Weight))
 	}
@@ -192,7 +194,7 @@
 	// Storage: Refungible AccountBalance (r:1 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
 	fn transfer_from_removing() -> Weight {
-		(29_896_000 as Weight)
+		(35_446_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(4 as Weight))
 			.saturating_add(T::DbWeight::get().writes(5 as Weight))
 	}
@@ -201,7 +203,7 @@
 	// Storage: Refungible AccountBalance (r:2 w:2)
 	// Storage: Refungible Owned (r:0 w:2)
 	fn transfer_from_creating_removing() -> Weight {
-		(32_070_000 as Weight)
+		(37_762_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(5 as Weight))
 			.saturating_add(T::DbWeight::get().writes(7 as Weight))
 	}
@@ -214,15 +216,15 @@
 	// Storage: Refungible Owned (r:0 w:1)
 	// Storage: Refungible TokenProperties (r:0 w:1)
 	fn burn_from() -> Weight {
-		(36_789_000 as Weight)
+		(42_620_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(5 as Weight))
 			.saturating_add(T::DbWeight::get().writes(8 as Weight))
 	}
 	// Storage: Common CollectionPropertyPermissions (r:1 w:1)
 	fn set_token_property_permissions(b: u32, ) -> Weight {
 		(0 as Weight)
-			// Standard Error: 62_000
-			.saturating_add((15_803_000 as Weight).saturating_mul(b as Weight))
+			// Standard Error: 65_000
+			.saturating_add((16_513_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(T::DbWeight::get().reads(1 as Weight))
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
@@ -230,8 +232,8 @@
 	// Storage: Refungible TokenProperties (r:1 w:1)
 	fn set_token_properties(b: u32, ) -> Weight {
 		(0 as Weight)
-			// Standard Error: 1_668_000
-			.saturating_add((302_308_000 as Weight).saturating_mul(b as Weight))
+			// Standard Error: 1_583_000
+			.saturating_add((291_392_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(T::DbWeight::get().reads(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
@@ -239,18 +241,31 @@
 	// Storage: Refungible TokenProperties (r:1 w:1)
 	fn delete_token_properties(b: u32, ) -> Weight {
 		(0 as Weight)
-			// Standard Error: 1_619_000
-			.saturating_add((294_574_000 as Weight).saturating_mul(b as Weight))
+			// Standard Error: 1_699_000
+			.saturating_add((293_270_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(T::DbWeight::get().reads(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
 	// Storage: Refungible TotalSupply (r:1 w:1)
 	// Storage: Refungible Balance (r:1 w:1)
 	fn repartition_item() -> Weight {
-		(8_325_000 as Weight)
+		(19_206_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(2 as Weight))
 			.saturating_add(T::DbWeight::get().writes(2 as Weight))
 	}
+	// Storage: Refungible Balance (r:1 w:0)
+	// Storage: Refungible TotalSupply (r:1 w:0)
+	// Storage: Refungible TokenProperties (r:1 w:1)
+	fn set_parent_nft_unchecked() -> Weight {
+		(10_189_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(3 as Weight))
+			.saturating_add(T::DbWeight::get().writes(1 as Weight))
+	}
+	// Storage: Refungible Balance (r:2 w:0)
+	fn token_owner() -> Weight {
+		(8_205_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(2 as Weight))
+	}
 }
 
 // For backwards compatibility and tests
@@ -262,7 +277,7 @@
 	// Storage: Refungible TokenData (r:0 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
 	fn create_item() -> Weight {
-		(21_310_000 as Weight)
+		(25_197_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(6 as Weight))
 	}
@@ -273,9 +288,9 @@
 	// Storage: Refungible TokenData (r:0 w:4)
 	// Storage: Refungible Owned (r:0 w:4)
 	fn create_multiple_items(b: u32, ) -> Weight {
-		(9_552_000 as Weight)
+		(10_852_000 as Weight)
 			// Standard Error: 2_000
-			.saturating_add((7_056_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add((8_087_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes((4 as Weight).saturating_mul(b as Weight)))
@@ -287,9 +302,9 @@
 	// Storage: Refungible TokenData (r:0 w:4)
 	// Storage: Refungible Owned (r:0 w:4)
 	fn create_multiple_items_ex_multiple_items(b: u32, ) -> Weight {
-		(4_857_000 as Weight)
+		(9_978_000 as Weight)
 			// Standard Error: 2_000
-			.saturating_add((9_838_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add((10_848_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
 			.saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
@@ -302,9 +317,9 @@
 	// Storage: Refungible Balance (r:0 w:4)
 	// Storage: Refungible Owned (r:0 w:4)
 	fn create_multiple_items_ex_multiple_owners(b: u32, ) -> Weight {
-		(11_335_000 as Weight)
+		(15_419_000 as Weight)
 			// Standard Error: 2_000
-			.saturating_add((6_784_000 as Weight).saturating_mul(b as Weight))
+			.saturating_add((7_813_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
 			.saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
 			.saturating_add(RocksDbWeight::get().writes(3 as Weight))
@@ -315,7 +330,7 @@
 	// Storage: Refungible AccountBalance (r:1 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
 	fn burn_item_partial() -> Weight {
-		(21_239_000 as Weight)
+		(25_578_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(3 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(4 as Weight))
 	}
@@ -327,13 +342,13 @@
 	// Storage: Refungible Owned (r:0 w:1)
 	// Storage: Refungible TokenProperties (r:0 w:1)
 	fn burn_item_fully() -> Weight {
-		(29_426_000 as Weight)
+		(33_593_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(4 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(7 as Weight))
 	}
 	// Storage: Refungible Balance (r:2 w:2)
 	fn transfer_normal() -> Weight {
-		(17_743_000 as Weight)
+		(21_049_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(2 as Weight))
 	}
@@ -341,7 +356,7 @@
 	// Storage: Refungible AccountBalance (r:1 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
 	fn transfer_creating() -> Weight {
-		(20_699_000 as Weight)
+		(24_646_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(3 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(4 as Weight))
 	}
@@ -349,7 +364,7 @@
 	// Storage: Refungible AccountBalance (r:1 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
 	fn transfer_removing() -> Weight {
-		(22_833_000 as Weight)
+		(26_570_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(3 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(4 as Weight))
 	}
@@ -357,21 +372,21 @@
 	// Storage: Refungible AccountBalance (r:2 w:2)
 	// Storage: Refungible Owned (r:0 w:2)
 	fn transfer_creating_removing() -> Weight {
-		(24_936_000 as Weight)
+		(28_906_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(4 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(6 as Weight))
 	}
 	// Storage: Refungible Balance (r:1 w:0)
 	// Storage: Refungible Allowance (r:0 w:1)
 	fn approve() -> Weight {
-		(13_446_000 as Weight)
+		(16_451_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}
 	// Storage: Refungible Allowance (r:1 w:1)
 	// Storage: Refungible Balance (r:2 w:2)
 	fn transfer_from_normal() -> Weight {
-		(24_777_000 as Weight)
+		(29_545_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(3 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(3 as Weight))
 	}
@@ -380,7 +395,7 @@
 	// Storage: Refungible AccountBalance (r:1 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
 	fn transfer_from_creating() -> Weight {
-		(28_483_000 as Weight)
+		(33_392_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(4 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(5 as Weight))
 	}
@@ -389,7 +404,7 @@
 	// Storage: Refungible AccountBalance (r:1 w:1)
 	// Storage: Refungible Owned (r:0 w:1)
 	fn transfer_from_removing() -> Weight {
-		(29_896_000 as Weight)
+		(35_446_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(4 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(5 as Weight))
 	}
@@ -398,7 +413,7 @@
 	// Storage: Refungible AccountBalance (r:2 w:2)
 	// Storage: Refungible Owned (r:0 w:2)
 	fn transfer_from_creating_removing() -> Weight {
-		(32_070_000 as Weight)
+		(37_762_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(5 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(7 as Weight))
 	}
@@ -411,15 +426,15 @@
 	// Storage: Refungible Owned (r:0 w:1)
 	// Storage: Refungible TokenProperties (r:0 w:1)
 	fn burn_from() -> Weight {
-		(36_789_000 as Weight)
+		(42_620_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(5 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(8 as Weight))
 	}
 	// Storage: Common CollectionPropertyPermissions (r:1 w:1)
 	fn set_token_property_permissions(b: u32, ) -> Weight {
 		(0 as Weight)
-			// Standard Error: 62_000
-			.saturating_add((15_803_000 as Weight).saturating_mul(b as Weight))
+			// Standard Error: 65_000
+			.saturating_add((16_513_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(RocksDbWeight::get().reads(1 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}
@@ -427,8 +442,8 @@
 	// Storage: Refungible TokenProperties (r:1 w:1)
 	fn set_token_properties(b: u32, ) -> Weight {
 		(0 as Weight)
-			// Standard Error: 1_668_000
-			.saturating_add((302_308_000 as Weight).saturating_mul(b as Weight))
+			// Standard Error: 1_583_000
+			.saturating_add((291_392_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}
@@ -436,16 +451,29 @@
 	// Storage: Refungible TokenProperties (r:1 w:1)
 	fn delete_token_properties(b: u32, ) -> Weight {
 		(0 as Weight)
-			// Standard Error: 1_619_000
-			.saturating_add((294_574_000 as Weight).saturating_mul(b as Weight))
+			// Standard Error: 1_699_000
+			.saturating_add((293_270_000 as Weight).saturating_mul(b as Weight))
 			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}
 	// Storage: Refungible TotalSupply (r:1 w:1)
 	// Storage: Refungible Balance (r:1 w:1)
 	fn repartition_item() -> Weight {
-		(8_325_000 as Weight)
+		(19_206_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(2 as Weight))
 	}
+	// Storage: Refungible Balance (r:1 w:0)
+	// Storage: Refungible TotalSupply (r:1 w:0)
+	// Storage: Refungible TokenProperties (r:1 w:1)
+	fn set_parent_nft_unchecked() -> Weight {
+		(10_189_000 as Weight)
+			.saturating_add(RocksDbWeight::get().reads(3 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
+	}
+	// Storage: Refungible Balance (r:2 w:0)
+	fn token_owner() -> Weight {
+		(8_205_000 as Weight)
+			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
+	}
 }
modifiedpallets/unique/src/eth/mod.rsdiffbeforeafterboth
--- a/pallets/unique/src/eth/mod.rs
+++ b/pallets/unique/src/eth/mod.rs
@@ -17,26 +17,29 @@
 //! Implementation of CollectionHelpers contract.
 
 use core::marker::PhantomData;
-use evm_coder::{execution::*, generate_stubgen, solidity_interface, solidity, weight, types::*};
 use ethereum as _;
-use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
-use pallet_evm::{OnMethodCall, PrecompileResult, account::CrossAccountId, PrecompileHandle};
-use up_data_structs::{
-	CollectionName, CollectionDescription, CollectionTokenPrefix, CreateCollectionData,
-	CollectionMode, PropertyValue,
-};
+use evm_coder::{execution::*, generate_stubgen, solidity_interface, solidity, weight, types::*};
 use frame_support::traits::Get;
 use pallet_common::{
-	CollectionById,
+	CollectionById, CollectionHandle,
+	dispatch::CollectionDispatch,
 	erc::{
+		CollectionHelpersEvents,
 		static_property::{key, value as property_value},
-		CollectionHelpersEvents,
 	},
-	dispatch::CollectionDispatch,
+	Pallet as PalletCommon,
 };
-use crate::{SelfWeightOf, Config, weights::WeightInfo};
+use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
+use pallet_evm::{account::CrossAccountId, OnMethodCall, PrecompileHandle, PrecompileResult};
+use pallet_evm_coder_substrate::dispatch_to_evm;
+use up_data_structs::{
+	CollectionName, CollectionDescription, CollectionTokenPrefix, CreateCollectionData,
+	CollectionMode, PropertyKeyPermission, PropertyPermission, PropertyScope, PropertyValue,
+};
+
+use crate::{Config, SelfWeightOf, weights::WeightInfo};
 
-use sp_std::vec::Vec;
+use sp_std::{vec, vec::Vec};
 use alloc::format;
 
 /// See [`CollectionHelpersCall`]
@@ -151,6 +154,54 @@
 	Ok(data)
 }
 
+fn parent_nft_property_permissions() -> PropertyKeyPermission {
+	PropertyKeyPermission {
+		key: key::parent_nft(),
+		permission: PropertyPermission {
+			mutable: false,
+			collection_admin: false,
+			token_owner: true,
+		},
+	}
+}
+
+fn create_refungible_collection_internal<
+	T: Config + pallet_nonfungible::Config + pallet_refungible::Config,
+>(
+	caller: caller,
+	name: string,
+	description: string,
+	token_prefix: string,
+	base_uri: string,
+	add_properties: bool,
+) -> Result<address> {
+	let (caller, name, description, token_prefix, base_uri_value) =
+		convert_data::<T>(caller, name, description, token_prefix, base_uri)?;
+	let data = make_data::<T>(
+		name,
+		CollectionMode::ReFungible,
+		description,
+		token_prefix,
+		base_uri_value,
+		add_properties,
+	)?;
+
+	let collection_id = T::CollectionDispatch::create(caller.clone(), data)
+		.map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;
+
+	let handle = <CollectionHandle<T>>::try_get(collection_id).map_err(dispatch_to_evm::<T>)?;
+	<PalletCommon<T>>::set_scoped_token_property_permissions(
+		&handle,
+		&caller,
+		PropertyScope::Eth,
+		vec![parent_nft_property_permissions()],
+	)
+	.map_err(dispatch_to_evm::<T>)?;
+
+	let address = pallet_common::eth::collection_id_to_address(collection_id);
+	Ok(address)
+}
+
 /// @title Contract, which allows users to operate with collections
 #[solidity_interface(name = "CollectionHelpers", events(CollectionHelpersEvents))]
 impl<T> EvmCollectionHelpers<T>
@@ -216,27 +267,20 @@
 
 	#[weight(<SelfWeightOf<T>>::create_collection())]
 	fn create_refungible_collection(
-		&self,
+		&mut self,
 		caller: caller,
 		name: string,
 		description: string,
 		token_prefix: string,
 	) -> Result<address> {
-		let (caller, name, description, token_prefix, _base_uri) =
-			convert_data::<T>(caller, name, description, token_prefix, "".into())?;
-		let data = make_data::<T>(
+		create_refungible_collection_internal::<T>(
+			caller,
 			name,
-			CollectionMode::ReFungible,
 			description,
 			token_prefix,
 			Default::default(),
 			false,
-		)?;
-		let collection_id = T::CollectionDispatch::create(caller, data)
-			.map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;
-
-		let address = pallet_common::eth::collection_id_to_address(collection_id);
-		Ok(address)
+		)
 	}
 
 	#[weight(<SelfWeightOf<T>>::create_collection())]
@@ -249,21 +293,14 @@
 		token_prefix: string,
 		base_uri: string,
 	) -> Result<address> {
-		let (caller, name, description, token_prefix, base_uri_value) =
-			convert_data::<T>(caller, name, description, token_prefix, base_uri)?;
-		let data = make_data::<T>(
+		create_refungible_collection_internal::<T>(
+			caller,
 			name,
-			CollectionMode::NFT,
 			description,
 			token_prefix,
-			base_uri_value,
+			base_uri,
 			true,
-		)?;
-		let collection_id = T::CollectionDispatch::create(caller, data)
-			.map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;
-
-		let address = pallet_common::eth::collection_id_to_address(collection_id);
-		Ok(address)
+		)
 	}
 
 	/// Check if a collection exists
modifiedpallets/unique/src/eth/stubs/CollectionHelpers.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/unique/src/eth/stubs/CollectionHelpers.soldiffbeforeafterboth
--- a/pallets/unique/src/eth/stubs/CollectionHelpers.sol
+++ b/pallets/unique/src/eth/stubs/CollectionHelpers.sol
@@ -72,12 +72,12 @@
 		string memory name,
 		string memory description,
 		string memory tokenPrefix
-	) public view returns (address) {
+	) public returns (address) {
 		require(false, stub_error);
 		name;
 		description;
 		tokenPrefix;
-		dummy;
+		dummy = 0;
 		return 0x0000000000000000000000000000000000000000;
 	}
 
addedprimitives/common/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/primitives/common/Cargo.toml
@@ -0,0 +1,56 @@
+[package]
+authors = ['Unique Network <support@uniquenetwork.io>']
+description = 'Unique Runtime Common Primitives'
+edition = '2021'
+homepage = 'https://unique.network'
+license = 'All Rights Reserved'
+name = 'up-common'
+repository = 'https://github.com/UniqueNetwork/unique-chain'
+version = '0.9.24'
+
+[features]
+default = ['std']
+std = [
+    'sp-std/std',
+    'frame-support/std',
+    'sp-runtime/std',
+    'sp-core/std',
+    'sp-consensus-aura/std',
+    'fp-rpc/std',
+    'pallet-evm/std',
+]
+
+[dependencies.sp-std]
+default-features = false
+git = "https://github.com/paritytech/substrate"
+branch = "polkadot-v0.9.24"
+
+[dependencies.frame-support]
+default-features = false
+git = "https://github.com/paritytech/substrate"
+branch = "polkadot-v0.9.24"
+
+[dependencies.sp-runtime]
+default-features = false
+git = "https://github.com/paritytech/substrate"
+branch = "polkadot-v0.9.24"
+
+[dependencies.sp-core]
+default-features = false
+git = "https://github.com/paritytech/substrate"
+branch = "polkadot-v0.9.24"
+
+[dependencies.sp-consensus-aura]
+default-features = false
+git = "https://github.com/paritytech/substrate"
+branch = "polkadot-v0.9.24"
+
+[dependencies.fp-rpc]
+default-features = false
+git = "https://github.com/uniquenetwork/frontier"
+branch = "unique-polkadot-v0.9.24"
+
+[dependencies.pallet-evm]
+default-features = false
+git = "https://github.com/uniquenetwork/frontier"
+branch = "unique-polkadot-v0.9.24"
addedprimitives/common/src/constants.rsdiffbeforeafterboth
--- /dev/null
+++ b/primitives/common/src/constants.rs
@@ -0,0 +1,55 @@
+// 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 sp_runtime::Perbill;
+use frame_support::{
+	parameter_types,
+	weights::{Weight, constants::WEIGHT_PER_SECOND},
+};
+use crate::types::{BlockNumber, Balance};
+
+pub const MILLISECS_PER_BLOCK: u64 = 12000;
+
+pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK;
+
+// These time units are defined in number of blocks.
+pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber);
+pub const HOURS: BlockNumber = MINUTES * 60;
+pub const DAYS: BlockNumber = HOURS * 24;
+
+pub const MICROUNIQUE: Balance = 1_000_000_000_000;
+pub const MILLIUNIQUE: Balance = 1_000 * MICROUNIQUE;
+pub const CENTIUNIQUE: Balance = 10 * MILLIUNIQUE;
+pub const UNIQUE: Balance = 100 * CENTIUNIQUE;
+
+// Targeting 0.1 UNQ per transfer
+pub const WEIGHT_TO_FEE_COEFF: u32 = 207_890_902;
+
+// Targeting 0.15 UNQ per transfer via ETH
+pub const MIN_GAS_PRICE: u64 = 1_019_493_469_850;
+
+/// We assume that ~10% of the block weight is consumed by `on_initalize` handlers.
+/// This is used to limit the maximal weight of a single extrinsic.
+pub const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(10);
+/// We allow `Normal` extrinsics to fill up the block up to 75%, the rest can be used
+/// by  Operational  extrinsics.
+pub const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75);
+/// We allow for 2 seconds of compute with a 6 second average block time.
+pub const MAXIMUM_BLOCK_WEIGHT: Weight = WEIGHT_PER_SECOND / 2;
+
+parameter_types! {
+	pub const TransactionByteFee: Balance = 501 * MICROUNIQUE;
+}
addedprimitives/common/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/primitives/common/src/lib.rs
@@ -0,0 +1,20 @@
+// 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/>.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+pub mod constants;
+pub mod types;
addedprimitives/common/src/types.rsdiffbeforeafterboth
--- /dev/null
+++ b/primitives/common/src/types.rs
@@ -0,0 +1,81 @@
+// 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 sp_runtime::{
+	generic,
+	traits::{Verify, IdentifyAccount},
+	MultiSignature,
+};
+
+/// Opaque types. These are used by the CLI to instantiate machinery that don't need to know
+/// the specifics of the runtime. They can then be made to be agnostic over specific formats
+/// of data like extrinsics, allowing for them to continue syncing the network through upgrades
+/// to even the core data structures.
+pub mod opaque {
+	pub use sp_runtime::{generic, traits::BlakeTwo256, OpaqueExtrinsic as UncheckedExtrinsic};
+
+	pub use super::{BlockNumber, Signature, AccountId, Balance, Index, Hash, AuraId};
+
+	/// Opaque block header type.
+	pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
+
+	/// Opaque block type.
+	pub type Block = generic::Block<Header, UncheckedExtrinsic>;
+
+	pub trait RuntimeInstance {
+		type CrossAccountId: pallet_evm::account::CrossAccountId<sp_runtime::AccountId32>
+			+ Send
+			+ Sync
+			+ 'static;
+
+		type TransactionConverter: fp_rpc::ConvertTransaction<UncheckedExtrinsic>
+			+ Send
+			+ Sync
+			+ 'static;
+
+		fn get_transaction_converter() -> Self::TransactionConverter;
+	}
+}
+
+pub type SessionHandlers = ();
+
+/// An index to a block.
+pub type BlockNumber = u32;
+
+/// Alias to 512-bit hash when used in the context of a transaction signature on the chain.
+pub type Signature = MultiSignature;
+
+/// Some way of identifying an account on the chain. We intentionally make it equivalent
+/// to the public key of our transaction signing scheme.
+pub type AccountId = <<Signature as Verify>::Signer as IdentifyAccount>::AccountId;
+
+/// The type for looking up accounts. We don't expect more than 4 billion of them, but you
+/// never know...
+pub type AccountIndex = u32;
+
+/// Balance of an account.
+pub type Balance = u128;
+
+/// Index of a transaction in the chain.
+pub type Index = u32;
+
+/// A hash of some data used by the chain.
+pub type Hash = sp_core::H256;
+
+/// Digest item type.
+pub type DigestItem = generic::DigestItem;
+
+pub use sp_consensus_aura::sr25519::AuthorityId as AuraId;
modifiedprimitives/data-structs/src/lib.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -1050,6 +1050,7 @@
 pub enum PropertyScope {
 	None,
 	Rmrk,
+	Eth,
 }
 
 impl PropertyScope {
@@ -1058,6 +1059,7 @@
 		let scope_str: &[u8] = match self {
 			Self::None => return Ok(key),
 			Self::Rmrk => b"rmrk",
+			Self::Eth => b"eth",
 		};
 
 		[scope_str, b":", key.as_slice()]
deletedruntime/common/Cargo.tomldiffbeforeafterboth
--- a/runtime/common/Cargo.toml
+++ /dev/null
@@ -1,115 +0,0 @@
-[package]
-authors = ['Unique Network <support@uniquenetwork.io>']
-description = 'Unique Runtime Common'
-edition = '2021'
-homepage = 'https://unique.network'
-license = 'All Rights Reserved'
-name = 'unique-runtime-common'
-repository = 'https://github.com/UniqueNetwork/unique-chain'
-version = '0.9.24'
-
-[features]
-default = ['std']
-std = [
-    'sp-core/std',
-    'sp-std/std',
-    'sp-runtime/std',
-    'codec/std',
-    'frame-support/std',
-    'frame-system/std',
-    'sp-consensus-aura/std',
-    'pallet-common/std',
-    'pallet-unique/std',
-    'pallet-fungible/std',
-    'pallet-nonfungible/std',
-    'pallet-refungible/std',
-    'up-data-structs/std',
-    'pallet-evm/std',
-    'fp-rpc/std',
-]
-runtime-benchmarks = [
-    'sp-runtime/runtime-benchmarks',
-    'frame-support/runtime-benchmarks',
-    'frame-system/runtime-benchmarks',
-]
-
-[dependencies.sp-core]
-default-features = false
-git = "https://github.com/paritytech/substrate"
-branch = "polkadot-v0.9.24"
-
-[dependencies.sp-std]
-default-features = false
-git = 'https://github.com/paritytech/substrate'
-branch = 'polkadot-v0.9.24'
-
-[dependencies.sp-runtime]
-default-features = false
-git = "https://github.com/paritytech/substrate"
-branch = "polkadot-v0.9.24"
-
-[dependencies.codec]
-default-features = false
-features = ['derive']
-package = 'parity-scale-codec'
-version = '3.1.2'
-
-[dependencies.scale-info]
-default-features = false
-features = ["derive"]
-version = "2.0.1"
-
-[dependencies.frame-support]
-default-features = false
-git = "https://github.com/paritytech/substrate"
-branch = "polkadot-v0.9.24"
-
-[dependencies.frame-system]
-default-features = false
-git = "https://github.com/paritytech/substrate"
-branch = "polkadot-v0.9.24"
-
-[dependencies.pallet-common]
-default-features = false
-path = "../../pallets/common"
-
-[dependencies.pallet-unique]
-default-features = false
-path = "../../pallets/unique"
-
-[dependencies.pallet-fungible]
-default-features = false
-path = "../../pallets/fungible"
-
-[dependencies.pallet-nonfungible]
-default-features = false
-path = "../../pallets/nonfungible"
-
-[dependencies.pallet-refungible]
-default-features = false
-path = "../../pallets/refungible"
-
-[dependencies.pallet-unique-scheduler]
-default-features = false
-path = "../../pallets/scheduler"
-
-[dependencies.up-data-structs]
-default-features = false
-path = "../../primitives/data-structs"
-
-[dependencies.sp-consensus-aura]
-default-features = false
-git = "https://github.com/paritytech/substrate"
-branch = "polkadot-v0.9.24"
-
-[dependencies.fp-rpc]
-default-features = false
-git = "https://github.com/uniquenetwork/frontier"
-branch = "unique-polkadot-v0.9.24"
-
-[dependencies]
-pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" }
-evm-coder = { default-features = false, path = '../../crates/evm-coder' }
-up-sponsorship = { default-features = false, git = "https://github.com/UniqueNetwork/pallet-sponsoring", branch = 'polkadot-v0.9.24' }
-
-rmrk-rpc = { default-features = false, path = "../../primitives/rmrk-rpc" }
addedruntime/common/config/ethereum.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/config/ethereum.rs
@@ -0,0 +1,131 @@
+use sp_core::{U256, H160};
+use frame_support::{
+	weights::{Weight, constants::WEIGHT_PER_SECOND},
+	traits::{FindAuthor},
+	parameter_types, ConsensusEngineId,
+};
+use sp_runtime::{RuntimeAppPublic, Perbill};
+use crate::{
+	runtime_common::{
+		dispatch::CollectionDispatchT, ethereum::sponsoring::EvmSponsorshipHandler,
+		config::sponsoring::DefaultSponsoringRateLimit, DealWithFees,
+	},
+	Runtime, Aura, Balances, Event, ChainId,
+};
+use pallet_evm::{EnsureAddressTruncated, HashedAddressMapping};
+use up_common::constants::*;
+
+pub type CrossAccountId = pallet_evm::account::BasicCrossAccountId<Runtime>;
+
+impl pallet_evm::account::Config for Runtime {
+	type CrossAccountId = CrossAccountId;
+	type EvmAddressMapping = pallet_evm::HashedAddressMapping<Self::Hashing>;
+	type EvmBackwardsAddressMapping = fp_evm_mapping::MapBackwardsAddressTruncated;
+}
+
+// Assuming slowest ethereum opcode is SSTORE, with gas price of 20000 as our worst case
+// (contract, which only writes a lot of data),
+// approximating on top of our real store write weight
+parameter_types! {
+	pub const WritesPerSecond: u64 = WEIGHT_PER_SECOND / <Runtime as frame_system::Config>::DbWeight::get().write;
+	pub const GasPerSecond: u64 = WritesPerSecond::get() * 20000;
+	pub const WeightPerGas: u64 = WEIGHT_PER_SECOND / GasPerSecond::get();
+}
+
+/// Limiting EVM execution to 50% of block for substrate users and management tasks
+/// EVM transaction consumes more weight than substrate's, so we can't rely on them being
+/// scheduled fairly
+const EVM_DISPATCH_RATIO: Perbill = Perbill::from_percent(50);
+parameter_types! {
+	pub BlockGasLimit: U256 = U256::from(NORMAL_DISPATCH_RATIO * EVM_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT / WeightPerGas::get());
+}
+
+pub enum FixedGasWeightMapping {}
+impl pallet_evm::GasWeightMapping for FixedGasWeightMapping {
+	fn gas_to_weight(gas: u64) -> Weight {
+		gas.saturating_mul(WeightPerGas::get())
+	}
+	fn weight_to_gas(weight: Weight) -> u64 {
+		weight / WeightPerGas::get()
+	}
+}
+
+pub struct FixedFee;
+impl pallet_evm::FeeCalculator for FixedFee {
+	fn min_gas_price() -> (U256, u64) {
+		(MIN_GAS_PRICE.into(), 0)
+	}
+}
+
+pub struct EthereumFindAuthor<F>(core::marker::PhantomData<F>);
+impl<F: FindAuthor<u32>> FindAuthor<H160> for EthereumFindAuthor<F> {
+	fn find_author<'a, I>(digests: I) -> Option<H160>
+	where
+		I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
+	{
+		if let Some(author_index) = F::find_author(digests) {
+			let authority_id = Aura::authorities()[author_index as usize].clone();
+			return Some(H160::from_slice(&authority_id.to_raw_vec()[4..24]));
+		}
+		None
+	}
+}
+
+impl pallet_evm::Config for Runtime {
+	type BlockGasLimit = BlockGasLimit;
+	type FeeCalculator = FixedFee;
+	type GasWeightMapping = FixedGasWeightMapping;
+	type BlockHashMapping = pallet_ethereum::EthereumBlockHashMapping<Self>;
+	type CallOrigin = EnsureAddressTruncated<Self>;
+	type WithdrawOrigin = EnsureAddressTruncated<Self>;
+	type AddressMapping = HashedAddressMapping<Self::Hashing>;
+	type PrecompilesType = ();
+	type PrecompilesValue = ();
+	type Currency = Balances;
+	type Event = Event;
+	type OnMethodCall = (
+		pallet_evm_migration::OnMethodCall<Self>,
+		pallet_evm_contract_helpers::HelpersOnMethodCall<Self>,
+		CollectionDispatchT<Self>,
+		pallet_unique::eth::CollectionHelpersOnMethodCall<Self>,
+	);
+	type OnCreate = pallet_evm_contract_helpers::HelpersOnCreate<Self>;
+	type ChainId = ChainId;
+	type Runner = pallet_evm::runner::stack::Runner<Self>;
+	type OnChargeTransaction = pallet_evm::EVMCurrencyAdapter<Balances, DealWithFees>;
+	type TransactionValidityHack = pallet_evm_transaction_payment::TransactionValidityHack<Self>;
+	type FindAuthor = EthereumFindAuthor<Aura>;
+}
+
+impl pallet_evm_migration::Config for Runtime {
+	type WeightInfo = pallet_evm_migration::weights::SubstrateWeight<Self>;
+}
+
+impl pallet_ethereum::Config for Runtime {
+	type Event = Event;
+	type StateRoot = pallet_ethereum::IntermediateStateRoot<Self>;
+}
+
+parameter_types! {
+	// 0x842899ECF380553E8a4de75bF534cdf6fBF64049
+	pub const HelpersContractAddress: H160 = H160([
+		0x84, 0x28, 0x99, 0xec, 0xf3, 0x80, 0x55, 0x3e, 0x8a, 0x4d, 0xe7, 0x5b, 0xf5, 0x34, 0xcd, 0xf6, 0xfb, 0xf6, 0x40, 0x49,
+	]);
+
+	// 0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f
+	pub const EvmCollectionHelpersAddress: H160 = H160([
+		0x6c, 0x4e, 0x9f, 0xe1, 0xae, 0x37, 0xa4, 0x1e, 0x93, 0xce, 0xe4, 0x29, 0xe8, 0xe1, 0x88, 0x1a, 0xbd, 0xcb, 0xb5, 0x4f,
+	]);
+}
+
+impl pallet_evm_contract_helpers::Config for Runtime {
+	type ContractAddress = HelpersContractAddress;
+	type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit;
+}
+
+impl pallet_evm_coder_substrate::Config for Runtime {}
+
+impl pallet_evm_transaction_payment::Config for Runtime {
+	type EvmSponsorshipHandler = EvmSponsorshipHandler;
+	type Currency = Balances;
+}
addedruntime/common/config/mod.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/config/mod.rs
@@ -0,0 +1,23 @@
+// 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/>.
+
+pub mod ethereum;
+pub mod orml;
+pub mod pallets;
+pub mod parachain;
+pub mod sponsoring;
+pub mod substrate;
+pub mod xcm;
addedruntime/common/config/orml.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/config/orml.rs
@@ -0,0 +1,38 @@
+// 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;
+use frame_system::EnsureSigned;
+use crate::{Runtime, Event, RelayChainBlockNumberProvider};
+use up_common::{
+	types::{AccountId, Balance},
+	constants::*,
+};
+
+parameter_types! {
+	pub const MinVestedTransfer: Balance = 10 * UNIQUE;
+	pub const MaxVestingSchedules: u32 = 28;
+}
+
+impl orml_vesting::Config for Runtime {
+	type Event = Event;
+	type Currency = pallet_balances::Pallet<Runtime>;
+	type MinVestedTransfer = MinVestedTransfer;
+	type VestedTransferOrigin = EnsureSigned<AccountId>;
+	type WeightInfo = ();
+	type MaxVestingSchedules = MaxVestingSchedules;
+	type BlockNumberProvider = RelayChainBlockNumberProvider<Runtime>;
+}
addedruntime/common/config/pallets/mod.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/config/pallets/mod.rs
@@ -0,0 +1,93 @@
+// 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;
+use sp_runtime::traits::AccountIdConversion;
+use crate::{
+	runtime_common::{
+		dispatch::CollectionDispatchT,
+		config::{substrate::TreasuryModuleId, ethereum::EvmCollectionHelpersAddress},
+		weights::CommonWeights,
+		RelayChainBlockNumberProvider,
+	},
+	Runtime, Event, Call, Balances,
+};
+use up_common::{
+	types::{AccountId, Balance, BlockNumber},
+	constants::*,
+};
+use up_data_structs::{
+	mapping::{EvmTokenAddressMapping, CrossTokenAddressMapping},
+};
+
+#[cfg(feature = "rmrk")]
+pub mod rmrk;
+
+#[cfg(feature = "scheduler")]
+pub mod scheduler;
+
+parameter_types! {
+	pub TreasuryAccountId: AccountId = TreasuryModuleId::get().into_account_truncating();
+	pub const CollectionCreationPrice: Balance = 2 * UNIQUE;
+}
+
+impl pallet_common::Config for Runtime {
+	type WeightInfo = pallet_common::weights::SubstrateWeight<Self>;
+	type Event = Event;
+	type Currency = Balances;
+	type CollectionCreationPrice = CollectionCreationPrice;
+	type TreasuryAccountId = TreasuryAccountId;
+	type CollectionDispatch = CollectionDispatchT<Self>;
+
+	type EvmTokenAddressMapping = EvmTokenAddressMapping;
+	type CrossTokenAddressMapping = CrossTokenAddressMapping<Self::AccountId>;
+	type ContractAddress = EvmCollectionHelpersAddress;
+}
+
+impl pallet_structure::Config for Runtime {
+	type Event = Event;
+	type Call = Call;
+	type WeightInfo = pallet_structure::weights::SubstrateWeight<Self>;
+}
+
+impl pallet_fungible::Config for Runtime {
+	type WeightInfo = pallet_fungible::weights::SubstrateWeight<Self>;
+}
+impl pallet_refungible::Config for Runtime {
+	type WeightInfo = pallet_refungible::weights::SubstrateWeight<Self>;
+}
+impl pallet_nonfungible::Config for Runtime {
+	type WeightInfo = pallet_nonfungible::weights::SubstrateWeight<Self>;
+}
+
+parameter_types! {
+	pub const InflationBlockInterval: BlockNumber = 100; // every time per how many blocks inflation is applied
+}
+
+/// Used for the pallet inflation
+impl pallet_inflation::Config for Runtime {
+	type Currency = Balances;
+	type TreasuryAccountId = TreasuryAccountId;
+	type InflationBlockInterval = InflationBlockInterval;
+	type BlockNumberProvider = RelayChainBlockNumberProvider<Runtime>;
+}
+
+impl pallet_unique::Config for Runtime {
+	type Event = Event;
+	type WeightInfo = pallet_unique::weights::SubstrateWeight<Self>;
+	type CommonWeightInfo = CommonWeights<Self>;
+	type RefungibleExtensionsWeightInfo = CommonWeights<Self>;
+}
addedruntime/common/config/pallets/rmrk.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/config/pallets/rmrk.rs
@@ -0,0 +1,27 @@
+// 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 crate::{Runtime, Event};
+
+impl pallet_proxy_rmrk_core::Config for Runtime {
+	type WeightInfo = pallet_proxy_rmrk_core::weights::SubstrateWeight<Self>;
+	type Event = Event;
+}
+
+impl pallet_proxy_rmrk_equip::Config for Runtime {
+	type WeightInfo = pallet_proxy_rmrk_equip::weights::SubstrateWeight<Self>;
+	type Event = Event;
+}
addedruntime/common/config/pallets/scheduler.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/config/pallets/scheduler.rs
@@ -0,0 +1,59 @@
+// 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::{traits::PrivilegeCmp, weights::Weight, parameter_types};
+use frame_system::EnsureSigned;
+use sp_runtime::Perbill;
+use sp_std::cmp::Ordering;
+use crate::{
+	runtime_common::{scheduler::SchedulerPaymentExecutor, config::substrate::RuntimeBlockWeights},
+	Runtime, Call, Event, Origin, OriginCaller, Balances,
+};
+use up_common::types::AccountId;
+
+parameter_types! {
+	pub MaximumSchedulerWeight: Weight = Perbill::from_percent(50) *
+		RuntimeBlockWeights::get().max_block;
+	pub const MaxScheduledPerBlock: u32 = 50;
+
+	pub const NoPreimagePostponement: Option<u32> = Some(10);
+	pub const Preimage: Option<u32> = Some(10);
+}
+
+/// Used the compare the privilege of an origin inside the scheduler.
+pub struct OriginPrivilegeCmp;
+
+impl PrivilegeCmp<OriginCaller> for OriginPrivilegeCmp {
+	fn cmp_privilege(_left: &OriginCaller, _right: &OriginCaller) -> Option<Ordering> {
+		Some(Ordering::Equal)
+	}
+}
+
+impl pallet_unique_scheduler::Config for Runtime {
+	type Event = Event;
+	type Origin = Origin;
+	type Currency = Balances;
+	type PalletsOrigin = OriginCaller;
+	type Call = Call;
+	type MaximumWeight = MaximumSchedulerWeight;
+	type ScheduleOrigin = EnsureSigned<AccountId>;
+	type MaxScheduledPerBlock = MaxScheduledPerBlock;
+	type WeightInfo = ();
+	type CallExecutor = SchedulerPaymentExecutor;
+	type OriginPrivilegeCmp = OriginPrivilegeCmp;
+	type PreimageProvider = ();
+	type NoPreimagePostponement = NoPreimagePostponement;
+}
addedruntime/common/config/parachain.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/config/parachain.rs
@@ -0,0 +1,44 @@
+// 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::{weights::Weight, parameter_types};
+use crate::{Runtime, Event, XcmpQueue, DmpQueue};
+use up_common::constants::*;
+
+parameter_types! {
+	pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 4;
+	pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 4;
+}
+
+impl cumulus_pallet_parachain_system::Config for Runtime {
+	type Event = Event;
+	type SelfParaId = parachain_info::Pallet<Self>;
+	type OnSystemEvent = ();
+	// type DownwardMessageHandlers = cumulus_primitives_utility::UnqueuedDmpAsParent<
+	// 	MaxDownwardMessageWeight,
+	// 	XcmExecutor<XcmConfig>,
+	// 	Call,
+	// >;
+	type OutboundXcmpMessageSource = XcmpQueue;
+	type DmpMessageHandler = DmpQueue;
+	type ReservedDmpWeight = ReservedDmpWeight;
+	type ReservedXcmpWeight = ReservedXcmpWeight;
+	type XcmpMessageHandler = XcmpQueue;
+}
+
+impl parachain_info::Config for Runtime {}
+
+impl cumulus_pallet_aura_ext::Config for Runtime {}
addedruntime/common/config/sponsoring.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/config/sponsoring.rs
@@ -0,0 +1,35 @@
+// 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;
+use crate::{
+	runtime_common::{sponsoring::UniqueSponsorshipHandler},
+	Runtime,
+};
+use up_common::{types::BlockNumber, constants::*};
+
+parameter_types! {
+	pub const DefaultSponsoringRateLimit: BlockNumber = 1 * DAYS;
+}
+
+type SponsorshipHandler = (
+	UniqueSponsorshipHandler<Runtime>,
+	pallet_evm_transaction_payment::BridgeSponsorshipHandler<Runtime>,
+);
+
+impl pallet_charge_transaction::Config for Runtime {
+	type SponsorshipHandler = SponsorshipHandler;
+}
addedruntime/common/config/substrate.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/config/substrate.rs
@@ -0,0 +1,237 @@
+// 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::{
+	traits::{Everything, ConstU32},
+	weights::{
+		constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight},
+		DispatchClass, WeightToFeePolynomial, WeightToFeeCoefficients, ConstantMultiplier,
+		WeightToFeeCoefficient,
+	},
+	parameter_types, PalletId,
+};
+use sp_runtime::{
+	generic,
+	traits::{BlakeTwo256, AccountIdLookup},
+	Perbill, Permill, Percent,
+};
+use frame_system::{
+	limits::{BlockLength, BlockWeights},
+	EnsureRoot,
+};
+use sp_arithmetic::traits::{BaseArithmetic, Unsigned};
+use smallvec::smallvec;
+use crate::{
+	runtime_common::DealWithFees, Runtime, Event, Call, Origin, PalletInfo, System, Balances,
+	Treasury, SS58Prefix, Version,
+};
+use up_common::{types::*, constants::*};
+
+parameter_types! {
+	pub const BlockHashCount: BlockNumber = 2400;
+	pub RuntimeBlockLength: BlockLength =
+		BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO);
+	pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75);
+	pub const MaximumBlockLength: u32 = 5 * 1024 * 1024;
+	pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder()
+		.base_block(BlockExecutionWeight::get())
+		.for_class(DispatchClass::all(), |weights| {
+			weights.base_extrinsic = ExtrinsicBaseWeight::get();
+		})
+		.for_class(DispatchClass::Normal, |weights| {
+			weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT);
+		})
+		.for_class(DispatchClass::Operational, |weights| {
+			weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT);
+			// Operational transactions have some extra reserved space, so that they
+			// are included even if block reached `MAXIMUM_BLOCK_WEIGHT`.
+			weights.reserved = Some(
+				MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT
+			);
+		})
+		.avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO)
+		.build_or_panic();
+}
+
+impl frame_system::Config for Runtime {
+	/// The data to be stored in an account.
+	type AccountData = pallet_balances::AccountData<Balance>;
+	/// The identifier used to distinguish between accounts.
+	type AccountId = AccountId;
+	/// The basic call filter to use in dispatchable.
+	type BaseCallFilter = Everything;
+	/// Maximum number of block number to block hash mappings to keep (oldest pruned first).
+	type BlockHashCount = BlockHashCount;
+	/// The maximum length of a block (in bytes).
+	type BlockLength = RuntimeBlockLength;
+	/// The index type for blocks.
+	type BlockNumber = BlockNumber;
+	/// The weight of the overhead invoked on the block import process, independent of the extrinsics included in that block.
+	type BlockWeights = RuntimeBlockWeights;
+	/// The aggregated dispatch type that is available for extrinsics.
+	type Call = Call;
+	/// The weight of database operations that the runtime can invoke.
+	type DbWeight = RocksDbWeight;
+	/// The ubiquitous event type.
+	type Event = Event;
+	/// The type for hashing blocks and tries.
+	type Hash = Hash;
+	/// The hashing algorithm used.
+	type Hashing = BlakeTwo256;
+	/// The header type.
+	type Header = generic::Header<BlockNumber, BlakeTwo256>;
+	/// The index type for storing how many extrinsics an account has signed.
+	type Index = Index;
+	/// The lookup mechanism to get account ID from whatever is passed in dispatchers.
+	type Lookup = AccountIdLookup<AccountId, ()>;
+	/// What to do if an account is fully reaped from the system.
+	type OnKilledAccount = ();
+	/// What to do if a new account is created.
+	type OnNewAccount = ();
+	type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode<Self>;
+	/// The ubiquitous origin type.
+	type Origin = Origin;
+	/// This type is being generated by `construct_runtime!`.
+	type PalletInfo = PalletInfo;
+	/// This is used as an identifier of the chain. 42 is the generic substrate prefix.
+	type SS58Prefix = SS58Prefix;
+	/// Weight information for the extrinsics of this pallet.
+	type SystemWeightInfo = frame_system::weights::SubstrateWeight<Self>;
+	/// Version of the runtime.
+	type Version = Version;
+	type MaxConsumers = ConstU32<16>;
+}
+
+impl pallet_randomness_collective_flip::Config for Runtime {}
+
+parameter_types! {
+	pub const MinimumPeriod: u64 = SLOT_DURATION / 2;
+}
+
+impl pallet_timestamp::Config for Runtime {
+	/// A timestamp: milliseconds since the unix epoch.
+	type Moment = u64;
+	type OnTimestampSet = ();
+	type MinimumPeriod = MinimumPeriod;
+	type WeightInfo = ();
+}
+
+parameter_types! {
+	// pub const ExistentialDeposit: u128 = 500;
+	pub const ExistentialDeposit: u128 = 0;
+	pub const MaxLocks: u32 = 50;
+	pub const MaxReserves: u32 = 50;
+}
+
+impl pallet_balances::Config for Runtime {
+	type MaxLocks = MaxLocks;
+	type MaxReserves = MaxReserves;
+	type ReserveIdentifier = [u8; 16];
+	/// The type for recording an account's balance.
+	type Balance = Balance;
+	/// The ubiquitous event type.
+	type Event = Event;
+	type DustRemoval = Treasury;
+	type ExistentialDeposit = ExistentialDeposit;
+	type AccountStore = System;
+	type WeightInfo = pallet_balances::weights::SubstrateWeight<Self>;
+}
+
+parameter_types! {
+	/// This value increases the priority of `Operational` transactions by adding
+	/// a "virtual tip" that's equal to the `OperationalFeeMultiplier * final_fee`.
+	pub const OperationalFeeMultiplier: u8 = 5;
+}
+
+/// Linear implementor of `WeightToFeePolynomial`
+pub struct LinearFee<T>(sp_std::marker::PhantomData<T>);
+
+impl<T> WeightToFeePolynomial for LinearFee<T>
+where
+	T: BaseArithmetic + From<u32> + Copy + Unsigned,
+{
+	type Balance = T;
+
+	fn polynomial() -> WeightToFeeCoefficients<Self::Balance> {
+		smallvec!(WeightToFeeCoefficient {
+			coeff_integer: WEIGHT_TO_FEE_COEFF.into(),
+			coeff_frac: Perbill::zero(),
+			negative: false,
+			degree: 1,
+		})
+	}
+}
+
+impl pallet_transaction_payment::Config for Runtime {
+	type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter<Balances, DealWithFees>;
+	type LengthToFee = ConstantMultiplier<Balance, TransactionByteFee>;
+	type OperationalFeeMultiplier = OperationalFeeMultiplier;
+	type WeightToFee = LinearFee<Balance>;
+	type FeeMultiplierUpdate = ();
+}
+
+parameter_types! {
+	pub const ProposalBond: Permill = Permill::from_percent(5);
+	pub const ProposalBondMinimum: Balance = 1 * UNIQUE;
+	pub const ProposalBondMaximum: Balance = 1000 * UNIQUE;
+	pub const SpendPeriod: BlockNumber = 5 * MINUTES;
+	pub const Burn: Permill = Permill::from_percent(0);
+	pub const TipCountdown: BlockNumber = 1 * DAYS;
+	pub const TipFindersFee: Percent = Percent::from_percent(20);
+	pub const TipReportDepositBase: Balance = 1 * UNIQUE;
+	pub const DataDepositPerByte: Balance = 1 * CENTIUNIQUE;
+	pub const BountyDepositBase: Balance = 1 * UNIQUE;
+	pub const BountyDepositPayoutDelay: BlockNumber = 1 * DAYS;
+	pub const TreasuryModuleId: PalletId = PalletId(*b"py/trsry");
+	pub const BountyUpdatePeriod: BlockNumber = 14 * DAYS;
+	pub const MaximumReasonLength: u32 = 16384;
+	pub const BountyCuratorDeposit: Permill = Permill::from_percent(50);
+	pub const BountyValueMinimum: Balance = 5 * UNIQUE;
+	pub const MaxApprovals: u32 = 100;
+}
+
+impl pallet_treasury::Config for Runtime {
+	type PalletId = TreasuryModuleId;
+	type Currency = Balances;
+	type ApproveOrigin = EnsureRoot<AccountId>;
+	type RejectOrigin = EnsureRoot<AccountId>;
+	type Event = Event;
+	type OnSlash = ();
+	type ProposalBond = ProposalBond;
+	type ProposalBondMinimum = ProposalBondMinimum;
+	type ProposalBondMaximum = ProposalBondMaximum;
+	type SpendPeriod = SpendPeriod;
+	type Burn = Burn;
+	type BurnDestination = ();
+	type SpendFunds = ();
+	type WeightInfo = pallet_treasury::weights::SubstrateWeight<Self>;
+	type MaxApprovals = MaxApprovals;
+}
+
+impl pallet_sudo::Config for Runtime {
+	type Event = Event;
+	type Call = Call;
+}
+
+parameter_types! {
+	pub const MaxAuthorities: u32 = 100_000;
+}
+
+impl pallet_aura::Config for Runtime {
+	type AuthorityId = AuraId;
+	type DisabledValidators = ();
+	type MaxAuthorities = MaxAuthorities;
+}
addedruntime/common/config/xcm.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/config/xcm.rs
@@ -0,0 +1,295 @@
+// 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::{
+	traits::{
+		tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT, Get, Everything,
+	},
+	weights::{Weight, WeightToFeePolynomial, WeightToFee},
+	parameter_types, match_types,
+};
+use frame_system::EnsureRoot;
+use sp_runtime::{
+	traits::{Saturating, CheckedConversion, Zero},
+	SaturatedConversion,
+};
+use pallet_xcm::XcmPassthrough;
+use polkadot_parachain::primitives::Sibling;
+use xcm::v1::{BodyId, Junction::*, MultiLocation, NetworkId, Junctions::*};
+use xcm::latest::{
+	AssetId::{Concrete},
+	Fungibility::Fungible as XcmFungible,
+	MultiAsset, Error as XcmError,
+};
+use xcm_executor::traits::{MatchesFungible, WeightTrader};
+use xcm_builder::{
+	AccountId32Aliases, AllowTopLevelPaidExecutionFrom, CurrencyAdapter, EnsureXcmOrigin,
+	FixedWeightBounds, LocationInverter, NativeAsset, ParentAsSuperuser, RelayChainAsNative,
+	SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative,
+	SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, ParentIsPreset,
+};
+use xcm_executor::{Config, XcmExecutor, Assets};
+use sp_std::marker::PhantomData;
+use crate::{
+	runtime_common::config::substrate::LinearFee, Runtime, Call, Event, Origin, Balances,
+	ParachainInfo, ParachainSystem, PolkadotXcm, XcmpQueue,
+};
+use up_common::{
+	types::{AccountId, Balance},
+	constants::*,
+};
+
+parameter_types! {
+	pub const RelayLocation: MultiLocation = MultiLocation::parent();
+	pub const RelayNetwork: NetworkId = NetworkId::Polkadot;
+	pub RelayOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into();
+	pub Ancestry: MultiLocation = Parachain(ParachainInfo::parachain_id().into()).into();
+}
+
+/// 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>,
+);
+
+pub struct OnlySelfCurrency;
+impl<B: TryFrom<u128>> MatchesFungible<B> for OnlySelfCurrency {
+	fn matches_fungible(a: &MultiAsset) -> Option<B> {
+		match (&a.id, &a.fun) {
+			(Concrete(_), 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.
+	(),
+>;
+
+/// No local origins on this chain are allowed to dispatch XCM sends/executions.
+pub type LocalOriginToLocation = (SignedToAccountId32<Origin, 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, ()>,
+	// ..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, Origin>,
+	// Native converter for Relay-chain (Parent) location; will converts to a `Relay` origin when
+	// recognised.
+	RelayChainAsNative<RelayOrigin, Origin>,
+	// Native converter for sibling Parachains; will convert to a `SiblingPara` origin when
+	// recognised.
+	SiblingParachainAsNative<cumulus_pallet_xcm::Origin, Origin>,
+	// Superuser converter for the Relay-chain (Parent) location. This will allow it to issue a
+	// transaction from the Root origin.
+	ParentAsSuperuser<Origin>,
+	// Native signed account converter; this just converts an `AccountId32` origin into a normal
+	// `Origin::Signed` origin of the same 32-byte value.
+	SignedAccountId32AsNative<RelayNetwork, Origin>,
+	// Xcm origins can be represented natively under the Xcm pallet's Xcm origin.
+	XcmPassthrough<Origin>,
+);
+
+parameter_types! {
+	// One XCM operation is 1_000_000 weight - almost certainly a conservative estimate.
+	pub UnitWeightCost: Weight = 1_000_000;
+	// 1200 UNIQUEs buy 1 second of weight.
+	pub const WeightPrice: (MultiLocation, u128) = (MultiLocation::parent(), 1_200 * UNIQUE);
+	pub const MaxInstructions: u32 = 100;
+}
+
+match_types! {
+	pub type ParentOrParentsUnitPlurality: impl Contains<MultiLocation> = {
+		MultiLocation { parents: 1, interior: Here } |
+		MultiLocation { parents: 1, interior: X1(Plurality { id: BodyId::Unit, .. }) }
+	};
+}
+
+pub type Barrier = (
+	TakeWeightCredit,
+	AllowTopLevelPaidExecutionFrom<Everything>,
+	// ^^^ Parent & its unit plurality gets free execution
+);
+
+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 {
+		Self(0, Zero::zero(), PhantomData)
+	}
+
+	fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, XcmError> {
+		let amount = WeightToFee::weight_to_fee(&weight);
+		let u128_amount: u128 = amount.try_into().map_err(|_| XcmError::Overflow)?;
+
+		// location to this parachain through relay chain
+		let option1: xcm::v1::AssetId = Concrete(MultiLocation {
+			parents: 1,
+			interior: X1(Parachain(ParachainInfo::parachain_id().into())),
+		});
+		// direct location
+		let option2: xcm::v1::AssetId = Concrete(MultiLocation {
+			parents: 0,
+			interior: Here,
+		});
+
+		let required = if payment.fungible.contains_key(&option1) {
+			(option1, u128_amount).into()
+		} else if payment.fungible.contains_key(&option2) {
+			(option2, u128_amount).into()
+		} else {
+			(Concrete(MultiLocation::default()), u128_amount).into()
+		};
+
+		let unused = payment
+			.checked_sub(required)
+			.map_err(|_| XcmError::TooExpensive)?;
+		self.0 = self.0.saturating_add(weight);
+		self.1 = self.1.saturating_add(amount);
+		Ok(unused)
+	}
+
+	fn refund_weight(&mut self, weight: Weight) -> Option<MultiAsset> {
+		let weight = weight.min(self.0);
+		let amount = WeightToFee::weight_to_fee(&weight);
+		self.0 -= weight;
+		self.1 = self.1.saturating_sub(amount);
+		let amount: u128 = amount.saturated_into();
+		if amount > 0 {
+			Some((AssetId::get(), amount).into())
+		} else {
+			None
+		}
+	}
+}
+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 struct XcmConfig;
+impl Config for XcmConfig {
+	type Call = Call;
+	type XcmSender = XcmRouter;
+	// How to withdraw and deposit an asset.
+	type AssetTransactor = LocalAssetTransactor;
+	type OriginConverter = XcmOriginToTransactDispatchOrigin;
+	type IsReserve = NativeAsset;
+	type IsTeleporter = (); // Teleportation is disabled
+	type LocationInverter = LocationInverter<Ancestry>;
+	type Barrier = Barrier;
+	type Weigher = FixedWeightBounds<UnitWeightCost, Call, MaxInstructions>;
+	type Trader =
+		UsingOnlySelfCurrencyComponents<LinearFee<Balance>, RelayLocation, AccountId, Balances, ()>;
+	type ResponseHandler = (); // Don't handle responses for now.
+	type SubscriptionService = PolkadotXcm;
+
+	type AssetTrap = PolkadotXcm;
+	type AssetClaims = PolkadotXcm;
+}
+
+impl pallet_xcm::Config for Runtime {
+	type Event = Event;
+	type SendXcmOrigin = EnsureXcmOrigin<Origin, LocalOriginToLocation>;
+	type XcmRouter = XcmRouter;
+	type ExecuteXcmOrigin = EnsureXcmOrigin<Origin, LocalOriginToLocation>;
+	type XcmExecuteFilter = Everything;
+	type XcmExecutor = XcmExecutor<XcmConfig>;
+	type XcmTeleportFilter = Everything;
+	type XcmReserveTransferFilter = Everything;
+	type Weigher = FixedWeightBounds<UnitWeightCost, Call, MaxInstructions>;
+	type LocationInverter = LocationInverter<Ancestry>;
+	type Origin = Origin;
+	type Call = Call;
+	const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
+	type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion;
+}
+
+impl cumulus_pallet_xcm::Config for Runtime {
+	type Event = Event;
+	type XcmExecutor = XcmExecutor<XcmConfig>;
+}
+
+impl cumulus_pallet_xcmp_queue::Config for Runtime {
+	type WeightInfo = ();
+	type Event = Event;
+	type XcmExecutor = XcmExecutor<XcmConfig>;
+	type ChannelInfo = ParachainSystem;
+	type VersionWrapper = ();
+	type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
+	type ControllerOrigin = EnsureRoot<AccountId>;
+	type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin;
+}
+
+impl cumulus_pallet_dmp_queue::Config for Runtime {
+	type Event = Event;
+	type XcmExecutor = XcmExecutor<XcmConfig>;
+	type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
+}
addedruntime/common/constants.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/constants.rs
@@ -0,0 +1,55 @@
+// 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 sp_runtime::Perbill;
+use frame_support::{
+	parameter_types,
+	weights::{Weight, constants::WEIGHT_PER_SECOND},
+};
+use up_common::types::{BlockNumber, Balance};
+
+pub const MILLISECS_PER_BLOCK: u64 = 12000;
+
+pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK;
+
+// These time units are defined in number of blocks.
+pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber);
+pub const HOURS: BlockNumber = MINUTES * 60;
+pub const DAYS: BlockNumber = HOURS * 24;
+
+pub const MICROUNIQUE: Balance = 1_000_000_000_000;
+pub const MILLIUNIQUE: Balance = 1_000 * MICROUNIQUE;
+pub const CENTIUNIQUE: Balance = 10 * MILLIUNIQUE;
+pub const UNIQUE: Balance = 100 * CENTIUNIQUE;
+
+// Targeting 0.1 UNQ per transfer
+pub const WEIGHT_TO_FEE_COEFF: u32 = 207_890_902;
+
+// Targeting 0.15 UNQ per transfer via ETH
+pub const MIN_GAS_PRICE: u64 = 1_019_493_469_850;
+
+/// We assume that ~10% of the block weight is consumed by `on_initalize` handlers.
+/// This is used to limit the maximal weight of a single extrinsic.
+pub const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(10);
+/// We allow `Normal` extrinsics to fill up the block up to 75%, the rest can be used
+/// by  Operational  extrinsics.
+pub const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75);
+/// We allow for 2 seconds of compute with a 6 second average block time.
+pub const MAXIMUM_BLOCK_WEIGHT: Weight = WEIGHT_PER_SECOND / 2;
+
+parameter_types! {
+	pub const TransactionByteFee: Balance = 501 * MICROUNIQUE;
+}
addedruntime/common/construct_runtime/mod.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/construct_runtime/mod.rs
@@ -0,0 +1,90 @@
+// 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/>.
+
+mod util;
+
+#[macro_export]
+macro_rules! construct_runtime {
+    ($select_runtime:ident) => {
+        $crate::construct_runtime_impl! {
+            select_runtime($select_runtime);
+
+            pub enum Runtime where
+                Block = Block,
+                NodeBlock = opaque::Block,
+                UncheckedExtrinsic = UncheckedExtrinsic
+            {
+                ParachainSystem: cumulus_pallet_parachain_system::{Pallet, Call, Config, Storage, Inherent, Event<T>, ValidateUnsigned} = 20,
+                ParachainInfo: parachain_info::{Pallet, Storage, Config} = 21,
+
+                Aura: pallet_aura::{Pallet, Config<T>} = 22,
+                AuraExt: cumulus_pallet_aura_ext::{Pallet, Config} = 23,
+
+                Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>} = 30,
+                RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Pallet, Storage} = 31,
+                Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent} = 32,
+                TransactionPayment: pallet_transaction_payment::{Pallet, Storage} = 33,
+                Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event<T>} = 34,
+                Sudo: pallet_sudo::{Pallet, Call, Storage, Config<T>, Event<T>} = 35,
+                System: frame_system::{Pallet, Call, Storage, Config, Event<T>} = 36,
+                Vesting: orml_vesting::{Pallet, Storage, Call, Event<T>, Config<T>} = 37,
+                // Vesting: pallet_vesting::{Pallet, Call, Config<T>, Storage, Event<T>} = 37,
+                // Contracts: pallet_contracts::{Pallet, Call, Storage, Event<T>} = 38,
+
+                // XCM helpers.
+                XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event<T>} = 50,
+                PolkadotXcm: pallet_xcm::{Pallet, Call, Event<T>, Origin} = 51,
+                CumulusXcm: cumulus_pallet_xcm::{Pallet, Call, Event<T>, Origin} = 52,
+                DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event<T>} = 53,
+
+                // Unique Pallets
+                Inflation: pallet_inflation::{Pallet, Call, Storage} = 60,
+                Unique: pallet_unique::{Pallet, Call, Storage, Event<T>} = 61,
+
+                #[runtimes(opal)]
+                Scheduler: pallet_unique_scheduler::{Pallet, Call, Storage, Event<T>} = 62,
+
+                // free = 63
+
+                Charging: pallet_charge_transaction::{Pallet, Call, Storage } = 64,
+                // ContractHelpers: pallet_contract_helpers::{Pallet, Call, Storage} = 65,
+                Common: pallet_common::{Pallet, Storage, Event<T>} = 66,
+                Fungible: pallet_fungible::{Pallet, Storage} = 67,
+
+                #[runtimes(opal)]
+                Refungible: pallet_refungible::{Pallet, Storage} = 68,
+
+                Nonfungible: pallet_nonfungible::{Pallet, Storage} = 69,
+                Structure: pallet_structure::{Pallet, Call, Storage, Event<T>} = 70,
+
+                #[runtimes(opal)]
+                RmrkCore: pallet_proxy_rmrk_core::{Pallet, Call, Storage, Event<T>} = 71,
+
+                #[runtimes(opal)]
+                RmrkEquip: pallet_proxy_rmrk_equip::{Pallet, Call, Storage, Event<T>} = 72,
+
+                // Frontier
+                EVM: pallet_evm::{Pallet, Config, Call, Storage, Event<T>} = 100,
+                Ethereum: pallet_ethereum::{Pallet, Config, Call, Storage, Event, Origin} = 101,
+
+                EvmCoderSubstrate: pallet_evm_coder_substrate::{Pallet, Storage} = 150,
+                EvmContractHelpers: pallet_evm_contract_helpers::{Pallet, Storage} = 151,
+                EvmTransactionPayment: pallet_evm_transaction_payment::{Pallet} = 152,
+                EvmMigration: pallet_evm_migration::{Pallet, Call, Storage} = 153,
+            }
+        }
+    }
+}
addedruntime/common/construct_runtime/util.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/construct_runtime/util.rs
@@ -0,0 +1,222 @@
+// 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/>.
+
+#[macro_export]
+macro_rules! construct_runtime_impl {
+    (
+        select_runtime($select_runtime:ident);
+
+        pub enum Runtime where
+            $($where_ident:ident = $where_ty:ty),* $(,)?
+        {
+            $(
+                $(#[runtimes($($pallet_runtimes:ident),+ $(,)?)])?
+                $pallet_name:ident: $pallet_mod:ident::{$($pallet_parts:ty),*} = $index:literal
+            ),*
+            $(,)?
+        }
+    ) => {
+        $crate::construct_runtime_helper! {
+            select_runtime($select_runtime),
+            selected_pallets(),
+
+            where_clause($($where_ident = $where_ty),*),
+            pallets(
+                $(
+                    $(#[runtimes($($pallet_runtimes),+)])?
+                    $pallet_name: $pallet_mod::{$($pallet_parts),*} = $index
+                ),*,
+            )
+        }
+    }
+}
+
+#[macro_export]
+macro_rules! construct_runtime_helper {
+    (
+        select_runtime($select_runtime:ident),
+        selected_pallets($($selected_pallets:tt)*),
+
+        where_clause($($where_clause:tt)*),
+        pallets(
+            #[runtimes($($pallet_runtimes:ident),+)]
+            $pallet_name:ident: $pallet_mod:ident::{$($pallet_parts:ty),*} = $index:literal,
+
+            $($pallets_tl:tt)*
+        )
+    ) => {
+        $crate::add_runtime_specific_pallets! {
+            select_runtime($select_runtime),
+            runtimes($($pallet_runtimes),+,),
+            selected_pallets($($selected_pallets)*),
+
+            where_clause($($where_clause)*),
+            pallets(
+                $pallet_name: $pallet_mod::{$($pallet_parts),*} = $index,
+                $($pallets_tl)*
+            )
+        }
+    };
+
+    (
+        select_runtime($select_runtime:ident),
+        selected_pallets($($selected_pallets:tt)*),
+
+        where_clause($($where_clause:tt)*),
+        pallets(
+            $pallet_name:ident: $pallet_mod:ident::{$($pallet_parts:ty),*} = $index:literal,
+
+            $($pallets_tl:tt)*
+        )
+    ) => {
+        $crate::construct_runtime_helper! {
+            select_runtime($select_runtime),
+            selected_pallets(
+                $($selected_pallets)*
+                $pallet_name: $pallet_mod::{$($pallet_parts),*} = $index,
+            ),
+
+            where_clause($($where_clause)*),
+            pallets($($pallets_tl)*)
+        }
+    };
+
+    (
+        select_runtime($select_runtime:ident),
+        selected_pallets($($selected_pallets:tt)*),
+
+        where_clause($($where_clause:tt)*),
+        pallets()
+    ) => {
+        frame_support::construct_runtime! {
+            pub enum Runtime where
+                $($where_clause)*
+            {
+                $($selected_pallets)*
+            }
+        }
+    };
+}
+
+#[macro_export]
+macro_rules! add_runtime_specific_pallets {
+    (
+        select_runtime(opal),
+        runtimes(opal, $($_runtime_tl:tt)*),
+        selected_pallets($($selected_pallets:tt)*),
+
+        where_clause($($where_clause:tt)*),
+        pallets(
+            $pallet_name:ident: $pallet_mod:ident::{$($pallet_parts:ty),*} = $index:literal,
+            $($pallets_tl:tt)*
+        )
+    ) => {
+        $crate::construct_runtime_helper! {
+            select_runtime(opal),
+            selected_pallets(
+                $($selected_pallets)*
+                $pallet_name: $pallet_mod::{$($pallet_parts),*} = $index,
+            ),
+
+            where_clause($($where_clause)*),
+            pallets($($pallets_tl)*)
+        }
+    };
+
+    (
+        select_runtime(quartz),
+        runtimes(quartz, $($_runtime_tl:tt)*),
+        selected_pallets($($selected_pallets:tt)*),
+
+        where_clause($($where_clause:tt)*),
+        pallets(
+            $pallet_name:ident: $pallet_mod:ident::{$($pallet_parts:ty),*} = $index:literal,
+            $($pallets_tl:tt)*
+        )
+    ) => {
+        $crate::construct_runtime_helper! {
+            select_runtime(quartz),
+            selected_pallets(
+                $($selected_pallets)*
+                $pallet_name: $pallet_mod::{$($pallet_parts),*} = $index,
+            ),
+
+            where_clause($($where_clause)*),
+            pallets($($pallets_tl)*)
+        }
+    };
+
+    (
+        select_runtime(unique),
+        runtimes(unique, $($_runtime_tl:tt)*),
+        selected_pallets($($selected_pallets:tt)*),
+
+        where_clause($($where_clause:tt)*),
+        pallets(
+            $pallet_name:ident: $pallet_mod:ident::{$($pallet_parts:ty),*} = $index:literal,
+            $($pallets_tl:tt)*
+        )
+    ) => {
+        $crate::construct_runtime_helper! {
+            select_runtime(unique),
+            selected_pallets(
+                $($selected_pallets)*
+                $pallet_name: $pallet_mod::{$($pallet_parts),*} = $index,
+            ),
+
+            where_clause($($where_clause)*),
+            pallets($($pallets_tl)*)
+        }
+    };
+
+    (
+        select_runtime($select_runtime:ident),
+        runtimes($_current_runtime:ident, $($runtime_tl:tt)*),
+        selected_pallets($($selected_pallets:tt)*),
+
+        where_clause($($where_clause:tt)*),
+        pallets($($pallets:tt)*)
+    ) => {
+        $crate::add_runtime_specific_pallets! {
+            select_runtime($select_runtime),
+            runtimes($($runtime_tl)*),
+            selected_pallets($($selected_pallets)*),
+
+            where_clause($($where_clause)*),
+            pallets($($pallets)*)
+        }
+    };
+
+    (
+        select_runtime($select_runtime:ident),
+        runtimes(),
+        selected_pallets($($selected_pallets:tt)*),
+
+        where_clause($($where_clause:tt)*),
+        pallets(
+            $_pallet_name:ident: $_pallet_mod:ident::{$($_pallet_parts:ty),*} = $_index:literal,
+            $($pallets_tl:tt)*
+        )
+    ) => {
+        $crate::construct_runtime_helper! {
+            select_runtime($select_runtime),
+            selected_pallets($($selected_pallets)*),
+
+            where_clause($($where_clause)*),
+            pallets($($pallets_tl)*)
+        }
+    };
+}
addedruntime/common/dispatch.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/dispatch.rs
@@ -0,0 +1,189 @@
+// 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::{dispatch::DispatchResult, ensure};
+use pallet_evm::{PrecompileHandle, PrecompileResult};
+use sp_core::H160;
+use sp_runtime::DispatchError;
+use sp_std::{borrow::ToOwned, vec::Vec};
+use pallet_common::{
+	CollectionById, CollectionHandle, CommonCollectionOperations, erc::CommonEvmHandler,
+	eth::map_eth_to_id,
+};
+pub use pallet_common::dispatch::CollectionDispatch;
+use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};
+use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};
+use pallet_refungible::{
+	Pallet as PalletRefungible, RefungibleHandle, erc_token::RefungibleTokenHandle,
+};
+use up_data_structs::{
+	CollectionMode, CreateCollectionData, MAX_DECIMAL_POINTS, mapping::TokenAddressMapping,
+	CollectionId,
+};
+
+#[cfg(not(feature = "refungible"))]
+use pallet_common::unsupported;
+
+pub enum CollectionDispatchT<T>
+where
+	T: pallet_fungible::Config + pallet_nonfungible::Config + pallet_refungible::Config,
+{
+	Fungible(FungibleHandle<T>),
+	Nonfungible(NonfungibleHandle<T>),
+	Refungible(RefungibleHandle<T>),
+}
+impl<T> CollectionDispatch<T> for CollectionDispatchT<T>
+where
+	T: pallet_common::Config
+		+ pallet_unique::Config
+		+ pallet_fungible::Config
+		+ pallet_nonfungible::Config
+		+ pallet_refungible::Config,
+{
+	fn create(
+		sender: T::CrossAccountId,
+		data: CreateCollectionData<T::AccountId>,
+	) -> Result<CollectionId, DispatchError> {
+		let id = match data.mode {
+			CollectionMode::NFT => <PalletNonfungible<T>>::init_collection(sender, data, false)?,
+			CollectionMode::Fungible(decimal_points) => {
+				// check params
+				ensure!(
+					decimal_points <= MAX_DECIMAL_POINTS,
+					pallet_unique::Error::<T>::CollectionDecimalPointLimitExceeded
+				);
+				<PalletFungible<T>>::init_collection(sender, data)?
+			}
+
+			#[cfg(feature = "refungible")]
+			CollectionMode::ReFungible => <PalletRefungible<T>>::init_collection(sender, data)?,
+
+			#[cfg(not(feature = "refungible"))]
+			CollectionMode::ReFungible => return unsupported!(T),
+		};
+		Ok(id)
+	}
+
+	fn destroy(sender: T::CrossAccountId, collection: CollectionHandle<T>) -> DispatchResult {
+		match collection.mode {
+			CollectionMode::ReFungible => {
+				PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?
+			}
+			CollectionMode::Fungible(_) => {
+				PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?
+			}
+			CollectionMode::NFT => {
+				PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?
+			}
+		}
+		Ok(())
+	}
+
+	fn dispatch(handle: CollectionHandle<T>) -> Self {
+		match handle.mode {
+			CollectionMode::Fungible(_) => Self::Fungible(FungibleHandle::cast(handle)),
+			CollectionMode::NFT => Self::Nonfungible(NonfungibleHandle::cast(handle)),
+			CollectionMode::ReFungible => Self::Refungible(RefungibleHandle::cast(handle)),
+		}
+	}
+
+	fn into_inner(self) -> CollectionHandle<T> {
+		match self {
+			Self::Fungible(f) => f.into_inner(),
+			Self::Nonfungible(f) => f.into_inner(),
+			Self::Refungible(f) => f.into_inner(),
+		}
+	}
+
+	fn as_dyn(&self) -> &dyn CommonCollectionOperations<T> {
+		match self {
+			Self::Fungible(h) => h,
+			Self::Nonfungible(h) => h,
+			Self::Refungible(h) => h,
+		}
+	}
+}
+
+impl<T> pallet_evm::OnMethodCall<T> for CollectionDispatchT<T>
+where
+	T: pallet_common::Config
+		+ pallet_unique::Config
+		+ pallet_fungible::Config
+		+ pallet_nonfungible::Config
+		+ pallet_refungible::Config,
+	T::AccountId: From<[u8; 32]>,
+{
+	fn is_reserved(target: &H160) -> bool {
+		map_eth_to_id(target).is_some()
+	}
+	fn is_used(target: &H160) -> bool {
+		map_eth_to_id(target)
+			.map(<CollectionById<T>>::contains_key)
+			.unwrap_or(false)
+	}
+	fn get_code(target: &H160) -> Option<Vec<u8>> {
+		if let Some(collection_id) = map_eth_to_id(target) {
+			let collection = <CollectionById<T>>::get(collection_id)?;
+			Some(
+				match collection.mode {
+					CollectionMode::NFT => <NonfungibleHandle<T>>::CODE,
+					CollectionMode::Fungible(_) => <FungibleHandle<T>>::CODE,
+					CollectionMode::ReFungible => <RefungibleHandle<T>>::CODE,
+				}
+				.to_owned(),
+			)
+		} else if let Some((collection_id, _token_id)) =
+			<T as pallet_common::Config>::EvmTokenAddressMapping::address_to_token(target)
+		{
+			let collection = <CollectionById<T>>::get(collection_id)?;
+			if collection.mode != CollectionMode::ReFungible {
+				return None;
+			}
+			// TODO: check token existence
+			Some(<RefungibleTokenHandle<T>>::CODE.to_owned())
+		} else {
+			None
+		}
+	}
+	fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {
+		if let Some(collection_id) = map_eth_to_id(&handle.code_address()) {
+			let collection =
+				<CollectionHandle<T>>::new_with_gas_limit(collection_id, handle.remaining_gas())?;
+			let dispatched = Self::dispatch(collection);
+
+			match dispatched {
+				Self::Fungible(h) => h.call(handle),
+				Self::Nonfungible(h) => h.call(handle),
+				Self::Refungible(h) => h.call(handle),
+			}
+		} else if let Some((collection_id, token_id)) =
+			<T as pallet_common::Config>::EvmTokenAddressMapping::address_to_token(
+				&handle.code_address(),
+			) {
+			let collection =
+				<CollectionHandle<T>>::new_with_gas_limit(collection_id, handle.remaining_gas())?;
+			if collection.mode != CollectionMode::ReFungible {
+				return None;
+			}
+
+			let h = RefungibleHandle::cast(collection);
+			// TODO: check token existence
+			RefungibleTokenHandle(h, token_id).call(handle)
+		} else {
+			None
+		}
+	}
+}
addedruntime/common/ethereum/mod.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/ethereum/mod.rs
@@ -0,0 +1,19 @@
+// 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/>.
+
+pub mod self_contained_call;
+pub mod sponsoring;
+pub mod transaction_converter;
addedruntime/common/ethereum/self_contained_call.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/ethereum/self_contained_call.rs
@@ -0,0 +1,74 @@
+// 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 sp_core::H160;
+use sp_runtime::{
+	traits::{Dispatchable, DispatchInfoOf, PostDispatchInfoOf},
+	transaction_validity::{TransactionValidityError, TransactionValidity},
+};
+use crate::{Origin, Call};
+
+impl fp_self_contained::SelfContainedCall for Call {
+	type SignedInfo = H160;
+
+	fn is_self_contained(&self) -> bool {
+		match self {
+			Call::Ethereum(call) => call.is_self_contained(),
+			_ => false,
+		}
+	}
+
+	fn check_self_contained(&self) -> Option<Result<Self::SignedInfo, TransactionValidityError>> {
+		match self {
+			Call::Ethereum(call) => call.check_self_contained(),
+			_ => None,
+		}
+	}
+
+	fn validate_self_contained(
+		&self,
+		info: &Self::SignedInfo,
+		dispatch_info: &DispatchInfoOf<Call>,
+		len: usize,
+	) -> Option<TransactionValidity> {
+		match self {
+			Call::Ethereum(call) => call.validate_self_contained(info, dispatch_info, len),
+			_ => None,
+		}
+	}
+
+	fn pre_dispatch_self_contained(
+		&self,
+		info: &Self::SignedInfo,
+	) -> Option<Result<(), TransactionValidityError>> {
+		match self {
+			Call::Ethereum(call) => call.pre_dispatch_self_contained(info),
+			_ => None,
+		}
+	}
+
+	fn apply_self_contained(
+		self,
+		info: Self::SignedInfo,
+	) -> Option<sp_runtime::DispatchResultWithInfo<PostDispatchInfoOf<Self>>> {
+		match self {
+			call @ Call::Ethereum(pallet_ethereum::Call::transact { .. }) => Some(call.dispatch(
+				Origin::from(pallet_ethereum::RawOrigin::EthereumTransaction(info)),
+			)),
+			_ => None,
+		}
+	}
+}
addedruntime/common/ethereum/sponsoring.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/ethereum/sponsoring.rs
@@ -0,0 +1,127 @@
+// 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/>.
+
+//! Implements EVM sponsoring logic via TransactionValidityHack
+
+use evm_coder::{Call, abi::AbiReader};
+use pallet_common::{CollectionHandle, eth::map_eth_to_id};
+use sp_core::H160;
+use sp_std::prelude::*;
+use up_sponsorship::SponsorshipHandler;
+use core::marker::PhantomData;
+use core::convert::TryInto;
+use pallet_evm::account::CrossAccountId;
+use up_data_structs::{TokenId, CreateItemData, CreateNftData, CollectionMode};
+use pallet_unique::Config as UniqueConfig;
+
+use crate::{Runtime, runtime_common::sponsoring::*};
+
+use pallet_nonfungible::erc::{
+	UniqueNFTCall, ERC721UniqueExtensionsCall, ERC721MintableCall, ERC721Call, TokenPropertiesCall,
+};
+use pallet_fungible::erc::{UniqueFungibleCall, ERC20Call};
+use pallet_fungible::Config as FungibleConfig;
+use pallet_nonfungible::Config as NonfungibleConfig;
+use pallet_refungible::Config as RefungibleConfig;
+
+pub type EvmSponsorshipHandler = (
+	UniqueEthSponsorshipHandler<Runtime>,
+	pallet_evm_contract_helpers::HelpersContractSponsoring<Runtime>,
+);
+
+pub struct UniqueEthSponsorshipHandler<T: UniqueConfig>(PhantomData<*const T>);
+impl<T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig>
+	SponsorshipHandler<T::CrossAccountId, (H160, Vec<u8>)> for UniqueEthSponsorshipHandler<T>
+{
+	fn get_sponsor(who: &T::CrossAccountId, call: &(H160, Vec<u8>)) -> Option<T::CrossAccountId> {
+		let collection_id = map_eth_to_id(&call.0)?;
+		let collection = <CollectionHandle<T>>::new(collection_id)?;
+		let sponsor = collection.sponsorship.sponsor()?.clone();
+		let (method_id, mut reader) = AbiReader::new_call(&call.1).ok()?;
+		Some(T::CrossAccountId::from_sub(match &collection.mode {
+			CollectionMode::NFT => {
+				let call = <UniqueNFTCall<T>>::parse(method_id, &mut reader).ok()??;
+				match call {
+					UniqueNFTCall::TokenProperties(TokenPropertiesCall::SetProperty {
+						token_id,
+						key,
+						value,
+						..
+					}) => {
+						let token_id: TokenId = token_id.try_into().ok()?;
+						withdraw_set_token_property::<T>(
+							&collection,
+							&who,
+							&token_id,
+							key.len() + value.len(),
+						)
+						.map(|()| sponsor)
+					}
+					UniqueNFTCall::ERC721UniqueExtensions(
+						ERC721UniqueExtensionsCall::Transfer { token_id, .. },
+					) => {
+						let token_id: TokenId = token_id.try_into().ok()?;
+						withdraw_transfer::<T>(&collection, &who, &token_id).map(|()| sponsor)
+					}
+					UniqueNFTCall::ERC721Mintable(
+						ERC721MintableCall::Mint { token_id, .. }
+						| ERC721MintableCall::MintWithTokenUri { token_id, .. },
+					) => {
+						let _token_id: TokenId = token_id.try_into().ok()?;
+						withdraw_create_item::<T>(
+							&collection,
+							&who,
+							&CreateItemData::NFT(CreateNftData::default()),
+						)
+						.map(|()| sponsor)
+					}
+					UniqueNFTCall::ERC721(ERC721Call::TransferFrom { token_id, from, .. }) => {
+						let token_id: TokenId = token_id.try_into().ok()?;
+						let from = T::CrossAccountId::from_eth(from);
+						withdraw_transfer::<T>(&collection, &from, &token_id).map(|()| sponsor)
+					}
+					UniqueNFTCall::ERC721(ERC721Call::Approve { token_id, .. }) => {
+						let token_id: TokenId = token_id.try_into().ok()?;
+						withdraw_approve::<T>(&collection, who.as_sub(), &token_id)
+							.map(|()| sponsor)
+					}
+					_ => None,
+				}
+			}
+			CollectionMode::Fungible(_) => {
+				let call = <UniqueFungibleCall<T>>::parse(method_id, &mut reader).ok()??;
+				#[allow(clippy::single_match)]
+				match call {
+					UniqueFungibleCall::ERC20(ERC20Call::Transfer { .. }) => {
+						withdraw_transfer::<T>(&collection, who, &TokenId::default())
+							.map(|()| sponsor)
+					}
+					UniqueFungibleCall::ERC20(ERC20Call::TransferFrom { from, .. }) => {
+						let from = T::CrossAccountId::from_eth(from);
+						withdraw_transfer::<T>(&collection, &from, &TokenId::default())
+							.map(|()| sponsor)
+					}
+					UniqueFungibleCall::ERC20(ERC20Call::Approve { .. }) => {
+						withdraw_approve::<T>(&collection, who.as_sub(), &TokenId::default())
+							.map(|()| sponsor)
+					}
+					_ => None,
+				}
+			}
+			_ => None,
+		}?))
+	}
+}
addedruntime/common/ethereum/transaction_converter.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/ethereum/transaction_converter.rs
@@ -0,0 +1,42 @@
+// 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 codec::{Encode, Decode};
+use crate::{opaque, Runtime, UncheckedExtrinsic};
+
+pub struct TransactionConverter;
+
+impl fp_rpc::ConvertTransaction<UncheckedExtrinsic> for TransactionConverter {
+	fn convert_transaction(&self, transaction: pallet_ethereum::Transaction) -> UncheckedExtrinsic {
+		UncheckedExtrinsic::new_unsigned(
+			pallet_ethereum::Call::<Runtime>::transact { transaction }.into(),
+		)
+	}
+}
+
+impl fp_rpc::ConvertTransaction<opaque::UncheckedExtrinsic> for TransactionConverter {
+	fn convert_transaction(
+		&self,
+		transaction: pallet_ethereum::Transaction,
+	) -> opaque::UncheckedExtrinsic {
+		let extrinsic = UncheckedExtrinsic::new_unsigned(
+			pallet_ethereum::Call::<Runtime>::transact { transaction }.into(),
+		);
+		let encoded = extrinsic.encode();
+		opaque::UncheckedExtrinsic::decode(&mut &encoded[..])
+			.expect("Encoded extrinsic is always valid")
+	}
+}
addedruntime/common/instance.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/instance.rs
@@ -0,0 +1,16 @@
+use crate::{
+	runtime_common::{
+		config::ethereum::CrossAccountId, ethereum::transaction_converter::TransactionConverter,
+	},
+	Runtime,
+};
+use up_common::types::opaque::RuntimeInstance;
+
+impl RuntimeInstance for Runtime {
+	type CrossAccountId = CrossAccountId;
+	type TransactionConverter = TransactionConverter;
+
+	fn get_transaction_converter() -> TransactionConverter {
+		TransactionConverter
+	}
+}
addedruntime/common/mod.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/mod.rs
@@ -0,0 +1,168 @@
+// 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/>.
+
+pub mod config;
+pub mod constants;
+pub mod construct_runtime;
+pub mod dispatch;
+pub mod ethereum;
+pub mod instance;
+pub mod runtime_apis;
+pub mod scheduler;
+pub mod sponsoring;
+pub mod weights;
+
+use sp_core::H160;
+use frame_support::traits::{Currency, OnUnbalanced, Imbalance};
+use sp_runtime::{
+	generic,
+	traits::{BlakeTwo256, BlockNumberProvider},
+	impl_opaque_keys,
+};
+use sp_std::vec::Vec;
+
+#[cfg(feature = "std")]
+use sp_version::NativeVersion;
+
+use crate::{
+	Runtime, Call, Balances, Treasury, Aura, Signature, AllPalletsReversedWithSystemFirst,
+	InherentDataExt,
+};
+use up_common::types::{AccountId, BlockNumber};
+
+#[macro_export]
+macro_rules! unsupported {
+	() => {
+		pallet_common::unsupported!($crate::Runtime)
+	};
+}
+
+/// The address format for describing accounts.
+pub type Address = sp_runtime::MultiAddress<AccountId, ()>;
+/// Block header type as expected by this runtime.
+pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
+/// Block type as expected by this runtime.
+pub type Block = generic::Block<Header, UncheckedExtrinsic>;
+/// A Block signed with a Justification
+pub type SignedBlock = generic::SignedBlock<Block>;
+/// BlockId type as expected by this runtime.
+pub type BlockId = generic::BlockId<Block>;
+
+impl_opaque_keys! {
+	pub struct SessionKeys {
+		pub aura: Aura,
+	}
+}
+
+/// The version information used to identify this runtime when compiled natively.
+#[cfg(feature = "std")]
+pub fn native_version() -> NativeVersion {
+	NativeVersion {
+		runtime_version: crate::VERSION,
+		can_author_with: Default::default(),
+	}
+}
+
+pub type ChargeTransactionPayment = pallet_charge_transaction::ChargeTransactionPayment<Runtime>;
+
+pub type SignedExtra = (
+	frame_system::CheckSpecVersion<Runtime>,
+	// system::CheckTxVersion<Runtime>,
+	frame_system::CheckGenesis<Runtime>,
+	frame_system::CheckEra<Runtime>,
+	frame_system::CheckNonce<Runtime>,
+	frame_system::CheckWeight<Runtime>,
+	ChargeTransactionPayment,
+	//pallet_contract_helpers::ContractHelpersExtension<Runtime>,
+	pallet_ethereum::FakeTransactionFinalizer<Runtime>,
+);
+
+/// Unchecked extrinsic type as expected by this runtime.
+pub type UncheckedExtrinsic =
+	fp_self_contained::UncheckedExtrinsic<Address, Call, Signature, SignedExtra>;
+
+/// Extrinsic type that has already been checked.
+pub type CheckedExtrinsic = fp_self_contained::CheckedExtrinsic<AccountId, Call, SignedExtra, H160>;
+
+/// Executive: handles dispatch to the various modules.
+pub type Executive = frame_executive::Executive<
+	Runtime,
+	Block,
+	frame_system::ChainContext<Runtime>,
+	Runtime,
+	AllPalletsReversedWithSystemFirst,
+>;
+
+type NegativeImbalance = <Balances as Currency<AccountId>>::NegativeImbalance;
+
+pub struct DealWithFees;
+impl OnUnbalanced<NegativeImbalance> for DealWithFees {
+	fn on_unbalanceds<B>(mut fees_then_tips: impl Iterator<Item = NegativeImbalance>) {
+		if let Some(fees) = fees_then_tips.next() {
+			// for fees, 100% to treasury
+			let mut split = fees.ration(100, 0);
+			if let Some(tips) = fees_then_tips.next() {
+				// for tips, if any, 100% to treasury
+				tips.ration_merge_into(100, 0, &mut split);
+			}
+			Treasury::on_unbalanced(split.0);
+			// Author::on_unbalanced(split.1);
+		}
+	}
+}
+
+pub struct RelayChainBlockNumberProvider<T>(sp_std::marker::PhantomData<T>);
+
+impl<T: cumulus_pallet_parachain_system::Config> BlockNumberProvider
+	for RelayChainBlockNumberProvider<T>
+{
+	type BlockNumber = BlockNumber;
+
+	fn current_block_number() -> Self::BlockNumber {
+		cumulus_pallet_parachain_system::Pallet::<T>::validation_data()
+			.map(|d| d.relay_parent_number)
+			.unwrap_or_default()
+	}
+}
+
+pub(crate) struct CheckInherents;
+
+impl cumulus_pallet_parachain_system::CheckInherents<Block> for CheckInherents {
+	fn check_inherents(
+		block: &Block,
+		relay_state_proof: &cumulus_pallet_parachain_system::RelayChainStateProof,
+	) -> sp_inherents::CheckInherentsResult {
+		let relay_chain_slot = relay_state_proof
+			.read_slot()
+			.expect("Could not read the relay chain slot from the proof");
+
+		let inherent_data =
+			cumulus_primitives_timestamp::InherentDataProvider::from_relay_chain_slot_and_duration(
+				relay_chain_slot,
+				sp_std::time::Duration::from_secs(6),
+			)
+			.create_inherent_data()
+			.expect("Could not create the timestamp inherent data");
+
+		inherent_data.check_extrinsics(block)
+	}
+}
+
+#[derive(codec::Encode, codec::Decode)]
+pub enum XCMPMessage<XAccountId, XBalance> {
+	/// Transfer tokens to the given account from the Parachain account.
+	TransferToken(XAccountId, XBalance),
+}
addedruntime/common/runtime_apis.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/runtime_apis.rs
@@ -0,0 +1,724 @@
+// 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/>.
+
+#[macro_export]
+macro_rules! dispatch_unique_runtime {
+	($collection:ident.$method:ident($($name:ident),*)) => {{
+		let collection = <Runtime as pallet_common::Config>::CollectionDispatch::dispatch(<pallet_common::CollectionHandle<Runtime>>::try_get($collection)?);
+		let dispatch = collection.as_dyn();
+
+		Ok::<_, DispatchError>(dispatch.$method($($name),*))
+	}};
+}
+
+#[macro_export]
+macro_rules! impl_common_runtime_apis {
+    (
+        $(
+            #![custom_apis]
+
+            $($custom_apis:tt)+
+        )?
+    ) => {
+        use sp_std::prelude::*;
+        use sp_api::impl_runtime_apis;
+        use sp_core::{crypto::KeyTypeId, OpaqueMetadata, H256, U256, H160};
+        use sp_runtime::{
+            Permill,
+            traits::Block as BlockT,
+            transaction_validity::{TransactionSource, TransactionValidity},
+            ApplyExtrinsicResult, DispatchError,
+        };
+        use fp_rpc::TransactionStatus;
+        use pallet_transaction_payment::{
+            FeeDetails, RuntimeDispatchInfo,
+        };
+        use pallet_evm::{
+            Runner, account::CrossAccountId as _,
+            Account as EVMAccount, FeeCalculator,
+        };
+        use runtime_common::{
+            sponsoring::{SponsorshipPredict, UniqueSponsorshipPredict},
+            dispatch::CollectionDispatch,
+            config::ethereum::CrossAccountId,
+        };
+        use up_data_structs::*;
+
+
+        impl_runtime_apis! {
+            $($($custom_apis)+)?
+
+            impl up_rpc::UniqueApi<Block, CrossAccountId, AccountId> for Runtime {
+                fn account_tokens(collection: CollectionId, account: CrossAccountId) -> Result<Vec<TokenId>, DispatchError> {
+                    dispatch_unique_runtime!(collection.account_tokens(account))
+                }
+                fn collection_tokens(collection: CollectionId) -> Result<Vec<TokenId>, DispatchError> {
+                    dispatch_unique_runtime!(collection.collection_tokens())
+                }
+                fn token_exists(collection: CollectionId, token: TokenId) -> Result<bool, DispatchError> {
+                    dispatch_unique_runtime!(collection.token_exists(token))
+                }
+
+                fn token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>, DispatchError> {
+                    dispatch_unique_runtime!(collection.token_owner(token))
+                }
+
+                fn token_owners(collection: CollectionId, token: TokenId) -> Result<Vec::<CrossAccountId>, DispatchError>  {
+                   dispatch_unique_runtime!(collection.token_owners(token))
+                }
+
+                fn topmost_token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>, DispatchError> {
+                    let budget = up_data_structs::budget::Value::new(10);
+
+                    Ok(Some(<pallet_structure::Pallet<Runtime>>::find_topmost_owner(collection, token, &budget)?))
+                }
+                fn token_children(collection: CollectionId, token: TokenId) -> Result<Vec<TokenChild>, DispatchError> {
+                    Ok(<pallet_nonfungible::Pallet<Runtime>>::token_children_ids(collection, token))
+                }
+                fn collection_properties(
+                    collection: CollectionId,
+                    keys: Option<Vec<Vec<u8>>>
+                ) -> Result<Vec<Property>, DispatchError> {
+                    let keys = keys.map(
+                        |keys| Common::bytes_keys_to_property_keys(keys)
+                    ).transpose()?;
+
+                    Common::filter_collection_properties(collection, keys)
+                }
+
+                fn token_properties(
+                    collection: CollectionId,
+                    token_id: TokenId,
+                    keys: Option<Vec<Vec<u8>>>
+                ) -> Result<Vec<Property>, DispatchError> {
+                    let keys = keys.map(
+                        |keys| Common::bytes_keys_to_property_keys(keys)
+                    ).transpose()?;
+
+                    dispatch_unique_runtime!(collection.token_properties(token_id, keys))
+                }
+
+                fn property_permissions(
+                    collection: CollectionId,
+                    keys: Option<Vec<Vec<u8>>>
+                ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {
+                    let keys = keys.map(
+                        |keys| Common::bytes_keys_to_property_keys(keys)
+                    ).transpose()?;
+
+                    Common::filter_property_permissions(collection, keys)
+                }
+
+                fn token_data(
+                    collection: CollectionId,
+                    token_id: TokenId,
+                    keys: Option<Vec<Vec<u8>>>
+                ) -> Result<TokenData<CrossAccountId>, DispatchError> {
+                    let token_data = TokenData {
+                        properties: Self::token_properties(collection, token_id, keys)?,
+                        owner: Self::token_owner(collection, token_id)?,
+                        pieces: Self::total_pieces(collection, token_id)?.unwrap_or(0),
+                    };
+
+                    Ok(token_data)
+                }
+
+                fn total_supply(collection: CollectionId) -> Result<u32, DispatchError> {
+                    dispatch_unique_runtime!(collection.total_supply())
+                }
+                fn account_balance(collection: CollectionId, account: CrossAccountId) -> Result<u32, DispatchError> {
+                    dispatch_unique_runtime!(collection.account_balance(account))
+                }
+                fn balance(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Result<u128, DispatchError> {
+                    dispatch_unique_runtime!(collection.balance(account, token))
+                }
+                fn allowance(
+                    collection: CollectionId,
+                    sender: CrossAccountId,
+                    spender: CrossAccountId,
+                    token: TokenId,
+                ) -> Result<u128, DispatchError> {
+                    dispatch_unique_runtime!(collection.allowance(sender, spender, token))
+                }
+
+                fn adminlist(collection: CollectionId) -> Result<Vec<CrossAccountId>, DispatchError> {
+                    Ok(<pallet_common::Pallet<Runtime>>::adminlist(collection))
+                }
+                fn allowlist(collection: CollectionId) -> Result<Vec<CrossAccountId>, DispatchError> {
+                    Ok(<pallet_common::Pallet<Runtime>>::allowlist(collection))
+                }
+                fn allowed(collection: CollectionId, user: CrossAccountId) -> Result<bool, DispatchError> {
+                    Ok(<pallet_common::Pallet<Runtime>>::allowed(collection, user))
+                }
+                fn last_token_id(collection: CollectionId) -> Result<TokenId, DispatchError> {
+                    dispatch_unique_runtime!(collection.last_token_id())
+                }
+                fn collection_by_id(collection: CollectionId) -> Result<Option<RpcCollection<AccountId>>, DispatchError> {
+                    Ok(<pallet_common::Pallet<Runtime>>::rpc_collection(collection))
+                }
+                fn collection_stats() -> Result<CollectionStats, DispatchError> {
+                    Ok(<pallet_common::Pallet<Runtime>>::collection_stats())
+                }
+                fn next_sponsored(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Result<Option<u64>, DispatchError> {
+                    Ok(<UniqueSponsorshipPredict<Runtime> as SponsorshipPredict<Runtime>>::predict(
+                        collection,
+                        account,
+                        token
+                    ))
+                }
+
+                fn effective_collection_limits(collection: CollectionId) -> Result<Option<CollectionLimits>, DispatchError> {
+                    Ok(<pallet_common::Pallet<Runtime>>::effective_collection_limits(collection))
+                }
+
+                fn total_pieces(collection: CollectionId, token_id: TokenId) -> Result<Option<u128>, DispatchError> {
+                    dispatch_unique_runtime!(collection.total_pieces(token_id))
+                }
+            }
+
+            impl rmrk_rpc::RmrkApi<
+                Block,
+                AccountId,
+                RmrkCollectionInfo<AccountId>,
+                RmrkInstanceInfo<AccountId>,
+                RmrkResourceInfo,
+                RmrkPropertyInfo,
+                RmrkBaseInfo<AccountId>,
+                RmrkPartType,
+                RmrkTheme
+            > for Runtime {
+                fn last_collection_idx() -> Result<RmrkCollectionId, DispatchError> {
+                    #[cfg(feature = "rmrk")]
+                    return pallet_proxy_rmrk_core::rpc::last_collection_idx::<Runtime>();
+
+                    #[cfg(not(feature = "rmrk"))]
+                    return unsupported!();
+                }
+
+                #[allow(unused_variables)]
+                fn collection_by_id(collection_id: RmrkCollectionId) -> Result<Option<RmrkCollectionInfo<AccountId>>, DispatchError> {
+                    #[cfg(feature = "rmrk")]
+                    return pallet_proxy_rmrk_core::rpc::collection_by_id::<Runtime>(collection_id);
+
+                    #[cfg(not(feature = "rmrk"))]
+                    return unsupported!();
+                }
+
+                #[allow(unused_variables)]
+                fn nft_by_id(collection_id: RmrkCollectionId, nft_by_id: RmrkNftId) -> Result<Option<RmrkInstanceInfo<AccountId>>, DispatchError> {
+                    #[cfg(feature = "rmrk")]
+                    return pallet_proxy_rmrk_core::rpc::nft_by_id::<Runtime>(collection_id, nft_by_id);
+
+                    #[cfg(not(feature = "rmrk"))]
+                    return unsupported!();
+                }
+
+                #[allow(unused_variables)]
+                fn account_tokens(account_id: AccountId, collection_id: RmrkCollectionId) -> Result<Vec<RmrkNftId>, DispatchError> {
+                    #[cfg(feature = "rmrk")]
+                    return pallet_proxy_rmrk_core::rpc::account_tokens::<Runtime>(account_id, collection_id);
+
+                    #[cfg(not(feature = "rmrk"))]
+                    return unsupported!();
+                }
+
+                #[allow(unused_variables)]
+                fn nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkNftChild>, DispatchError> {
+                    #[cfg(feature = "rmrk")]
+                    return pallet_proxy_rmrk_core::rpc::nft_children::<Runtime>(collection_id, nft_id);
+
+                    #[cfg(not(feature = "rmrk"))]
+                    return unsupported!();
+                }
+
+                #[allow(unused_variables)]
+                fn collection_properties(
+                    collection_id: RmrkCollectionId,
+                    filter_keys: Option<Vec<RmrkPropertyKey>>
+                ) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
+                    #[cfg(feature = "rmrk")]
+                    return pallet_proxy_rmrk_core::rpc::collection_properties::<Runtime>(collection_id, filter_keys);
+
+                    #[cfg(not(feature = "rmrk"))]
+                    return unsupported!();
+                }
+
+                #[allow(unused_variables)]
+                fn nft_properties(
+                    collection_id: RmrkCollectionId,
+                    nft_id: RmrkNftId,
+                    filter_keys: Option<Vec<RmrkPropertyKey>>
+                ) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
+                    #[cfg(feature = "rmrk")]
+                    return pallet_proxy_rmrk_core::rpc::nft_properties::<Runtime>(collection_id, nft_id, filter_keys);
+
+                    #[cfg(not(feature = "rmrk"))]
+                    return unsupported!();
+                }
+
+                #[allow(unused_variables)]
+                fn nft_resources(collection_id: RmrkCollectionId,nft_id: RmrkNftId) -> Result<Vec<RmrkResourceInfo>, DispatchError> {
+                    #[cfg(feature = "rmrk")]
+                    return pallet_proxy_rmrk_core::rpc::nft_resources::<Runtime>(collection_id, nft_id);
+
+                    #[cfg(not(feature = "rmrk"))]
+                    return unsupported!();
+                }
+
+                #[allow(unused_variables)]
+                fn nft_resource_priority(
+                    collection_id: RmrkCollectionId,
+                    nft_id: RmrkNftId,
+                    resource_id: RmrkResourceId
+                ) -> Result<Option<u32>, DispatchError> {
+                    #[cfg(feature = "rmrk")]
+                    return pallet_proxy_rmrk_core::rpc::nft_resource_priority::<Runtime>(collection_id, nft_id, resource_id);
+
+                    #[cfg(not(feature = "rmrk"))]
+                    return unsupported!();
+                }
+
+                #[allow(unused_variables)]
+                fn base(base_id: RmrkBaseId) -> Result<Option<RmrkBaseInfo<AccountId>>, DispatchError> {
+                    #[cfg(feature = "rmrk")]
+                    return pallet_proxy_rmrk_equip::rpc::base::<Runtime>(base_id);
+
+                    #[cfg(not(feature = "rmrk"))]
+                    return unsupported!();
+                }
+
+                #[allow(unused_variables)]
+                fn base_parts(base_id: RmrkBaseId) -> Result<Vec<RmrkPartType>, DispatchError> {
+                    #[cfg(feature = "rmrk")]
+                    return pallet_proxy_rmrk_equip::rpc::base_parts::<Runtime>(base_id);
+
+                    #[cfg(not(feature = "rmrk"))]
+                    return unsupported!();
+                }
+
+                #[allow(unused_variables)]
+                fn theme_names(base_id: RmrkBaseId) -> Result<Vec<RmrkThemeName>, DispatchError> {
+                    #[cfg(feature = "rmrk")]
+                    return pallet_proxy_rmrk_equip::rpc::theme_names::<Runtime>(base_id);
+
+                    #[cfg(not(feature = "rmrk"))]
+                    return unsupported!();
+                }
+
+                #[allow(unused_variables)]
+                fn theme(
+                    base_id: RmrkBaseId,
+                    theme_name: RmrkThemeName,
+                    filter_keys: Option<Vec<RmrkPropertyKey>>
+                ) -> Result<Option<RmrkTheme>, DispatchError> {
+                    #[cfg(feature = "rmrk")]
+                    return pallet_proxy_rmrk_equip::rpc::theme::<Runtime>(base_id, theme_name, filter_keys);
+
+                    #[cfg(not(feature = "rmrk"))]
+                    return unsupported!();
+                }
+            }
+
+            impl sp_api::Core<Block> for Runtime {
+                fn version() -> RuntimeVersion {
+                    VERSION
+                }
+
+                fn execute_block(block: Block) {
+                    Executive::execute_block(block)
+                }
+
+                fn initialize_block(header: &<Block as BlockT>::Header) {
+                    Executive::initialize_block(header)
+                }
+            }
+
+            impl sp_api::Metadata<Block> for Runtime {
+                fn metadata() -> OpaqueMetadata {
+                    OpaqueMetadata::new(Runtime::metadata().into())
+                }
+            }
+
+            impl sp_block_builder::BlockBuilder<Block> for Runtime {
+                fn apply_extrinsic(extrinsic: <Block as BlockT>::Extrinsic) -> ApplyExtrinsicResult {
+                    Executive::apply_extrinsic(extrinsic)
+                }
+
+                fn finalize_block() -> <Block as BlockT>::Header {
+                    Executive::finalize_block()
+                }
+
+                fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<<Block as BlockT>::Extrinsic> {
+                    data.create_extrinsics()
+                }
+
+                fn check_inherents(
+                    block: Block,
+                    data: sp_inherents::InherentData,
+                ) -> sp_inherents::CheckInherentsResult {
+                    data.check_extrinsics(&block)
+                }
+
+                // fn random_seed() -> <Block as BlockT>::Hash {
+                //     RandomnessCollectiveFlip::random_seed().0
+                // }
+            }
+
+            impl sp_transaction_pool::runtime_api::TaggedTransactionQueue<Block> for Runtime {
+                fn validate_transaction(
+                    source: TransactionSource,
+                    tx: <Block as BlockT>::Extrinsic,
+                    hash: <Block as BlockT>::Hash,
+                ) -> TransactionValidity {
+                    Executive::validate_transaction(source, tx, hash)
+                }
+            }
+
+            impl sp_offchain::OffchainWorkerApi<Block> for Runtime {
+                fn offchain_worker(header: &<Block as BlockT>::Header) {
+                    Executive::offchain_worker(header)
+                }
+            }
+
+            impl fp_rpc::EthereumRuntimeRPCApi<Block> for Runtime {
+                fn chain_id() -> u64 {
+                    <Runtime as pallet_evm::Config>::ChainId::get()
+                }
+
+                fn account_basic(address: H160) -> EVMAccount {
+                    let (account, _) = EVM::account_basic(&address);
+                    account
+                }
+
+                fn gas_price() -> U256 {
+                    let (price, _) = <Runtime as pallet_evm::Config>::FeeCalculator::min_gas_price();
+                    price
+                }
+
+                fn account_code_at(address: H160) -> Vec<u8> {
+                    EVM::account_codes(address)
+                }
+
+                fn author() -> H160 {
+                    <pallet_evm::Pallet<Runtime>>::find_author()
+                }
+
+                fn storage_at(address: H160, index: U256) -> H256 {
+                    let mut tmp = [0u8; 32];
+                    index.to_big_endian(&mut tmp);
+                    EVM::account_storages(address, H256::from_slice(&tmp[..]))
+                }
+
+                #[allow(clippy::redundant_closure)]
+                fn call(
+                    from: H160,
+                    to: H160,
+                    data: Vec<u8>,
+                    value: U256,
+                    gas_limit: U256,
+                    max_fee_per_gas: Option<U256>,
+                    max_priority_fee_per_gas: Option<U256>,
+                    nonce: Option<U256>,
+                    estimate: bool,
+                    access_list: Option<Vec<(H160, Vec<H256>)>>,
+                ) -> Result<pallet_evm::CallInfo, sp_runtime::DispatchError> {
+                    let config = if estimate {
+                        let mut config = <Runtime as pallet_evm::Config>::config().clone();
+                        config.estimate = true;
+                        Some(config)
+                    } else {
+                        None
+                    };
+
+                    let is_transactional = false;
+                    <Runtime as pallet_evm::Config>::Runner::call(
+                        CrossAccountId::from_eth(from),
+                        to,
+                        data,
+                        value,
+                        gas_limit.low_u64(),
+                        max_fee_per_gas,
+                        max_priority_fee_per_gas,
+                        nonce,
+                        access_list.unwrap_or_default(),
+                        is_transactional,
+                        config.as_ref().unwrap_or_else(|| <Runtime as pallet_evm::Config>::config()),
+                    ).map_err(|err| err.error.into())
+                }
+
+                #[allow(clippy::redundant_closure)]
+                fn create(
+                    from: H160,
+                    data: Vec<u8>,
+                    value: U256,
+                    gas_limit: U256,
+                    max_fee_per_gas: Option<U256>,
+                    max_priority_fee_per_gas: Option<U256>,
+                    nonce: Option<U256>,
+                    estimate: bool,
+                    access_list: Option<Vec<(H160, Vec<H256>)>>,
+                ) -> Result<pallet_evm::CreateInfo, sp_runtime::DispatchError> {
+                    let config = if estimate {
+                        let mut config = <Runtime as pallet_evm::Config>::config().clone();
+                        config.estimate = true;
+                        Some(config)
+                    } else {
+                        None
+                    };
+
+                    let is_transactional = false;
+                    <Runtime as pallet_evm::Config>::Runner::create(
+                        CrossAccountId::from_eth(from),
+                        data,
+                        value,
+                        gas_limit.low_u64(),
+                        max_fee_per_gas,
+                        max_priority_fee_per_gas,
+                        nonce,
+                        access_list.unwrap_or_default(),
+                        is_transactional,
+                        config.as_ref().unwrap_or_else(|| <Runtime as pallet_evm::Config>::config()),
+                    ).map_err(|err| err.error.into())
+                }
+
+                fn current_transaction_statuses() -> Option<Vec<TransactionStatus>> {
+                    Ethereum::current_transaction_statuses()
+                }
+
+                fn current_block() -> Option<pallet_ethereum::Block> {
+                    Ethereum::current_block()
+                }
+
+                fn current_receipts() -> Option<Vec<pallet_ethereum::Receipt>> {
+                    Ethereum::current_receipts()
+                }
+
+                fn current_all() -> (
+                    Option<pallet_ethereum::Block>,
+                    Option<Vec<pallet_ethereum::Receipt>>,
+                    Option<Vec<TransactionStatus>>
+                ) {
+                    (
+                        Ethereum::current_block(),
+                        Ethereum::current_receipts(),
+                        Ethereum::current_transaction_statuses()
+                    )
+                }
+
+                fn extrinsic_filter(xts: Vec<<Block as sp_api::BlockT>::Extrinsic>) -> Vec<pallet_ethereum::Transaction> {
+                    xts.into_iter().filter_map(|xt| match xt.0.function {
+                        Call::Ethereum(pallet_ethereum::Call::transact { transaction }) => Some(transaction),
+                        _ => None
+                    }).collect()
+                }
+
+                fn elasticity() -> Option<Permill> {
+                    None
+                }
+            }
+
+            impl fp_rpc::ConvertTransactionRuntimeApi<Block> for Runtime {
+                fn convert_transaction(transaction: pallet_ethereum::Transaction) -> <Block as BlockT>::Extrinsic  {
+                    UncheckedExtrinsic::new_unsigned(
+                        pallet_ethereum::Call::<Runtime>::transact { transaction }.into(),
+                    )
+                }
+            }
+
+            impl sp_session::SessionKeys<Block> for Runtime {
+                fn decode_session_keys(
+                    encoded: Vec<u8>,
+                ) -> Option<Vec<(Vec<u8>, KeyTypeId)>> {
+                    SessionKeys::decode_into_raw_public_keys(&encoded)
+                }
+
+                fn generate_session_keys(seed: Option<Vec<u8>>) -> Vec<u8> {
+                    SessionKeys::generate(seed)
+                }
+            }
+
+            impl sp_consensus_aura::AuraApi<Block, AuraId> for Runtime {
+                fn slot_duration() -> sp_consensus_aura::SlotDuration {
+                    sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration())
+                }
+
+                fn authorities() -> Vec<AuraId> {
+                    Aura::authorities().to_vec()
+                }
+            }
+
+            impl cumulus_primitives_core::CollectCollationInfo<Block> for Runtime {
+                fn collect_collation_info(header: &<Block as BlockT>::Header) -> cumulus_primitives_core::CollationInfo {
+                    ParachainSystem::collect_collation_info(header)
+                }
+            }
+
+            impl frame_system_rpc_runtime_api::AccountNonceApi<Block, AccountId, Index> for Runtime {
+                fn account_nonce(account: AccountId) -> Index {
+                    System::account_nonce(account)
+                }
+            }
+
+            impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi<Block, Balance> for Runtime {
+                fn query_info(uxt: <Block as BlockT>::Extrinsic, len: u32) -> RuntimeDispatchInfo<Balance> {
+                    TransactionPayment::query_info(uxt, len)
+                }
+                fn query_fee_details(uxt: <Block as BlockT>::Extrinsic, len: u32) -> FeeDetails<Balance> {
+                    TransactionPayment::query_fee_details(uxt, len)
+                }
+            }
+
+            /*
+            impl pallet_contracts_rpc_runtime_api::ContractsApi<Block, AccountId, Balance, BlockNumber, Hash>
+                for Runtime
+            {
+                fn call(
+                    origin: AccountId,
+                    dest: AccountId,
+                    value: Balance,
+                    gas_limit: u64,
+                    input_data: Vec<u8>,
+                ) -> pallet_contracts_primitives::ContractExecResult {
+                    Contracts::bare_call(origin, dest, value, gas_limit, input_data, false)
+                }
+
+                fn instantiate(
+                    origin: AccountId,
+                    endowment: Balance,
+                    gas_limit: u64,
+                    code: pallet_contracts_primitives::Code<Hash>,
+                    data: Vec<u8>,
+                    salt: Vec<u8>,
+                ) -> pallet_contracts_primitives::ContractInstantiateResult<AccountId, BlockNumber>
+                {
+                    Contracts::bare_instantiate(origin, endowment, gas_limit, code, data, salt, true, false)
+                }
+
+                fn get_storage(
+                    address: AccountId,
+                    key: [u8; 32],
+                ) -> pallet_contracts_primitives::GetStorageResult {
+                    Contracts::get_storage(address, key)
+                }
+
+                fn rent_projection(
+                    address: AccountId,
+                ) -> pallet_contracts_primitives::RentProjectionResult<BlockNumber> {
+                    Contracts::rent_projection(address)
+                }
+            }
+            */
+
+            #[cfg(feature = "runtime-benchmarks")]
+            impl frame_benchmarking::Benchmark<Block> for Runtime {
+                fn benchmark_metadata(extra: bool) -> (
+                    Vec<frame_benchmarking::BenchmarkList>,
+                    Vec<frame_support::traits::StorageInfo>,
+                ) {
+                    use frame_benchmarking::{list_benchmark, Benchmarking, BenchmarkList};
+                    use frame_support::traits::StorageInfoTrait;
+
+                    let mut list = Vec::<BenchmarkList>::new();
+
+                    list_benchmark!(list, extra, pallet_evm_migration, EvmMigration);
+                    list_benchmark!(list, extra, pallet_common, Common);
+                    list_benchmark!(list, extra, pallet_unique, Unique);
+                    list_benchmark!(list, extra, pallet_structure, Structure);
+                    list_benchmark!(list, extra, pallet_inflation, Inflation);
+                    list_benchmark!(list, extra, pallet_fungible, Fungible);
+                    list_benchmark!(list, extra, pallet_refungible, Refungible);
+                    list_benchmark!(list, extra, pallet_nonfungible, Nonfungible);
+                    list_benchmark!(list, extra, pallet_unique_scheduler, Scheduler);
+
+                    #[cfg(not(feature = "unique-runtime"))]
+                    list_benchmark!(list, extra, pallet_proxy_rmrk_core, RmrkCore);
+
+                    #[cfg(not(feature = "unique-runtime"))]
+                    list_benchmark!(list, extra, pallet_proxy_rmrk_equip, RmrkEquip);
+
+                    // list_benchmark!(list, extra, pallet_evm_coder_substrate, EvmCoderSubstrate);
+
+                    let storage_info = AllPalletsReversedWithSystemFirst::storage_info();
+
+                    return (list, storage_info)
+                }
+
+                fn dispatch_benchmark(
+                    config: frame_benchmarking::BenchmarkConfig
+                ) -> Result<Vec<frame_benchmarking::BenchmarkBatch>, sp_runtime::RuntimeString> {
+                    use frame_benchmarking::{Benchmarking, BenchmarkBatch, add_benchmark, TrackedStorageKey};
+
+                    let allowlist: Vec<TrackedStorageKey> = vec![
+                        // Total Issuance
+                        hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(),
+
+                        // Block Number
+                        hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(),
+                        // Execution Phase
+                        hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(),
+                        // Event Count
+                        hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(),
+                        // System Events
+                        hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(),
+
+                        // Evm CurrentLogs
+                        hex_literal::hex!("1da53b775b270400e7e61ed5cbc5a146547f210cec367e9af919603343b9cb56").to_vec().into(),
+
+                        // Transactional depth
+                        hex_literal::hex!("3a7472616e73616374696f6e5f6c6576656c3a").to_vec().into(),
+                    ];
+
+                    let mut batches = Vec::<BenchmarkBatch>::new();
+                    let params = (&config, &allowlist);
+
+                    add_benchmark!(params, batches, pallet_evm_migration, EvmMigration);
+                    add_benchmark!(params, batches, pallet_common, Common);
+                    add_benchmark!(params, batches, pallet_unique, Unique);
+                    add_benchmark!(params, batches, pallet_structure, Structure);
+                    add_benchmark!(params, batches, pallet_inflation, Inflation);
+                    add_benchmark!(params, batches, pallet_fungible, Fungible);
+                    add_benchmark!(params, batches, pallet_refungible, Refungible);
+                    add_benchmark!(params, batches, pallet_nonfungible, Nonfungible);
+                    add_benchmark!(params, batches, pallet_unique_scheduler, Scheduler);
+
+                    #[cfg(not(feature = "unique-runtime"))]
+                    add_benchmark!(params, batches, pallet_proxy_rmrk_core, RmrkCore);
+
+                    #[cfg(not(feature = "unique-runtime"))]
+                    add_benchmark!(params, batches, pallet_proxy_rmrk_equip, RmrkEquip);
+
+                    // add_benchmark!(params, batches, pallet_evm_coder_substrate, EvmCoderSubstrate);
+
+                    if batches.is_empty() { return Err("Benchmark not found for this pallet.".into()) }
+                    Ok(batches)
+                }
+            }
+
+            #[cfg(feature = "try-runtime")]
+            impl frame_try_runtime::TryRuntime<Block> for Runtime {
+                fn on_runtime_upgrade() -> (Weight, Weight) {
+                    log::info!("try-runtime::on_runtime_upgrade unique-chain.");
+                    let weight = Executive::try_runtime_upgrade().unwrap();
+                    (weight, RuntimeBlockWeights::get().max_block)
+                }
+
+                fn execute_block_no_check(block: Block) -> Weight {
+                    Executive::execute_block_no_check(block)
+                }
+            }
+        }
+    }
+}
addedruntime/common/scheduler.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/scheduler.rs
@@ -0,0 +1,141 @@
+// 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::{
+	traits::NamedReservableCurrency,
+	weights::{GetDispatchInfo, PostDispatchInfo, DispatchInfo},
+};
+use sp_runtime::{
+	traits::{Dispatchable, Applyable, Member},
+	generic::Era,
+	transaction_validity::TransactionValidityError,
+	DispatchErrorWithPostInfo, DispatchError,
+};
+use crate::{Runtime, Call, Origin, Balances, ChargeTransactionPayment};
+use up_common::types::{AccountId, Balance};
+use fp_self_contained::SelfContainedCall;
+use pallet_unique_scheduler::DispatchCall;
+
+/// The SignedExtension to the basic transaction logic.
+pub type SignedExtraScheduler = (
+	frame_system::CheckSpecVersion<Runtime>,
+	frame_system::CheckGenesis<Runtime>,
+	frame_system::CheckEra<Runtime>,
+	frame_system::CheckNonce<Runtime>,
+	frame_system::CheckWeight<Runtime>,
+);
+
+fn get_signed_extras(from: <Runtime as frame_system::Config>::AccountId) -> SignedExtraScheduler {
+	(
+		frame_system::CheckSpecVersion::<Runtime>::new(),
+		frame_system::CheckGenesis::<Runtime>::new(),
+		frame_system::CheckEra::<Runtime>::from(Era::Immortal),
+		frame_system::CheckNonce::<Runtime>::from(frame_system::Pallet::<Runtime>::account_nonce(
+			from,
+		)),
+		frame_system::CheckWeight::<Runtime>::new(),
+		// sponsoring transaction logic
+		// pallet_charge_transaction::ChargeTransactionPayment::<Runtime>::new(0),
+	)
+}
+
+pub struct SchedulerPaymentExecutor;
+
+impl<T: frame_system::Config + pallet_unique_scheduler::Config, SelfContainedSignedInfo>
+	DispatchCall<T, SelfContainedSignedInfo> for SchedulerPaymentExecutor
+where
+	<T as frame_system::Config>::Call: Member
+		+ Dispatchable<Origin = Origin, Info = DispatchInfo>
+		+ SelfContainedCall<SignedInfo = SelfContainedSignedInfo>
+		+ GetDispatchInfo
+		+ From<frame_system::Call<Runtime>>,
+	SelfContainedSignedInfo: Send + Sync + 'static,
+	Call: From<<T as frame_system::Config>::Call>
+		+ From<<T as pallet_unique_scheduler::Config>::Call>
+		+ SelfContainedCall<SignedInfo = SelfContainedSignedInfo>,
+	sp_runtime::AccountId32: From<<T as frame_system::Config>::AccountId>,
+{
+	fn dispatch_call(
+		signer: <T as frame_system::Config>::AccountId,
+		call: <T as pallet_unique_scheduler::Config>::Call,
+	) -> Result<
+		Result<PostDispatchInfo, DispatchErrorWithPostInfo<PostDispatchInfo>>,
+		TransactionValidityError,
+	> {
+		let dispatch_info = call.get_dispatch_info();
+		let extrinsic = fp_self_contained::CheckedExtrinsic::<
+			AccountId,
+			Call,
+			SignedExtraScheduler,
+			SelfContainedSignedInfo,
+		> {
+			signed: fp_self_contained::CheckedSignature::<
+				AccountId,
+				SignedExtraScheduler,
+				SelfContainedSignedInfo,
+			>::Signed(signer.clone().into(), get_signed_extras(signer.into())),
+			function: call.into(),
+		};
+
+		extrinsic.apply::<Runtime>(&dispatch_info, 0)
+	}
+
+	fn reserve_balance(
+		id: [u8; 16],
+		sponsor: <T as frame_system::Config>::AccountId,
+		call: <T as pallet_unique_scheduler::Config>::Call,
+		count: u32,
+	) -> Result<(), DispatchError> {
+		let dispatch_info = call.get_dispatch_info();
+		let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0)
+			.saturating_mul(count.into());
+
+		<Balances as NamedReservableCurrency<AccountId>>::reserve_named(
+			&id,
+			&(sponsor.into()),
+			weight,
+		)
+	}
+
+	fn pay_for_call(
+		id: [u8; 16],
+		sponsor: <T as frame_system::Config>::AccountId,
+		call: <T as pallet_unique_scheduler::Config>::Call,
+	) -> Result<u128, DispatchError> {
+		let dispatch_info = call.get_dispatch_info();
+		let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0);
+		Ok(
+			<Balances as NamedReservableCurrency<AccountId>>::unreserve_named(
+				&id,
+				&(sponsor.into()),
+				weight,
+			),
+		)
+	}
+
+	fn cancel_reserve(
+		id: [u8; 16],
+		sponsor: <T as frame_system::Config>::AccountId,
+	) -> Result<u128, DispatchError> {
+		Ok(
+			<Balances as NamedReservableCurrency<AccountId>>::unreserve_named(
+				&id,
+				&(sponsor.into()),
+				u128::MAX,
+			),
+		)
+	}
+}
addedruntime/common/sponsoring.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/sponsoring.rs
@@ -0,0 +1,359 @@
+// 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 core::marker::PhantomData;
+use up_sponsorship::SponsorshipHandler;
+use frame_support::{
+	traits::{IsSubType},
+	storage::{StorageMap, StorageDoubleMap, StorageNMap},
+};
+use up_data_structs::{
+	CollectionId, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, NFT_SPONSOR_TRANSFER_TIMEOUT,
+	REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, TokenId, CollectionMode, CreateItemData,
+};
+use sp_runtime::traits::Saturating;
+use pallet_common::{CollectionHandle};
+use pallet_evm::account::CrossAccountId;
+use pallet_unique::{
+	Call as UniqueCall, Config as UniqueConfig, FungibleApproveBasket, RefungibleApproveBasket,
+	NftApproveBasket, CreateItemBasket, ReFungibleTransferBasket, FungibleTransferBasket,
+	NftTransferBasket, TokenPropertyBasket,
+};
+use pallet_fungible::Config as FungibleConfig;
+use pallet_nonfungible::Config as NonfungibleConfig;
+use pallet_refungible::Config as RefungibleConfig;
+
+pub trait Config: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig {}
+impl<T> Config for T where T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig {}
+
+// TODO: permission check?
+pub fn withdraw_set_token_property<T: Config>(
+	collection: &CollectionHandle<T>,
+	who: &T::CrossAccountId,
+	item_id: &TokenId,
+	data_size: usize,
+) -> Option<()> {
+	// preliminary sponsoring correctness check
+	match collection.mode {
+		CollectionMode::NFT => {
+			let owner = pallet_nonfungible::TokenData::<T>::get((collection.id, item_id))?.owner;
+			if !owner.conv_eq(who) {
+				return None;
+			}
+		}
+		CollectionMode::Fungible(_) => {
+			// Fungible tokens have no properties
+			return None;
+		}
+		CollectionMode::ReFungible => {
+			if !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {
+				return None;
+			}
+		}
+	}
+
+	if data_size > collection.limits.sponsored_data_size() as usize {
+		return None;
+	}
+
+	let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+	let limit = collection.limits.sponsored_data_rate_limit()?;
+
+	if let Some(last_tx_block) = TokenPropertyBasket::<T>::get(collection.id, item_id) {
+		let timeout = last_tx_block + limit.into();
+		if block_number < timeout {
+			return None;
+		}
+	}
+
+	<TokenPropertyBasket<T>>::insert(collection.id, item_id, block_number);
+
+	Some(())
+}
+
+pub fn withdraw_transfer<T: Config>(
+	collection: &CollectionHandle<T>,
+	who: &T::CrossAccountId,
+	item_id: &TokenId,
+) -> Option<()> {
+	// preliminary sponsoring correctness check
+	match collection.mode {
+		CollectionMode::NFT => {
+			let owner = pallet_nonfungible::TokenData::<T>::get((collection.id, item_id))?.owner;
+			if !owner.conv_eq(who) {
+				return None;
+			}
+		}
+		CollectionMode::Fungible(_) => {
+			if item_id != &TokenId::default() {
+				return None;
+			}
+			if <pallet_fungible::Balance<T>>::get((collection.id, who)) == 0 {
+				return None;
+			}
+		}
+		CollectionMode::ReFungible => {
+			if !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {
+				return None;
+			}
+		}
+	}
+
+	// sponsor timeout
+	let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+	let limit = collection
+		.limits
+		.sponsor_transfer_timeout(match collection.mode {
+			CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,
+			CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+			CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+		});
+
+	let last_tx_block = match collection.mode {
+		CollectionMode::NFT => <NftTransferBasket<T>>::get(collection.id, item_id),
+		CollectionMode::Fungible(_) => {
+			<FungibleTransferBasket<T>>::get(collection.id, who.as_sub())
+		}
+		CollectionMode::ReFungible => {
+			<ReFungibleTransferBasket<T>>::get((collection.id, item_id, who.as_sub()))
+		}
+	};
+
+	if let Some(last_tx_block) = last_tx_block {
+		let timeout = last_tx_block + limit.into();
+		if block_number < timeout {
+			return None;
+		}
+	}
+
+	match collection.mode {
+		CollectionMode::NFT => <NftTransferBasket<T>>::insert(collection.id, item_id, block_number),
+		CollectionMode::Fungible(_) => {
+			<FungibleTransferBasket<T>>::insert(collection.id, who.as_sub(), block_number)
+		}
+		CollectionMode::ReFungible => <ReFungibleTransferBasket<T>>::insert(
+			(collection.id, item_id, who.as_sub()),
+			block_number,
+		),
+	};
+
+	Some(())
+}
+
+pub fn withdraw_create_item<T: Config>(
+	collection: &CollectionHandle<T>,
+	who: &T::CrossAccountId,
+	properties: &CreateItemData,
+) -> Option<()> {
+	// sponsor timeout
+	let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+	let limit = collection
+		.limits
+		.sponsor_transfer_timeout(match properties {
+			CreateItemData::NFT(_) => NFT_SPONSOR_TRANSFER_TIMEOUT,
+			CreateItemData::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+			CreateItemData::ReFungible(_) => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+		});
+
+	if let Some(last_tx_block) = <CreateItemBasket<T>>::get((collection.id, who.as_sub())) {
+		let timeout = last_tx_block + limit.into();
+		if block_number < timeout {
+			return None;
+		}
+	}
+
+	CreateItemBasket::<T>::insert((collection.id, who.as_sub()), block_number);
+
+	Some(())
+}
+
+pub fn withdraw_approve<T: Config>(
+	collection: &CollectionHandle<T>,
+	who: &T::AccountId,
+	item_id: &TokenId,
+) -> Option<()> {
+	// sponsor timeout
+	let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+	let limit = collection.limits.sponsor_approve_timeout();
+
+	let last_tx_block = match collection.mode {
+		CollectionMode::NFT => <NftApproveBasket<T>>::get(collection.id, item_id),
+		CollectionMode::Fungible(_) => <FungibleApproveBasket<T>>::get(collection.id, who),
+		CollectionMode::ReFungible => {
+			<RefungibleApproveBasket<T>>::get((collection.id, item_id, who))
+		}
+	};
+
+	if let Some(last_tx_block) = last_tx_block {
+		let timeout = last_tx_block + limit.into();
+		if block_number < timeout {
+			return None;
+		}
+	}
+
+	match collection.mode {
+		CollectionMode::NFT => <NftApproveBasket<T>>::insert(collection.id, item_id, block_number),
+		CollectionMode::Fungible(_) => {
+			<FungibleApproveBasket<T>>::insert(collection.id, who, block_number)
+		}
+		CollectionMode::ReFungible => {
+			<RefungibleApproveBasket<T>>::insert((collection.id, item_id, who), block_number)
+		}
+	};
+
+	Some(())
+}
+
+fn load<T: UniqueConfig>(id: CollectionId) -> Option<(T::AccountId, CollectionHandle<T>)> {
+	let collection = CollectionHandle::new(id)?;
+	let sponsor = collection.sponsorship.sponsor().cloned()?;
+	Some((sponsor, collection))
+}
+
+pub struct UniqueSponsorshipHandler<T>(PhantomData<T>);
+impl<T, C> SponsorshipHandler<T::AccountId, C> for UniqueSponsorshipHandler<T>
+where
+	T: Config,
+	C: IsSubType<UniqueCall<T>>,
+{
+	fn get_sponsor(who: &T::AccountId, call: &C) -> Option<T::AccountId> {
+		match IsSubType::<UniqueCall<T>>::is_sub_type(call)? {
+			UniqueCall::set_token_properties {
+				collection_id,
+				token_id,
+				properties,
+				..
+			} => {
+				let (sponsor, collection) = load::<T>(*collection_id)?;
+				withdraw_set_token_property(
+					&collection,
+					&T::CrossAccountId::from_sub(who.clone()),
+					&token_id,
+					// No overflow may happen, as data larger than usize can't reach here
+					properties.iter().map(|p| p.key.len() + p.value.len()).sum(),
+				)
+				.map(|()| sponsor)
+			}
+			UniqueCall::create_item {
+				collection_id,
+				data,
+				..
+			} => {
+				let (sponsor, collection) = load(*collection_id)?;
+				withdraw_create_item::<T>(
+					&collection,
+					&T::CrossAccountId::from_sub(who.clone()),
+					data,
+				)
+				.map(|()| sponsor)
+			}
+			UniqueCall::transfer {
+				collection_id,
+				item_id,
+				..
+			} => {
+				let (sponsor, collection) = load(*collection_id)?;
+				withdraw_transfer::<T>(
+					&collection,
+					&T::CrossAccountId::from_sub(who.clone()),
+					item_id,
+				)
+				.map(|()| sponsor)
+			}
+			UniqueCall::transfer_from {
+				collection_id,
+				item_id,
+				from,
+				..
+			} => {
+				let (sponsor, collection) = load(*collection_id)?;
+				withdraw_transfer::<T>(&collection, from, item_id).map(|()| sponsor)
+			}
+			UniqueCall::approve {
+				collection_id,
+				item_id,
+				..
+			} => {
+				let (sponsor, collection) = load(*collection_id)?;
+				withdraw_approve::<T>(&collection, who, item_id).map(|()| sponsor)
+			}
+			_ => None,
+		}
+	}
+}
+
+pub trait SponsorshipPredict<T: Config> {
+	fn predict(collection: CollectionId, account: T::CrossAccountId, token: TokenId) -> Option<u64>
+	where
+		u64: From<<T as frame_system::Config>::BlockNumber>;
+}
+
+pub struct UniqueSponsorshipPredict<T>(PhantomData<T>);
+
+impl<T: Config> SponsorshipPredict<T> for UniqueSponsorshipPredict<T> {
+	fn predict(collection_id: CollectionId, who: T::CrossAccountId, token: TokenId) -> Option<u64>
+	where
+		u64: From<<T as frame_system::Config>::BlockNumber>,
+	{
+		let collection = <CollectionHandle<T>>::try_get(collection_id).ok()?;
+		let _ = collection.sponsorship.sponsor()?;
+
+		// sponsor timeout
+		let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+		let limit = collection
+			.limits
+			.sponsor_transfer_timeout(match collection.mode {
+				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,
+				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+			});
+
+		let last_tx_block = match collection.mode {
+			CollectionMode::NFT => <NftTransferBasket<T>>::get(collection.id, token),
+			CollectionMode::Fungible(_) => {
+				<FungibleTransferBasket<T>>::get(collection.id, who.as_sub())
+			}
+			CollectionMode::ReFungible => {
+				<ReFungibleTransferBasket<T>>::get((collection.id, token, who.as_sub()))
+			}
+		};
+
+		if let Some(last_tx_block) = last_tx_block {
+			return Some(
+				last_tx_block
+					.saturating_add(limit.into())
+					.saturating_sub(block_number)
+					.into(),
+			);
+		}
+
+		let token_exists = match collection.mode {
+			CollectionMode::NFT => {
+				<pallet_nonfungible::TokenData<T>>::contains_key((collection.id, token))
+			}
+			CollectionMode::Fungible(_) => token == TokenId::default(),
+			CollectionMode::ReFungible => {
+				<pallet_refungible::TotalSupply<T>>::contains_key((collection.id, token))
+			}
+		};
+
+		if token_exists {
+			Some(0)
+		} else {
+			None
+		}
+	}
+}
deletedruntime/common/src/constants.rsdiffbeforeafterboth
--- a/runtime/common/src/constants.rs
+++ /dev/null
@@ -1,57 +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 sp_runtime::Perbill;
-use frame_support::{
-	parameter_types,
-	weights::{Weight, constants::WEIGHT_PER_SECOND},
-};
-use crate::types::{BlockNumber, Balance};
-
-pub const MILLISECS_PER_BLOCK: u64 = 12000;
-
-pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK;
-
-// These time units are defined in number of blocks.
-pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber);
-pub const HOURS: BlockNumber = MINUTES * 60;
-pub const DAYS: BlockNumber = HOURS * 24;
-
-pub const MICROUNIQUE: Balance = 1_000_000_000_000;
-pub const MILLIUNIQUE: Balance = 1_000 * MICROUNIQUE;
-pub const CENTIUNIQUE: Balance = 10 * MILLIUNIQUE;
-pub const UNIQUE: Balance = 100 * CENTIUNIQUE;
-
-// Targeting 0.1 UNQ per transfer
-pub const WEIGHT_TO_FEE_COEFF: u32 = 207_890_902;
-
-// Targeting 0.15 UNQ per transfer via ETH
-pub const MIN_GAS_PRICE: u64 = 1_019_493_469_850;
-
-/// We assume that ~10% of the block weight is consumed by `on_initalize` handlers.
-/// This is used to limit the maximal weight of a single extrinsic.
-pub const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(10);
-/// We allow `Normal` extrinsics to fill up the block up to 75%, the rest can be used
-/// by  Operational  extrinsics.
-pub const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75);
-/// We allow for 2 seconds of compute with a 6 second average block time.
-pub const MAXIMUM_BLOCK_WEIGHT: Weight = WEIGHT_PER_SECOND / 2;
-
-parameter_types! {
-	pub const DefaultSponsoringRateLimit: BlockNumber = 1 * DAYS;
-
-	pub const TransactionByteFee: Balance = 501 * MICROUNIQUE;
-}
deletedruntime/common/src/dispatch.rsdiffbeforeafterboth
--- a/runtime/common/src/dispatch.rs
+++ /dev/null
@@ -1,181 +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::{dispatch::DispatchResult, ensure};
-use pallet_evm::{PrecompileHandle, PrecompileResult};
-use sp_core::H160;
-use sp_runtime::DispatchError;
-use sp_std::{borrow::ToOwned, vec::Vec};
-use pallet_common::{
-	CollectionById, CollectionHandle, CommonCollectionOperations, erc::CommonEvmHandler,
-	eth::map_eth_to_id,
-};
-pub use pallet_common::dispatch::CollectionDispatch;
-use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};
-use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};
-use pallet_refungible::{
-	Pallet as PalletRefungible, RefungibleHandle, erc_token::RefungibleTokenHandle,
-};
-use up_data_structs::{
-	CollectionMode, CreateCollectionData, MAX_DECIMAL_POINTS, mapping::TokenAddressMapping,
-	CollectionId,
-};
-
-pub enum CollectionDispatchT<T>
-where
-	T: pallet_fungible::Config + pallet_nonfungible::Config + pallet_refungible::Config,
-{
-	Fungible(FungibleHandle<T>),
-	Nonfungible(NonfungibleHandle<T>),
-	Refungible(RefungibleHandle<T>),
-}
-impl<T> CollectionDispatch<T> for CollectionDispatchT<T>
-where
-	T: pallet_common::Config
-		+ pallet_unique::Config
-		+ pallet_fungible::Config
-		+ pallet_nonfungible::Config
-		+ pallet_refungible::Config,
-{
-	fn create(
-		sender: T::CrossAccountId,
-		data: CreateCollectionData<T::AccountId>,
-	) -> Result<CollectionId, DispatchError> {
-		let id = match data.mode {
-			CollectionMode::NFT => <PalletNonfungible<T>>::init_collection(sender, data, false)?,
-			CollectionMode::Fungible(decimal_points) => {
-				// check params
-				ensure!(
-					decimal_points <= MAX_DECIMAL_POINTS,
-					pallet_unique::Error::<T>::CollectionDecimalPointLimitExceeded
-				);
-				<PalletFungible<T>>::init_collection(sender, data)?
-			}
-			CollectionMode::ReFungible => <PalletRefungible<T>>::init_collection(sender, data)?,
-		};
-		Ok(id)
-	}
-
-	fn destroy(sender: T::CrossAccountId, collection: CollectionHandle<T>) -> DispatchResult {
-		match collection.mode {
-			CollectionMode::ReFungible => {
-				PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?
-			}
-			CollectionMode::Fungible(_) => {
-				PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?
-			}
-			CollectionMode::NFT => {
-				PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?
-			}
-		}
-		Ok(())
-	}
-
-	fn dispatch(handle: CollectionHandle<T>) -> Self {
-		match handle.mode {
-			CollectionMode::Fungible(_) => Self::Fungible(FungibleHandle::cast(handle)),
-			CollectionMode::NFT => Self::Nonfungible(NonfungibleHandle::cast(handle)),
-			CollectionMode::ReFungible => Self::Refungible(RefungibleHandle::cast(handle)),
-		}
-	}
-
-	fn into_inner(self) -> CollectionHandle<T> {
-		match self {
-			Self::Fungible(f) => f.into_inner(),
-			Self::Nonfungible(f) => f.into_inner(),
-			Self::Refungible(f) => f.into_inner(),
-		}
-	}
-
-	fn as_dyn(&self) -> &dyn CommonCollectionOperations<T> {
-		match self {
-			Self::Fungible(h) => h,
-			Self::Nonfungible(h) => h,
-			Self::Refungible(h) => h,
-		}
-	}
-}
-
-impl<T> pallet_evm::OnMethodCall<T> for CollectionDispatchT<T>
-where
-	T: pallet_common::Config
-		+ pallet_unique::Config
-		+ pallet_fungible::Config
-		+ pallet_nonfungible::Config
-		+ pallet_refungible::Config,
-	T::AccountId: From<[u8; 32]>,
-{
-	fn is_reserved(target: &H160) -> bool {
-		map_eth_to_id(target).is_some()
-	}
-	fn is_used(target: &H160) -> bool {
-		map_eth_to_id(target)
-			.map(<CollectionById<T>>::contains_key)
-			.unwrap_or(false)
-	}
-	fn get_code(target: &H160) -> Option<Vec<u8>> {
-		if let Some(collection_id) = map_eth_to_id(target) {
-			let collection = <CollectionById<T>>::get(collection_id)?;
-			Some(
-				match collection.mode {
-					CollectionMode::NFT => <NonfungibleHandle<T>>::CODE,
-					CollectionMode::Fungible(_) => <FungibleHandle<T>>::CODE,
-					CollectionMode::ReFungible => <RefungibleHandle<T>>::CODE,
-				}
-				.to_owned(),
-			)
-		} else if let Some((collection_id, _token_id)) =
-			<T as pallet_common::Config>::EvmTokenAddressMapping::address_to_token(target)
-		{
-			let collection = <CollectionById<T>>::get(collection_id)?;
-			if collection.mode != CollectionMode::ReFungible {
-				return None;
-			}
-			// TODO: check token existence
-			Some(<RefungibleTokenHandle<T>>::CODE.to_owned())
-		} else {
-			None
-		}
-	}
-	fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {
-		if let Some(collection_id) = map_eth_to_id(&handle.code_address()) {
-			let collection =
-				<CollectionHandle<T>>::new_with_gas_limit(collection_id, handle.remaining_gas())?;
-			let dispatched = Self::dispatch(collection);
-
-			match dispatched {
-				Self::Fungible(h) => h.call(handle),
-				Self::Nonfungible(h) => h.call(handle),
-				Self::Refungible(h) => h.call(handle),
-			}
-		} else if let Some((collection_id, token_id)) =
-			<T as pallet_common::Config>::EvmTokenAddressMapping::address_to_token(
-				&handle.code_address(),
-			) {
-			let collection =
-				<CollectionHandle<T>>::new_with_gas_limit(collection_id, handle.remaining_gas())?;
-			if collection.mode != CollectionMode::ReFungible {
-				return None;
-			}
-
-			let h = RefungibleHandle::cast(collection);
-			// TODO: check token existence
-			RefungibleTokenHandle(h, token_id).call(handle)
-		} else {
-			None
-		}
-	}
-}
deletedruntime/common/src/eth_sponsoring.rsdiffbeforeafterboth
--- a/runtime/common/src/eth_sponsoring.rs
+++ /dev/null
@@ -1,122 +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/>.
-
-//! Implements EVM sponsoring logic via TransactionValidityHack
-
-use evm_coder::{Call, abi::AbiReader};
-use pallet_common::{CollectionHandle, eth::map_eth_to_id};
-use sp_core::H160;
-use sp_std::prelude::*;
-use up_sponsorship::SponsorshipHandler;
-use core::marker::PhantomData;
-use core::convert::TryInto;
-use pallet_evm::account::CrossAccountId;
-use up_data_structs::{TokenId, CreateItemData, CreateNftData, CollectionMode};
-use pallet_unique::Config as UniqueConfig;
-
-use crate::sponsoring::*;
-
-use pallet_nonfungible::erc::{
-	UniqueNFTCall, ERC721UniqueExtensionsCall, ERC721MintableCall, ERC721Call, TokenPropertiesCall,
-};
-use pallet_fungible::erc::{UniqueFungibleCall, ERC20Call};
-use pallet_fungible::Config as FungibleConfig;
-use pallet_nonfungible::Config as NonfungibleConfig;
-use pallet_refungible::Config as RefungibleConfig;
-
-pub struct UniqueEthSponsorshipHandler<T: UniqueConfig>(PhantomData<*const T>);
-impl<T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig>
-	SponsorshipHandler<T::CrossAccountId, (H160, Vec<u8>)> for UniqueEthSponsorshipHandler<T>
-{
-	fn get_sponsor(who: &T::CrossAccountId, call: &(H160, Vec<u8>)) -> Option<T::CrossAccountId> {
-		let collection_id = map_eth_to_id(&call.0)?;
-		let collection = <CollectionHandle<T>>::new(collection_id)?;
-		let sponsor = collection.sponsorship.sponsor()?.clone();
-		let (method_id, mut reader) = AbiReader::new_call(&call.1).ok()?;
-		Some(T::CrossAccountId::from_sub(match &collection.mode {
-			CollectionMode::NFT => {
-				let call = <UniqueNFTCall<T>>::parse(method_id, &mut reader).ok()??;
-				match call {
-					UniqueNFTCall::TokenProperties(TokenPropertiesCall::SetProperty {
-						token_id,
-						key,
-						value,
-						..
-					}) => {
-						let token_id: TokenId = token_id.try_into().ok()?;
-						withdraw_set_token_property::<T>(
-							&collection,
-							&who,
-							&token_id,
-							key.len() + value.len(),
-						)
-						.map(|()| sponsor)
-					}
-					UniqueNFTCall::ERC721UniqueExtensions(
-						ERC721UniqueExtensionsCall::Transfer { token_id, .. },
-					) => {
-						let token_id: TokenId = token_id.try_into().ok()?;
-						withdraw_transfer::<T>(&collection, &who, &token_id).map(|()| sponsor)
-					}
-					UniqueNFTCall::ERC721Mintable(
-						ERC721MintableCall::Mint { token_id, .. }
-						| ERC721MintableCall::MintWithTokenUri { token_id, .. },
-					) => {
-						let _token_id: TokenId = token_id.try_into().ok()?;
-						withdraw_create_item::<T>(
-							&collection,
-							&who,
-							&CreateItemData::NFT(CreateNftData::default()),
-						)
-						.map(|()| sponsor)
-					}
-					UniqueNFTCall::ERC721(ERC721Call::TransferFrom { token_id, from, .. }) => {
-						let token_id: TokenId = token_id.try_into().ok()?;
-						let from = T::CrossAccountId::from_eth(from);
-						withdraw_transfer::<T>(&collection, &from, &token_id).map(|()| sponsor)
-					}
-					UniqueNFTCall::ERC721(ERC721Call::Approve { token_id, .. }) => {
-						let token_id: TokenId = token_id.try_into().ok()?;
-						withdraw_approve::<T>(&collection, who.as_sub(), &token_id)
-							.map(|()| sponsor)
-					}
-					_ => None,
-				}
-			}
-			CollectionMode::Fungible(_) => {
-				let call = <UniqueFungibleCall<T>>::parse(method_id, &mut reader).ok()??;
-				#[allow(clippy::single_match)]
-				match call {
-					UniqueFungibleCall::ERC20(ERC20Call::Transfer { .. }) => {
-						withdraw_transfer::<T>(&collection, who, &TokenId::default())
-							.map(|()| sponsor)
-					}
-					UniqueFungibleCall::ERC20(ERC20Call::TransferFrom { from, .. }) => {
-						let from = T::CrossAccountId::from_eth(from);
-						withdraw_transfer::<T>(&collection, &from, &TokenId::default())
-							.map(|()| sponsor)
-					}
-					UniqueFungibleCall::ERC20(ERC20Call::Approve { .. }) => {
-						withdraw_approve::<T>(&collection, who.as_sub(), &TokenId::default())
-							.map(|()| sponsor)
-					}
-					_ => None,
-				}
-			}
-			_ => None,
-		}?))
-	}
-}
deletedruntime/common/src/lib.rsdiffbeforeafterboth
--- a/runtime/common/src/lib.rs
+++ /dev/null
@@ -1,25 +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/>.
-
-#![cfg_attr(not(feature = "std"), no_std)]
-
-pub mod constants;
-pub mod dispatch;
-pub mod eth_sponsoring;
-pub mod runtime_apis;
-pub mod sponsoring;
-pub mod types;
-pub mod weights;
deletedruntime/common/src/runtime_apis.rsdiffbeforeafterboth
--- a/runtime/common/src/runtime_apis.rs
+++ /dev/null
@@ -1,546 +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/>.
-
-#[macro_export]
-macro_rules! impl_common_runtime_apis {
-    (
-        $(
-            #![custom_apis]
-
-            $($custom_apis:tt)+
-        )?
-    ) => {
-        impl_runtime_apis! {
-            $($($custom_apis)+)?
-
-            impl up_rpc::UniqueApi<Block, CrossAccountId, AccountId> for Runtime {
-                fn account_tokens(collection: CollectionId, account: CrossAccountId) -> Result<Vec<TokenId>, DispatchError> {
-                    dispatch_unique_runtime!(collection.account_tokens(account))
-                }
-                fn collection_tokens(collection: CollectionId) -> Result<Vec<TokenId>, DispatchError> {
-                    dispatch_unique_runtime!(collection.collection_tokens())
-                }
-                fn token_exists(collection: CollectionId, token: TokenId) -> Result<bool, DispatchError> {
-                    dispatch_unique_runtime!(collection.token_exists(token))
-                }
-
-                fn token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>, DispatchError> {
-                    dispatch_unique_runtime!(collection.token_owner(token))
-                }
-
-                fn token_owners(collection: CollectionId, token: TokenId) -> Result<Vec::<CrossAccountId>, DispatchError>  {
-                   dispatch_unique_runtime!(collection.token_owners(token))
-                }
-
-                fn topmost_token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>, DispatchError> {
-                    let budget = up_data_structs::budget::Value::new(10);
-
-                    Ok(Some(<pallet_structure::Pallet<Runtime>>::find_topmost_owner(collection, token, &budget)?))
-                }
-                fn token_children(collection: CollectionId, token: TokenId) -> Result<Vec<TokenChild>, DispatchError> {
-                    Ok(<pallet_nonfungible::Pallet<Runtime>>::token_children_ids(collection, token))
-                }
-                fn collection_properties(
-                    collection: CollectionId,
-                    keys: Option<Vec<Vec<u8>>>
-                ) -> Result<Vec<Property>, DispatchError> {
-                    let keys = keys.map(
-                        |keys| Common::bytes_keys_to_property_keys(keys)
-                    ).transpose()?;
-
-                    Common::filter_collection_properties(collection, keys)
-                }
-
-                fn token_properties(
-                    collection: CollectionId,
-                    token_id: TokenId,
-                    keys: Option<Vec<Vec<u8>>>
-                ) -> Result<Vec<Property>, DispatchError> {
-                    let keys = keys.map(
-                        |keys| Common::bytes_keys_to_property_keys(keys)
-                    ).transpose()?;
-
-                    dispatch_unique_runtime!(collection.token_properties(token_id, keys))
-                }
-
-                fn property_permissions(
-                    collection: CollectionId,
-                    keys: Option<Vec<Vec<u8>>>
-                ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {
-                    let keys = keys.map(
-                        |keys| Common::bytes_keys_to_property_keys(keys)
-                    ).transpose()?;
-
-                    Common::filter_property_permissions(collection, keys)
-                }
-
-                fn token_data(
-                    collection: CollectionId,
-                    token_id: TokenId,
-                    keys: Option<Vec<Vec<u8>>>
-                ) -> Result<TokenData<CrossAccountId>, DispatchError> {
-                    let token_data = TokenData {
-                        properties: Self::token_properties(collection, token_id, keys)?,
-                        owner: Self::token_owner(collection, token_id)?,
-                        pieces: Self::total_pieces(collection, token_id)?.unwrap_or(0),
-                    };
-
-                    Ok(token_data)
-                }
-
-                fn total_supply(collection: CollectionId) -> Result<u32, DispatchError> {
-                    dispatch_unique_runtime!(collection.total_supply())
-                }
-                fn account_balance(collection: CollectionId, account: CrossAccountId) -> Result<u32, DispatchError> {
-                    dispatch_unique_runtime!(collection.account_balance(account))
-                }
-                fn balance(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Result<u128, DispatchError> {
-                    dispatch_unique_runtime!(collection.balance(account, token))
-                }
-                fn allowance(
-                    collection: CollectionId,
-                    sender: CrossAccountId,
-                    spender: CrossAccountId,
-                    token: TokenId,
-                ) -> Result<u128, DispatchError> {
-                    dispatch_unique_runtime!(collection.allowance(sender, spender, token))
-                }
-
-                fn adminlist(collection: CollectionId) -> Result<Vec<CrossAccountId>, DispatchError> {
-                    Ok(<pallet_common::Pallet<Runtime>>::adminlist(collection))
-                }
-                fn allowlist(collection: CollectionId) -> Result<Vec<CrossAccountId>, DispatchError> {
-                    Ok(<pallet_common::Pallet<Runtime>>::allowlist(collection))
-                }
-                fn allowed(collection: CollectionId, user: CrossAccountId) -> Result<bool, DispatchError> {
-                    Ok(<pallet_common::Pallet<Runtime>>::allowed(collection, user))
-                }
-                fn last_token_id(collection: CollectionId) -> Result<TokenId, DispatchError> {
-                    dispatch_unique_runtime!(collection.last_token_id())
-                }
-                fn collection_by_id(collection: CollectionId) -> Result<Option<RpcCollection<AccountId>>, DispatchError> {
-                    Ok(<pallet_common::Pallet<Runtime>>::rpc_collection(collection))
-                }
-                fn collection_stats() -> Result<CollectionStats, DispatchError> {
-                    Ok(<pallet_common::Pallet<Runtime>>::collection_stats())
-                }
-                fn next_sponsored(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Result<Option<u64>, DispatchError> {
-                    Ok(<$crate::sponsoring::UniqueSponsorshipPredict<Runtime> as
-                            $crate::sponsoring::SponsorshipPredict<Runtime>>::predict(
-                        collection,
-                        account,
-                        token))
-                }
-
-                fn effective_collection_limits(collection: CollectionId) -> Result<Option<CollectionLimits>, DispatchError> {
-                    Ok(<pallet_common::Pallet<Runtime>>::effective_collection_limits(collection))
-                }
-
-                fn total_pieces(collection: CollectionId, token_id: TokenId) -> Result<Option<u128>, DispatchError> {
-                    dispatch_unique_runtime!(collection.total_pieces(token_id))
-                }
-            }
-
-            impl sp_api::Core<Block> for Runtime {
-                fn version() -> RuntimeVersion {
-                    VERSION
-                }
-
-                fn execute_block(block: Block) {
-                    Executive::execute_block(block)
-                }
-
-                fn initialize_block(header: &<Block as BlockT>::Header) {
-                    Executive::initialize_block(header)
-                }
-            }
-
-            impl sp_api::Metadata<Block> for Runtime {
-                fn metadata() -> OpaqueMetadata {
-                    OpaqueMetadata::new(Runtime::metadata().into())
-                }
-            }
-
-            impl sp_block_builder::BlockBuilder<Block> for Runtime {
-                fn apply_extrinsic(extrinsic: <Block as BlockT>::Extrinsic) -> ApplyExtrinsicResult {
-                    Executive::apply_extrinsic(extrinsic)
-                }
-
-                fn finalize_block() -> <Block as BlockT>::Header {
-                    Executive::finalize_block()
-                }
-
-                fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<<Block as BlockT>::Extrinsic> {
-                    data.create_extrinsics()
-                }
-
-                fn check_inherents(
-                    block: Block,
-                    data: sp_inherents::InherentData,
-                ) -> sp_inherents::CheckInherentsResult {
-                    data.check_extrinsics(&block)
-                }
-
-                // fn random_seed() -> <Block as BlockT>::Hash {
-                //     RandomnessCollectiveFlip::random_seed().0
-                // }
-            }
-
-            impl sp_transaction_pool::runtime_api::TaggedTransactionQueue<Block> for Runtime {
-                fn validate_transaction(
-                    source: TransactionSource,
-                    tx: <Block as BlockT>::Extrinsic,
-                    hash: <Block as BlockT>::Hash,
-                ) -> TransactionValidity {
-                    Executive::validate_transaction(source, tx, hash)
-                }
-            }
-
-            impl sp_offchain::OffchainWorkerApi<Block> for Runtime {
-                fn offchain_worker(header: &<Block as BlockT>::Header) {
-                    Executive::offchain_worker(header)
-                }
-            }
-
-            impl fp_rpc::EthereumRuntimeRPCApi<Block> for Runtime {
-                fn chain_id() -> u64 {
-                    <Runtime as pallet_evm::Config>::ChainId::get()
-                }
-
-                fn account_basic(address: H160) -> EVMAccount {
-                    let (account, _) = EVM::account_basic(&address);
-                    account
-                }
-
-                fn gas_price() -> U256 {
-                    let (price, _) = <Runtime as pallet_evm::Config>::FeeCalculator::min_gas_price();
-                    price
-                }
-
-                fn account_code_at(address: H160) -> Vec<u8> {
-                    EVM::account_codes(address)
-                }
-
-                fn author() -> H160 {
-                    <pallet_evm::Pallet<Runtime>>::find_author()
-                }
-
-                fn storage_at(address: H160, index: U256) -> H256 {
-                    let mut tmp = [0u8; 32];
-                    index.to_big_endian(&mut tmp);
-                    EVM::account_storages(address, H256::from_slice(&tmp[..]))
-                }
-
-                #[allow(clippy::redundant_closure)]
-                fn call(
-                    from: H160,
-                    to: H160,
-                    data: Vec<u8>,
-                    value: U256,
-                    gas_limit: U256,
-                    max_fee_per_gas: Option<U256>,
-                    max_priority_fee_per_gas: Option<U256>,
-                    nonce: Option<U256>,
-                    estimate: bool,
-                    access_list: Option<Vec<(H160, Vec<H256>)>>,
-                ) -> Result<pallet_evm::CallInfo, sp_runtime::DispatchError> {
-                    let config = if estimate {
-                        let mut config = <Runtime as pallet_evm::Config>::config().clone();
-                        config.estimate = true;
-                        Some(config)
-                    } else {
-                        None
-                    };
-
-                    let is_transactional = false;
-                    <Runtime as pallet_evm::Config>::Runner::call(
-                        CrossAccountId::from_eth(from),
-                        to,
-                        data,
-                        value,
-                        gas_limit.low_u64(),
-                        max_fee_per_gas,
-                        max_priority_fee_per_gas,
-                        nonce,
-                        access_list.unwrap_or_default(),
-                        is_transactional,
-                        config.as_ref().unwrap_or_else(|| <Runtime as pallet_evm::Config>::config()),
-                    ).map_err(|err| err.error.into())
-                }
-
-                #[allow(clippy::redundant_closure)]
-                fn create(
-                    from: H160,
-                    data: Vec<u8>,
-                    value: U256,
-                    gas_limit: U256,
-                    max_fee_per_gas: Option<U256>,
-                    max_priority_fee_per_gas: Option<U256>,
-                    nonce: Option<U256>,
-                    estimate: bool,
-                    access_list: Option<Vec<(H160, Vec<H256>)>>,
-                ) -> Result<pallet_evm::CreateInfo, sp_runtime::DispatchError> {
-                    let config = if estimate {
-                        let mut config = <Runtime as pallet_evm::Config>::config().clone();
-                        config.estimate = true;
-                        Some(config)
-                    } else {
-                        None
-                    };
-
-                    let is_transactional = false;
-                    <Runtime as pallet_evm::Config>::Runner::create(
-                        CrossAccountId::from_eth(from),
-                        data,
-                        value,
-                        gas_limit.low_u64(),
-                        max_fee_per_gas,
-                        max_priority_fee_per_gas,
-                        nonce,
-                        access_list.unwrap_or_default(),
-                        is_transactional,
-                        config.as_ref().unwrap_or_else(|| <Runtime as pallet_evm::Config>::config()),
-                    ).map_err(|err| err.error.into())
-                }
-
-                fn current_transaction_statuses() -> Option<Vec<TransactionStatus>> {
-                    Ethereum::current_transaction_statuses()
-                }
-
-                fn current_block() -> Option<pallet_ethereum::Block> {
-                    Ethereum::current_block()
-                }
-
-                fn current_receipts() -> Option<Vec<pallet_ethereum::Receipt>> {
-                    Ethereum::current_receipts()
-                }
-
-                fn current_all() -> (
-                    Option<pallet_ethereum::Block>,
-                    Option<Vec<pallet_ethereum::Receipt>>,
-                    Option<Vec<TransactionStatus>>
-                ) {
-                    (
-                        Ethereum::current_block(),
-                        Ethereum::current_receipts(),
-                        Ethereum::current_transaction_statuses()
-                    )
-                }
-
-                fn extrinsic_filter(xts: Vec<<Block as sp_api::BlockT>::Extrinsic>) -> Vec<pallet_ethereum::Transaction> {
-                    xts.into_iter().filter_map(|xt| match xt.0.function {
-                        Call::Ethereum(pallet_ethereum::Call::transact { transaction }) => Some(transaction),
-                        _ => None
-                    }).collect()
-                }
-
-                fn elasticity() -> Option<Permill> {
-                    None
-                }
-            }
-
-            impl fp_rpc::ConvertTransactionRuntimeApi<Block> for Runtime {
-                fn convert_transaction(transaction: pallet_ethereum::Transaction) -> <Block as BlockT>::Extrinsic  {
-                    UncheckedExtrinsic::new_unsigned(
-                        pallet_ethereum::Call::<Runtime>::transact { transaction }.into(),
-                    )
-                }
-            }
-
-            impl sp_session::SessionKeys<Block> for Runtime {
-                fn decode_session_keys(
-                    encoded: Vec<u8>,
-                ) -> Option<Vec<(Vec<u8>, KeyTypeId)>> {
-                    SessionKeys::decode_into_raw_public_keys(&encoded)
-                }
-
-                fn generate_session_keys(seed: Option<Vec<u8>>) -> Vec<u8> {
-                    SessionKeys::generate(seed)
-                }
-            }
-
-            impl sp_consensus_aura::AuraApi<Block, AuraId> for Runtime {
-                fn slot_duration() -> sp_consensus_aura::SlotDuration {
-                    sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration())
-                }
-
-                fn authorities() -> Vec<AuraId> {
-                    Aura::authorities().to_vec()
-                }
-            }
-
-            impl cumulus_primitives_core::CollectCollationInfo<Block> for Runtime {
-                fn collect_collation_info(header: &<Block as BlockT>::Header) -> cumulus_primitives_core::CollationInfo {
-                    ParachainSystem::collect_collation_info(header)
-                }
-            }
-
-            impl frame_system_rpc_runtime_api::AccountNonceApi<Block, AccountId, Index> for Runtime {
-                fn account_nonce(account: AccountId) -> Index {
-                    System::account_nonce(account)
-                }
-            }
-
-            impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi<Block, Balance> for Runtime {
-                fn query_info(uxt: <Block as BlockT>::Extrinsic, len: u32) -> RuntimeDispatchInfo<Balance> {
-                    TransactionPayment::query_info(uxt, len)
-                }
-                fn query_fee_details(uxt: <Block as BlockT>::Extrinsic, len: u32) -> FeeDetails<Balance> {
-                    TransactionPayment::query_fee_details(uxt, len)
-                }
-            }
-
-            /*
-            impl pallet_contracts_rpc_runtime_api::ContractsApi<Block, AccountId, Balance, BlockNumber, Hash>
-                for Runtime
-            {
-                fn call(
-                    origin: AccountId,
-                    dest: AccountId,
-                    value: Balance,
-                    gas_limit: u64,
-                    input_data: Vec<u8>,
-                ) -> pallet_contracts_primitives::ContractExecResult {
-                    Contracts::bare_call(origin, dest, value, gas_limit, input_data, false)
-                }
-
-                fn instantiate(
-                    origin: AccountId,
-                    endowment: Balance,
-                    gas_limit: u64,
-                    code: pallet_contracts_primitives::Code<Hash>,
-                    data: Vec<u8>,
-                    salt: Vec<u8>,
-                ) -> pallet_contracts_primitives::ContractInstantiateResult<AccountId, BlockNumber>
-                {
-                    Contracts::bare_instantiate(origin, endowment, gas_limit, code, data, salt, true, false)
-                }
-
-                fn get_storage(
-                    address: AccountId,
-                    key: [u8; 32],
-                ) -> pallet_contracts_primitives::GetStorageResult {
-                    Contracts::get_storage(address, key)
-                }
-
-                fn rent_projection(
-                    address: AccountId,
-                ) -> pallet_contracts_primitives::RentProjectionResult<BlockNumber> {
-                    Contracts::rent_projection(address)
-                }
-            }
-            */
-
-            #[cfg(feature = "runtime-benchmarks")]
-            impl frame_benchmarking::Benchmark<Block> for Runtime {
-                fn benchmark_metadata(extra: bool) -> (
-                    Vec<frame_benchmarking::BenchmarkList>,
-                    Vec<frame_support::traits::StorageInfo>,
-                ) {
-                    use frame_benchmarking::{list_benchmark, Benchmarking, BenchmarkList};
-                    use frame_support::traits::StorageInfoTrait;
-
-                    let mut list = Vec::<BenchmarkList>::new();
-
-                    list_benchmark!(list, extra, pallet_evm_migration, EvmMigration);
-                    list_benchmark!(list, extra, pallet_common, Common);
-                    list_benchmark!(list, extra, pallet_unique, Unique);
-                    list_benchmark!(list, extra, pallet_structure, Structure);
-                    list_benchmark!(list, extra, pallet_inflation, Inflation);
-                    list_benchmark!(list, extra, pallet_fungible, Fungible);
-                    list_benchmark!(list, extra, pallet_refungible, Refungible);
-                    list_benchmark!(list, extra, pallet_nonfungible, Nonfungible);
-                    list_benchmark!(list, extra, pallet_unique_scheduler, Scheduler);
-
-                    #[cfg(not(feature = "unique-runtime"))]
-                    list_benchmark!(list, extra, pallet_proxy_rmrk_core, RmrkCore);
-
-                    #[cfg(not(feature = "unique-runtime"))]
-                    list_benchmark!(list, extra, pallet_proxy_rmrk_equip, RmrkEquip);
-
-                    // list_benchmark!(list, extra, pallet_evm_coder_substrate, EvmCoderSubstrate);
-
-                    let storage_info = AllPalletsReversedWithSystemFirst::storage_info();
-
-                    return (list, storage_info)
-                }
-
-                fn dispatch_benchmark(
-                    config: frame_benchmarking::BenchmarkConfig
-                ) -> Result<Vec<frame_benchmarking::BenchmarkBatch>, sp_runtime::RuntimeString> {
-                    use frame_benchmarking::{Benchmarking, BenchmarkBatch, add_benchmark, TrackedStorageKey};
-
-                    let allowlist: Vec<TrackedStorageKey> = vec![
-                        // Total Issuance
-                        hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(),
-
-                        // Block Number
-                        hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(),
-                        // Execution Phase
-                        hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(),
-                        // Event Count
-                        hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(),
-                        // System Events
-                        hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(),
-
-                        // Evm CurrentLogs
-                        hex_literal::hex!("1da53b775b270400e7e61ed5cbc5a146547f210cec367e9af919603343b9cb56").to_vec().into(),
-
-                        // Transactional depth
-                        hex_literal::hex!("3a7472616e73616374696f6e5f6c6576656c3a").to_vec().into(),
-                    ];
-
-                    let mut batches = Vec::<BenchmarkBatch>::new();
-                    let params = (&config, &allowlist);
-
-                    add_benchmark!(params, batches, pallet_evm_migration, EvmMigration);
-                    add_benchmark!(params, batches, pallet_common, Common);
-                    add_benchmark!(params, batches, pallet_unique, Unique);
-                    add_benchmark!(params, batches, pallet_structure, Structure);
-                    add_benchmark!(params, batches, pallet_inflation, Inflation);
-                    add_benchmark!(params, batches, pallet_fungible, Fungible);
-                    add_benchmark!(params, batches, pallet_refungible, Refungible);
-                    add_benchmark!(params, batches, pallet_nonfungible, Nonfungible);
-                    add_benchmark!(params, batches, pallet_unique_scheduler, Scheduler);
-
-                    #[cfg(not(feature = "unique-runtime"))]
-                    add_benchmark!(params, batches, pallet_proxy_rmrk_core, RmrkCore);
-
-                    #[cfg(not(feature = "unique-runtime"))]
-                    add_benchmark!(params, batches, pallet_proxy_rmrk_equip, RmrkEquip);
-
-                    // add_benchmark!(params, batches, pallet_evm_coder_substrate, EvmCoderSubstrate);
-
-                    if batches.is_empty() { return Err("Benchmark not found for this pallet.".into()) }
-                    Ok(batches)
-                }
-            }
-
-            #[cfg(feature = "try-runtime")]
-            impl frame_try_runtime::TryRuntime<Block> for Runtime {
-                fn on_runtime_upgrade() -> (Weight, Weight) {
-                    log::info!("try-runtime::on_runtime_upgrade unique-chain.");
-                    let weight = Executive::try_runtime_upgrade().unwrap();
-                    (weight, RuntimeBlockWeights::get().max_block)
-                }
-
-                fn execute_block_no_check(block: Block) -> Weight {
-                    Executive::execute_block_no_check(block)
-                }
-            }
-        }
-    }
-}
deletedruntime/common/src/sponsoring.rsdiffbeforeafterboth
--- a/runtime/common/src/sponsoring.rs
+++ /dev/null
@@ -1,359 +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 core::marker::PhantomData;
-use up_sponsorship::SponsorshipHandler;
-use frame_support::{
-	traits::{IsSubType},
-	storage::{StorageMap, StorageDoubleMap, StorageNMap},
-};
-use up_data_structs::{
-	CollectionId, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, NFT_SPONSOR_TRANSFER_TIMEOUT,
-	REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, TokenId, CollectionMode, CreateItemData,
-};
-use sp_runtime::traits::Saturating;
-use pallet_common::{CollectionHandle};
-use pallet_evm::account::CrossAccountId;
-use pallet_unique::{
-	Call as UniqueCall, Config as UniqueConfig, FungibleApproveBasket, RefungibleApproveBasket,
-	NftApproveBasket, CreateItemBasket, ReFungibleTransferBasket, FungibleTransferBasket,
-	NftTransferBasket, TokenPropertyBasket,
-};
-use pallet_fungible::Config as FungibleConfig;
-use pallet_nonfungible::Config as NonfungibleConfig;
-use pallet_refungible::Config as RefungibleConfig;
-
-pub trait Config: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig {}
-impl<T> Config for T where T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig {}
-
-// TODO: permission check?
-pub fn withdraw_set_token_property<T: Config>(
-	collection: &CollectionHandle<T>,
-	who: &T::CrossAccountId,
-	item_id: &TokenId,
-	data_size: usize,
-) -> Option<()> {
-	// preliminary sponsoring correctness check
-	match collection.mode {
-		CollectionMode::NFT => {
-			let owner = pallet_nonfungible::TokenData::<T>::get((collection.id, item_id))?.owner;
-			if !owner.conv_eq(who) {
-				return None;
-			}
-		}
-		CollectionMode::Fungible(_) => {
-			// Fungible tokens have no properties
-			return None;
-		}
-		CollectionMode::ReFungible => {
-			if !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {
-				return None;
-			}
-		}
-	}
-
-	if data_size > collection.limits.sponsored_data_size() as usize {
-		return None;
-	}
-
-	let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
-	let limit = collection.limits.sponsored_data_rate_limit()?;
-
-	if let Some(last_tx_block) = TokenPropertyBasket::<T>::get(collection.id, item_id) {
-		let timeout = last_tx_block + limit.into();
-		if block_number < timeout {
-			return None;
-		}
-	}
-
-	<TokenPropertyBasket<T>>::insert(collection.id, item_id, block_number);
-
-	Some(())
-}
-
-pub fn withdraw_transfer<T: Config>(
-	collection: &CollectionHandle<T>,
-	who: &T::CrossAccountId,
-	item_id: &TokenId,
-) -> Option<()> {
-	// preliminary sponsoring correctness check
-	match collection.mode {
-		CollectionMode::NFT => {
-			let owner = pallet_nonfungible::TokenData::<T>::get((collection.id, item_id))?.owner;
-			if !owner.conv_eq(who) {
-				return None;
-			}
-		}
-		CollectionMode::Fungible(_) => {
-			if item_id != &TokenId::default() {
-				return None;
-			}
-			if <pallet_fungible::Balance<T>>::get((collection.id, who)) == 0 {
-				return None;
-			}
-		}
-		CollectionMode::ReFungible => {
-			if !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {
-				return None;
-			}
-		}
-	}
-
-	// sponsor timeout
-	let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
-	let limit = collection
-		.limits
-		.sponsor_transfer_timeout(match collection.mode {
-			CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,
-			CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
-			CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
-		});
-
-	let last_tx_block = match collection.mode {
-		CollectionMode::NFT => <NftTransferBasket<T>>::get(collection.id, item_id),
-		CollectionMode::Fungible(_) => {
-			<FungibleTransferBasket<T>>::get(collection.id, who.as_sub())
-		}
-		CollectionMode::ReFungible => {
-			<ReFungibleTransferBasket<T>>::get((collection.id, item_id, who.as_sub()))
-		}
-	};
-
-	if let Some(last_tx_block) = last_tx_block {
-		let timeout = last_tx_block + limit.into();
-		if block_number < timeout {
-			return None;
-		}
-	}
-
-	match collection.mode {
-		CollectionMode::NFT => <NftTransferBasket<T>>::insert(collection.id, item_id, block_number),
-		CollectionMode::Fungible(_) => {
-			<FungibleTransferBasket<T>>::insert(collection.id, who.as_sub(), block_number)
-		}
-		CollectionMode::ReFungible => <ReFungibleTransferBasket<T>>::insert(
-			(collection.id, item_id, who.as_sub()),
-			block_number,
-		),
-	};
-
-	Some(())
-}
-
-pub fn withdraw_create_item<T: Config>(
-	collection: &CollectionHandle<T>,
-	who: &T::CrossAccountId,
-	properties: &CreateItemData,
-) -> Option<()> {
-	// sponsor timeout
-	let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
-	let limit = collection
-		.limits
-		.sponsor_transfer_timeout(match properties {
-			CreateItemData::NFT(_) => NFT_SPONSOR_TRANSFER_TIMEOUT,
-			CreateItemData::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
-			CreateItemData::ReFungible(_) => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
-		});
-
-	if let Some(last_tx_block) = <CreateItemBasket<T>>::get((collection.id, who.as_sub())) {
-		let timeout = last_tx_block + limit.into();
-		if block_number < timeout {
-			return None;
-		}
-	}
-
-	CreateItemBasket::<T>::insert((collection.id, who.as_sub()), block_number);
-
-	Some(())
-}
-
-pub fn withdraw_approve<T: Config>(
-	collection: &CollectionHandle<T>,
-	who: &T::AccountId,
-	item_id: &TokenId,
-) -> Option<()> {
-	// sponsor timeout
-	let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
-	let limit = collection.limits.sponsor_approve_timeout();
-
-	let last_tx_block = match collection.mode {
-		CollectionMode::NFT => <NftApproveBasket<T>>::get(collection.id, item_id),
-		CollectionMode::Fungible(_) => <FungibleApproveBasket<T>>::get(collection.id, who),
-		CollectionMode::ReFungible => {
-			<RefungibleApproveBasket<T>>::get((collection.id, item_id, who))
-		}
-	};
-
-	if let Some(last_tx_block) = last_tx_block {
-		let timeout = last_tx_block + limit.into();
-		if block_number < timeout {
-			return None;
-		}
-	}
-
-	match collection.mode {
-		CollectionMode::NFT => <NftApproveBasket<T>>::insert(collection.id, item_id, block_number),
-		CollectionMode::Fungible(_) => {
-			<FungibleApproveBasket<T>>::insert(collection.id, who, block_number)
-		}
-		CollectionMode::ReFungible => {
-			<RefungibleApproveBasket<T>>::insert((collection.id, item_id, who), block_number)
-		}
-	};
-
-	Some(())
-}
-
-fn load<T: UniqueConfig>(id: CollectionId) -> Option<(T::AccountId, CollectionHandle<T>)> {
-	let collection = CollectionHandle::new(id)?;
-	let sponsor = collection.sponsorship.sponsor().cloned()?;
-	Some((sponsor, collection))
-}
-
-pub struct UniqueSponsorshipHandler<T>(PhantomData<T>);
-impl<T, C> SponsorshipHandler<T::AccountId, C> for UniqueSponsorshipHandler<T>
-where
-	T: Config,
-	C: IsSubType<UniqueCall<T>>,
-{
-	fn get_sponsor(who: &T::AccountId, call: &C) -> Option<T::AccountId> {
-		match IsSubType::<UniqueCall<T>>::is_sub_type(call)? {
-			UniqueCall::set_token_properties {
-				collection_id,
-				token_id,
-				properties,
-				..
-			} => {
-				let (sponsor, collection) = load::<T>(*collection_id)?;
-				withdraw_set_token_property(
-					&collection,
-					&T::CrossAccountId::from_sub(who.clone()),
-					&token_id,
-					// No overflow may happen, as data larger than usize can't reach here
-					properties.iter().map(|p| p.key.len() + p.value.len()).sum(),
-				)
-				.map(|()| sponsor)
-			}
-			UniqueCall::create_item {
-				collection_id,
-				data,
-				..
-			} => {
-				let (sponsor, collection) = load(*collection_id)?;
-				withdraw_create_item::<T>(
-					&collection,
-					&T::CrossAccountId::from_sub(who.clone()),
-					data,
-				)
-				.map(|()| sponsor)
-			}
-			UniqueCall::transfer {
-				collection_id,
-				item_id,
-				..
-			} => {
-				let (sponsor, collection) = load(*collection_id)?;
-				withdraw_transfer::<T>(
-					&collection,
-					&T::CrossAccountId::from_sub(who.clone()),
-					item_id,
-				)
-				.map(|()| sponsor)
-			}
-			UniqueCall::transfer_from {
-				collection_id,
-				item_id,
-				from,
-				..
-			} => {
-				let (sponsor, collection) = load(*collection_id)?;
-				withdraw_transfer::<T>(&collection, from, item_id).map(|()| sponsor)
-			}
-			UniqueCall::approve {
-				collection_id,
-				item_id,
-				..
-			} => {
-				let (sponsor, collection) = load(*collection_id)?;
-				withdraw_approve::<T>(&collection, who, item_id).map(|()| sponsor)
-			}
-			_ => None,
-		}
-	}
-}
-
-pub trait SponsorshipPredict<T: Config> {
-	fn predict(collection: CollectionId, account: T::CrossAccountId, token: TokenId) -> Option<u64>
-	where
-		u64: From<<T as frame_system::Config>::BlockNumber>;
-}
-
-pub struct UniqueSponsorshipPredict<T>(PhantomData<T>);
-
-impl<T: Config> SponsorshipPredict<T> for UniqueSponsorshipPredict<T> {
-	fn predict(collection_id: CollectionId, who: T::CrossAccountId, token: TokenId) -> Option<u64>
-	where
-		u64: From<<T as frame_system::Config>::BlockNumber>,
-	{
-		let collection = <CollectionHandle<T>>::try_get(collection_id).ok()?;
-		let _ = collection.sponsorship.sponsor()?;
-
-		// sponsor timeout
-		let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
-		let limit = collection
-			.limits
-			.sponsor_transfer_timeout(match collection.mode {
-				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,
-				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
-				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
-			});
-
-		let last_tx_block = match collection.mode {
-			CollectionMode::NFT => <NftTransferBasket<T>>::get(collection.id, token),
-			CollectionMode::Fungible(_) => {
-				<FungibleTransferBasket<T>>::get(collection.id, who.as_sub())
-			}
-			CollectionMode::ReFungible => {
-				<ReFungibleTransferBasket<T>>::get((collection.id, token, who.as_sub()))
-			}
-		};
-
-		if let Some(last_tx_block) = last_tx_block {
-			return Some(
-				last_tx_block
-					.saturating_add(limit.into())
-					.saturating_sub(block_number)
-					.into(),
-			);
-		}
-
-		let token_exists = match collection.mode {
-			CollectionMode::NFT => {
-				<pallet_nonfungible::TokenData<T>>::contains_key((collection.id, token))
-			}
-			CollectionMode::Fungible(_) => token == TokenId::default(),
-			CollectionMode::ReFungible => {
-				<pallet_refungible::TotalSupply<T>>::contains_key((collection.id, token))
-			}
-		};
-
-		if token_exists {
-			Some(0)
-		} else {
-			None
-		}
-	}
-}
deletedruntime/common/src/types.rsdiffbeforeafterboth
--- a/runtime/common/src/types.rs
+++ /dev/null
@@ -1,72 +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 sp_runtime::{
-	traits::{Verify, IdentifyAccount, BlakeTwo256},
-	generic, MultiSignature,
-};
-
-pub use sp_runtime::OpaqueExtrinsic as UncheckedExtrinsic;
-
-/// Opaque block header type.
-pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
-
-/// Opaque block type.
-pub type Block = generic::Block<Header, UncheckedExtrinsic>;
-
-pub type SessionHandlers = ();
-
-/// An index to a block.
-pub type BlockNumber = u32;
-
-/// Alias to 512-bit hash when used in the context of a transaction signature on the chain.
-pub type Signature = MultiSignature;
-
-/// Some way of identifying an account on the chain. We intentionally make it equivalent
-/// to the public key of our transaction signing scheme.
-pub type AccountId = <<Signature as Verify>::Signer as IdentifyAccount>::AccountId;
-
-/// The type for looking up accounts. We don't expect more than 4 billion of them, but you
-/// never know...
-pub type AccountIndex = u32;
-
-/// Balance of an account.
-pub type Balance = u128;
-
-/// Index of a transaction in the chain.
-pub type Index = u32;
-
-/// A hash of some data used by the chain.
-pub type Hash = sp_core::H256;
-
-/// Digest item type.
-pub type DigestItem = generic::DigestItem;
-
-pub use sp_consensus_aura::sr25519::AuthorityId as AuraId;
-
-pub trait RuntimeInstance {
-	type CrossAccountId: pallet_evm::account::CrossAccountId<sp_runtime::AccountId32>
-		+ Send
-		+ Sync
-		+ 'static;
-
-	type TransactionConverter: fp_rpc::ConvertTransaction<UncheckedExtrinsic>
-		+ Send
-		+ Sync
-		+ 'static;
-
-	fn get_transaction_converter() -> Self::TransactionConverter;
-}
deletedruntime/common/src/weights.rsdiffbeforeafterboth
--- a/runtime/common/src/weights.rs
+++ /dev/null
@@ -1,111 +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 core::marker::PhantomData;
-use frame_support::{weights::Weight};
-use pallet_common::{CommonWeightInfo, dispatch::dispatch_weight, RefungibleExtensionsWeightInfo};
-
-use pallet_fungible::{Config as FungibleConfig, common::CommonWeights as FungibleWeights};
-use pallet_nonfungible::{Config as NonfungibleConfig, common::CommonWeights as NonfungibleWeights};
-use pallet_refungible::{
-	Config as RefungibleConfig, weights::WeightInfo, common::CommonWeights as RefungibleWeights,
-};
-use up_data_structs::{CreateItemExData, CreateItemData};
-
-macro_rules! max_weight_of {
-	($method:ident ( $($args:tt)* )) => {
-		<FungibleWeights<T>>::$method($($args)*)
-		.max(<NonfungibleWeights<T>>::$method($($args)*))
-		.max(<RefungibleWeights<T>>::$method($($args)*))
-	};
-}
-
-pub struct CommonWeights<T>(PhantomData<T>)
-where
-	T: FungibleConfig + NonfungibleConfig + RefungibleConfig;
-impl<T> CommonWeightInfo<T::CrossAccountId> for CommonWeights<T>
-where
-	T: FungibleConfig + NonfungibleConfig + RefungibleConfig,
-{
-	fn create_item() -> Weight {
-		dispatch_weight::<T>() + max_weight_of!(create_item())
-	}
-
-	fn create_multiple_items(data: &[CreateItemData]) -> Weight {
-		dispatch_weight::<T>() + max_weight_of!(create_multiple_items(data))
-	}
-
-	fn create_multiple_items_ex(data: &CreateItemExData<T::CrossAccountId>) -> Weight {
-		dispatch_weight::<T>() + max_weight_of!(create_multiple_items_ex(data))
-	}
-
-	fn burn_item() -> Weight {
-		dispatch_weight::<T>() + max_weight_of!(burn_item())
-	}
-
-	fn set_collection_properties(amount: u32) -> Weight {
-		dispatch_weight::<T>() + max_weight_of!(set_collection_properties(amount))
-	}
-
-	fn delete_collection_properties(amount: u32) -> Weight {
-		dispatch_weight::<T>() + max_weight_of!(delete_collection_properties(amount))
-	}
-
-	fn set_token_properties(amount: u32) -> Weight {
-		dispatch_weight::<T>() + max_weight_of!(set_token_properties(amount))
-	}
-
-	fn delete_token_properties(amount: u32) -> Weight {
-		dispatch_weight::<T>() + max_weight_of!(delete_token_properties(amount))
-	}
-
-	fn set_token_property_permissions(amount: u32) -> Weight {
-		dispatch_weight::<T>() + max_weight_of!(set_token_property_permissions(amount))
-	}
-
-	fn transfer() -> Weight {
-		dispatch_weight::<T>() + max_weight_of!(transfer())
-	}
-
-	fn approve() -> Weight {
-		dispatch_weight::<T>() + max_weight_of!(approve())
-	}
-
-	fn transfer_from() -> Weight {
-		dispatch_weight::<T>() + max_weight_of!(transfer_from())
-	}
-
-	fn burn_from() -> Weight {
-		dispatch_weight::<T>() + max_weight_of!(burn_from())
-	}
-
-	fn burn_recursively_self_raw() -> Weight {
-		max_weight_of!(burn_recursively_self_raw())
-	}
-
-	fn burn_recursively_breadth_raw(amount: u32) -> Weight {
-		max_weight_of!(burn_recursively_breadth_raw(amount))
-	}
-}
-
-impl<T> RefungibleExtensionsWeightInfo for CommonWeights<T>
-where
-	T: FungibleConfig + NonfungibleConfig + RefungibleConfig,
-{
-	fn repartition() -> Weight {
-		dispatch_weight::<T>() + <<T as RefungibleConfig>::WeightInfo>::repartition_item()
-	}
-}
addedruntime/common/weights.rsdiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/weights.rs
@@ -0,0 +1,143 @@
+// 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 core::marker::PhantomData;
+use frame_support::{weights::Weight};
+use pallet_common::{CommonWeightInfo, dispatch::dispatch_weight, RefungibleExtensionsWeightInfo};
+
+use pallet_fungible::{Config as FungibleConfig, common::CommonWeights as FungibleWeights};
+use pallet_nonfungible::{Config as NonfungibleConfig, common::CommonWeights as NonfungibleWeights};
+
+#[cfg(feature = "refungible")]
+use pallet_refungible::{
+	Config as RefungibleConfig, weights::WeightInfo, common::CommonWeights as RefungibleWeights,
+};
+use up_data_structs::{CreateItemExData, CreateItemData};
+
+macro_rules! max_weight_of {
+	($method:ident ( $($args:tt)* )) => {{
+		let max_weight = <FungibleWeights<T>>::$method($($args)*)
+			.max(<NonfungibleWeights<T>>::$method($($args)*));
+
+		#[cfg(feature = "refungible")]
+		let max_weight = max_weight.max(<RefungibleWeights<T>>::$method($($args)*));
+
+		max_weight
+	}};
+}
+
+#[cfg(not(feature = "refungible"))]
+pub trait CommonWeightConfigs: FungibleConfig + NonfungibleConfig {}
+
+#[cfg(not(feature = "refungible"))]
+impl<T: FungibleConfig + NonfungibleConfig> CommonWeightConfigs for T {}
+
+#[cfg(feature = "refungible")]
+pub trait CommonWeightConfigs: FungibleConfig + NonfungibleConfig + RefungibleConfig {}
+
+#[cfg(feature = "refungible")]
+impl<T: FungibleConfig + NonfungibleConfig + RefungibleConfig> CommonWeightConfigs for T {}
+
+pub struct CommonWeights<T>(PhantomData<T>);
+
+impl<T> CommonWeightInfo<T::CrossAccountId> for CommonWeights<T>
+where
+	T: CommonWeightConfigs,
+{
+	fn create_item() -> Weight {
+		dispatch_weight::<T>() + max_weight_of!(create_item())
+	}
+
+	fn create_multiple_items(data: &[CreateItemData]) -> Weight {
+		dispatch_weight::<T>() + max_weight_of!(create_multiple_items(data))
+	}
+
+	fn create_multiple_items_ex(data: &CreateItemExData<T::CrossAccountId>) -> Weight {
+		dispatch_weight::<T>() + max_weight_of!(create_multiple_items_ex(data))
+	}
+
+	fn burn_item() -> Weight {
+		dispatch_weight::<T>() + max_weight_of!(burn_item())
+	}
+
+	fn set_collection_properties(amount: u32) -> Weight {
+		dispatch_weight::<T>() + max_weight_of!(set_collection_properties(amount))
+	}
+
+	fn delete_collection_properties(amount: u32) -> Weight {
+		dispatch_weight::<T>() + max_weight_of!(delete_collection_properties(amount))
+	}
+
+	fn set_token_properties(amount: u32) -> Weight {
+		dispatch_weight::<T>() + max_weight_of!(set_token_properties(amount))
+	}
+
+	fn delete_token_properties(amount: u32) -> Weight {
+		dispatch_weight::<T>() + max_weight_of!(delete_token_properties(amount))
+	}
+
+	fn set_token_property_permissions(amount: u32) -> Weight {
+		dispatch_weight::<T>() + max_weight_of!(set_token_property_permissions(amount))
+	}
+
+	fn transfer() -> Weight {
+		dispatch_weight::<T>() + max_weight_of!(transfer())
+	}
+
+	fn approve() -> Weight {
+		dispatch_weight::<T>() + max_weight_of!(approve())
+	}
+
+	fn transfer_from() -> Weight {
+		dispatch_weight::<T>() + max_weight_of!(transfer_from())
+	}
+
+	fn burn_from() -> Weight {
+		dispatch_weight::<T>() + max_weight_of!(burn_from())
+	}
+
+	fn burn_recursively_self_raw() -> Weight {
+		max_weight_of!(burn_recursively_self_raw())
+	}
+
+	fn burn_recursively_breadth_raw(amount: u32) -> Weight {
+		max_weight_of!(burn_recursively_breadth_raw(amount))
+	}
+
+	fn token_owner() -> Weight {
+		max_weight_of!(token_owner())
+	}
+}
+
+#[cfg(feature = "refungible")]
+impl<T> RefungibleExtensionsWeightInfo for CommonWeights<T>
+where
+	T: FungibleConfig + NonfungibleConfig + RefungibleConfig,
+{
+	fn repartition() -> Weight {
+		dispatch_weight::<T>() + <<T as RefungibleConfig>::WeightInfo>::repartition_item()
+	}
+}
+
+#[cfg(not(feature = "refungible"))]
+impl<T> RefungibleExtensionsWeightInfo for CommonWeights<T>
+where
+	T: FungibleConfig + NonfungibleConfig,
+{
+	fn repartition() -> Weight {
+		dispatch_weight::<T>()
+	}
+}
modifiedruntime/opal/Cargo.tomldiffbeforeafterboth
--- a/runtime/opal/Cargo.toml
+++ b/runtime/opal/Cargo.toml
@@ -16,7 +16,7 @@
 targets = ['x86_64-unknown-linux-gnu']
 
 [features]
-default = ['std']
+default = ['std', 'opal-runtime']
 runtime-benchmarks = [
     'hex-literal',
     'frame-benchmarking',
@@ -113,13 +113,20 @@
     'xcm/std',
     'xcm-builder/std',
     'xcm-executor/std',
-    'unique-runtime-common/std',
+    'up-common/std',
     'rmrk-rpc/std',
+    'evm-coder/std',
+    'up-sponsorship/std',
 
     "orml-vesting/std",
 ]
 limit-testing = ['pallet-unique/limit-testing', 'up-data-structs/limit-testing']
+opal-runtime = ['refungible', 'scheduler', 'rmrk']
 
+refungible = []
+scheduler = []
+rmrk = []
+
 ################################################################################
 # Substrate Dependencies
 
@@ -396,7 +403,7 @@
 
 [dependencies]
 log = { version = "0.4.16", default-features = false }
-unique-runtime-common = { path = "../common", default-features = false }
+up-common = { path = "../../primitives/common", default-features = false }
 scale-info = { version = "2.0.1", default-features = false, features = [
     "derive",
 ] }
@@ -426,6 +433,8 @@
 pallet-base-fee = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" }
 fp-rpc = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" }
 fp-self-contained = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" }
+evm-coder = { default-features = false, path = '../../crates/evm-coder' }
+up-sponsorship = { default-features = false, git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = 'polkadot-v0.9.24' }
 
 ################################################################################
 # Build Dependencies
modifiedruntime/opal/src/lib.rsdiffbeforeafterboth
--- a/runtime/opal/src/lib.rs
+++ b/runtime/opal/src/lib.rs
@@ -25,168 +25,21 @@
 #[cfg(feature = "std")]
 include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
 
-use sp_api::impl_runtime_apis;
-use sp_core::{crypto::KeyTypeId, OpaqueMetadata, H256, U256, H160};
-use sp_runtime::DispatchError;
-use fp_self_contained::*;
-// #[cfg(any(feature = "std", test))]
-// pub use sp_runtime::BuildStorage;
-
-use sp_runtime::{
-	Permill, Perbill, Percent, create_runtime_str, generic, impl_opaque_keys,
-	traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, AccountIdConversion, Zero, Member},
-	transaction_validity::{TransactionSource, TransactionValidity},
-	ApplyExtrinsicResult, RuntimeAppPublic,
-};
+use frame_support::parameter_types;
 
-use sp_std::prelude::*;
-
-#[cfg(feature = "std")]
-use sp_version::NativeVersion;
 use sp_version::RuntimeVersion;
-pub use pallet_transaction_payment::{
-	Multiplier, TargetedFeeAdjustment, FeeDetails, RuntimeDispatchInfo,
-};
-// A few exports that help ease life for downstream crates.
-pub use pallet_balances::Call as BalancesCall;
-pub use pallet_evm::{
-	EnsureAddressTruncated, HashedAddressMapping, Runner, account::CrossAccountId as _,
-	OnMethodCall, Account as EVMAccount, FeeCalculator, GasWeightMapping,
-};
-pub use frame_support::{
-	construct_runtime, match_types,
-	dispatch::DispatchResult,
-	PalletId, parameter_types, StorageValue, ConsensusEngineId,
-	traits::{
-		tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT, Everything,
-		Currency, ExistenceRequirement, Get, IsInVec, KeyOwnerProofSystem, LockIdentifier,
-		OnUnbalanced, Randomness, FindAuthor, ConstU32, Imbalance, PrivilegeCmp,
-	},
-	weights::{
-		constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},
-		DispatchClass, DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,
-		WeightToFeePolynomial, WeightToFeeCoefficient, WeightToFeeCoefficients, ConstantMultiplier,
-		WeightToFee,
-	},
-};
-use pallet_unique_scheduler::DispatchCall;
-use up_data_structs::{
-	CollectionId, TokenId, TokenData, Property, PropertyKeyPermission, CollectionLimits,
-	CollectionStats, RpcCollection,
-	mapping::{EvmTokenAddressMapping, CrossTokenAddressMapping},
-	TokenChild, RmrkCollectionInfo, RmrkInstanceInfo, RmrkResourceInfo, RmrkPropertyInfo,
-	RmrkBaseInfo, RmrkPartType, RmrkTheme, RmrkThemeName, RmrkCollectionId, RmrkNftId,
-	RmrkNftChild, RmrkPropertyKey, RmrkResourceId, RmrkBaseId,
-};
+use sp_runtime::create_runtime_str;
 
-// use pallet_contracts::weights::WeightInfo;
-// #[cfg(any(feature = "std", test))]
-use frame_system::{
-	self as frame_system, EnsureRoot, EnsureSigned,
-	limits::{BlockWeights, BlockLength},
-};
-use sp_arithmetic::{
-	traits::{BaseArithmetic, Unsigned},
-};
-use smallvec::smallvec;
-// use scale_info::TypeInfo;
-use codec::{Encode, Decode};
-use fp_rpc::TransactionStatus;
-use sp_runtime::{
-	traits::{
-		Applyable, BlockNumberProvider, Dispatchable, PostDispatchInfoOf, DispatchInfoOf,
-		Saturating, CheckedConversion,
-	},
-	generic::Era,
-	transaction_validity::TransactionValidityError,
-	DispatchErrorWithPostInfo, SaturatedConversion,
-};
+use up_common::types::*;
 
-// pub use pallet_timestamp::Call as TimestampCall;
-pub use sp_consensus_aura::sr25519::AuthorityId as AuraId;
+#[path = "../../common/mod.rs"]
+mod runtime_common;
 
-// Polkadot imports
-use pallet_xcm::XcmPassthrough;
-use polkadot_parachain::primitives::Sibling;
-use xcm::v1::{BodyId, Junction::*, MultiLocation, NetworkId, Junctions::*};
-use xcm_builder::{
-	AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter,
-	EnsureXcmOrigin, FixedWeightBounds, LocationInverter, NativeAsset, ParentAsSuperuser,
-	RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia,
-	SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit,
-	ParentIsPreset,
-};
-use xcm_executor::{Config, XcmExecutor, Assets};
-use sp_std::{cmp::Ordering, marker::PhantomData};
-
-use xcm::latest::{
-	//	Xcm,
-	AssetId::{Concrete},
-	Fungibility::Fungible as XcmFungible,
-	MultiAsset,
-	Error as XcmError,
-};
-use xcm_executor::traits::{MatchesFungible, WeightTrader};
-//use xcm_executor::traits::MatchesFungible;
+pub use runtime_common::*;
 
-use unique_runtime_common::{
-	impl_common_runtime_apis,
-	types::*,
-	constants::*,
-	dispatch::{CollectionDispatchT, CollectionDispatch},
-	sponsoring::UniqueSponsorshipHandler,
-	eth_sponsoring::UniqueEthSponsorshipHandler,
-	weights::CommonWeights,
-};
-
 pub const RUNTIME_NAME: &str = "opal";
 pub const TOKEN_SYMBOL: &str = "OPL";
-
-type CrossAccountId = pallet_evm::account::BasicCrossAccountId<Runtime>;
 
-impl RuntimeInstance for Runtime {
-	type CrossAccountId = self::CrossAccountId;
-	type TransactionConverter = self::TransactionConverter;
-
-	fn get_transaction_converter() -> TransactionConverter {
-		TransactionConverter
-	}
-}
-
-/// The type for looking up accounts. We don't expect more than 4 billion of them, but you
-/// never know...
-pub type AccountIndex = u32;
-
-/// Balance of an account.
-pub type Balance = u128;
-
-/// Index of a transaction in the chain.
-pub type Index = u32;
-
-/// A hash of some data used by the chain.
-pub type Hash = sp_core::H256;
-
-/// Digest item type.
-pub type DigestItem = generic::DigestItem;
-
-/// Opaque types. These are used by the CLI to instantiate machinery that don't need to know
-/// the specifics of the runtime. They can then be made to be agnostic over specific formats
-/// of data like extrinsics, allowing for them to continue syncing the network through upgrades
-/// to even the core data structures.
-pub mod opaque {
-	use sp_std::prelude::*;
-	use sp_runtime::impl_opaque_keys;
-	use super::Aura;
-
-	pub use unique_runtime_common::types::*;
-
-	impl_opaque_keys! {
-		pub struct SessionKeys {
-			pub aura: Aura,
-		}
-	}
-}
-
 /// This runtime version.
 pub const VERSION: RuntimeVersion = RuntimeVersion {
 	spec_name: create_runtime_str!(RUNTIME_NAME),
@@ -199,1206 +52,15 @@
 	state_version: 0,
 };
 
-#[derive(codec::Encode, codec::Decode)]
-pub enum XCMPMessage<XAccountId, XBalance> {
-	/// Transfer tokens to the given account from the Parachain account.
-	TransferToken(XAccountId, XBalance),
-}
-
-/// The version information used to identify this runtime when compiled natively.
-#[cfg(feature = "std")]
-pub fn native_version() -> NativeVersion {
-	NativeVersion {
-		runtime_version: VERSION,
-		can_author_with: Default::default(),
-	}
-}
-
-type NegativeImbalance = <Balances as Currency<AccountId>>::NegativeImbalance;
-
-pub struct DealWithFees;
-impl OnUnbalanced<NegativeImbalance> for DealWithFees {
-	fn on_unbalanceds<B>(mut fees_then_tips: impl Iterator<Item = NegativeImbalance>) {
-		if let Some(fees) = fees_then_tips.next() {
-			// for fees, 100% to treasury
-			let mut split = fees.ration(100, 0);
-			if let Some(tips) = fees_then_tips.next() {
-				// for tips, if any, 100% to treasury
-				tips.ration_merge_into(100, 0, &mut split);
-			}
-			Treasury::on_unbalanced(split.0);
-			// Author::on_unbalanced(split.1);
-		}
-	}
-}
-
 parameter_types! {
-	pub const BlockHashCount: BlockNumber = 2400;
-	pub RuntimeBlockLength: BlockLength =
-		BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO);
-	pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75);
-	pub const MaximumBlockLength: u32 = 5 * 1024 * 1024;
-	pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder()
-		.base_block(BlockExecutionWeight::get())
-		.for_class(DispatchClass::all(), |weights| {
-			weights.base_extrinsic = ExtrinsicBaseWeight::get();
-		})
-		.for_class(DispatchClass::Normal, |weights| {
-			weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT);
-		})
-		.for_class(DispatchClass::Operational, |weights| {
-			weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT);
-			// Operational transactions have some extra reserved space, so that they
-			// are included even if block reached `MAXIMUM_BLOCK_WEIGHT`.
-			weights.reserved = Some(
-				MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT
-			);
-		})
-		.avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO)
-		.build_or_panic();
 	pub const Version: RuntimeVersion = VERSION;
 	pub const SS58Prefix: u8 = 42;
-}
-
-parameter_types! {
 	pub const ChainId: u64 = 8882;
-}
-
-pub struct FixedFee;
-impl FeeCalculator for FixedFee {
-	fn min_gas_price() -> (U256, u64) {
-		(MIN_GAS_PRICE.into(), 0)
-	}
-}
-
-// Assuming slowest ethereum opcode is SSTORE, with gas price of 20000 as our worst case
-// (contract, which only writes a lot of data),
-// approximating on top of our real store write weight
-parameter_types! {
-	pub const WritesPerSecond: u64 = WEIGHT_PER_SECOND / <Runtime as frame_system::Config>::DbWeight::get().write;
-	pub const GasPerSecond: u64 = WritesPerSecond::get() * 20000;
-	pub const WeightPerGas: u64 = WEIGHT_PER_SECOND / GasPerSecond::get();
 }
 
-/// Limiting EVM execution to 50% of block for substrate users and management tasks
-/// EVM transaction consumes more weight than substrate's, so we can't rely on them being
-/// scheduled fairly
-const EVM_DISPATCH_RATIO: Perbill = Perbill::from_percent(50);
-parameter_types! {
-	pub BlockGasLimit: U256 = U256::from(NORMAL_DISPATCH_RATIO * EVM_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT / WeightPerGas::get());
-}
-
-pub enum FixedGasWeightMapping {}
-impl GasWeightMapping for FixedGasWeightMapping {
-	fn gas_to_weight(gas: u64) -> Weight {
-		gas.saturating_mul(WeightPerGas::get())
-	}
-	fn weight_to_gas(weight: Weight) -> u64 {
-		weight / WeightPerGas::get()
-	}
-}
-
-impl pallet_evm::account::Config for Runtime {
-	type CrossAccountId = pallet_evm::account::BasicCrossAccountId<Self>;
-	type EvmAddressMapping = pallet_evm::HashedAddressMapping<Self::Hashing>;
-	type EvmBackwardsAddressMapping = fp_evm_mapping::MapBackwardsAddressTruncated;
-}
-
-impl pallet_evm::Config for Runtime {
-	type BlockGasLimit = BlockGasLimit;
-	type FeeCalculator = FixedFee;
-	type GasWeightMapping = FixedGasWeightMapping;
-	type BlockHashMapping = pallet_ethereum::EthereumBlockHashMapping<Self>;
-	type CallOrigin = EnsureAddressTruncated<Self>;
-	type WithdrawOrigin = EnsureAddressTruncated<Self>;
-	type AddressMapping = HashedAddressMapping<Self::Hashing>;
-	type PrecompilesType = ();
-	type PrecompilesValue = ();
-	type Currency = Balances;
-	type Event = Event;
-	type OnMethodCall = (
-		pallet_evm_migration::OnMethodCall<Self>,
-		pallet_evm_contract_helpers::HelpersOnMethodCall<Self>,
-		CollectionDispatchT<Self>,
-		pallet_unique::eth::CollectionHelpersOnMethodCall<Self>,
-	);
-	type OnCreate = pallet_evm_contract_helpers::HelpersOnCreate<Self>;
-	type ChainId = ChainId;
-	type Runner = pallet_evm::runner::stack::Runner<Self>;
-	type OnChargeTransaction = pallet_evm::EVMCurrencyAdapter<Balances, DealWithFees>;
-	type TransactionValidityHack = pallet_evm_transaction_payment::TransactionValidityHack<Self>;
-	type FindAuthor = EthereumFindAuthor<Aura>;
-}
-
-impl pallet_evm_migration::Config for Runtime {
-	type WeightInfo = pallet_evm_migration::weights::SubstrateWeight<Self>;
-}
-
-pub struct EthereumFindAuthor<F>(core::marker::PhantomData<F>);
-impl<F: FindAuthor<u32>> FindAuthor<H160> for EthereumFindAuthor<F> {
-	fn find_author<'a, I>(digests: I) -> Option<H160>
-	where
-		I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
-	{
-		if let Some(author_index) = F::find_author(digests) {
-			let authority_id = Aura::authorities()[author_index as usize].clone();
-			return Some(H160::from_slice(&authority_id.to_raw_vec()[4..24]));
-		}
-		None
-	}
-}
-
-impl pallet_ethereum::Config for Runtime {
-	type Event = Event;
-	type StateRoot = pallet_ethereum::IntermediateStateRoot<Self>;
-}
-
-impl pallet_randomness_collective_flip::Config for Runtime {}
-
-impl frame_system::Config for Runtime {
-	/// The data to be stored in an account.
-	type AccountData = pallet_balances::AccountData<Balance>;
-	/// The identifier used to distinguish between accounts.
-	type AccountId = AccountId;
-	/// The basic call filter to use in dispatchable.
-	type BaseCallFilter = Everything;
-	/// Maximum number of block number to block hash mappings to keep (oldest pruned first).
-	type BlockHashCount = BlockHashCount;
-	/// The maximum length of a block (in bytes).
-	type BlockLength = RuntimeBlockLength;
-	/// The index type for blocks.
-	type BlockNumber = BlockNumber;
-	/// The weight of the overhead invoked on the block import process, independent of the extrinsics included in that block.
-	type BlockWeights = RuntimeBlockWeights;
-	/// The aggregated dispatch type that is available for extrinsics.
-	type Call = Call;
-	/// The weight of database operations that the runtime can invoke.
-	type DbWeight = RocksDbWeight;
-	/// The ubiquitous event type.
-	type Event = Event;
-	/// The type for hashing blocks and tries.
-	type Hash = Hash;
-	/// The hashing algorithm used.
-	type Hashing = BlakeTwo256;
-	/// The header type.
-	type Header = generic::Header<BlockNumber, BlakeTwo256>;
-	/// The index type for storing how many extrinsics an account has signed.
-	type Index = Index;
-	/// The lookup mechanism to get account ID from whatever is passed in dispatchers.
-	type Lookup = AccountIdLookup<AccountId, ()>;
-	/// What to do if an account is fully reaped from the system.
-	type OnKilledAccount = ();
-	/// What to do if a new account is created.
-	type OnNewAccount = ();
-	type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode<Self>;
-	/// The ubiquitous origin type.
-	type Origin = Origin;
-	/// This type is being generated by `construct_runtime!`.
-	type PalletInfo = PalletInfo;
-	/// This is used as an identifier of the chain. 42 is the generic substrate prefix.
-	type SS58Prefix = SS58Prefix;
-	/// Weight information for the extrinsics of this pallet.
-	type SystemWeightInfo = frame_system::weights::SubstrateWeight<Self>;
-	/// Version of the runtime.
-	type Version = Version;
-	type MaxConsumers = ConstU32<16>;
-}
-
-parameter_types! {
-	pub const MinimumPeriod: u64 = SLOT_DURATION / 2;
-}
-
-impl pallet_timestamp::Config for Runtime {
-	/// A timestamp: milliseconds since the unix epoch.
-	type Moment = u64;
-	type OnTimestampSet = ();
-	type MinimumPeriod = MinimumPeriod;
-	type WeightInfo = ();
-}
-
-parameter_types! {
-	// pub const ExistentialDeposit: u128 = 500;
-	pub const ExistentialDeposit: u128 = 0;
-	pub const MaxLocks: u32 = 50;
-	pub const MaxReserves: u32 = 50;
-}
-
-impl pallet_balances::Config for Runtime {
-	type MaxLocks = MaxLocks;
-	type MaxReserves = MaxReserves;
-	type ReserveIdentifier = [u8; 16];
-	/// The type for recording an account's balance.
-	type Balance = Balance;
-	/// The ubiquitous event type.
-	type Event = Event;
-	type DustRemoval = Treasury;
-	type ExistentialDeposit = ExistentialDeposit;
-	type AccountStore = System;
-	type WeightInfo = pallet_balances::weights::SubstrateWeight<Self>;
-}
-
-pub const fn deposit(items: u32, bytes: u32) -> Balance {
-	items as Balance * 15 * CENTIUNIQUE + (bytes as Balance) * 6 * CENTIUNIQUE
-}
-
-/*
-parameter_types! {
-	pub TombstoneDeposit: Balance = deposit(
-		1,
-		sp_std::mem::size_of::<pallet_contracts::Pallet<Runtime>> as u32,
-	);
-	pub DepositPerContract: Balance = TombstoneDeposit::get();
-	pub const DepositPerStorageByte: Balance = deposit(0, 1);
-	pub const DepositPerStorageItem: Balance = deposit(1, 0);
-	pub RentFraction: Perbill = Perbill::from_rational(1u32, 30 * DAYS);
-	pub const SurchargeReward: Balance = 150 * MILLIUNIQUE;
-	pub const SignedClaimHandicap: u32 = 2;
-	pub const MaxDepth: u32 = 32;
-	pub const MaxValueSize: u32 = 16 * 1024;
-	pub const MaxCodeSize: u32 = 1024 * 1024 * 25; // 25 Mb
-	// The lazy deletion runs inside on_initialize.
-	pub DeletionWeightLimit: Weight = AVERAGE_ON_INITIALIZE_RATIO *
-		RuntimeBlockWeights::get().max_block;
-	// The weight needed for decoding the queue should be less or equal than a fifth
-	// of the overall weight dedicated to the lazy deletion.
-	pub DeletionQueueDepth: u32 = ((DeletionWeightLimit::get() / (
-			<Runtime as pallet_contracts::Config>::WeightInfo::on_initialize_per_queue_item(1) -
-			<Runtime as pallet_contracts::Config>::WeightInfo::on_initialize_per_queue_item(0)
-		)) / 5) as u32;
-	pub Schedule: pallet_contracts::Schedule<Runtime> = Default::default();
-}
-
-impl pallet_contracts::Config for Runtime {
-	type Time = Timestamp;
-	type Randomness = RandomnessCollectiveFlip;
-	type Currency = Balances;
-	type Event = Event;
-	type RentPayment = ();
-	type SignedClaimHandicap = SignedClaimHandicap;
-	type TombstoneDeposit = TombstoneDeposit;
-	type DepositPerContract = DepositPerContract;
-	type DepositPerStorageByte = DepositPerStorageByte;
-	type DepositPerStorageItem = DepositPerStorageItem;
-	type RentFraction = RentFraction;
-	type SurchargeReward = SurchargeReward;
-	type WeightPrice = pallet_transaction_payment::Pallet<Self>;
-	type WeightInfo = pallet_contracts::weights::SubstrateWeight<Self>;
-	type ChainExtension = NFTExtension;
-	type DeletionQueueDepth = DeletionQueueDepth;
-	type DeletionWeightLimit = DeletionWeightLimit;
-	type Schedule = Schedule;
-	type CallStack = [pallet_contracts::Frame<Self>; 31];
-}
-*/
+construct_runtime!(opal);
 
-parameter_types! {
-	/// This value increases the priority of `Operational` transactions by adding
-	/// a "virtual tip" that's equal to the `OperationalFeeMultiplier * final_fee`.
-	pub const OperationalFeeMultiplier: u8 = 5;
-}
-
-/// Linear implementor of `WeightToFeePolynomial`
-pub struct LinearFee<T>(sp_std::marker::PhantomData<T>);
-
-impl<T> WeightToFeePolynomial for LinearFee<T>
-where
-	T: BaseArithmetic + From<u32> + Copy + Unsigned,
-{
-	type Balance = T;
-
-	fn polynomial() -> WeightToFeeCoefficients<Self::Balance> {
-		smallvec!(WeightToFeeCoefficient {
-			coeff_integer: WEIGHT_TO_FEE_COEFF.into(),
-			coeff_frac: Perbill::zero(),
-			negative: false,
-			degree: 1,
-		})
-	}
-}
-
-impl pallet_transaction_payment::Config for Runtime {
-	type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter<Balances, DealWithFees>;
-	type LengthToFee = ConstantMultiplier<Balance, TransactionByteFee>;
-	type OperationalFeeMultiplier = OperationalFeeMultiplier;
-	type WeightToFee = LinearFee<Balance>;
-	type FeeMultiplierUpdate = ();
-}
-
-parameter_types! {
-	pub const ProposalBond: Permill = Permill::from_percent(5);
-	pub const ProposalBondMinimum: Balance = 1 * UNIQUE;
-	pub const ProposalBondMaximum: Balance = 1000 * UNIQUE;
-	pub const SpendPeriod: BlockNumber = 5 * MINUTES;
-	pub const Burn: Permill = Permill::from_percent(0);
-	pub const TipCountdown: BlockNumber = 1 * DAYS;
-	pub const TipFindersFee: Percent = Percent::from_percent(20);
-	pub const TipReportDepositBase: Balance = 1 * UNIQUE;
-	pub const DataDepositPerByte: Balance = 1 * CENTIUNIQUE;
-	pub const BountyDepositBase: Balance = 1 * UNIQUE;
-	pub const BountyDepositPayoutDelay: BlockNumber = 1 * DAYS;
-	pub const TreasuryModuleId: PalletId = PalletId(*b"py/trsry");
-	pub const BountyUpdatePeriod: BlockNumber = 14 * DAYS;
-	pub const MaximumReasonLength: u32 = 16384;
-	pub const BountyCuratorDeposit: Permill = Permill::from_percent(50);
-	pub const BountyValueMinimum: Balance = 5 * UNIQUE;
-	pub const MaxApprovals: u32 = 100;
-}
-
-impl pallet_treasury::Config for Runtime {
-	type PalletId = TreasuryModuleId;
-	type Currency = Balances;
-	type ApproveOrigin = EnsureRoot<AccountId>;
-	type RejectOrigin = EnsureRoot<AccountId>;
-	type Event = Event;
-	type OnSlash = ();
-	type ProposalBond = ProposalBond;
-	type ProposalBondMinimum = ProposalBondMinimum;
-	type ProposalBondMaximum = ProposalBondMaximum;
-	type SpendPeriod = SpendPeriod;
-	type Burn = Burn;
-	type BurnDestination = ();
-	type SpendFunds = ();
-	type WeightInfo = pallet_treasury::weights::SubstrateWeight<Self>;
-	type MaxApprovals = MaxApprovals;
-}
-
-impl pallet_sudo::Config for Runtime {
-	type Event = Event;
-	type Call = Call;
-}
-
-pub struct RelayChainBlockNumberProvider<T>(sp_std::marker::PhantomData<T>);
-
-impl<T: cumulus_pallet_parachain_system::Config> BlockNumberProvider
-	for RelayChainBlockNumberProvider<T>
-{
-	type BlockNumber = BlockNumber;
-
-	fn current_block_number() -> Self::BlockNumber {
-		cumulus_pallet_parachain_system::Pallet::<T>::validation_data()
-			.map(|d| d.relay_parent_number)
-			.unwrap_or_default()
-	}
-}
-
-parameter_types! {
-	pub const MinVestedTransfer: Balance = 10 * UNIQUE;
-	pub const MaxVestingSchedules: u32 = 28;
-}
-
-impl orml_vesting::Config for Runtime {
-	type Event = Event;
-	type Currency = pallet_balances::Pallet<Runtime>;
-	type MinVestedTransfer = MinVestedTransfer;
-	type VestedTransferOrigin = EnsureSigned<AccountId>;
-	type WeightInfo = ();
-	type MaxVestingSchedules = MaxVestingSchedules;
-	type BlockNumberProvider = RelayChainBlockNumberProvider<Runtime>;
-}
-
-parameter_types! {
-	pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 4;
-	pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 4;
-}
-
-impl cumulus_pallet_parachain_system::Config for Runtime {
-	type Event = Event;
-	type SelfParaId = parachain_info::Pallet<Self>;
-	type OnSystemEvent = ();
-	// type DownwardMessageHandlers = cumulus_primitives_utility::UnqueuedDmpAsParent<
-	// 	MaxDownwardMessageWeight,
-	// 	XcmExecutor<XcmConfig>,
-	// 	Call,
-	// >;
-	type OutboundXcmpMessageSource = XcmpQueue;
-	type DmpMessageHandler = DmpQueue;
-	type ReservedDmpWeight = ReservedDmpWeight;
-	type ReservedXcmpWeight = ReservedXcmpWeight;
-	type XcmpMessageHandler = XcmpQueue;
-}
-
-impl parachain_info::Config for Runtime {}
-
-impl cumulus_pallet_aura_ext::Config for Runtime {}
-
-parameter_types! {
-	pub const RelayLocation: MultiLocation = MultiLocation::parent();
-	pub const RelayNetwork: NetworkId = NetworkId::Polkadot;
-	pub RelayOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into();
-	pub Ancestry: MultiLocation = Parachain(ParachainInfo::parachain_id().into()).into();
-}
-
-/// 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>,
-);
-
-pub struct OnlySelfCurrency;
-impl<B: TryFrom<u128>> MatchesFungible<B> for OnlySelfCurrency {
-	fn matches_fungible(a: &MultiAsset) -> Option<B> {
-		match (&a.id, &a.fun) {
-			(Concrete(_), 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.
-	(),
->;
-
-/// 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, Origin>,
-	// Native converter for Relay-chain (Parent) location; will converts to a `Relay` origin when
-	// recognised.
-	RelayChainAsNative<RelayOrigin, Origin>,
-	// Native converter for sibling Parachains; will convert to a `SiblingPara` origin when
-	// recognised.
-	SiblingParachainAsNative<cumulus_pallet_xcm::Origin, Origin>,
-	// Superuser converter for the Relay-chain (Parent) location. This will allow it to issue a
-	// transaction from the Root origin.
-	ParentAsSuperuser<Origin>,
-	// Native signed account converter; this just converts an `AccountId32` origin into a normal
-	// `Origin::Signed` origin of the same 32-byte value.
-	SignedAccountId32AsNative<RelayNetwork, Origin>,
-	// Xcm origins can be represented natively under the Xcm pallet's Xcm origin.
-	XcmPassthrough<Origin>,
-);
-
-parameter_types! {
-	// One XCM operation is 1_000_000 weight - almost certainly a conservative estimate.
-	pub UnitWeightCost: Weight = 1_000_000;
-	// 1200 UNIQUEs buy 1 second of weight.
-	pub const WeightPrice: (MultiLocation, u128) = (MultiLocation::parent(), 1_200 * UNIQUE);
-	pub const MaxInstructions: u32 = 100;
-	pub const MaxAuthorities: u32 = 100_000;
-}
-
-match_types! {
-	pub type ParentOrParentsUnitPlurality: impl Contains<MultiLocation> = {
-		MultiLocation { parents: 1, interior: Here } |
-		MultiLocation { parents: 1, interior: X1(Plurality { id: BodyId::Unit, .. }) }
-	};
-}
-
-pub type Barrier = (
-	TakeWeightCredit,
-	AllowTopLevelPaidExecutionFrom<Everything>,
-	// ^^^ Parent & its unit plurality gets free execution
-);
-
-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 {
-		Self(0, Zero::zero(), PhantomData)
-	}
-
-	fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, XcmError> {
-		let amount = WeightToFee::weight_to_fee(&weight);
-		let u128_amount: u128 = amount.try_into().map_err(|_| XcmError::Overflow)?;
-
-		// location to this parachain through relay chain
-		let option1: xcm::v1::AssetId = Concrete(MultiLocation {
-			parents: 1,
-			interior: X1(Parachain(ParachainInfo::parachain_id().into())),
-		});
-		// direct location
-		let option2: xcm::v1::AssetId = Concrete(MultiLocation {
-			parents: 0,
-			interior: Here,
-		});
-
-		let required = if payment.fungible.contains_key(&option1) {
-			(option1, u128_amount).into()
-		} else if payment.fungible.contains_key(&option2) {
-			(option2, u128_amount).into()
-		} else {
-			(Concrete(MultiLocation::default()), u128_amount).into()
-		};
-
-		let unused = payment
-			.checked_sub(required)
-			.map_err(|_| XcmError::TooExpensive)?;
-		self.0 = self.0.saturating_add(weight);
-		self.1 = self.1.saturating_add(amount);
-		Ok(unused)
-	}
-
-	fn refund_weight(&mut self, weight: Weight) -> Option<MultiAsset> {
-		let weight = weight.min(self.0);
-		let amount = WeightToFee::weight_to_fee(&weight);
-		self.0 -= weight;
-		self.1 = self.1.saturating_sub(amount);
-		let amount: u128 = amount.saturated_into();
-		if amount > 0 {
-			Some((AssetId::get(), amount).into())
-		} else {
-			None
-		}
-	}
-}
-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 struct XcmConfig;
-impl Config for XcmConfig {
-	type Call = Call;
-	type XcmSender = XcmRouter;
-	// How to withdraw and deposit an asset.
-	type AssetTransactor = LocalAssetTransactor;
-	type OriginConverter = XcmOriginToTransactDispatchOrigin;
-	type IsReserve = NativeAsset;
-	type IsTeleporter = (); // Teleportation is disabled
-	type LocationInverter = LocationInverter<Ancestry>;
-	type Barrier = Barrier;
-	type Weigher = FixedWeightBounds<UnitWeightCost, Call, MaxInstructions>;
-	type Trader =
-		UsingOnlySelfCurrencyComponents<LinearFee<Balance>, RelayLocation, AccountId, Balances, ()>;
-	type ResponseHandler = (); // Don't handle responses for now.
-	type SubscriptionService = PolkadotXcm;
-
-	type AssetTrap = PolkadotXcm;
-	type AssetClaims = PolkadotXcm;
-}
-
-// parameter_types! {
-// 	pub const MaxDownwardMessageWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 10;
-// }
-
-/// No local origins on this chain are allowed to dispatch XCM sends/executions.
-pub type LocalOriginToLocation = (SignedToAccountId32<Origin, 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, ()>,
-	// ..and XCMP to communicate with the sibling chains.
-	XcmpQueue,
-);
-
-impl pallet_evm_coder_substrate::Config for Runtime {}
-
-impl pallet_xcm::Config for Runtime {
-	type Event = Event;
-	type SendXcmOrigin = EnsureXcmOrigin<Origin, LocalOriginToLocation>;
-	type XcmRouter = XcmRouter;
-	type ExecuteXcmOrigin = EnsureXcmOrigin<Origin, LocalOriginToLocation>;
-	type XcmExecuteFilter = Everything;
-	type XcmExecutor = XcmExecutor<XcmConfig>;
-	type XcmTeleportFilter = Everything;
-	type XcmReserveTransferFilter = Everything;
-	type Weigher = FixedWeightBounds<UnitWeightCost, Call, MaxInstructions>;
-	type LocationInverter = LocationInverter<Ancestry>;
-	type Origin = Origin;
-	type Call = Call;
-	const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
-	type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion;
-}
-
-impl cumulus_pallet_xcm::Config for Runtime {
-	type Event = Event;
-	type XcmExecutor = XcmExecutor<XcmConfig>;
-}
-
-impl cumulus_pallet_xcmp_queue::Config for Runtime {
-	type WeightInfo = ();
-	type Event = Event;
-	type XcmExecutor = XcmExecutor<XcmConfig>;
-	type ChannelInfo = ParachainSystem;
-	type VersionWrapper = ();
-	type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
-	type ControllerOrigin = EnsureRoot<AccountId>;
-	type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin;
-}
-
-impl cumulus_pallet_dmp_queue::Config for Runtime {
-	type Event = Event;
-	type XcmExecutor = XcmExecutor<XcmConfig>;
-	type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
-}
-
-impl pallet_aura::Config for Runtime {
-	type AuthorityId = AuraId;
-	type DisabledValidators = ();
-	type MaxAuthorities = MaxAuthorities;
-}
-
-parameter_types! {
-	pub TreasuryAccountId: AccountId = TreasuryModuleId::get().into_account_truncating();
-	pub const CollectionCreationPrice: Balance = 2 * UNIQUE;
-}
-
-impl pallet_common::Config for Runtime {
-	type WeightInfo = pallet_common::weights::SubstrateWeight<Self>;
-	type Event = Event;
-	type Currency = Balances;
-	type CollectionCreationPrice = CollectionCreationPrice;
-	type TreasuryAccountId = TreasuryAccountId;
-	type CollectionDispatch = CollectionDispatchT<Self>;
-
-	type EvmTokenAddressMapping = EvmTokenAddressMapping;
-	type CrossTokenAddressMapping = CrossTokenAddressMapping<Self::AccountId>;
-	type ContractAddress = EvmCollectionHelpersAddress;
-}
-
-impl pallet_structure::Config for Runtime {
-	type Event = Event;
-	type Call = Call;
-	type WeightInfo = pallet_structure::weights::SubstrateWeight<Self>;
-}
-
-impl pallet_fungible::Config for Runtime {
-	type WeightInfo = pallet_fungible::weights::SubstrateWeight<Self>;
-}
-impl pallet_refungible::Config for Runtime {
-	type WeightInfo = pallet_refungible::weights::SubstrateWeight<Self>;
-}
-impl pallet_nonfungible::Config for Runtime {
-	type WeightInfo = pallet_nonfungible::weights::SubstrateWeight<Self>;
-}
-
-impl pallet_proxy_rmrk_core::Config for Runtime {
-	type WeightInfo = pallet_proxy_rmrk_core::weights::SubstrateWeight<Self>;
-	type Event = Event;
-}
-
-impl pallet_proxy_rmrk_equip::Config for Runtime {
-	type WeightInfo = pallet_proxy_rmrk_equip::weights::SubstrateWeight<Self>;
-	type Event = Event;
-}
-
-impl pallet_unique::Config for Runtime {
-	type Event = Event;
-	type WeightInfo = pallet_unique::weights::SubstrateWeight<Self>;
-	type CommonWeightInfo = CommonWeights<Self>;
-	type RefungibleExtensionsWeightInfo = CommonWeights<Self>;
-}
-
-parameter_types! {
-	pub const InflationBlockInterval: BlockNumber = 100; // every time per how many blocks inflation is applied
-}
-
-/// Used for the pallet inflation
-impl pallet_inflation::Config for Runtime {
-	type Currency = Balances;
-	type TreasuryAccountId = TreasuryAccountId;
-	type InflationBlockInterval = InflationBlockInterval;
-	type BlockNumberProvider = RelayChainBlockNumberProvider<Runtime>;
-}
-
-parameter_types! {
-	pub MaximumSchedulerWeight: Weight = Perbill::from_percent(50) *
-		RuntimeBlockWeights::get().max_block;
-	pub const MaxScheduledPerBlock: u32 = 50;
-}
-
-type ChargeTransactionPayment = pallet_charge_transaction::ChargeTransactionPayment<Runtime>;
-use frame_support::traits::NamedReservableCurrency;
-
-fn get_signed_extras(from: <Runtime as frame_system::Config>::AccountId) -> SignedExtraScheduler {
-	(
-		frame_system::CheckSpecVersion::<Runtime>::new(),
-		frame_system::CheckGenesis::<Runtime>::new(),
-		frame_system::CheckEra::<Runtime>::from(Era::Immortal),
-		frame_system::CheckNonce::<Runtime>::from(frame_system::Pallet::<Runtime>::account_nonce(
-			from,
-		)),
-		frame_system::CheckWeight::<Runtime>::new(),
-		// sponsoring transaction logic
-		// pallet_charge_transaction::ChargeTransactionPayment::<Runtime>::new(0),
-	)
-}
-
-pub struct SchedulerPaymentExecutor;
-impl<T: frame_system::Config + pallet_unique_scheduler::Config, SelfContainedSignedInfo>
-	DispatchCall<T, SelfContainedSignedInfo> for SchedulerPaymentExecutor
-where
-	<T as frame_system::Config>::Call: Member
-		+ Dispatchable<Origin = Origin, Info = DispatchInfo>
-		+ SelfContainedCall<SignedInfo = SelfContainedSignedInfo>
-		+ GetDispatchInfo
-		+ From<frame_system::Call<Runtime>>,
-	SelfContainedSignedInfo: Send + Sync + 'static,
-	Call: From<<T as frame_system::Config>::Call>
-		+ From<<T as pallet_unique_scheduler::Config>::Call>
-		+ SelfContainedCall<SignedInfo = SelfContainedSignedInfo>,
-	sp_runtime::AccountId32: From<<T as frame_system::Config>::AccountId>,
-{
-	fn dispatch_call(
-		signer: <T as frame_system::Config>::AccountId,
-		call: <T as pallet_unique_scheduler::Config>::Call,
-	) -> Result<
-		Result<PostDispatchInfo, DispatchErrorWithPostInfo<PostDispatchInfo>>,
-		TransactionValidityError,
-	> {
-		let dispatch_info = call.get_dispatch_info();
-		let extrinsic = fp_self_contained::CheckedExtrinsic::<
-			AccountId,
-			Call,
-			SignedExtraScheduler,
-			SelfContainedSignedInfo,
-		> {
-			signed:
-				CheckedSignature::<AccountId, SignedExtraScheduler, SelfContainedSignedInfo>::Signed(
-					signer.clone().into(),
-					get_signed_extras(signer.into()),
-				),
-			function: call.into(),
-		};
-
-		extrinsic.apply::<Runtime>(&dispatch_info, 0)
-	}
-
-	fn reserve_balance(
-		id: [u8; 16],
-		sponsor: <T as frame_system::Config>::AccountId,
-		call: <T as pallet_unique_scheduler::Config>::Call,
-		count: u32,
-	) -> Result<(), DispatchError> {
-		let dispatch_info = call.get_dispatch_info();
-		let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0)
-			.saturating_mul(count.into());
-
-		<Balances as NamedReservableCurrency<AccountId>>::reserve_named(
-			&id,
-			&(sponsor.into()),
-			weight,
-		)
-	}
-
-	fn pay_for_call(
-		id: [u8; 16],
-		sponsor: <T as frame_system::Config>::AccountId,
-		call: <T as pallet_unique_scheduler::Config>::Call,
-	) -> Result<u128, DispatchError> {
-		let dispatch_info = call.get_dispatch_info();
-		let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0);
-		Ok(
-			<Balances as NamedReservableCurrency<AccountId>>::unreserve_named(
-				&id,
-				&(sponsor.into()),
-				weight,
-			),
-		)
-	}
-
-	fn cancel_reserve(
-		id: [u8; 16],
-		sponsor: <T as frame_system::Config>::AccountId,
-	) -> Result<u128, DispatchError> {
-		Ok(
-			<Balances as NamedReservableCurrency<AccountId>>::unreserve_named(
-				&id,
-				&(sponsor.into()),
-				u128::MAX,
-			),
-		)
-	}
-}
-
-parameter_types! {
-	pub const NoPreimagePostponement: Option<u32> = Some(10);
-	pub const Preimage: Option<u32> = Some(10);
-}
-
-/// Used the compare the privilege of an origin inside the scheduler.
-pub struct OriginPrivilegeCmp;
-
-impl PrivilegeCmp<OriginCaller> for OriginPrivilegeCmp {
-	fn cmp_privilege(_left: &OriginCaller, _right: &OriginCaller) -> Option<Ordering> {
-		Some(Ordering::Equal)
-	}
-}
-
-impl pallet_unique_scheduler::Config for Runtime {
-	type Event = Event;
-	type Origin = Origin;
-	type Currency = Balances;
-	type PalletsOrigin = OriginCaller;
-	type Call = Call;
-	type MaximumWeight = MaximumSchedulerWeight;
-	type ScheduleOrigin = EnsureSigned<AccountId>;
-	type MaxScheduledPerBlock = MaxScheduledPerBlock;
-	type WeightInfo = ();
-	type CallExecutor = SchedulerPaymentExecutor;
-	type OriginPrivilegeCmp = OriginPrivilegeCmp;
-	type PreimageProvider = ();
-	type NoPreimagePostponement = NoPreimagePostponement;
-}
-
-type EvmSponsorshipHandler = (
-	UniqueEthSponsorshipHandler<Runtime>,
-	pallet_evm_contract_helpers::HelpersContractSponsoring<Runtime>,
-);
-
-type SponsorshipHandler = (
-	UniqueSponsorshipHandler<Runtime>,
-	//pallet_contract_helpers::ContractSponsorshipHandler<Runtime>,
-	pallet_evm_transaction_payment::BridgeSponsorshipHandler<Runtime>,
-);
-
-impl pallet_evm_transaction_payment::Config for Runtime {
-	type EvmSponsorshipHandler = EvmSponsorshipHandler;
-	type Currency = Balances;
-}
-
-impl pallet_charge_transaction::Config for Runtime {
-	type SponsorshipHandler = SponsorshipHandler;
-}
-
-// impl pallet_contract_helpers::Config for Runtime {
-//	 type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit;
-// }
-
-parameter_types! {
-	// 0x842899ECF380553E8a4de75bF534cdf6fBF64049
-	pub const HelpersContractAddress: H160 = H160([
-		0x84, 0x28, 0x99, 0xec, 0xf3, 0x80, 0x55, 0x3e, 0x8a, 0x4d, 0xe7, 0x5b, 0xf5, 0x34, 0xcd, 0xf6, 0xfb, 0xf6, 0x40, 0x49,
-	]);
-
-	// 0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f
-	pub const EvmCollectionHelpersAddress: H160 = H160([
-		0x6c, 0x4e, 0x9f, 0xe1, 0xae, 0x37, 0xa4, 0x1e, 0x93, 0xce, 0xe4, 0x29, 0xe8, 0xe1, 0x88, 0x1a, 0xbd, 0xcb, 0xb5, 0x4f,
-	]);
-}
-
-impl pallet_evm_contract_helpers::Config for Runtime {
-	type ContractAddress = HelpersContractAddress;
-	type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit;
-}
-
-construct_runtime!(
-	pub enum Runtime where
-		Block = Block,
-		NodeBlock = opaque::Block,
-		UncheckedExtrinsic = UncheckedExtrinsic
-	{
-		ParachainSystem: cumulus_pallet_parachain_system::{Pallet, Call, Config, Storage, Inherent, Event<T>, ValidateUnsigned} = 20,
-		ParachainInfo: parachain_info::{Pallet, Storage, Config} = 21,
-
-		Aura: pallet_aura::{Pallet, Config<T>} = 22,
-		AuraExt: cumulus_pallet_aura_ext::{Pallet, Config} = 23,
-
-		Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>} = 30,
-		RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Pallet, Storage} = 31,
-		Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent} = 32,
-		TransactionPayment: pallet_transaction_payment::{Pallet, Storage} = 33,
-		Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event<T>} = 34,
-		Sudo: pallet_sudo::{Pallet, Call, Storage, Config<T>, Event<T>} = 35,
-		System: frame_system::{Pallet, Call, Storage, Config, Event<T>} = 36,
-		Vesting: orml_vesting::{Pallet, Storage, Call, Event<T>, Config<T>} = 37,
-		// Vesting: pallet_vesting::{Pallet, Call, Config<T>, Storage, Event<T>} = 37,
-		// Contracts: pallet_contracts::{Pallet, Call, Storage, Event<T>} = 38,
-
-		// XCM helpers.
-		XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event<T>} = 50,
-		PolkadotXcm: pallet_xcm::{Pallet, Call, Event<T>, Origin} = 51,
-		CumulusXcm: cumulus_pallet_xcm::{Pallet, Call, Event<T>, Origin} = 52,
-		DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event<T>} = 53,
-
-		// Unique Pallets
-		Inflation: pallet_inflation::{Pallet, Call, Storage} = 60,
-		Unique: pallet_unique::{Pallet, Call, Storage, Event<T>} = 61,
-		Scheduler: pallet_unique_scheduler::{Pallet, Call, Storage, Event<T>} = 62,
-		// free = 63
-		Charging: pallet_charge_transaction::{Pallet, Call, Storage } = 64,
-		// ContractHelpers: pallet_contract_helpers::{Pallet, Call, Storage} = 65,
-		Common: pallet_common::{Pallet, Storage, Event<T>} = 66,
-		Fungible: pallet_fungible::{Pallet, Storage} = 67,
-		Refungible: pallet_refungible::{Pallet, Storage} = 68,
-		Nonfungible: pallet_nonfungible::{Pallet, Storage} = 69,
-		Structure: pallet_structure::{Pallet, Call, Storage, Event<T>} = 70,
-		RmrkCore: pallet_proxy_rmrk_core::{Pallet, Call, Storage, Event<T>} = 71,
-		RmrkEquip: pallet_proxy_rmrk_equip::{Pallet, Call, Storage, Event<T>} = 72,
-
-		// Frontier
-		EVM: pallet_evm::{Pallet, Config, Call, Storage, Event<T>} = 100,
-		Ethereum: pallet_ethereum::{Pallet, Config, Call, Storage, Event, Origin} = 101,
-
-		EvmCoderSubstrate: pallet_evm_coder_substrate::{Pallet, Storage} = 150,
-		EvmContractHelpers: pallet_evm_contract_helpers::{Pallet, Storage} = 151,
-		EvmTransactionPayment: pallet_evm_transaction_payment::{Pallet} = 152,
-		EvmMigration: pallet_evm_migration::{Pallet, Call, Storage} = 153,
-	}
-);
-
-pub struct TransactionConverter;
-
-impl fp_rpc::ConvertTransaction<UncheckedExtrinsic> for TransactionConverter {
-	fn convert_transaction(&self, transaction: pallet_ethereum::Transaction) -> UncheckedExtrinsic {
-		UncheckedExtrinsic::new_unsigned(
-			pallet_ethereum::Call::<Runtime>::transact { transaction }.into(),
-		)
-	}
-}
-
-impl fp_rpc::ConvertTransaction<opaque::UncheckedExtrinsic> for TransactionConverter {
-	fn convert_transaction(
-		&self,
-		transaction: pallet_ethereum::Transaction,
-	) -> opaque::UncheckedExtrinsic {
-		let extrinsic = UncheckedExtrinsic::new_unsigned(
-			pallet_ethereum::Call::<Runtime>::transact { transaction }.into(),
-		);
-		let encoded = extrinsic.encode();
-		opaque::UncheckedExtrinsic::decode(&mut &encoded[..])
-			.expect("Encoded extrinsic is always valid")
-	}
-}
-
-/// The address format for describing accounts.
-pub type Address = sp_runtime::MultiAddress<AccountId, ()>;
-/// Block header type as expected by this runtime.
-pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
-/// Block type as expected by this runtime.
-pub type Block = generic::Block<Header, UncheckedExtrinsic>;
-/// A Block signed with a Justification
-pub type SignedBlock = generic::SignedBlock<Block>;
-/// BlockId type as expected by this runtime.
-pub type BlockId = generic::BlockId<Block>;
-/// The SignedExtension to the basic transaction logic.
-pub type SignedExtra = (
-	frame_system::CheckSpecVersion<Runtime>,
-	// system::CheckTxVersion<Runtime>,
-	frame_system::CheckGenesis<Runtime>,
-	frame_system::CheckEra<Runtime>,
-	frame_system::CheckNonce<Runtime>,
-	frame_system::CheckWeight<Runtime>,
-	ChargeTransactionPayment,
-	//pallet_contract_helpers::ContractHelpersExtension<Runtime>,
-	pallet_ethereum::FakeTransactionFinalizer<Runtime>,
-);
-pub type SignedExtraScheduler = (
-	frame_system::CheckSpecVersion<Runtime>,
-	frame_system::CheckGenesis<Runtime>,
-	frame_system::CheckEra<Runtime>,
-	frame_system::CheckNonce<Runtime>,
-	frame_system::CheckWeight<Runtime>,
-);
-/// Unchecked extrinsic type as expected by this runtime.
-pub type UncheckedExtrinsic =
-	fp_self_contained::UncheckedExtrinsic<Address, Call, Signature, SignedExtra>;
-/// Extrinsic type that has already been checked.
-pub type CheckedExtrinsic = fp_self_contained::CheckedExtrinsic<AccountId, Call, SignedExtra, H160>;
-/// Executive: handles dispatch to the various modules.
-pub type Executive = frame_executive::Executive<
-	Runtime,
-	Block,
-	frame_system::ChainContext<Runtime>,
-	Runtime,
-	AllPalletsReversedWithSystemFirst,
->;
-
-impl_opaque_keys! {
-	pub struct SessionKeys {
-		pub aura: Aura,
-	}
-}
-
-impl fp_self_contained::SelfContainedCall for Call {
-	type SignedInfo = H160;
-
-	fn is_self_contained(&self) -> bool {
-		match self {
-			Call::Ethereum(call) => call.is_self_contained(),
-			_ => false,
-		}
-	}
-
-	fn check_self_contained(&self) -> Option<Result<Self::SignedInfo, TransactionValidityError>> {
-		match self {
-			Call::Ethereum(call) => call.check_self_contained(),
-			_ => None,
-		}
-	}
-
-	fn validate_self_contained(
-		&self,
-		info: &Self::SignedInfo,
-		dispatch_info: &DispatchInfoOf<Call>,
-		len: usize,
-	) -> Option<TransactionValidity> {
-		match self {
-			Call::Ethereum(call) => call.validate_self_contained(info, dispatch_info, len),
-			_ => None,
-		}
-	}
-
-	fn pre_dispatch_self_contained(
-		&self,
-		info: &Self::SignedInfo,
-	) -> Option<Result<(), TransactionValidityError>> {
-		match self {
-			Call::Ethereum(call) => call.pre_dispatch_self_contained(info),
-			_ => None,
-		}
-	}
-
-	fn apply_self_contained(
-		self,
-		info: Self::SignedInfo,
-	) -> Option<sp_runtime::DispatchResultWithInfo<PostDispatchInfoOf<Self>>> {
-		match self {
-			call @ Call::Ethereum(pallet_ethereum::Call::transact { .. }) => Some(call.dispatch(
-				Origin::from(pallet_ethereum::RawOrigin::EthereumTransaction(info)),
-			)),
-			_ => None,
-		}
-	}
-}
-
-macro_rules! dispatch_unique_runtime {
-	($collection:ident.$method:ident($($name:ident),*)) => {{
-		let collection = <Runtime as pallet_common::Config>::CollectionDispatch::dispatch(<pallet_common::CollectionHandle<Runtime>>::try_get($collection)?);
-		let dispatch = collection.as_dyn();
-
-		Ok::<_, DispatchError>(dispatch.$method($($name),*))
-	}};
-}
-
-impl_common_runtime_apis! {
-	#![custom_apis]
-
-	impl rmrk_rpc::RmrkApi<
-		Block,
-		AccountId,
-		RmrkCollectionInfo<AccountId>,
-		RmrkInstanceInfo<AccountId>,
-		RmrkResourceInfo,
-		RmrkPropertyInfo,
-		RmrkBaseInfo<AccountId>,
-		RmrkPartType,
-		RmrkTheme
-	> for Runtime {
-		fn last_collection_idx() -> Result<RmrkCollectionId, DispatchError> {
-			pallet_proxy_rmrk_core::rpc::last_collection_idx::<Runtime>()
-		}
-
-		fn collection_by_id(collection_id: RmrkCollectionId) -> Result<Option<RmrkCollectionInfo<AccountId>>, DispatchError> {
-			pallet_proxy_rmrk_core::rpc::collection_by_id::<Runtime>(collection_id)
-		}
-
-		fn nft_by_id(collection_id: RmrkCollectionId, nft_by_id: RmrkNftId) -> Result<Option<RmrkInstanceInfo<AccountId>>, DispatchError> {
-			pallet_proxy_rmrk_core::rpc::nft_by_id::<Runtime>(collection_id, nft_by_id)
-		}
-
-		fn account_tokens(account_id: AccountId, collection_id: RmrkCollectionId) -> Result<Vec<RmrkNftId>, DispatchError> {
-			pallet_proxy_rmrk_core::rpc::account_tokens::<Runtime>(account_id, collection_id)
-		}
-
-		fn nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkNftChild>, DispatchError> {
-			pallet_proxy_rmrk_core::rpc::nft_children::<Runtime>(collection_id, nft_id)
-		}
-
-		fn collection_properties(collection_id: RmrkCollectionId, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
-			pallet_proxy_rmrk_core::rpc::collection_properties::<Runtime>(collection_id, filter_keys)
-		}
-
-		fn nft_properties(collection_id: RmrkCollectionId, nft_id: RmrkNftId, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
-			pallet_proxy_rmrk_core::rpc::nft_properties::<Runtime>(collection_id, nft_id, filter_keys)
-		}
-
-		fn nft_resources(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkResourceInfo>, DispatchError> {
-			pallet_proxy_rmrk_core::rpc::nft_resources::<Runtime>(collection_id, nft_id)
-		}
-
-		fn nft_resource_priority(collection_id: RmrkCollectionId, nft_id: RmrkNftId, resource_id: RmrkResourceId) -> Result<Option<u32>, DispatchError> {
-			pallet_proxy_rmrk_core::rpc::nft_resource_priority::<Runtime>(collection_id, nft_id, resource_id)
-		}
-
-		fn base(base_id: RmrkBaseId) -> Result<Option<RmrkBaseInfo<AccountId>>, DispatchError> {
-			pallet_proxy_rmrk_equip::rpc::base::<Runtime>(base_id)
-		}
-
-		fn base_parts(base_id: RmrkBaseId) -> Result<Vec<RmrkPartType>, DispatchError> {
-			pallet_proxy_rmrk_equip::rpc::base_parts::<Runtime>(base_id)
-		}
-
-		fn theme_names(base_id: RmrkBaseId) -> Result<Vec<RmrkThemeName>, DispatchError> {
-			pallet_proxy_rmrk_equip::rpc::theme_names::<Runtime>(base_id)
-		}
-
-		fn theme(base_id: RmrkBaseId, theme_name: RmrkThemeName, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Option<RmrkTheme>, DispatchError> {
-			pallet_proxy_rmrk_equip::rpc::theme::<Runtime>(base_id, theme_name, filter_keys)
-		}
-	}
-}
-
-struct CheckInherents;
-
-impl cumulus_pallet_parachain_system::CheckInherents<Block> for CheckInherents {
-	fn check_inherents(
-		block: &Block,
-		relay_state_proof: &cumulus_pallet_parachain_system::RelayChainStateProof,
-	) -> sp_inherents::CheckInherentsResult {
-		let relay_chain_slot = relay_state_proof
-			.read_slot()
-			.expect("Could not read the relay chain slot from the proof");
-
-		let inherent_data =
-			cumulus_primitives_timestamp::InherentDataProvider::from_relay_chain_slot_and_duration(
-				relay_chain_slot,
-				sp_std::time::Duration::from_secs(6),
-			)
-			.create_inherent_data()
-			.expect("Could not create the timestamp inherent data");
-
-		inherent_data.check_extrinsics(block)
-	}
-}
+impl_common_runtime_apis!();
 
 cumulus_pallet_parachain_system::register_validate_block!(
 	Runtime = Runtime,
modifiedruntime/quartz/Cargo.tomldiffbeforeafterboth
before · runtime/quartz/Cargo.toml
1################################################################################2# Package34[package]5authors = ['Unique Network <support@uniquenetwork.io>']6build = 'build.rs'7description = 'Quartz Runtime'8edition = '2021'9homepage = 'https://unique.network'10license = 'GPLv3'11name = 'quartz-runtime'12repository = 'https://github.com/UniqueNetwork/unique-chain'13version = '0.9.24'1415[package.metadata.docs.rs]16targets = ['x86_64-unknown-linux-gnu']1718[features]19default = ['std']20runtime-benchmarks = [21    'hex-literal',22    'frame-benchmarking',23    'frame-support/runtime-benchmarks',24    'frame-system-benchmarking',25    'frame-system/runtime-benchmarks',26    'pallet-ethereum/runtime-benchmarks',27    'pallet-evm-migration/runtime-benchmarks',28    'pallet-evm-coder-substrate/runtime-benchmarks',29    'pallet-balances/runtime-benchmarks',30    'pallet-timestamp/runtime-benchmarks',31    'pallet-common/runtime-benchmarks',32    'pallet-structure/runtime-benchmarks',33    'pallet-fungible/runtime-benchmarks',34    'pallet-refungible/runtime-benchmarks',35    'pallet-nonfungible/runtime-benchmarks',36    'pallet-proxy-rmrk-core/runtime-benchmarks',37    'pallet-proxy-rmrk-equip/runtime-benchmarks',38    'pallet-unique/runtime-benchmarks',39    'pallet-inflation/runtime-benchmarks',40    'pallet-unique-scheduler/runtime-benchmarks',41    'pallet-xcm/runtime-benchmarks',42    'sp-runtime/runtime-benchmarks',43    'xcm-builder/runtime-benchmarks',44]45try-runtime = [46    'frame-try-runtime',47    'frame-executive/try-runtime',48    'frame-system/try-runtime',49]50std = [51    'codec/std',52    'cumulus-pallet-aura-ext/std',53    'cumulus-pallet-parachain-system/std',54    'cumulus-pallet-xcm/std',55    'cumulus-pallet-xcmp-queue/std',56    'cumulus-primitives-core/std',57    'cumulus-primitives-utility/std',58    'frame-try-runtime/std',59    'frame-executive/std',60    'frame-support/std',61    'frame-system/std',62    'frame-system-rpc-runtime-api/std',63    'pallet-aura/std',64    'pallet-balances/std',65    # 'pallet-contracts/std',66    # 'pallet-contracts-primitives/std',67    # 'pallet-contracts-rpc-runtime-api/std',68    # 'pallet-contract-helpers/std',69    'pallet-randomness-collective-flip/std',70    'pallet-sudo/std',71    'pallet-timestamp/std',72    'pallet-transaction-payment/std',73    'pallet-transaction-payment-rpc-runtime-api/std',74    'pallet-treasury/std',75    # 'pallet-vesting/std',76    'pallet-evm/std',77    'pallet-evm-migration/std',78    'pallet-evm-contract-helpers/std',79    'pallet-evm-transaction-payment/std',80    'pallet-evm-coder-substrate/std',81    'pallet-ethereum/std',82    'pallet-base-fee/std',83    'fp-rpc/std',84    'up-rpc/std',85    'fp-evm-mapping/std',86    'fp-self-contained/std',87    'parachain-info/std',88    'serde',89    'pallet-inflation/std',90    'pallet-common/std',91    'pallet-structure/std',92    'pallet-fungible/std',93    'pallet-refungible/std',94    'pallet-nonfungible/std',95    'pallet-proxy-rmrk-core/std',96    'pallet-proxy-rmrk-equip/std',97    'pallet-unique/std',98    'pallet-unique-scheduler/std',99    'pallet-charge-transaction/std',100    'up-data-structs/std',101    'sp-api/std',102    'sp-block-builder/std',103    "sp-consensus-aura/std",104    'sp-core/std',105    'sp-inherents/std',106    'sp-io/std',107    'sp-offchain/std',108    'sp-runtime/std',109    'sp-session/std',110    'sp-std/std',111    'sp-transaction-pool/std',112    'sp-version/std',113    'xcm/std',114    'xcm-builder/std',115    'xcm-executor/std',116    'unique-runtime-common/std',117118    "orml-vesting/std",119]120limit-testing = ['pallet-unique/limit-testing', 'up-data-structs/limit-testing']121122################################################################################123# Substrate Dependencies124125[dependencies.codec]126default-features = false127features = ['derive']128package = 'parity-scale-codec'129version = '3.1.2'130131[dependencies.frame-benchmarking]132default-features = false133git = "https://github.com/paritytech/substrate"134optional = true135branch = "polkadot-v0.9.24"136137[dependencies.frame-try-runtime]138default-features = false139git = 'https://github.com/paritytech/substrate'140optional = true141branch = 'polkadot-v0.9.24'142143[dependencies.frame-executive]144default-features = false145git = "https://github.com/paritytech/substrate"146branch = "polkadot-v0.9.24"147148[dependencies.frame-support]149default-features = false150git = "https://github.com/paritytech/substrate"151branch = "polkadot-v0.9.24"152153[dependencies.frame-system]154default-features = false155git = "https://github.com/paritytech/substrate"156branch = "polkadot-v0.9.24"157158[dependencies.frame-system-benchmarking]159default-features = false160git = "https://github.com/paritytech/substrate"161optional = true162branch = "polkadot-v0.9.24"163164[dependencies.frame-system-rpc-runtime-api]165default-features = false166git = "https://github.com/paritytech/substrate"167branch = "polkadot-v0.9.24"168169[dependencies.hex-literal]170optional = true171version = '0.3.3'172173[dependencies.serde]174default-features = false175features = ['derive']176optional = true177version = '1.0.130'178179[dependencies.pallet-aura]180default-features = false181git = "https://github.com/paritytech/substrate"182branch = "polkadot-v0.9.24"183184[dependencies.pallet-balances]185default-features = false186git = "https://github.com/paritytech/substrate"187branch = "polkadot-v0.9.24"188189# Contracts specific packages190# [dependencies.pallet-contracts]191# git = 'https://github.com/paritytech/substrate'192# default-features = false193# branch = 'master'194# version = '4.0.0-dev'195196# [dependencies.pallet-contracts-primitives]197# git = 'https://github.com/paritytech/substrate'198# default-features = false199# branch = 'master'200# version = '4.0.0-dev'201202# [dependencies.pallet-contracts-rpc-runtime-api]203# git = 'https://github.com/paritytech/substrate'204# default-features = false205# branch = 'master'206# version = '4.0.0-dev'207208[dependencies.pallet-randomness-collective-flip]209default-features = false210git = "https://github.com/paritytech/substrate"211branch = "polkadot-v0.9.24"212213[dependencies.pallet-sudo]214default-features = false215git = "https://github.com/paritytech/substrate"216branch = "polkadot-v0.9.24"217218[dependencies.pallet-timestamp]219default-features = false220git = "https://github.com/paritytech/substrate"221branch = "polkadot-v0.9.24"222223[dependencies.pallet-transaction-payment]224default-features = false225git = "https://github.com/paritytech/substrate"226branch = "polkadot-v0.9.24"227228[dependencies.pallet-transaction-payment-rpc-runtime-api]229default-features = false230git = "https://github.com/paritytech/substrate"231branch = "polkadot-v0.9.24"232233[dependencies.pallet-treasury]234default-features = false235git = "https://github.com/paritytech/substrate"236branch = "polkadot-v0.9.24"237238# [dependencies.pallet-vesting]239# default-features = false240# git = 'https://github.com/paritytech/substrate'241# branch = 'master'242243[dependencies.sp-arithmetic]244default-features = false245git = "https://github.com/paritytech/substrate"246branch = "polkadot-v0.9.24"247248[dependencies.sp-api]249default-features = false250git = "https://github.com/paritytech/substrate"251branch = "polkadot-v0.9.24"252253[dependencies.sp-block-builder]254default-features = false255git = "https://github.com/paritytech/substrate"256branch = "polkadot-v0.9.24"257258[dependencies.sp-core]259default-features = false260git = "https://github.com/paritytech/substrate"261branch = "polkadot-v0.9.24"262263[dependencies.sp-consensus-aura]264default-features = false265git = "https://github.com/paritytech/substrate"266branch = "polkadot-v0.9.24"267268[dependencies.sp-inherents]269default-features = false270git = "https://github.com/paritytech/substrate"271branch = "polkadot-v0.9.24"272273[dependencies.sp-io]274default-features = false275git = "https://github.com/paritytech/substrate"276branch = "polkadot-v0.9.24"277278[dependencies.sp-offchain]279default-features = false280git = "https://github.com/paritytech/substrate"281branch = "polkadot-v0.9.24"282283[dependencies.sp-runtime]284default-features = false285git = "https://github.com/paritytech/substrate"286branch = "polkadot-v0.9.24"287288[dependencies.sp-session]289default-features = false290git = "https://github.com/paritytech/substrate"291branch = "polkadot-v0.9.24"292293[dependencies.sp-std]294default-features = false295git = "https://github.com/paritytech/substrate"296branch = "polkadot-v0.9.24"297298[dependencies.sp-transaction-pool]299default-features = false300git = "https://github.com/paritytech/substrate"301branch = "polkadot-v0.9.24"302303[dependencies.sp-version]304default-features = false305git = "https://github.com/paritytech/substrate"306branch = "polkadot-v0.9.24"307308[dependencies.smallvec]309version = '1.6.1'310311################################################################################312# Cumulus dependencies313314[dependencies.parachain-info]315default-features = false316git = "https://github.com/paritytech/cumulus"317branch = "polkadot-v0.9.24"318319[dependencies.cumulus-pallet-aura-ext]320git = "https://github.com/paritytech/cumulus"321branch = "polkadot-v0.9.24"322default-features = false323324[dependencies.cumulus-pallet-parachain-system]325git = "https://github.com/paritytech/cumulus"326branch = "polkadot-v0.9.24"327default-features = false328329[dependencies.cumulus-primitives-core]330git = "https://github.com/paritytech/cumulus"331branch = "polkadot-v0.9.24"332default-features = false333334[dependencies.cumulus-pallet-xcm]335git = "https://github.com/paritytech/cumulus"336branch = "polkadot-v0.9.24"337default-features = false338339[dependencies.cumulus-pallet-dmp-queue]340git = "https://github.com/paritytech/cumulus"341branch = "polkadot-v0.9.24"342default-features = false343344[dependencies.cumulus-pallet-xcmp-queue]345git = "https://github.com/paritytech/cumulus"346branch = "polkadot-v0.9.24"347default-features = false348349[dependencies.cumulus-primitives-utility]350git = "https://github.com/paritytech/cumulus"351branch = "polkadot-v0.9.24"352default-features = false353354[dependencies.cumulus-primitives-timestamp]355git = "https://github.com/paritytech/cumulus"356branch = "polkadot-v0.9.24"357default-features = false358359################################################################################360# Polkadot dependencies361362[dependencies.polkadot-parachain]363git = "https://github.com/paritytech/polkadot"364branch = "release-v0.9.24"365default-features = false366367[dependencies.xcm]368git = "https://github.com/paritytech/polkadot"369branch = "release-v0.9.24"370default-features = false371372[dependencies.xcm-builder]373git = "https://github.com/paritytech/polkadot"374branch = "release-v0.9.24"375default-features = false376377[dependencies.xcm-executor]378git = "https://github.com/paritytech/polkadot"379branch = "release-v0.9.24"380default-features = false381382[dependencies.pallet-xcm]383git = "https://github.com/paritytech/polkadot"384branch = "release-v0.9.24"385default-features = false386387[dependencies.orml-vesting]388git = "https://github.com/uniquenetwork/open-runtime-module-library"389branch = "unique-polkadot-v0.9.24"390version = "0.4.1-dev"391default-features = false392393################################################################################394# RMRK dependencies395396# todo git397[dependencies.rmrk-rpc]398default-features = false399path = "../../primitives/rmrk-rpc"400401################################################################################402# local dependencies403404[dependencies]405log = { version = "0.4.16", default-features = false }406unique-runtime-common = { path = "../common", default-features = false }407scale-info = { version = "2.0.1", default-features = false, features = [408    "derive",409] }410derivative = "2.2.0"411pallet-unique = { path = '../../pallets/unique', default-features = false }412up-rpc = { path = "../../primitives/rpc", default-features = false }413fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" }414pallet-inflation = { path = '../../pallets/inflation', default-features = false }415up-data-structs = { path = '../../primitives/data-structs', default-features = false }416pallet-common = { default-features = false, path = "../../pallets/common" }417pallet-structure = { default-features = false, path = "../../pallets/structure" }418pallet-fungible = { default-features = false, path = "../../pallets/fungible" }419pallet-refungible = { default-features = false, path = "../../pallets/refungible" }420pallet-nonfungible = { default-features = false, path = "../../pallets/nonfungible" }421pallet-proxy-rmrk-core = { default-features = false, path = "../../pallets/proxy-rmrk-core", package = "pallet-rmrk-core" }422pallet-proxy-rmrk-equip = { default-features = false, path = "../../pallets/proxy-rmrk-equip", package = "pallet-rmrk-equip" }423pallet-unique-scheduler = { path = '../../pallets/scheduler', default-features = false }424# pallet-contract-helpers = { path = '../pallets/contract-helpers', default-features = false, version = '0.1.0' }425pallet-charge-transaction = { git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = "polkadot-v0.9.24", package = "pallet-template-transaction-payment", default-features = false, version = "3.0.0" }426pallet-evm-migration = { path = '../../pallets/evm-migration', default-features = false }427pallet-evm-contract-helpers = { path = '../../pallets/evm-contract-helpers', default-features = false }428pallet-evm-transaction-payment = { path = '../../pallets/evm-transaction-payment', default-features = false }429pallet-evm-coder-substrate = { default-features = false, path = "../../pallets/evm-coder-substrate" }430pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" }431pallet-ethereum = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" }432pallet-base-fee = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" }433fp-rpc = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" }434fp-self-contained = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" }435436################################################################################437# Build Dependencies438439[build-dependencies.substrate-wasm-builder]440git = "https://github.com/paritytech/substrate"441branch = "polkadot-v0.9.24"
after · runtime/quartz/Cargo.toml
1################################################################################2# Package34[package]5authors = ['Unique Network <support@uniquenetwork.io>']6build = 'build.rs'7description = 'Quartz Runtime'8edition = '2021'9homepage = 'https://unique.network'10license = 'GPLv3'11name = 'quartz-runtime'12repository = 'https://github.com/UniqueNetwork/unique-chain'13version = '0.9.24'1415[package.metadata.docs.rs]16targets = ['x86_64-unknown-linux-gnu']1718[features]19default = ['std', 'quartz-runtime']20runtime-benchmarks = [21    'hex-literal',22    'frame-benchmarking',23    'frame-support/runtime-benchmarks',24    'frame-system-benchmarking',25    'frame-system/runtime-benchmarks',26    'pallet-ethereum/runtime-benchmarks',27    'pallet-evm-migration/runtime-benchmarks',28    'pallet-evm-coder-substrate/runtime-benchmarks',29    'pallet-balances/runtime-benchmarks',30    'pallet-timestamp/runtime-benchmarks',31    'pallet-common/runtime-benchmarks',32    'pallet-structure/runtime-benchmarks',33    'pallet-fungible/runtime-benchmarks',34    'pallet-refungible/runtime-benchmarks',35    'pallet-nonfungible/runtime-benchmarks',36    'pallet-proxy-rmrk-core/runtime-benchmarks',37    'pallet-proxy-rmrk-equip/runtime-benchmarks',38    'pallet-unique/runtime-benchmarks',39    'pallet-inflation/runtime-benchmarks',40    'pallet-unique-scheduler/runtime-benchmarks',41    'pallet-xcm/runtime-benchmarks',42    'sp-runtime/runtime-benchmarks',43    'xcm-builder/runtime-benchmarks',44]45try-runtime = [46    'frame-try-runtime',47    'frame-executive/try-runtime',48    'frame-system/try-runtime',49]50std = [51    'codec/std',52    'cumulus-pallet-aura-ext/std',53    'cumulus-pallet-parachain-system/std',54    'cumulus-pallet-xcm/std',55    'cumulus-pallet-xcmp-queue/std',56    'cumulus-primitives-core/std',57    'cumulus-primitives-utility/std',58    'frame-try-runtime/std',59    'frame-executive/std',60    'frame-support/std',61    'frame-system/std',62    'frame-system-rpc-runtime-api/std',63    'pallet-aura/std',64    'pallet-balances/std',65    # 'pallet-contracts/std',66    # 'pallet-contracts-primitives/std',67    # 'pallet-contracts-rpc-runtime-api/std',68    # 'pallet-contract-helpers/std',69    'pallet-randomness-collective-flip/std',70    'pallet-sudo/std',71    'pallet-timestamp/std',72    'pallet-transaction-payment/std',73    'pallet-transaction-payment-rpc-runtime-api/std',74    'pallet-treasury/std',75    # 'pallet-vesting/std',76    'pallet-evm/std',77    'pallet-evm-migration/std',78    'pallet-evm-contract-helpers/std',79    'pallet-evm-transaction-payment/std',80    'pallet-evm-coder-substrate/std',81    'pallet-ethereum/std',82    'pallet-base-fee/std',83    'fp-rpc/std',84    'up-rpc/std',85    'fp-evm-mapping/std',86    'fp-self-contained/std',87    'parachain-info/std',88    'serde',89    'pallet-inflation/std',90    'pallet-common/std',91    'pallet-structure/std',92    'pallet-fungible/std',93    'pallet-refungible/std',94    'pallet-nonfungible/std',95    'pallet-proxy-rmrk-core/std',96    'pallet-proxy-rmrk-equip/std',97    'pallet-unique/std',98    'pallet-unique-scheduler/std',99    'pallet-charge-transaction/std',100    'up-data-structs/std',101    'sp-api/std',102    'sp-block-builder/std',103    "sp-consensus-aura/std",104    'sp-core/std',105    'sp-inherents/std',106    'sp-io/std',107    'sp-offchain/std',108    'sp-runtime/std',109    'sp-session/std',110    'sp-std/std',111    'sp-transaction-pool/std',112    'sp-version/std',113    'xcm/std',114    'xcm-builder/std',115    'xcm-executor/std',116    'up-common/std',117118    "orml-vesting/std",119]120limit-testing = ['pallet-unique/limit-testing', 'up-data-structs/limit-testing']121quartz-runtime = []122123refungible = []124scheduler = []125rmrk = []126127################################################################################128# Substrate Dependencies129130[dependencies.codec]131default-features = false132features = ['derive']133package = 'parity-scale-codec'134version = '3.1.2'135136[dependencies.frame-benchmarking]137default-features = false138git = "https://github.com/paritytech/substrate"139optional = true140branch = "polkadot-v0.9.24"141142[dependencies.frame-try-runtime]143default-features = false144git = 'https://github.com/paritytech/substrate'145optional = true146branch = 'polkadot-v0.9.24'147148[dependencies.frame-executive]149default-features = false150git = "https://github.com/paritytech/substrate"151branch = "polkadot-v0.9.24"152153[dependencies.frame-support]154default-features = false155git = "https://github.com/paritytech/substrate"156branch = "polkadot-v0.9.24"157158[dependencies.frame-system]159default-features = false160git = "https://github.com/paritytech/substrate"161branch = "polkadot-v0.9.24"162163[dependencies.frame-system-benchmarking]164default-features = false165git = "https://github.com/paritytech/substrate"166optional = true167branch = "polkadot-v0.9.24"168169[dependencies.frame-system-rpc-runtime-api]170default-features = false171git = "https://github.com/paritytech/substrate"172branch = "polkadot-v0.9.24"173174[dependencies.hex-literal]175optional = true176version = '0.3.3'177178[dependencies.serde]179default-features = false180features = ['derive']181optional = true182version = '1.0.130'183184[dependencies.pallet-aura]185default-features = false186git = "https://github.com/paritytech/substrate"187branch = "polkadot-v0.9.24"188189[dependencies.pallet-balances]190default-features = false191git = "https://github.com/paritytech/substrate"192branch = "polkadot-v0.9.24"193194# Contracts specific packages195# [dependencies.pallet-contracts]196# git = 'https://github.com/paritytech/substrate'197# default-features = false198# branch = 'master'199# version = '4.0.0-dev'200201# [dependencies.pallet-contracts-primitives]202# git = 'https://github.com/paritytech/substrate'203# default-features = false204# branch = 'master'205# version = '4.0.0-dev'206207# [dependencies.pallet-contracts-rpc-runtime-api]208# git = 'https://github.com/paritytech/substrate'209# default-features = false210# branch = 'master'211# version = '4.0.0-dev'212213[dependencies.pallet-randomness-collective-flip]214default-features = false215git = "https://github.com/paritytech/substrate"216branch = "polkadot-v0.9.24"217218[dependencies.pallet-sudo]219default-features = false220git = "https://github.com/paritytech/substrate"221branch = "polkadot-v0.9.24"222223[dependencies.pallet-timestamp]224default-features = false225git = "https://github.com/paritytech/substrate"226branch = "polkadot-v0.9.24"227228[dependencies.pallet-transaction-payment]229default-features = false230git = "https://github.com/paritytech/substrate"231branch = "polkadot-v0.9.24"232233[dependencies.pallet-transaction-payment-rpc-runtime-api]234default-features = false235git = "https://github.com/paritytech/substrate"236branch = "polkadot-v0.9.24"237238[dependencies.pallet-treasury]239default-features = false240git = "https://github.com/paritytech/substrate"241branch = "polkadot-v0.9.24"242243# [dependencies.pallet-vesting]244# default-features = false245# git = 'https://github.com/paritytech/substrate'246# branch = 'master'247248[dependencies.sp-arithmetic]249default-features = false250git = "https://github.com/paritytech/substrate"251branch = "polkadot-v0.9.24"252253[dependencies.sp-api]254default-features = false255git = "https://github.com/paritytech/substrate"256branch = "polkadot-v0.9.24"257258[dependencies.sp-block-builder]259default-features = false260git = "https://github.com/paritytech/substrate"261branch = "polkadot-v0.9.24"262263[dependencies.sp-core]264default-features = false265git = "https://github.com/paritytech/substrate"266branch = "polkadot-v0.9.24"267268[dependencies.sp-consensus-aura]269default-features = false270git = "https://github.com/paritytech/substrate"271branch = "polkadot-v0.9.24"272273[dependencies.sp-inherents]274default-features = false275git = "https://github.com/paritytech/substrate"276branch = "polkadot-v0.9.24"277278[dependencies.sp-io]279default-features = false280git = "https://github.com/paritytech/substrate"281branch = "polkadot-v0.9.24"282283[dependencies.sp-offchain]284default-features = false285git = "https://github.com/paritytech/substrate"286branch = "polkadot-v0.9.24"287288[dependencies.sp-runtime]289default-features = false290git = "https://github.com/paritytech/substrate"291branch = "polkadot-v0.9.24"292293[dependencies.sp-session]294default-features = false295git = "https://github.com/paritytech/substrate"296branch = "polkadot-v0.9.24"297298[dependencies.sp-std]299default-features = false300git = "https://github.com/paritytech/substrate"301branch = "polkadot-v0.9.24"302303[dependencies.sp-transaction-pool]304default-features = false305git = "https://github.com/paritytech/substrate"306branch = "polkadot-v0.9.24"307308[dependencies.sp-version]309default-features = false310git = "https://github.com/paritytech/substrate"311branch = "polkadot-v0.9.24"312313[dependencies.smallvec]314version = '1.6.1'315316################################################################################317# Cumulus dependencies318319[dependencies.parachain-info]320default-features = false321git = "https://github.com/paritytech/cumulus"322branch = "polkadot-v0.9.24"323324[dependencies.cumulus-pallet-aura-ext]325git = "https://github.com/paritytech/cumulus"326branch = "polkadot-v0.9.24"327default-features = false328329[dependencies.cumulus-pallet-parachain-system]330git = "https://github.com/paritytech/cumulus"331branch = "polkadot-v0.9.24"332default-features = false333334[dependencies.cumulus-primitives-core]335git = "https://github.com/paritytech/cumulus"336branch = "polkadot-v0.9.24"337default-features = false338339[dependencies.cumulus-pallet-xcm]340git = "https://github.com/paritytech/cumulus"341branch = "polkadot-v0.9.24"342default-features = false343344[dependencies.cumulus-pallet-dmp-queue]345git = "https://github.com/paritytech/cumulus"346branch = "polkadot-v0.9.24"347default-features = false348349[dependencies.cumulus-pallet-xcmp-queue]350git = "https://github.com/paritytech/cumulus"351branch = "polkadot-v0.9.24"352default-features = false353354[dependencies.cumulus-primitives-utility]355git = "https://github.com/paritytech/cumulus"356branch = "polkadot-v0.9.24"357default-features = false358359[dependencies.cumulus-primitives-timestamp]360git = "https://github.com/paritytech/cumulus"361branch = "polkadot-v0.9.24"362default-features = false363364################################################################################365# Polkadot dependencies366367[dependencies.polkadot-parachain]368git = "https://github.com/paritytech/polkadot"369branch = "release-v0.9.24"370default-features = false371372[dependencies.xcm]373git = "https://github.com/paritytech/polkadot"374branch = "release-v0.9.24"375default-features = false376377[dependencies.xcm-builder]378git = "https://github.com/paritytech/polkadot"379branch = "release-v0.9.24"380default-features = false381382[dependencies.xcm-executor]383git = "https://github.com/paritytech/polkadot"384branch = "release-v0.9.24"385default-features = false386387[dependencies.pallet-xcm]388git = "https://github.com/paritytech/polkadot"389branch = "release-v0.9.24"390default-features = false391392[dependencies.orml-vesting]393git = "https://github.com/uniquenetwork/open-runtime-module-library"394branch = "unique-polkadot-v0.9.24"395version = "0.4.1-dev"396default-features = false397398################################################################################399# RMRK dependencies400401# todo git402[dependencies.rmrk-rpc]403default-features = false404path = "../../primitives/rmrk-rpc"405406################################################################################407# local dependencies408409[dependencies]410log = { version = "0.4.16", default-features = false }411up-common = { path = "../../primitives/common", default-features = false }412scale-info = { version = "2.0.1", default-features = false, features = [413    "derive",414] }415derivative = "2.2.0"416pallet-unique = { path = '../../pallets/unique', default-features = false }417up-rpc = { path = "../../primitives/rpc", default-features = false }418fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" }419pallet-inflation = { path = '../../pallets/inflation', default-features = false }420up-data-structs = { path = '../../primitives/data-structs', default-features = false }421pallet-common = { default-features = false, path = "../../pallets/common" }422pallet-structure = { default-features = false, path = "../../pallets/structure" }423pallet-fungible = { default-features = false, path = "../../pallets/fungible" }424pallet-refungible = { default-features = false, path = "../../pallets/refungible" }425pallet-nonfungible = { default-features = false, path = "../../pallets/nonfungible" }426pallet-proxy-rmrk-core = { default-features = false, path = "../../pallets/proxy-rmrk-core", package = "pallet-rmrk-core" }427pallet-proxy-rmrk-equip = { default-features = false, path = "../../pallets/proxy-rmrk-equip", package = "pallet-rmrk-equip" }428pallet-unique-scheduler = { path = '../../pallets/scheduler', default-features = false }429# pallet-contract-helpers = { path = '../pallets/contract-helpers', default-features = false, version = '0.1.0' }430pallet-charge-transaction = { git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = "polkadot-v0.9.24", package = "pallet-template-transaction-payment", default-features = false, version = "3.0.0" }431pallet-evm-migration = { path = '../../pallets/evm-migration', default-features = false }432pallet-evm-contract-helpers = { path = '../../pallets/evm-contract-helpers', default-features = false }433pallet-evm-transaction-payment = { path = '../../pallets/evm-transaction-payment', default-features = false }434pallet-evm-coder-substrate = { default-features = false, path = "../../pallets/evm-coder-substrate" }435pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" }436pallet-ethereum = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" }437pallet-base-fee = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" }438fp-rpc = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" }439fp-self-contained = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" }440evm-coder = { default-features = false, path = '../../crates/evm-coder' }441up-sponsorship = { default-features = false, git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = 'polkadot-v0.9.24' }442443################################################################################444# Build Dependencies445446[build-dependencies.substrate-wasm-builder]447git = "https://github.com/paritytech/substrate"448branch = "polkadot-v0.9.24"
modifiedruntime/quartz/src/lib.rsdiffbeforeafterboth
--- a/runtime/quartz/src/lib.rs
+++ b/runtime/quartz/src/lib.rs
@@ -25,167 +25,21 @@
 #[cfg(feature = "std")]
 include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
 
-use sp_api::impl_runtime_apis;
-use sp_core::{crypto::KeyTypeId, OpaqueMetadata, H256, U256, H160};
-use sp_runtime::DispatchError;
-use fp_self_contained::*;
-// #[cfg(any(feature = "std", test))]
-// pub use sp_runtime::BuildStorage;
-
-use sp_runtime::{
-	Permill, Perbill, Percent, create_runtime_str, generic, impl_opaque_keys,
-	traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, AccountIdConversion, Zero, Member},
-	transaction_validity::{TransactionSource, TransactionValidity},
-	ApplyExtrinsicResult, RuntimeAppPublic,
-};
+use frame_support::parameter_types;
 
-use sp_std::prelude::*;
-
-#[cfg(feature = "std")]
-use sp_version::NativeVersion;
 use sp_version::RuntimeVersion;
-pub use pallet_transaction_payment::{
-	Multiplier, TargetedFeeAdjustment, FeeDetails, RuntimeDispatchInfo,
-};
-// A few exports that help ease life for downstream crates.
-pub use pallet_balances::Call as BalancesCall;
-pub use pallet_evm::{
-	EnsureAddressTruncated, HashedAddressMapping, Runner, account::CrossAccountId as _,
-	OnMethodCall, Account as EVMAccount, FeeCalculator, GasWeightMapping,
-};
-pub use frame_support::{
-	construct_runtime, match_types,
-	dispatch::DispatchResult,
-	PalletId, parameter_types, StorageValue, ConsensusEngineId,
-	traits::{
-		tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT, Everything,
-		Currency, ExistenceRequirement, Get, IsInVec, KeyOwnerProofSystem, LockIdentifier,
-		OnUnbalanced, Randomness, FindAuthor, ConstU32, Imbalance, PrivilegeCmp,
-	},
-	weights::{
-		constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},
-		DispatchClass, DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,
-		WeightToFeePolynomial, WeightToFeeCoefficient, WeightToFeeCoefficients, ConstantMultiplier,
-		WeightToFee,
-	},
-};
-use pallet_unique_scheduler::DispatchCall;
-use up_data_structs::{
-	CollectionId, TokenId, TokenData, Property, PropertyKeyPermission, CollectionLimits,
-	CollectionStats, RpcCollection,
-	mapping::{EvmTokenAddressMapping, CrossTokenAddressMapping},
-	TokenChild, RmrkCollectionInfo, RmrkInstanceInfo, RmrkResourceInfo, RmrkPropertyInfo,
-	RmrkBaseInfo, RmrkPartType, RmrkTheme, RmrkThemeName, RmrkCollectionId, RmrkNftId,
-	RmrkNftChild, RmrkPropertyKey, RmrkResourceId, RmrkBaseId,
-};
+use sp_runtime::create_runtime_str;
 
-// use pallet_contracts::weights::WeightInfo;
-// #[cfg(any(feature = "std", test))]
-use frame_system::{
-	self as frame_system, EnsureRoot, EnsureSigned,
-	limits::{BlockWeights, BlockLength},
-};
-use sp_arithmetic::{
-	traits::{BaseArithmetic, Unsigned},
-};
-use smallvec::smallvec;
-use codec::{Encode, Decode};
-use fp_rpc::TransactionStatus;
-use sp_runtime::{
-	traits::{
-		Applyable, BlockNumberProvider, Dispatchable, PostDispatchInfoOf, DispatchInfoOf,
-		Saturating, CheckedConversion,
-	},
-	generic::Era,
-	transaction_validity::TransactionValidityError,
-	DispatchErrorWithPostInfo, SaturatedConversion,
-};
+use up_common::types::*;
 
-// pub use pallet_timestamp::Call as TimestampCall;
-pub use sp_consensus_aura::sr25519::AuthorityId as AuraId;
+#[path = "../../common/mod.rs"]
+mod runtime_common;
 
-// Polkadot imports
-use pallet_xcm::XcmPassthrough;
-use polkadot_parachain::primitives::Sibling;
-use xcm::v1::{BodyId, Junction::*, MultiLocation, NetworkId, Junctions::*};
-use xcm_builder::{
-	AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter,
-	EnsureXcmOrigin, FixedWeightBounds, LocationInverter, NativeAsset, ParentAsSuperuser,
-	RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia,
-	SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit,
-	ParentIsPreset,
-};
-use xcm_executor::{Config, XcmExecutor, Assets};
-use sp_std::{cmp::Ordering, marker::PhantomData};
-
-use xcm::latest::{
-	//	Xcm,
-	AssetId::{Concrete},
-	Fungibility::Fungible as XcmFungible,
-	MultiAsset,
-	Error as XcmError,
-};
-use xcm_executor::traits::{MatchesFungible, WeightTrader};
+pub use runtime_common::*;
 
-use unique_runtime_common::{
-	impl_common_runtime_apis,
-	types::*,
-	constants::*,
-	dispatch::{CollectionDispatchT, CollectionDispatch},
-	sponsoring::UniqueSponsorshipHandler,
-	eth_sponsoring::UniqueEthSponsorshipHandler,
-	weights::CommonWeights,
-};
-
 pub const RUNTIME_NAME: &str = "quartz";
 pub const TOKEN_SYMBOL: &str = "QTZ";
-
-type CrossAccountId = pallet_evm::account::BasicCrossAccountId<Runtime>;
-
-impl RuntimeInstance for Runtime {
-	type CrossAccountId = self::CrossAccountId;
-
-	type TransactionConverter = self::TransactionConverter;
-
-	fn get_transaction_converter() -> TransactionConverter {
-		TransactionConverter
-	}
-}
-
-/// The type for looking up accounts. We don't expect more than 4 billion of them, but you
-/// never know...
-pub type AccountIndex = u32;
 
-/// Balance of an account.
-pub type Balance = u128;
-
-/// Index of a transaction in the chain.
-pub type Index = u32;
-
-/// A hash of some data used by the chain.
-pub type Hash = sp_core::H256;
-
-/// Digest item type.
-pub type DigestItem = generic::DigestItem;
-
-/// Opaque types. These are used by the CLI to instantiate machinery that don't need to know
-/// the specifics of the runtime. They can then be made to be agnostic over specific formats
-/// of data like extrinsics, allowing for them to continue syncing the network through upgrades
-/// to even the core data structures.
-pub mod opaque {
-	use sp_std::prelude::*;
-	use sp_runtime::impl_opaque_keys;
-	use super::Aura;
-
-	pub use unique_runtime_common::types::*;
-
-	impl_opaque_keys! {
-		pub struct SessionKeys {
-			pub aura: Aura,
-		}
-	}
-}
-
 /// This runtime version.
 pub const VERSION: RuntimeVersion = RuntimeVersion {
 	spec_name: create_runtime_str!(RUNTIME_NAME),
@@ -198,1207 +52,15 @@
 	state_version: 0,
 };
 
-#[derive(codec::Encode, codec::Decode)]
-pub enum XCMPMessage<XAccountId, XBalance> {
-	/// Transfer tokens to the given account from the Parachain account.
-	TransferToken(XAccountId, XBalance),
-}
-
-/// The version information used to identify this runtime when compiled natively.
-#[cfg(feature = "std")]
-pub fn native_version() -> NativeVersion {
-	NativeVersion {
-		runtime_version: VERSION,
-		can_author_with: Default::default(),
-	}
-}
-
-type NegativeImbalance = <Balances as Currency<AccountId>>::NegativeImbalance;
-
-pub struct DealWithFees;
-impl OnUnbalanced<NegativeImbalance> for DealWithFees {
-	fn on_unbalanceds<B>(mut fees_then_tips: impl Iterator<Item = NegativeImbalance>) {
-		if let Some(fees) = fees_then_tips.next() {
-			// for fees, 100% to treasury
-			let mut split = fees.ration(100, 0);
-			if let Some(tips) = fees_then_tips.next() {
-				// for tips, if any, 100% to treasury
-				tips.ration_merge_into(100, 0, &mut split);
-			}
-			Treasury::on_unbalanced(split.0);
-			// Author::on_unbalanced(split.1);
-		}
-	}
-}
-
 parameter_types! {
-	pub const BlockHashCount: BlockNumber = 2400;
-	pub RuntimeBlockLength: BlockLength =
-		BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO);
-	pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75);
-	pub const MaximumBlockLength: u32 = 5 * 1024 * 1024;
-	pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder()
-		.base_block(BlockExecutionWeight::get())
-		.for_class(DispatchClass::all(), |weights| {
-			weights.base_extrinsic = ExtrinsicBaseWeight::get();
-		})
-		.for_class(DispatchClass::Normal, |weights| {
-			weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT);
-		})
-		.for_class(DispatchClass::Operational, |weights| {
-			weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT);
-			// Operational transactions have some extra reserved space, so that they
-			// are included even if block reached `MAXIMUM_BLOCK_WEIGHT`.
-			weights.reserved = Some(
-				MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT
-			);
-		})
-		.avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO)
-		.build_or_panic();
 	pub const Version: RuntimeVersion = VERSION;
 	pub const SS58Prefix: u8 = 255;
-}
-
-parameter_types! {
 	pub const ChainId: u64 = 8881;
-}
-
-pub struct FixedFee;
-impl FeeCalculator for FixedFee {
-	fn min_gas_price() -> (U256, u64) {
-		(MIN_GAS_PRICE.into(), 0)
-	}
-}
-
-// Assuming slowest ethereum opcode is SSTORE, with gas price of 20000 as our worst case
-// (contract, which only writes a lot of data),
-// approximating on top of our real store write weight
-parameter_types! {
-	pub const WritesPerSecond: u64 = WEIGHT_PER_SECOND / <Runtime as frame_system::Config>::DbWeight::get().write;
-	pub const GasPerSecond: u64 = WritesPerSecond::get() * 20000;
-	pub const WeightPerGas: u64 = WEIGHT_PER_SECOND / GasPerSecond::get();
-}
-
-/// Limiting EVM execution to 50% of block for substrate users and management tasks
-/// EVM transaction consumes more weight than substrate's, so we can't rely on them being
-/// scheduled fairly
-const EVM_DISPATCH_RATIO: Perbill = Perbill::from_percent(50);
-parameter_types! {
-	pub BlockGasLimit: U256 = U256::from(NORMAL_DISPATCH_RATIO * EVM_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT / WeightPerGas::get());
 }
 
-pub enum FixedGasWeightMapping {}
-impl GasWeightMapping for FixedGasWeightMapping {
-	fn gas_to_weight(gas: u64) -> Weight {
-		gas.saturating_mul(WeightPerGas::get())
-	}
-	fn weight_to_gas(weight: Weight) -> u64 {
-		weight / WeightPerGas::get()
-	}
-}
-
-impl pallet_evm::account::Config for Runtime {
-	type CrossAccountId = pallet_evm::account::BasicCrossAccountId<Self>;
-	type EvmAddressMapping = HashedAddressMapping<Self::Hashing>;
-	type EvmBackwardsAddressMapping = fp_evm_mapping::MapBackwardsAddressTruncated;
-}
-
-impl pallet_evm::Config for Runtime {
-	type BlockGasLimit = BlockGasLimit;
-	type FeeCalculator = FixedFee;
-	type GasWeightMapping = FixedGasWeightMapping;
-	type BlockHashMapping = pallet_ethereum::EthereumBlockHashMapping<Self>;
-	type CallOrigin = EnsureAddressTruncated<Self>;
-	type WithdrawOrigin = EnsureAddressTruncated<Self>;
-	type AddressMapping = HashedAddressMapping<Self::Hashing>;
-	type PrecompilesType = ();
-	type PrecompilesValue = ();
-	type Currency = Balances;
-	type Event = Event;
-	type OnMethodCall = (
-		pallet_evm_migration::OnMethodCall<Self>,
-		pallet_evm_contract_helpers::HelpersOnMethodCall<Self>,
-		CollectionDispatchT<Self>,
-		pallet_unique::eth::CollectionHelpersOnMethodCall<Self>,
-	);
-	type OnCreate = pallet_evm_contract_helpers::HelpersOnCreate<Self>;
-	type ChainId = ChainId;
-	type Runner = pallet_evm::runner::stack::Runner<Self>;
-	type OnChargeTransaction = pallet_evm::EVMCurrencyAdapter<Balances, DealWithFees>;
-	type TransactionValidityHack = pallet_evm_transaction_payment::TransactionValidityHack<Self>;
-	type FindAuthor = EthereumFindAuthor<Aura>;
-}
-
-impl pallet_evm_migration::Config for Runtime {
-	type WeightInfo = pallet_evm_migration::weights::SubstrateWeight<Self>;
-}
-
-pub struct EthereumFindAuthor<F>(core::marker::PhantomData<F>);
-impl<F: FindAuthor<u32>> FindAuthor<H160> for EthereumFindAuthor<F> {
-	fn find_author<'a, I>(digests: I) -> Option<H160>
-	where
-		I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
-	{
-		if let Some(author_index) = F::find_author(digests) {
-			let authority_id = Aura::authorities()[author_index as usize].clone();
-			return Some(H160::from_slice(&authority_id.to_raw_vec()[4..24]));
-		}
-		None
-	}
-}
-
-impl pallet_ethereum::Config for Runtime {
-	type Event = Event;
-	type StateRoot = pallet_ethereum::IntermediateStateRoot<Self>;
-}
-
-impl pallet_randomness_collective_flip::Config for Runtime {}
-
-impl frame_system::Config for Runtime {
-	/// The data to be stored in an account.
-	type AccountData = pallet_balances::AccountData<Balance>;
-	/// The identifier used to distinguish between accounts.
-	type AccountId = AccountId;
-	/// The basic call filter to use in dispatchable.
-	type BaseCallFilter = Everything;
-	/// Maximum number of block number to block hash mappings to keep (oldest pruned first).
-	type BlockHashCount = BlockHashCount;
-	/// The maximum length of a block (in bytes).
-	type BlockLength = RuntimeBlockLength;
-	/// The index type for blocks.
-	type BlockNumber = BlockNumber;
-	/// The weight of the overhead invoked on the block import process, independent of the extrinsics included in that block.
-	type BlockWeights = RuntimeBlockWeights;
-	/// The aggregated dispatch type that is available for extrinsics.
-	type Call = Call;
-	/// The weight of database operations that the runtime can invoke.
-	type DbWeight = RocksDbWeight;
-	/// The ubiquitous event type.
-	type Event = Event;
-	/// The type for hashing blocks and tries.
-	type Hash = Hash;
-	/// The hashing algorithm used.
-	type Hashing = BlakeTwo256;
-	/// The header type.
-	type Header = generic::Header<BlockNumber, BlakeTwo256>;
-	/// The index type for storing how many extrinsics an account has signed.
-	type Index = Index;
-	/// The lookup mechanism to get account ID from whatever is passed in dispatchers.
-	type Lookup = AccountIdLookup<AccountId, ()>;
-	/// What to do if an account is fully reaped from the system.
-	type OnKilledAccount = ();
-	/// What to do if a new account is created.
-	type OnNewAccount = ();
-	type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode<Self>;
-	/// The ubiquitous origin type.
-	type Origin = Origin;
-	/// This type is being generated by `construct_runtime!`.
-	type PalletInfo = PalletInfo;
-	/// This is used as an identifier of the chain. 42 is the generic substrate prefix.
-	type SS58Prefix = SS58Prefix;
-	/// Weight information for the extrinsics of this pallet.
-	type SystemWeightInfo = frame_system::weights::SubstrateWeight<Self>;
-	/// Version of the runtime.
-	type Version = Version;
-	type MaxConsumers = ConstU32<16>;
-}
-
-parameter_types! {
-	pub const MinimumPeriod: u64 = SLOT_DURATION / 2;
-}
-
-impl pallet_timestamp::Config for Runtime {
-	/// A timestamp: milliseconds since the unix epoch.
-	type Moment = u64;
-	type OnTimestampSet = ();
-	type MinimumPeriod = MinimumPeriod;
-	type WeightInfo = ();
-}
-
-parameter_types! {
-	// pub const ExistentialDeposit: u128 = 500;
-	pub const ExistentialDeposit: u128 = 0;
-	pub const MaxLocks: u32 = 50;
-	pub const MaxReserves: u32 = 50;
-}
-
-impl pallet_balances::Config for Runtime {
-	type MaxLocks = MaxLocks;
-	type MaxReserves = MaxReserves;
-	type ReserveIdentifier = [u8; 16];
-	/// The type for recording an account's balance.
-	type Balance = Balance;
-	/// The ubiquitous event type.
-	type Event = Event;
-	type DustRemoval = Treasury;
-	type ExistentialDeposit = ExistentialDeposit;
-	type AccountStore = System;
-	type WeightInfo = pallet_balances::weights::SubstrateWeight<Self>;
-}
-
-pub const fn deposit(items: u32, bytes: u32) -> Balance {
-	items as Balance * 15 * CENTIUNIQUE + (bytes as Balance) * 6 * CENTIUNIQUE
-}
-
-/*
-parameter_types! {
-	pub TombstoneDeposit: Balance = deposit(
-		1,
-		sp_std::mem::size_of::<pallet_contracts::Pallet<Runtime>> as u32,
-	);
-	pub DepositPerContract: Balance = TombstoneDeposit::get();
-	pub const DepositPerStorageByte: Balance = deposit(0, 1);
-	pub const DepositPerStorageItem: Balance = deposit(1, 0);
-	pub RentFraction: Perbill = Perbill::from_rational(1u32, 30 * DAYS);
-	pub const SurchargeReward: Balance = 150 * MILLIUNIQUE;
-	pub const SignedClaimHandicap: u32 = 2;
-	pub const MaxDepth: u32 = 32;
-	pub const MaxValueSize: u32 = 16 * 1024;
-	pub const MaxCodeSize: u32 = 1024 * 1024 * 25; // 25 Mb
-	// The lazy deletion runs inside on_initialize.
-	pub DeletionWeightLimit: Weight = AVERAGE_ON_INITIALIZE_RATIO *
-		RuntimeBlockWeights::get().max_block;
-	// The weight needed for decoding the queue should be less or equal than a fifth
-	// of the overall weight dedicated to the lazy deletion.
-	pub DeletionQueueDepth: u32 = ((DeletionWeightLimit::get() / (
-			<Runtime as pallet_contracts::Config>::WeightInfo::on_initialize_per_queue_item(1) -
-			<Runtime as pallet_contracts::Config>::WeightInfo::on_initialize_per_queue_item(0)
-		)) / 5) as u32;
-	pub Schedule: pallet_contracts::Schedule<Runtime> = Default::default();
-}
-
-impl pallet_contracts::Config for Runtime {
-	type Time = Timestamp;
-	type Randomness = RandomnessCollectiveFlip;
-	type Currency = Balances;
-	type Event = Event;
-	type RentPayment = ();
-	type SignedClaimHandicap = SignedClaimHandicap;
-	type TombstoneDeposit = TombstoneDeposit;
-	type DepositPerContract = DepositPerContract;
-	type DepositPerStorageByte = DepositPerStorageByte;
-	type DepositPerStorageItem = DepositPerStorageItem;
-	type RentFraction = RentFraction;
-	type SurchargeReward = SurchargeReward;
-	type WeightPrice = pallet_transaction_payment::Pallet<Self>;
-	type WeightInfo = pallet_contracts::weights::SubstrateWeight<Self>;
-	type ChainExtension = NFTExtension;
-	type DeletionQueueDepth = DeletionQueueDepth;
-	type DeletionWeightLimit = DeletionWeightLimit;
-	type Schedule = Schedule;
-	type CallStack = [pallet_contracts::Frame<Self>; 31];
-}
-*/
+construct_runtime!(quartz);
 
-parameter_types! {
-	/// This value increases the priority of `Operational` transactions by adding
-	/// a "virtual tip" that's equal to the `OperationalFeeMultiplier * final_fee`.
-	pub const OperationalFeeMultiplier: u8 = 5;
-}
-
-/// Linear implementor of `WeightToFeePolynomial`
-pub struct LinearFee<T>(sp_std::marker::PhantomData<T>);
-
-impl<T> WeightToFeePolynomial for LinearFee<T>
-where
-	T: BaseArithmetic + From<u32> + Copy + Unsigned,
-{
-	type Balance = T;
-
-	fn polynomial() -> WeightToFeeCoefficients<Self::Balance> {
-		smallvec!(WeightToFeeCoefficient {
-			coeff_integer: WEIGHT_TO_FEE_COEFF.into(),
-			coeff_frac: Perbill::zero(),
-			negative: false,
-			degree: 1,
-		})
-	}
-}
-
-impl pallet_transaction_payment::Config for Runtime {
-	type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter<Balances, DealWithFees>;
-	type LengthToFee = ConstantMultiplier<Balance, TransactionByteFee>;
-	type OperationalFeeMultiplier = OperationalFeeMultiplier;
-	type WeightToFee = LinearFee<Balance>;
-	type FeeMultiplierUpdate = ();
-}
-
-parameter_types! {
-	pub const ProposalBond: Permill = Permill::from_percent(5);
-	pub const ProposalBondMinimum: Balance = 1 * UNIQUE;
-	pub const ProposalBondMaximum: Balance = 1000 * UNIQUE;
-	pub const SpendPeriod: BlockNumber = 5 * MINUTES;
-	pub const Burn: Permill = Permill::from_percent(0);
-	pub const TipCountdown: BlockNumber = 1 * DAYS;
-	pub const TipFindersFee: Percent = Percent::from_percent(20);
-	pub const TipReportDepositBase: Balance = 1 * UNIQUE;
-	pub const DataDepositPerByte: Balance = 1 * CENTIUNIQUE;
-	pub const BountyDepositBase: Balance = 1 * UNIQUE;
-	pub const BountyDepositPayoutDelay: BlockNumber = 1 * DAYS;
-	pub const TreasuryModuleId: PalletId = PalletId(*b"py/trsry");
-	pub const BountyUpdatePeriod: BlockNumber = 14 * DAYS;
-	pub const MaximumReasonLength: u32 = 16384;
-	pub const BountyCuratorDeposit: Permill = Permill::from_percent(50);
-	pub const BountyValueMinimum: Balance = 5 * UNIQUE;
-	pub const MaxApprovals: u32 = 100;
-}
-
-impl pallet_treasury::Config for Runtime {
-	type PalletId = TreasuryModuleId;
-	type Currency = Balances;
-	type ApproveOrigin = EnsureRoot<AccountId>;
-	type RejectOrigin = EnsureRoot<AccountId>;
-	type Event = Event;
-	type OnSlash = ();
-	type ProposalBond = ProposalBond;
-	type ProposalBondMinimum = ProposalBondMinimum;
-	type ProposalBondMaximum = ProposalBondMaximum;
-	type SpendPeriod = SpendPeriod;
-	type Burn = Burn;
-	type BurnDestination = ();
-	type SpendFunds = ();
-	type WeightInfo = pallet_treasury::weights::SubstrateWeight<Self>;
-	type MaxApprovals = MaxApprovals;
-}
-
-impl pallet_sudo::Config for Runtime {
-	type Event = Event;
-	type Call = Call;
-}
-
-pub struct RelayChainBlockNumberProvider<T>(sp_std::marker::PhantomData<T>);
-
-impl<T: cumulus_pallet_parachain_system::Config> BlockNumberProvider
-	for RelayChainBlockNumberProvider<T>
-{
-	type BlockNumber = BlockNumber;
-
-	fn current_block_number() -> Self::BlockNumber {
-		cumulus_pallet_parachain_system::Pallet::<T>::validation_data()
-			.map(|d| d.relay_parent_number)
-			.unwrap_or_default()
-	}
-}
-
-parameter_types! {
-	pub const MinVestedTransfer: Balance = 10 * UNIQUE;
-	pub const MaxVestingSchedules: u32 = 28;
-}
-
-impl orml_vesting::Config for Runtime {
-	type Event = Event;
-	type Currency = pallet_balances::Pallet<Runtime>;
-	type MinVestedTransfer = MinVestedTransfer;
-	type VestedTransferOrigin = EnsureSigned<AccountId>;
-	type WeightInfo = ();
-	type MaxVestingSchedules = MaxVestingSchedules;
-	type BlockNumberProvider = RelayChainBlockNumberProvider<Runtime>;
-}
-
-parameter_types! {
-	pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 4;
-	pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 4;
-}
-
-impl cumulus_pallet_parachain_system::Config for Runtime {
-	type Event = Event;
-	type SelfParaId = parachain_info::Pallet<Self>;
-	type OnSystemEvent = ();
-	// type DownwardMessageHandlers = cumulus_primitives_utility::UnqueuedDmpAsParent<
-	// 	MaxDownwardMessageWeight,
-	// 	XcmExecutor<XcmConfig>,
-	// 	Call,
-	// >;
-	type OutboundXcmpMessageSource = XcmpQueue;
-	type DmpMessageHandler = DmpQueue;
-	type ReservedDmpWeight = ReservedDmpWeight;
-	type ReservedXcmpWeight = ReservedXcmpWeight;
-	type XcmpMessageHandler = XcmpQueue;
-}
-
-impl parachain_info::Config for Runtime {}
-
-impl cumulus_pallet_aura_ext::Config for Runtime {}
-
-parameter_types! {
-	pub const RelayLocation: MultiLocation = MultiLocation::parent();
-	pub const RelayNetwork: NetworkId = NetworkId::Polkadot;
-	pub RelayOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into();
-	pub Ancestry: MultiLocation = Parachain(ParachainInfo::parachain_id().into()).into();
-}
-
-/// 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>,
-);
-
-pub struct OnlySelfCurrency;
-impl<B: TryFrom<u128>> MatchesFungible<B> for OnlySelfCurrency {
-	fn matches_fungible(a: &MultiAsset) -> Option<B> {
-		match (&a.id, &a.fun) {
-			(Concrete(_), 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.
-	(),
->;
-
-/// 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, Origin>,
-	// Native converter for Relay-chain (Parent) location; will converts to a `Relay` origin when
-	// recognised.
-	RelayChainAsNative<RelayOrigin, Origin>,
-	// Native converter for sibling Parachains; will convert to a `SiblingPara` origin when
-	// recognised.
-	SiblingParachainAsNative<cumulus_pallet_xcm::Origin, Origin>,
-	// Superuser converter for the Relay-chain (Parent) location. This will allow it to issue a
-	// transaction from the Root origin.
-	ParentAsSuperuser<Origin>,
-	// Native signed account converter; this just converts an `AccountId32` origin into a normal
-	// `Origin::Signed` origin of the same 32-byte value.
-	SignedAccountId32AsNative<RelayNetwork, Origin>,
-	// Xcm origins can be represented natively under the Xcm pallet's Xcm origin.
-	XcmPassthrough<Origin>,
-);
-
-parameter_types! {
-	// One XCM operation is 1_000_000 weight - almost certainly a conservative estimate.
-	pub UnitWeightCost: Weight = 1_000_000;
-	// 1200 UNIQUEs buy 1 second of weight.
-	pub const WeightPrice: (MultiLocation, u128) = (MultiLocation::parent(), 1_200 * UNIQUE);
-	pub const MaxInstructions: u32 = 100;
-	pub const MaxAuthorities: u32 = 100_000;
-}
-
-match_types! {
-	pub type ParentOrParentsUnitPlurality: impl Contains<MultiLocation> = {
-		MultiLocation { parents: 1, interior: Here } |
-		MultiLocation { parents: 1, interior: X1(Plurality { id: BodyId::Unit, .. }) }
-	};
-}
-
-pub type Barrier = (
-	TakeWeightCredit,
-	AllowTopLevelPaidExecutionFrom<Everything>,
-	// ^^^ Parent & its unit plurality gets free execution
-);
-
-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 {
-		Self(0, Zero::zero(), PhantomData)
-	}
-
-	fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, XcmError> {
-		let amount = WeightToFee::weight_to_fee(&weight);
-		let u128_amount: u128 = amount.try_into().map_err(|_| XcmError::Overflow)?;
-
-		// location to this parachain through relay chain
-		let option1: xcm::v1::AssetId = Concrete(MultiLocation {
-			parents: 1,
-			interior: X1(Parachain(ParachainInfo::parachain_id().into())),
-		});
-		// direct location
-		let option2: xcm::v1::AssetId = Concrete(MultiLocation {
-			parents: 0,
-			interior: Here,
-		});
-
-		let required = if payment.fungible.contains_key(&option1) {
-			(option1, u128_amount).into()
-		} else if payment.fungible.contains_key(&option2) {
-			(option2, u128_amount).into()
-		} else {
-			(Concrete(MultiLocation::default()), u128_amount).into()
-		};
-
-		let unused = payment
-			.checked_sub(required)
-			.map_err(|_| XcmError::TooExpensive)?;
-		self.0 = self.0.saturating_add(weight);
-		self.1 = self.1.saturating_add(amount);
-		Ok(unused)
-	}
-
-	fn refund_weight(&mut self, weight: Weight) -> Option<MultiAsset> {
-		let weight = weight.min(self.0);
-		let amount = WeightToFee::weight_to_fee(&weight);
-		self.0 -= weight;
-		self.1 = self.1.saturating_sub(amount);
-		let amount: u128 = amount.saturated_into();
-		if amount > 0 {
-			Some((AssetId::get(), amount).into())
-		} else {
-			None
-		}
-	}
-}
-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 struct XcmConfig;
-impl Config for XcmConfig {
-	type Call = Call;
-	type XcmSender = XcmRouter;
-	// How to withdraw and deposit an asset.
-	type AssetTransactor = LocalAssetTransactor;
-	type OriginConverter = XcmOriginToTransactDispatchOrigin;
-	type IsReserve = NativeAsset;
-	type IsTeleporter = (); // Teleportation is disabled
-	type LocationInverter = LocationInverter<Ancestry>;
-	type Barrier = Barrier;
-	type Weigher = FixedWeightBounds<UnitWeightCost, Call, MaxInstructions>;
-	type Trader =
-		UsingOnlySelfCurrencyComponents<LinearFee<Balance>, RelayLocation, AccountId, Balances, ()>;
-	type ResponseHandler = (); // Don't handle responses for now.
-	type SubscriptionService = PolkadotXcm;
-
-	type AssetTrap = PolkadotXcm;
-	type AssetClaims = PolkadotXcm;
-}
-
-// parameter_types! {
-// 	pub const MaxDownwardMessageWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 10;
-// }
-
-/// No local origins on this chain are allowed to dispatch XCM sends/executions.
-pub type LocalOriginToLocation = (SignedToAccountId32<Origin, 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, ()>,
-	// ..and XCMP to communicate with the sibling chains.
-	XcmpQueue,
-);
-
-impl pallet_evm_coder_substrate::Config for Runtime {}
-
-impl pallet_xcm::Config for Runtime {
-	type Event = Event;
-	type SendXcmOrigin = EnsureXcmOrigin<Origin, LocalOriginToLocation>;
-	type XcmRouter = XcmRouter;
-	type ExecuteXcmOrigin = EnsureXcmOrigin<Origin, LocalOriginToLocation>;
-	type XcmExecuteFilter = Everything;
-	type XcmExecutor = XcmExecutor<XcmConfig>;
-	type XcmTeleportFilter = Everything;
-	type XcmReserveTransferFilter = Everything;
-	type Weigher = FixedWeightBounds<UnitWeightCost, Call, MaxInstructions>;
-	type LocationInverter = LocationInverter<Ancestry>;
-	type Origin = Origin;
-	type Call = Call;
-	const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
-	type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion;
-}
-
-impl cumulus_pallet_xcm::Config for Runtime {
-	type Event = Event;
-	type XcmExecutor = XcmExecutor<XcmConfig>;
-}
-
-impl cumulus_pallet_xcmp_queue::Config for Runtime {
-	type WeightInfo = ();
-	type Event = Event;
-	type XcmExecutor = XcmExecutor<XcmConfig>;
-	type ChannelInfo = ParachainSystem;
-	type VersionWrapper = ();
-	type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
-	type ControllerOrigin = EnsureRoot<AccountId>;
-	type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin;
-}
-
-impl cumulus_pallet_dmp_queue::Config for Runtime {
-	type Event = Event;
-	type XcmExecutor = XcmExecutor<XcmConfig>;
-	type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
-}
-
-impl pallet_aura::Config for Runtime {
-	type AuthorityId = AuraId;
-	type DisabledValidators = ();
-	type MaxAuthorities = MaxAuthorities;
-}
-
-parameter_types! {
-	pub TreasuryAccountId: AccountId = TreasuryModuleId::get().into_account_truncating();
-	pub const CollectionCreationPrice: Balance = 2 * UNIQUE;
-}
-
-impl pallet_common::Config for Runtime {
-	type WeightInfo = pallet_common::weights::SubstrateWeight<Self>;
-	type Event = Event;
-	type Currency = Balances;
-	type CollectionCreationPrice = CollectionCreationPrice;
-	type TreasuryAccountId = TreasuryAccountId;
-	type CollectionDispatch = CollectionDispatchT<Self>;
-
-	type EvmTokenAddressMapping = EvmTokenAddressMapping;
-	type CrossTokenAddressMapping = CrossTokenAddressMapping<Self::AccountId>;
-	type ContractAddress = EvmCollectionHelpersAddress;
-}
-
-impl pallet_structure::Config for Runtime {
-	type Event = Event;
-	type Call = Call;
-	type WeightInfo = pallet_structure::weights::SubstrateWeight<Self>;
-}
-
-impl pallet_fungible::Config for Runtime {
-	type WeightInfo = pallet_fungible::weights::SubstrateWeight<Self>;
-}
-impl pallet_refungible::Config for Runtime {
-	type WeightInfo = pallet_refungible::weights::SubstrateWeight<Self>;
-}
-impl pallet_nonfungible::Config for Runtime {
-	type WeightInfo = pallet_nonfungible::weights::SubstrateWeight<Self>;
-}
-
-impl pallet_proxy_rmrk_core::Config for Runtime {
-	type WeightInfo = pallet_proxy_rmrk_core::weights::SubstrateWeight<Self>;
-	type Event = Event;
-}
-
-impl pallet_proxy_rmrk_equip::Config for Runtime {
-	type WeightInfo = pallet_proxy_rmrk_equip::weights::SubstrateWeight<Self>;
-	type Event = Event;
-}
-
-impl pallet_unique::Config for Runtime {
-	type Event = Event;
-	type WeightInfo = pallet_unique::weights::SubstrateWeight<Self>;
-	type CommonWeightInfo = CommonWeights<Self>;
-	type RefungibleExtensionsWeightInfo = CommonWeights<Self>;
-}
-
-parameter_types! {
-	pub const InflationBlockInterval: BlockNumber = 100; // every time per how many blocks inflation is applied
-}
-
-/// Used for the pallet inflation
-impl pallet_inflation::Config for Runtime {
-	type Currency = Balances;
-	type TreasuryAccountId = TreasuryAccountId;
-	type InflationBlockInterval = InflationBlockInterval;
-	type BlockNumberProvider = RelayChainBlockNumberProvider<Runtime>;
-}
-
-parameter_types! {
-	pub MaximumSchedulerWeight: Weight = Perbill::from_percent(50) *
-		RuntimeBlockWeights::get().max_block;
-	pub const MaxScheduledPerBlock: u32 = 50;
-}
-
-type ChargeTransactionPayment = pallet_charge_transaction::ChargeTransactionPayment<Runtime>;
-use frame_support::traits::NamedReservableCurrency;
-
-fn get_signed_extras(from: <Runtime as frame_system::Config>::AccountId) -> SignedExtraScheduler {
-	(
-		frame_system::CheckSpecVersion::<Runtime>::new(),
-		frame_system::CheckGenesis::<Runtime>::new(),
-		frame_system::CheckEra::<Runtime>::from(Era::Immortal),
-		frame_system::CheckNonce::<Runtime>::from(frame_system::Pallet::<Runtime>::account_nonce(
-			from,
-		)),
-		frame_system::CheckWeight::<Runtime>::new(),
-		// sponsoring transaction logic
-		// pallet_charge_transaction::ChargeTransactionPayment::<Runtime>::new(0),
-	)
-}
-
-pub struct SchedulerPaymentExecutor;
-impl<T: frame_system::Config + pallet_unique_scheduler::Config, SelfContainedSignedInfo>
-	DispatchCall<T, SelfContainedSignedInfo> for SchedulerPaymentExecutor
-where
-	<T as frame_system::Config>::Call: Member
-		+ Dispatchable<Origin = Origin, Info = DispatchInfo>
-		+ SelfContainedCall<SignedInfo = SelfContainedSignedInfo>
-		+ GetDispatchInfo
-		+ From<frame_system::Call<Runtime>>,
-	SelfContainedSignedInfo: Send + Sync + 'static,
-	Call: From<<T as frame_system::Config>::Call>
-		+ From<<T as pallet_unique_scheduler::Config>::Call>
-		+ SelfContainedCall<SignedInfo = SelfContainedSignedInfo>,
-	sp_runtime::AccountId32: From<<T as frame_system::Config>::AccountId>,
-{
-	fn dispatch_call(
-		signer: <T as frame_system::Config>::AccountId,
-		call: <T as pallet_unique_scheduler::Config>::Call,
-	) -> Result<
-		Result<PostDispatchInfo, DispatchErrorWithPostInfo<PostDispatchInfo>>,
-		TransactionValidityError,
-	> {
-		let dispatch_info = call.get_dispatch_info();
-		let extrinsic = fp_self_contained::CheckedExtrinsic::<
-			AccountId,
-			Call,
-			SignedExtraScheduler,
-			SelfContainedSignedInfo,
-		> {
-			signed:
-				CheckedSignature::<AccountId, SignedExtraScheduler, SelfContainedSignedInfo>::Signed(
-					signer.clone().into(),
-					get_signed_extras(signer.into()),
-				),
-			function: call.into(),
-		};
-
-		extrinsic.apply::<Runtime>(&dispatch_info, 0)
-	}
-
-	fn reserve_balance(
-		id: [u8; 16],
-		sponsor: <T as frame_system::Config>::AccountId,
-		call: <T as pallet_unique_scheduler::Config>::Call,
-		count: u32,
-	) -> Result<(), DispatchError> {
-		let dispatch_info = call.get_dispatch_info();
-		let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0)
-			.saturating_mul(count.into());
-
-		<Balances as NamedReservableCurrency<AccountId>>::reserve_named(
-			&id,
-			&(sponsor.into()),
-			weight.into(),
-		)
-	}
-
-	fn pay_for_call(
-		id: [u8; 16],
-		sponsor: <T as frame_system::Config>::AccountId,
-		call: <T as pallet_unique_scheduler::Config>::Call,
-	) -> Result<u128, DispatchError> {
-		let dispatch_info = call.get_dispatch_info();
-		let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0);
-		Ok(
-			<Balances as NamedReservableCurrency<AccountId>>::unreserve_named(
-				&id,
-				&(sponsor.into()),
-				weight.into(),
-			),
-		)
-	}
-
-	fn cancel_reserve(
-		id: [u8; 16],
-		sponsor: <T as frame_system::Config>::AccountId,
-	) -> Result<u128, DispatchError> {
-		Ok(
-			<Balances as NamedReservableCurrency<AccountId>>::unreserve_named(
-				&id,
-				&(sponsor.into()),
-				u128::MAX,
-			),
-		)
-	}
-}
-
-parameter_types! {
-	pub const NoPreimagePostponement: Option<u32> = Some(10);
-	pub const Preimage: Option<u32> = Some(10);
-}
-
-/// Used the compare the privilege of an origin inside the scheduler.
-pub struct OriginPrivilegeCmp;
-
-impl PrivilegeCmp<OriginCaller> for OriginPrivilegeCmp {
-	fn cmp_privilege(_left: &OriginCaller, _right: &OriginCaller) -> Option<Ordering> {
-		Some(Ordering::Equal)
-	}
-}
-
-impl pallet_unique_scheduler::Config for Runtime {
-	type Event = Event;
-	type Origin = Origin;
-	type Currency = Balances;
-	type PalletsOrigin = OriginCaller;
-	type Call = Call;
-	type MaximumWeight = MaximumSchedulerWeight;
-	type ScheduleOrigin = EnsureSigned<AccountId>;
-	type MaxScheduledPerBlock = MaxScheduledPerBlock;
-	type WeightInfo = ();
-	type CallExecutor = SchedulerPaymentExecutor;
-	type OriginPrivilegeCmp = OriginPrivilegeCmp;
-	type PreimageProvider = ();
-	type NoPreimagePostponement = NoPreimagePostponement;
-}
-
-type EvmSponsorshipHandler = (
-	UniqueEthSponsorshipHandler<Runtime>,
-	pallet_evm_contract_helpers::HelpersContractSponsoring<Runtime>,
-);
-type SponsorshipHandler = (
-	UniqueSponsorshipHandler<Runtime>,
-	//pallet_contract_helpers::ContractSponsorshipHandler<Runtime>,
-	pallet_evm_transaction_payment::BridgeSponsorshipHandler<Runtime>,
-);
-
-impl pallet_evm_transaction_payment::Config for Runtime {
-	type EvmSponsorshipHandler = EvmSponsorshipHandler;
-	type Currency = Balances;
-}
-
-impl pallet_charge_transaction::Config for Runtime {
-	type SponsorshipHandler = SponsorshipHandler;
-}
-
-// impl pallet_contract_helpers::Config for Runtime {
-//	 type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit;
-// }
-
-parameter_types! {
-	// 0x842899ECF380553E8a4de75bF534cdf6fBF64049
-	pub const HelpersContractAddress: H160 = H160([
-		0x84, 0x28, 0x99, 0xec, 0xf3, 0x80, 0x55, 0x3e, 0x8a, 0x4d, 0xe7, 0x5b, 0xf5, 0x34, 0xcd, 0xf6, 0xfb, 0xf6, 0x40, 0x49,
-	]);
-
-	// 0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f
-	pub const EvmCollectionHelpersAddress: H160 = H160([
-		0x6c, 0x4e, 0x9f, 0xe1, 0xae, 0x37, 0xa4, 0x1e, 0x93, 0xce, 0xe4, 0x29, 0xe8, 0xe1, 0x88, 0x1a, 0xbd, 0xcb, 0xb5, 0x4f,
-	]);
-}
-
-impl pallet_evm_contract_helpers::Config for Runtime {
-	type ContractAddress = HelpersContractAddress;
-	type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit;
-}
-
-construct_runtime!(
-	pub enum Runtime where
-		Block = Block,
-		NodeBlock = opaque::Block,
-		UncheckedExtrinsic = UncheckedExtrinsic
-	{
-		ParachainSystem: cumulus_pallet_parachain_system::{Pallet, Call, Config, Storage, Inherent, Event<T>, ValidateUnsigned} = 20,
-		ParachainInfo: parachain_info::{Pallet, Storage, Config} = 21,
-
-		Aura: pallet_aura::{Pallet, Config<T>} = 22,
-		AuraExt: cumulus_pallet_aura_ext::{Pallet, Config} = 23,
-
-		Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>} = 30,
-		RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Pallet, Storage} = 31,
-		Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent} = 32,
-		TransactionPayment: pallet_transaction_payment::{Pallet, Storage} = 33,
-		Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event<T>} = 34,
-		Sudo: pallet_sudo::{Pallet, Call, Storage, Config<T>, Event<T>} = 35,
-		System: frame_system::{Pallet, Call, Storage, Config, Event<T>} = 36,
-		Vesting: orml_vesting::{Pallet, Storage, Call, Event<T>, Config<T>} = 37,
-		// Vesting: pallet_vesting::{Pallet, Call, Config<T>, Storage, Event<T>} = 37,
-		// Contracts: pallet_contracts::{Pallet, Call, Storage, Event<T>} = 38,
-
-		// XCM helpers.
-		XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event<T>} = 50,
-		PolkadotXcm: pallet_xcm::{Pallet, Call, Event<T>, Origin} = 51,
-		CumulusXcm: cumulus_pallet_xcm::{Pallet, Call, Event<T>, Origin} = 52,
-		DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event<T>} = 53,
-
-		// Unique Pallets
-		Inflation: pallet_inflation::{Pallet, Call, Storage} = 60,
-		Unique: pallet_unique::{Pallet, Call, Storage, Event<T>} = 61,
-		Scheduler: pallet_unique_scheduler::{Pallet, Call, Storage, Event<T>} = 62,
-		// free = 63
-		Charging: pallet_charge_transaction::{Pallet, Call, Storage } = 64,
-		// ContractHelpers: pallet_contract_helpers::{Pallet, Call, Storage} = 65,
-		Common: pallet_common::{Pallet, Storage, Event<T>} = 66,
-		Fungible: pallet_fungible::{Pallet, Storage} = 67,
-		Refungible: pallet_refungible::{Pallet, Storage} = 68,
-		Nonfungible: pallet_nonfungible::{Pallet, Storage} = 69,
-		Structure: pallet_structure::{Pallet, Call, Storage, Event<T>} = 70,
-		RmrkCore: pallet_proxy_rmrk_core::{Pallet, Call, Storage, Event<T>} = 71,
-		RmrkEquip: pallet_proxy_rmrk_equip::{Pallet, Call, Storage, Event<T>} = 72,
-
-		// Frontier
-		EVM: pallet_evm::{Pallet, Config, Call, Storage, Event<T>} = 100,
-		Ethereum: pallet_ethereum::{Pallet, Config, Call, Storage, Event, Origin} = 101,
-
-		EvmCoderSubstrate: pallet_evm_coder_substrate::{Pallet, Storage} = 150,
-		EvmContractHelpers: pallet_evm_contract_helpers::{Pallet, Storage} = 151,
-		EvmTransactionPayment: pallet_evm_transaction_payment::{Pallet} = 152,
-		EvmMigration: pallet_evm_migration::{Pallet, Call, Storage} = 153,
-	}
-);
-
-pub struct TransactionConverter;
-
-impl fp_rpc::ConvertTransaction<UncheckedExtrinsic> for TransactionConverter {
-	fn convert_transaction(&self, transaction: pallet_ethereum::Transaction) -> UncheckedExtrinsic {
-		UncheckedExtrinsic::new_unsigned(
-			pallet_ethereum::Call::<Runtime>::transact { transaction }.into(),
-		)
-	}
-}
-
-impl fp_rpc::ConvertTransaction<opaque::UncheckedExtrinsic> for TransactionConverter {
-	fn convert_transaction(
-		&self,
-		transaction: pallet_ethereum::Transaction,
-	) -> opaque::UncheckedExtrinsic {
-		let extrinsic = UncheckedExtrinsic::new_unsigned(
-			pallet_ethereum::Call::<Runtime>::transact { transaction }.into(),
-		);
-		let encoded = extrinsic.encode();
-		opaque::UncheckedExtrinsic::decode(&mut &encoded[..])
-			.expect("Encoded extrinsic is always valid")
-	}
-}
-
-/// The address format for describing accounts.
-pub type Address = sp_runtime::MultiAddress<AccountId, ()>;
-/// Block header type as expected by this runtime.
-pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
-/// Block type as expected by this runtime.
-pub type Block = generic::Block<Header, UncheckedExtrinsic>;
-/// A Block signed with a Justification
-pub type SignedBlock = generic::SignedBlock<Block>;
-/// BlockId type as expected by this runtime.
-pub type BlockId = generic::BlockId<Block>;
-/// The SignedExtension to the basic transaction logic.
-pub type SignedExtra = (
-	frame_system::CheckSpecVersion<Runtime>,
-	// system::CheckTxVersion<Runtime>,
-	frame_system::CheckGenesis<Runtime>,
-	frame_system::CheckEra<Runtime>,
-	frame_system::CheckNonce<Runtime>,
-	frame_system::CheckWeight<Runtime>,
-	ChargeTransactionPayment,
-	//pallet_contract_helpers::ContractHelpersExtension<Runtime>,
-	pallet_ethereum::FakeTransactionFinalizer<Runtime>,
-);
-
-pub type SignedExtraScheduler = (
-	frame_system::CheckSpecVersion<Runtime>,
-	frame_system::CheckGenesis<Runtime>,
-	frame_system::CheckEra<Runtime>,
-	frame_system::CheckNonce<Runtime>,
-	frame_system::CheckWeight<Runtime>,
-	// pallet_charge_transaction::ChargeTransactionPayment<Runtime>,
-);
-/// Unchecked extrinsic type as expected by this runtime.
-pub type UncheckedExtrinsic =
-	fp_self_contained::UncheckedExtrinsic<Address, Call, Signature, SignedExtra>;
-/// Extrinsic type that has already been checked.
-pub type CheckedExtrinsic = fp_self_contained::CheckedExtrinsic<AccountId, Call, SignedExtra, H160>;
-/// Executive: handles dispatch to the various modules.
-pub type Executive = frame_executive::Executive<
-	Runtime,
-	Block,
-	frame_system::ChainContext<Runtime>,
-	Runtime,
-	AllPalletsReversedWithSystemFirst,
->;
-
-impl_opaque_keys! {
-	pub struct SessionKeys {
-		pub aura: Aura,
-	}
-}
-
-impl fp_self_contained::SelfContainedCall for Call {
-	type SignedInfo = H160;
-
-	fn is_self_contained(&self) -> bool {
-		match self {
-			Call::Ethereum(call) => call.is_self_contained(),
-			_ => false,
-		}
-	}
-
-	fn check_self_contained(&self) -> Option<Result<Self::SignedInfo, TransactionValidityError>> {
-		match self {
-			Call::Ethereum(call) => call.check_self_contained(),
-			_ => None,
-		}
-	}
-
-	fn validate_self_contained(
-		&self,
-		info: &Self::SignedInfo,
-		dispatch_info: &DispatchInfoOf<Call>,
-		len: usize,
-	) -> Option<TransactionValidity> {
-		match self {
-			Call::Ethereum(call) => call.validate_self_contained(info, dispatch_info, len),
-			_ => None,
-		}
-	}
-
-	fn pre_dispatch_self_contained(
-		&self,
-		info: &Self::SignedInfo,
-	) -> Option<Result<(), TransactionValidityError>> {
-		match self {
-			Call::Ethereum(call) => call.pre_dispatch_self_contained(info),
-			_ => None,
-		}
-	}
-
-	fn apply_self_contained(
-		self,
-		info: Self::SignedInfo,
-	) -> Option<sp_runtime::DispatchResultWithInfo<PostDispatchInfoOf<Self>>> {
-		match self {
-			call @ Call::Ethereum(pallet_ethereum::Call::transact { .. }) => Some(call.dispatch(
-				Origin::from(pallet_ethereum::RawOrigin::EthereumTransaction(info)),
-			)),
-			_ => None,
-		}
-	}
-}
-
-macro_rules! dispatch_unique_runtime {
-	($collection:ident.$method:ident($($name:ident),*)) => {{
-		let collection = <Runtime as pallet_common::Config>::CollectionDispatch::dispatch(<pallet_common::CollectionHandle<Runtime>>::try_get($collection)?);
-		let dispatch = collection.as_dyn();
-
-		Ok::<_, DispatchError>(dispatch.$method($($name),*))
-	}};
-}
-
-impl_common_runtime_apis! {
-	#![custom_apis]
-
-	impl rmrk_rpc::RmrkApi<
-		Block,
-		AccountId,
-		RmrkCollectionInfo<AccountId>,
-		RmrkInstanceInfo<AccountId>,
-		RmrkResourceInfo,
-		RmrkPropertyInfo,
-		RmrkBaseInfo<AccountId>,
-		RmrkPartType,
-		RmrkTheme
-	> for Runtime {
-		fn last_collection_idx() -> Result<RmrkCollectionId, DispatchError> {
-			pallet_proxy_rmrk_core::rpc::last_collection_idx::<Runtime>()
-		}
-
-		fn collection_by_id(collection_id: RmrkCollectionId) -> Result<Option<RmrkCollectionInfo<AccountId>>, DispatchError> {
-			pallet_proxy_rmrk_core::rpc::collection_by_id::<Runtime>(collection_id)
-		}
-
-		fn nft_by_id(collection_id: RmrkCollectionId, nft_by_id: RmrkNftId) -> Result<Option<RmrkInstanceInfo<AccountId>>, DispatchError> {
-			pallet_proxy_rmrk_core::rpc::nft_by_id::<Runtime>(collection_id, nft_by_id)
-		}
-
-		fn account_tokens(account_id: AccountId, collection_id: RmrkCollectionId) -> Result<Vec<RmrkNftId>, DispatchError> {
-			pallet_proxy_rmrk_core::rpc::account_tokens::<Runtime>(account_id, collection_id)
-		}
-
-		fn nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkNftChild>, DispatchError> {
-			pallet_proxy_rmrk_core::rpc::nft_children::<Runtime>(collection_id, nft_id)
-		}
-
-		fn collection_properties(collection_id: RmrkCollectionId, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
-			pallet_proxy_rmrk_core::rpc::collection_properties::<Runtime>(collection_id, filter_keys)
-		}
-
-		fn nft_properties(collection_id: RmrkCollectionId, nft_id: RmrkNftId, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
-			pallet_proxy_rmrk_core::rpc::nft_properties::<Runtime>(collection_id, nft_id, filter_keys)
-		}
-
-		fn nft_resources(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkResourceInfo>, DispatchError> {
-			pallet_proxy_rmrk_core::rpc::nft_resources::<Runtime>(collection_id, nft_id)
-		}
-
-		fn nft_resource_priority(collection_id: RmrkCollectionId, nft_id: RmrkNftId, resource_id: RmrkResourceId) -> Result<Option<u32>, DispatchError> {
-			pallet_proxy_rmrk_core::rpc::nft_resource_priority::<Runtime>(collection_id, nft_id, resource_id)
-		}
-
-		fn base(base_id: RmrkBaseId) -> Result<Option<RmrkBaseInfo<AccountId>>, DispatchError> {
-			pallet_proxy_rmrk_equip::rpc::base::<Runtime>(base_id)
-		}
-
-		fn base_parts(base_id: RmrkBaseId) -> Result<Vec<RmrkPartType>, DispatchError> {
-			pallet_proxy_rmrk_equip::rpc::base_parts::<Runtime>(base_id)
-		}
-
-		fn theme_names(base_id: RmrkBaseId) -> Result<Vec<RmrkThemeName>, DispatchError> {
-			pallet_proxy_rmrk_equip::rpc::theme_names::<Runtime>(base_id)
-		}
-
-		fn theme(base_id: RmrkBaseId, theme_name: RmrkThemeName, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Option<RmrkTheme>, DispatchError> {
-			pallet_proxy_rmrk_equip::rpc::theme::<Runtime>(base_id, theme_name, filter_keys)
-		}
-	}
-}
-
-struct CheckInherents;
-
-impl cumulus_pallet_parachain_system::CheckInherents<Block> for CheckInherents {
-	fn check_inherents(
-		block: &Block,
-		relay_state_proof: &cumulus_pallet_parachain_system::RelayChainStateProof,
-	) -> sp_inherents::CheckInherentsResult {
-		let relay_chain_slot = relay_state_proof
-			.read_slot()
-			.expect("Could not read the relay chain slot from the proof");
-
-		let inherent_data =
-			cumulus_primitives_timestamp::InherentDataProvider::from_relay_chain_slot_and_duration(
-				relay_chain_slot,
-				sp_std::time::Duration::from_secs(6),
-			)
-			.create_inherent_data()
-			.expect("Could not create the timestamp inherent data");
-
-		inherent_data.check_extrinsics(block)
-	}
-}
+impl_common_runtime_apis!();
 
 cumulus_pallet_parachain_system::register_validate_block!(
 	Runtime = Runtime,
modifiedruntime/tests/Cargo.tomldiffbeforeafterboth
--- a/runtime/tests/Cargo.toml
+++ b/runtime/tests/Cargo.toml
@@ -3,8 +3,12 @@
 version = "0.1.0"
 edition = "2021"
 
+[features]
+default = ['refungible']
+
+refungible = []
+
 [dependencies]
-unique-runtime-common = { path = '../common' }
 up-data-structs = { default-features = false, path = '../../primitives/data-structs' }
 
 sp-core = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.24' }
@@ -37,3 +41,6 @@
 	"derive",
 ] }
 scale-info = "*"
+
+evm-coder = { default-features = false, path = '../../crates/evm-coder' }
+up-sponsorship = { default-features = false, git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = 'polkadot-v0.9.24' }
modifiedruntime/tests/src/lib.rsdiffbeforeafterboth
--- a/runtime/tests/src/lib.rs
+++ b/runtime/tests/src/lib.rs
@@ -35,9 +35,18 @@
 use parity_scale_codec::{Encode, Decode, MaxEncodedLen};
 use scale_info::TypeInfo;
 
-use unique_runtime_common::{dispatch::CollectionDispatchT, weights::CommonWeights};
 use up_data_structs::mapping::{CrossTokenAddressMapping, EvmTokenAddressMapping};
 
+#[path = "../../common/dispatch.rs"]
+mod dispatch;
+
+use dispatch::CollectionDispatchT;
+
+#[path = "../../common/weights.rs"]
+mod weights;
+
+use weights::CommonWeights;
+
 type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
 type Block = frame_system::mocking::MockBlock<Test>;
 
modifiedruntime/unique/Cargo.tomldiffbeforeafterboth
--- a/runtime/unique/Cargo.toml
+++ b/runtime/unique/Cargo.toml
@@ -114,13 +114,17 @@
     'xcm/std',
     'xcm-builder/std',
     'xcm-executor/std',
-    'unique-runtime-common/std',
+    'up-common/std',
 
     "orml-vesting/std",
 ]
 limit-testing = ['pallet-unique/limit-testing', 'up-data-structs/limit-testing']
 unique-runtime = []
 
+refungible = []
+scheduler = []
+rmrk = []
+
 ################################################################################
 # Substrate Dependencies
 
@@ -397,7 +401,7 @@
 
 [dependencies]
 log = { version = "0.4.16", default-features = false }
-unique-runtime-common = { path = "../common", default-features = false }
+up-common = { path = "../../primitives/common", default-features = false }
 scale-info = { version = "2.0.1", default-features = false, features = [
     "derive",
 ] }
@@ -427,6 +431,8 @@
 fp-rpc = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" }
 fp-self-contained = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" }
 fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" }
+evm-coder = { default-features = false, path = '../../crates/evm-coder' }
+up-sponsorship = { default-features = false, git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = 'polkadot-v0.9.24' }
 
 ################################################################################
 # Build Dependencies
modifiedruntime/unique/src/lib.rsdiffbeforeafterboth
--- a/runtime/unique/src/lib.rs
+++ b/runtime/unique/src/lib.rs
@@ -25,166 +25,21 @@
 #[cfg(feature = "std")]
 include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
 
-use sp_api::impl_runtime_apis;
-use sp_core::{crypto::KeyTypeId, OpaqueMetadata, H256, U256, H160};
-use sp_runtime::DispatchError;
-use fp_self_contained::*;
-// #[cfg(any(feature = "std", test))]
-// pub use sp_runtime::BuildStorage;
-
-use sp_runtime::{
-	Permill, Perbill, Percent, create_runtime_str, generic, impl_opaque_keys,
-	traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, AccountIdConversion, Zero, Member},
-	transaction_validity::{TransactionSource, TransactionValidity},
-	ApplyExtrinsicResult, RuntimeAppPublic,
-};
-
-use sp_std::prelude::*;
+use frame_support::parameter_types;
 
-#[cfg(feature = "std")]
-use sp_version::NativeVersion;
 use sp_version::RuntimeVersion;
-pub use pallet_transaction_payment::{
-	Multiplier, TargetedFeeAdjustment, FeeDetails, RuntimeDispatchInfo,
-};
-// A few exports that help ease life for downstream crates.
-pub use pallet_balances::Call as BalancesCall;
-pub use pallet_evm::{
-	EnsureAddressTruncated, HashedAddressMapping, Runner, account::CrossAccountId as _,
-	OnMethodCall, Account as EVMAccount, FeeCalculator, GasWeightMapping,
-};
-pub use frame_support::{
-	construct_runtime, match_types,
-	dispatch::DispatchResult,
-	PalletId, parameter_types, StorageValue, ConsensusEngineId,
-	traits::{
-		tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT, Everything,
-		Currency, ExistenceRequirement, Get, IsInVec, KeyOwnerProofSystem, LockIdentifier,
-		OnUnbalanced, Randomness, FindAuthor, ConstU32, Imbalance, PrivilegeCmp,
-	},
-	weights::{
-		constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},
-		DispatchClass, DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,
-		WeightToFeePolynomial, WeightToFeeCoefficient, WeightToFeeCoefficients, ConstantMultiplier,
-		WeightToFee,
-	},
-};
-use pallet_unique_scheduler::DispatchCall;
-use up_data_structs::{
-	CollectionId, TokenId, TokenData, Property, PropertyKeyPermission, CollectionLimits,
-	CollectionStats, RpcCollection,
-	mapping::{EvmTokenAddressMapping, CrossTokenAddressMapping},
-	TokenChild, RmrkCollectionInfo, RmrkInstanceInfo, RmrkResourceInfo, RmrkPropertyInfo,
-	RmrkBaseInfo, RmrkPartType, RmrkTheme, RmrkThemeName, RmrkCollectionId, RmrkNftId,
-	RmrkNftChild, RmrkPropertyKey, RmrkResourceId, RmrkBaseId,
-};
+use sp_runtime::create_runtime_str;
 
-// use pallet_contracts::weights::WeightInfo;
-// #[cfg(any(feature = "std", test))]
-use frame_system::{
-	self as frame_system, EnsureRoot, EnsureSigned,
-	limits::{BlockWeights, BlockLength},
-};
-use sp_arithmetic::{
-	traits::{BaseArithmetic, Unsigned},
-};
-use smallvec::smallvec;
-use codec::{Encode, Decode};
-use fp_rpc::TransactionStatus;
-use sp_runtime::{
-	traits::{
-		Applyable, BlockNumberProvider, Dispatchable, PostDispatchInfoOf, DispatchInfoOf,
-		Saturating, CheckedConversion,
-	},
-	generic::Era,
-	transaction_validity::TransactionValidityError,
-	DispatchErrorWithPostInfo, SaturatedConversion,
-};
+use up_common::types::*;
 
-// pub use pallet_timestamp::Call as TimestampCall;
-pub use sp_consensus_aura::sr25519::AuthorityId as AuraId;
+#[path = "../../common/mod.rs"]
+mod runtime_common;
 
-// Polkadot imports
-use pallet_xcm::XcmPassthrough;
-use polkadot_parachain::primitives::Sibling;
-use xcm::v1::{BodyId, Junction::*, MultiLocation, NetworkId, Junctions::*};
-use xcm_builder::{
-	AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter,
-	EnsureXcmOrigin, FixedWeightBounds, LocationInverter, NativeAsset, ParentAsSuperuser,
-	RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia,
-	SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit,
-	ParentIsPreset,
-};
-use xcm_executor::{Config, XcmExecutor, Assets};
-use sp_std::{cmp::Ordering, marker::PhantomData};
-
-use xcm::latest::{
-	//	Xcm,
-	AssetId::{Concrete},
-	Fungibility::Fungible as XcmFungible,
-	MultiAsset,
-	Error as XcmError,
-};
-use xcm_executor::traits::{MatchesFungible, WeightTrader};
-
-use unique_runtime_common::{
-	impl_common_runtime_apis,
-	types::*,
-	constants::*,
-	dispatch::{CollectionDispatchT, CollectionDispatch},
-	sponsoring::UniqueSponsorshipHandler,
-	eth_sponsoring::UniqueEthSponsorshipHandler,
-	weights::CommonWeights,
-};
+pub use runtime_common::*;
 
 pub const RUNTIME_NAME: &str = "unique";
 pub const TOKEN_SYMBOL: &str = "UNQ";
-
-type CrossAccountId = pallet_evm::account::BasicCrossAccountId<Runtime>;
-
-impl RuntimeInstance for Runtime {
-	type CrossAccountId = self::CrossAccountId;
-	type TransactionConverter = self::TransactionConverter;
-
-	fn get_transaction_converter() -> TransactionConverter {
-		TransactionConverter
-	}
-}
-
-/// The type for looking up accounts. We don't expect more than 4 billion of them, but you
-/// never know...
-pub type AccountIndex = u32;
-
-/// Balance of an account.
-pub type Balance = u128;
-
-/// Index of a transaction in the chain.
-pub type Index = u32;
-
-/// A hash of some data used by the chain.
-pub type Hash = sp_core::H256;
-
-/// Digest item type.
-pub type DigestItem = generic::DigestItem;
-
-/// Opaque types. These are used by the CLI to instantiate machinery that don't need to know
-/// the specifics of the runtime. They can then be made to be agnostic over specific formats
-/// of data like extrinsics, allowing for them to continue syncing the network through upgrades
-/// to even the core data structures.
-pub mod opaque {
-	use sp_std::prelude::*;
-	use sp_runtime::impl_opaque_keys;
-	use super::Aura;
-
-	pub use unique_runtime_common::types::*;
 
-	impl_opaque_keys! {
-		pub struct SessionKeys {
-			pub aura: Aura,
-		}
-	}
-}
-
 /// This runtime version.
 pub const VERSION: RuntimeVersion = RuntimeVersion {
 	spec_name: create_runtime_str!(RUNTIME_NAME),
@@ -197,1194 +52,15 @@
 	state_version: 0,
 };
 
-#[derive(codec::Encode, codec::Decode)]
-pub enum XCMPMessage<XAccountId, XBalance> {
-	/// Transfer tokens to the given account from the Parachain account.
-	TransferToken(XAccountId, XBalance),
-}
-
-/// The version information used to identify this runtime when compiled natively.
-#[cfg(feature = "std")]
-pub fn native_version() -> NativeVersion {
-	NativeVersion {
-		runtime_version: VERSION,
-		can_author_with: Default::default(),
-	}
-}
-
-type NegativeImbalance = <Balances as Currency<AccountId>>::NegativeImbalance;
-
-pub struct DealWithFees;
-impl OnUnbalanced<NegativeImbalance> for DealWithFees {
-	fn on_unbalanceds<B>(mut fees_then_tips: impl Iterator<Item = NegativeImbalance>) {
-		if let Some(fees) = fees_then_tips.next() {
-			// for fees, 100% to treasury
-			let mut split = fees.ration(100, 0);
-			if let Some(tips) = fees_then_tips.next() {
-				// for tips, if any, 100% to treasury
-				tips.ration_merge_into(100, 0, &mut split);
-			}
-			Treasury::on_unbalanced(split.0);
-			// Author::on_unbalanced(split.1);
-		}
-	}
-}
-
 parameter_types! {
-	pub const BlockHashCount: BlockNumber = 2400;
-	pub RuntimeBlockLength: BlockLength =
-		BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO);
-	pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75);
-	pub const MaximumBlockLength: u32 = 5 * 1024 * 1024;
-	pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder()
-		.base_block(BlockExecutionWeight::get())
-		.for_class(DispatchClass::all(), |weights| {
-			weights.base_extrinsic = ExtrinsicBaseWeight::get();
-		})
-		.for_class(DispatchClass::Normal, |weights| {
-			weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT);
-		})
-		.for_class(DispatchClass::Operational, |weights| {
-			weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT);
-			// Operational transactions have some extra reserved space, so that they
-			// are included even if block reached `MAXIMUM_BLOCK_WEIGHT`.
-			weights.reserved = Some(
-				MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT
-			);
-		})
-		.avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO)
-		.build_or_panic();
 	pub const Version: RuntimeVersion = VERSION;
 	pub const SS58Prefix: u16 = 7391;
-}
-
-parameter_types! {
 	pub const ChainId: u64 = 8880;
-}
-
-pub struct FixedFee;
-impl FeeCalculator for FixedFee {
-	fn min_gas_price() -> (U256, u64) {
-		(MIN_GAS_PRICE.into(), 0)
-	}
-}
-
-// Assuming slowest ethereum opcode is SSTORE, with gas price of 20000 as our worst case
-// (contract, which only writes a lot of data),
-// approximating on top of our real store write weight
-parameter_types! {
-	pub const WritesPerSecond: u64 = WEIGHT_PER_SECOND / <Runtime as frame_system::Config>::DbWeight::get().write;
-	pub const GasPerSecond: u64 = WritesPerSecond::get() * 20000;
-	pub const WeightPerGas: u64 = WEIGHT_PER_SECOND / GasPerSecond::get();
 }
 
-/// Limiting EVM execution to 50% of block for substrate users and management tasks
-/// EVM transaction consumes more weight than substrate's, so we can't rely on them being
-/// scheduled fairly
-const EVM_DISPATCH_RATIO: Perbill = Perbill::from_percent(50);
-parameter_types! {
-	pub BlockGasLimit: U256 = U256::from(NORMAL_DISPATCH_RATIO * EVM_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT / WeightPerGas::get());
-}
-
-pub enum FixedGasWeightMapping {}
-impl GasWeightMapping for FixedGasWeightMapping {
-	fn gas_to_weight(gas: u64) -> Weight {
-		gas.saturating_mul(WeightPerGas::get())
-	}
-	fn weight_to_gas(weight: Weight) -> u64 {
-		weight / WeightPerGas::get()
-	}
-}
-
-impl pallet_evm::account::Config for Runtime {
-	type CrossAccountId = pallet_evm::account::BasicCrossAccountId<Self>;
-	type EvmAddressMapping = pallet_evm::HashedAddressMapping<Self::Hashing>;
-	type EvmBackwardsAddressMapping = fp_evm_mapping::MapBackwardsAddressTruncated;
-}
-
-impl pallet_evm::Config for Runtime {
-	type BlockGasLimit = BlockGasLimit;
-	type FeeCalculator = FixedFee;
-	type GasWeightMapping = FixedGasWeightMapping;
-	type BlockHashMapping = pallet_ethereum::EthereumBlockHashMapping<Self>;
-	type CallOrigin = EnsureAddressTruncated<Self>;
-	type WithdrawOrigin = EnsureAddressTruncated<Self>;
-	type AddressMapping = HashedAddressMapping<Self::Hashing>;
-	type PrecompilesType = ();
-	type PrecompilesValue = ();
-	type Currency = Balances;
-	type Event = Event;
-	type OnMethodCall = (
-		pallet_evm_migration::OnMethodCall<Self>,
-		pallet_evm_contract_helpers::HelpersOnMethodCall<Self>,
-		CollectionDispatchT<Self>,
-		pallet_unique::eth::CollectionHelpersOnMethodCall<Self>,
-	);
-	type OnCreate = pallet_evm_contract_helpers::HelpersOnCreate<Self>;
-	type ChainId = ChainId;
-	type Runner = pallet_evm::runner::stack::Runner<Self>;
-	type OnChargeTransaction = pallet_evm::EVMCurrencyAdapter<Balances, DealWithFees>;
-	type TransactionValidityHack = pallet_evm_transaction_payment::TransactionValidityHack<Self>;
-	type FindAuthor = EthereumFindAuthor<Aura>;
-}
-
-impl pallet_evm_migration::Config for Runtime {
-	type WeightInfo = pallet_evm_migration::weights::SubstrateWeight<Self>;
-}
-
-pub struct EthereumFindAuthor<F>(core::marker::PhantomData<F>);
-impl<F: FindAuthor<u32>> FindAuthor<H160> for EthereumFindAuthor<F> {
-	fn find_author<'a, I>(digests: I) -> Option<H160>
-	where
-		I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
-	{
-		if let Some(author_index) = F::find_author(digests) {
-			let authority_id = Aura::authorities()[author_index as usize].clone();
-			return Some(H160::from_slice(&authority_id.to_raw_vec()[4..24]));
-		}
-		None
-	}
-}
-
-impl pallet_ethereum::Config for Runtime {
-	type Event = Event;
-	type StateRoot = pallet_ethereum::IntermediateStateRoot<Self>;
-}
-
-impl pallet_randomness_collective_flip::Config for Runtime {}
+construct_runtime!(unique);
 
-impl frame_system::Config for Runtime {
-	/// The data to be stored in an account.
-	type AccountData = pallet_balances::AccountData<Balance>;
-	/// The identifier used to distinguish between accounts.
-	type AccountId = AccountId;
-	/// The basic call filter to use in dispatchable.
-	type BaseCallFilter = Everything;
-	/// Maximum number of block number to block hash mappings to keep (oldest pruned first).
-	type BlockHashCount = BlockHashCount;
-	/// The maximum length of a block (in bytes).
-	type BlockLength = RuntimeBlockLength;
-	/// The index type for blocks.
-	type BlockNumber = BlockNumber;
-	/// The weight of the overhead invoked on the block import process, independent of the extrinsics included in that block.
-	type BlockWeights = RuntimeBlockWeights;
-	/// The aggregated dispatch type that is available for extrinsics.
-	type Call = Call;
-	/// The weight of database operations that the runtime can invoke.
-	type DbWeight = RocksDbWeight;
-	/// The ubiquitous event type.
-	type Event = Event;
-	/// The type for hashing blocks and tries.
-	type Hash = Hash;
-	/// The hashing algorithm used.
-	type Hashing = BlakeTwo256;
-	/// The header type.
-	type Header = generic::Header<BlockNumber, BlakeTwo256>;
-	/// The index type for storing how many extrinsics an account has signed.
-	type Index = Index;
-	/// The lookup mechanism to get account ID from whatever is passed in dispatchers.
-	type Lookup = AccountIdLookup<AccountId, ()>;
-	/// What to do if an account is fully reaped from the system.
-	type OnKilledAccount = ();
-	/// What to do if a new account is created.
-	type OnNewAccount = ();
-	type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode<Self>;
-	/// The ubiquitous origin type.
-	type Origin = Origin;
-	/// This type is being generated by `construct_runtime!`.
-	type PalletInfo = PalletInfo;
-	/// This is used as an identifier of the chain. 42 is the generic substrate prefix.
-	type SS58Prefix = SS58Prefix;
-	/// Weight information for the extrinsics of this pallet.
-	type SystemWeightInfo = frame_system::weights::SubstrateWeight<Self>;
-	/// Version of the runtime.
-	type Version = Version;
-	type MaxConsumers = ConstU32<16>;
-}
-
-parameter_types! {
-	pub const MinimumPeriod: u64 = SLOT_DURATION / 2;
-}
-
-impl pallet_timestamp::Config for Runtime {
-	/// A timestamp: milliseconds since the unix epoch.
-	type Moment = u64;
-	type OnTimestampSet = ();
-	type MinimumPeriod = MinimumPeriod;
-	type WeightInfo = ();
-}
-
-parameter_types! {
-	// pub const ExistentialDeposit: u128 = 500;
-	pub const ExistentialDeposit: u128 = 0;
-	pub const MaxLocks: u32 = 50;
-	pub const MaxReserves: u32 = 50;
-}
-
-impl pallet_balances::Config for Runtime {
-	type MaxLocks = MaxLocks;
-	type MaxReserves = MaxReserves;
-	type ReserveIdentifier = [u8; 16];
-	/// The type for recording an account's balance.
-	type Balance = Balance;
-	/// The ubiquitous event type.
-	type Event = Event;
-	type DustRemoval = Treasury;
-	type ExistentialDeposit = ExistentialDeposit;
-	type AccountStore = System;
-	type WeightInfo = pallet_balances::weights::SubstrateWeight<Self>;
-}
-
-pub const fn deposit(items: u32, bytes: u32) -> Balance {
-	items as Balance * 15 * CENTIUNIQUE + (bytes as Balance) * 6 * CENTIUNIQUE
-}
-
-/*
-parameter_types! {
-	pub TombstoneDeposit: Balance = deposit(
-		1,
-		sp_std::mem::size_of::<pallet_contracts::Pallet<Runtime>> as u32,
-	);
-	pub DepositPerContract: Balance = TombstoneDeposit::get();
-	pub const DepositPerStorageByte: Balance = deposit(0, 1);
-	pub const DepositPerStorageItem: Balance = deposit(1, 0);
-	pub RentFraction: Perbill = Perbill::from_rational(1u32, 30 * DAYS);
-	pub const SurchargeReward: Balance = 150 * MILLIUNIQUE;
-	pub const SignedClaimHandicap: u32 = 2;
-	pub const MaxDepth: u32 = 32;
-	pub const MaxValueSize: u32 = 16 * 1024;
-	pub const MaxCodeSize: u32 = 1024 * 1024 * 25; // 25 Mb
-	// The lazy deletion runs inside on_initialize.
-	pub DeletionWeightLimit: Weight = AVERAGE_ON_INITIALIZE_RATIO *
-		RuntimeBlockWeights::get().max_block;
-	// The weight needed for decoding the queue should be less or equal than a fifth
-	// of the overall weight dedicated to the lazy deletion.
-	pub DeletionQueueDepth: u32 = ((DeletionWeightLimit::get() / (
-			<Runtime as pallet_contracts::Config>::WeightInfo::on_initialize_per_queue_item(1) -
-			<Runtime as pallet_contracts::Config>::WeightInfo::on_initialize_per_queue_item(0)
-		)) / 5) as u32;
-	pub Schedule: pallet_contracts::Schedule<Runtime> = Default::default();
-}
-
-impl pallet_contracts::Config for Runtime {
-	type Time = Timestamp;
-	type Randomness = RandomnessCollectiveFlip;
-	type Currency = Balances;
-	type Event = Event;
-	type RentPayment = ();
-	type SignedClaimHandicap = SignedClaimHandicap;
-	type TombstoneDeposit = TombstoneDeposit;
-	type DepositPerContract = DepositPerContract;
-	type DepositPerStorageByte = DepositPerStorageByte;
-	type DepositPerStorageItem = DepositPerStorageItem;
-	type RentFraction = RentFraction;
-	type SurchargeReward = SurchargeReward;
-	type WeightPrice = pallet_transaction_payment::Pallet<Self>;
-	type WeightInfo = pallet_contracts::weights::SubstrateWeight<Self>;
-	type ChainExtension = NFTExtension;
-	type DeletionQueueDepth = DeletionQueueDepth;
-	type DeletionWeightLimit = DeletionWeightLimit;
-	type Schedule = Schedule;
-	type CallStack = [pallet_contracts::Frame<Self>; 31];
-}
-*/
-
-parameter_types! {
-	/// This value increases the priority of `Operational` transactions by adding
-	/// a "virtual tip" that's equal to the `OperationalFeeMultiplier * final_fee`.
-	pub const OperationalFeeMultiplier: u8 = 5;
-}
-
-/// Linear implementor of `WeightToFeePolynomial`
-pub struct LinearFee<T>(sp_std::marker::PhantomData<T>);
-
-impl<T> WeightToFeePolynomial for LinearFee<T>
-where
-	T: BaseArithmetic + From<u32> + Copy + Unsigned,
-{
-	type Balance = T;
-
-	fn polynomial() -> WeightToFeeCoefficients<Self::Balance> {
-		smallvec!(WeightToFeeCoefficient {
-			coeff_integer: WEIGHT_TO_FEE_COEFF.into(),
-			coeff_frac: Perbill::zero(),
-			negative: false,
-			degree: 1,
-		})
-	}
-}
-
-impl pallet_transaction_payment::Config for Runtime {
-	type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter<Balances, DealWithFees>;
-	type LengthToFee = ConstantMultiplier<Balance, TransactionByteFee>;
-	type OperationalFeeMultiplier = OperationalFeeMultiplier;
-	type WeightToFee = LinearFee<Balance>;
-	type FeeMultiplierUpdate = ();
-}
-
-parameter_types! {
-	pub const ProposalBond: Permill = Permill::from_percent(5);
-	pub const ProposalBondMinimum: Balance = 1 * UNIQUE;
-	pub const ProposalBondMaximum: Balance = 1000 * UNIQUE;
-	pub const SpendPeriod: BlockNumber = 5 * MINUTES;
-	pub const Burn: Permill = Permill::from_percent(0);
-	pub const TipCountdown: BlockNumber = 1 * DAYS;
-	pub const TipFindersFee: Percent = Percent::from_percent(20);
-	pub const TipReportDepositBase: Balance = 1 * UNIQUE;
-	pub const DataDepositPerByte: Balance = 1 * CENTIUNIQUE;
-	pub const BountyDepositBase: Balance = 1 * UNIQUE;
-	pub const BountyDepositPayoutDelay: BlockNumber = 1 * DAYS;
-	pub const TreasuryModuleId: PalletId = PalletId(*b"py/trsry");
-	pub const BountyUpdatePeriod: BlockNumber = 14 * DAYS;
-	pub const MaximumReasonLength: u32 = 16384;
-	pub const BountyCuratorDeposit: Permill = Permill::from_percent(50);
-	pub const BountyValueMinimum: Balance = 5 * UNIQUE;
-	pub const MaxApprovals: u32 = 100;
-}
-
-impl pallet_treasury::Config for Runtime {
-	type PalletId = TreasuryModuleId;
-	type Currency = Balances;
-	type ApproveOrigin = EnsureRoot<AccountId>;
-	type RejectOrigin = EnsureRoot<AccountId>;
-	type Event = Event;
-	type OnSlash = ();
-	type ProposalBond = ProposalBond;
-	type ProposalBondMinimum = ProposalBondMinimum;
-	type ProposalBondMaximum = ProposalBondMaximum;
-	type SpendPeriod = SpendPeriod;
-	type Burn = Burn;
-	type BurnDestination = ();
-	type SpendFunds = ();
-	type WeightInfo = pallet_treasury::weights::SubstrateWeight<Self>;
-	type MaxApprovals = MaxApprovals;
-}
-
-impl pallet_sudo::Config for Runtime {
-	type Event = Event;
-	type Call = Call;
-}
-
-pub struct RelayChainBlockNumberProvider<T>(sp_std::marker::PhantomData<T>);
-
-impl<T: cumulus_pallet_parachain_system::Config> BlockNumberProvider
-	for RelayChainBlockNumberProvider<T>
-{
-	type BlockNumber = BlockNumber;
-
-	fn current_block_number() -> Self::BlockNumber {
-		cumulus_pallet_parachain_system::Pallet::<T>::validation_data()
-			.map(|d| d.relay_parent_number)
-			.unwrap_or_default()
-	}
-}
-
-parameter_types! {
-	pub const MinVestedTransfer: Balance = 10 * UNIQUE;
-	pub const MaxVestingSchedules: u32 = 28;
-}
-
-impl orml_vesting::Config for Runtime {
-	type Event = Event;
-	type Currency = pallet_balances::Pallet<Runtime>;
-	type MinVestedTransfer = MinVestedTransfer;
-	type VestedTransferOrigin = EnsureSigned<AccountId>;
-	type WeightInfo = ();
-	type MaxVestingSchedules = MaxVestingSchedules;
-	type BlockNumberProvider = RelayChainBlockNumberProvider<Runtime>;
-}
-
-parameter_types! {
-	pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 4;
-	pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 4;
-}
-
-impl cumulus_pallet_parachain_system::Config for Runtime {
-	type Event = Event;
-	type SelfParaId = parachain_info::Pallet<Self>;
-	type OnSystemEvent = ();
-	// type DownwardMessageHandlers = cumulus_primitives_utility::UnqueuedDmpAsParent<
-	// 	MaxDownwardMessageWeight,
-	// 	XcmExecutor<XcmConfig>,
-	// 	Call,
-	// >;
-	type OutboundXcmpMessageSource = XcmpQueue;
-	type DmpMessageHandler = DmpQueue;
-	type ReservedDmpWeight = ReservedDmpWeight;
-	type ReservedXcmpWeight = ReservedXcmpWeight;
-	type XcmpMessageHandler = XcmpQueue;
-}
-
-impl parachain_info::Config for Runtime {}
-
-impl cumulus_pallet_aura_ext::Config for Runtime {}
-
-parameter_types! {
-	pub const RelayLocation: MultiLocation = MultiLocation::parent();
-	pub const RelayNetwork: NetworkId = NetworkId::Polkadot;
-	pub RelayOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into();
-	pub Ancestry: MultiLocation = Parachain(ParachainInfo::parachain_id().into()).into();
-}
-
-/// 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>,
-);
-
-pub struct OnlySelfCurrency;
-impl<B: TryFrom<u128>> MatchesFungible<B> for OnlySelfCurrency {
-	fn matches_fungible(a: &MultiAsset) -> Option<B> {
-		match (&a.id, &a.fun) {
-			(Concrete(_), 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.
-	(),
->;
-
-/// 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, Origin>,
-	// Native converter for Relay-chain (Parent) location; will converts to a `Relay` origin when
-	// recognised.
-	RelayChainAsNative<RelayOrigin, Origin>,
-	// Native converter for sibling Parachains; will convert to a `SiblingPara` origin when
-	// recognised.
-	SiblingParachainAsNative<cumulus_pallet_xcm::Origin, Origin>,
-	// Superuser converter for the Relay-chain (Parent) location. This will allow it to issue a
-	// transaction from the Root origin.
-	ParentAsSuperuser<Origin>,
-	// Native signed account converter; this just converts an `AccountId32` origin into a normal
-	// `Origin::Signed` origin of the same 32-byte value.
-	SignedAccountId32AsNative<RelayNetwork, Origin>,
-	// Xcm origins can be represented natively under the Xcm pallet's Xcm origin.
-	XcmPassthrough<Origin>,
-);
-
-parameter_types! {
-	// One XCM operation is 1_000_000 weight - almost certainly a conservative estimate.
-	pub UnitWeightCost: Weight = 1_000_000;
-	// 1200 UNIQUEs buy 1 second of weight.
-	pub const WeightPrice: (MultiLocation, u128) = (MultiLocation::parent(), 1_200 * UNIQUE);
-	pub const MaxInstructions: u32 = 100;
-	pub const MaxAuthorities: u32 = 100_000;
-}
-
-match_types! {
-	pub type ParentOrParentsUnitPlurality: impl Contains<MultiLocation> = {
-		MultiLocation { parents: 1, interior: Here } |
-		MultiLocation { parents: 1, interior: X1(Plurality { id: BodyId::Unit, .. }) }
-	};
-}
-
-pub type Barrier = (
-	TakeWeightCredit,
-	AllowTopLevelPaidExecutionFrom<Everything>,
-	// ^^^ Parent & its unit plurality gets free execution
-);
-
-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 {
-		Self(0, Zero::zero(), PhantomData)
-	}
-
-	fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, XcmError> {
-		let amount = WeightToFee::weight_to_fee(&weight);
-		let u128_amount: u128 = amount.try_into().map_err(|_| XcmError::Overflow)?;
-
-		// location to this parachain through relay chain
-		let option1: xcm::v1::AssetId = Concrete(MultiLocation {
-			parents: 1,
-			interior: X1(Parachain(ParachainInfo::parachain_id().into())),
-		});
-		// direct location
-		let option2: xcm::v1::AssetId = Concrete(MultiLocation {
-			parents: 0,
-			interior: Here,
-		});
-
-		let required = if payment.fungible.contains_key(&option1) {
-			(option1, u128_amount).into()
-		} else if payment.fungible.contains_key(&option2) {
-			(option2, u128_amount).into()
-		} else {
-			(Concrete(MultiLocation::default()), u128_amount).into()
-		};
-
-		let unused = payment
-			.checked_sub(required)
-			.map_err(|_| XcmError::TooExpensive)?;
-		self.0 = self.0.saturating_add(weight);
-		self.1 = self.1.saturating_add(amount);
-		Ok(unused)
-	}
-
-	fn refund_weight(&mut self, weight: Weight) -> Option<MultiAsset> {
-		let weight = weight.min(self.0);
-		let amount = WeightToFee::weight_to_fee(&weight);
-		self.0 -= weight;
-		self.1 = self.1.saturating_sub(amount);
-		let amount: u128 = amount.saturated_into();
-		if amount > 0 {
-			Some((AssetId::get(), amount).into())
-		} else {
-			None
-		}
-	}
-}
-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 struct XcmConfig;
-impl Config for XcmConfig {
-	type Call = Call;
-	type XcmSender = XcmRouter;
-	// How to withdraw and deposit an asset.
-	type AssetTransactor = LocalAssetTransactor;
-	type OriginConverter = XcmOriginToTransactDispatchOrigin;
-	type IsReserve = NativeAsset;
-	type IsTeleporter = (); // Teleportation is disabled
-	type LocationInverter = LocationInverter<Ancestry>;
-	type Barrier = Barrier;
-	type Weigher = FixedWeightBounds<UnitWeightCost, Call, MaxInstructions>;
-	type Trader =
-		UsingOnlySelfCurrencyComponents<LinearFee<Balance>, RelayLocation, AccountId, Balances, ()>;
-	type ResponseHandler = (); // Don't handle responses for now.
-	type SubscriptionService = PolkadotXcm;
-
-	type AssetTrap = PolkadotXcm;
-	type AssetClaims = PolkadotXcm;
-}
-
-// parameter_types! {
-// 	pub const MaxDownwardMessageWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 10;
-// }
-
-/// No local origins on this chain are allowed to dispatch XCM sends/executions.
-pub type LocalOriginToLocation = (SignedToAccountId32<Origin, 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, ()>,
-	// ..and XCMP to communicate with the sibling chains.
-	XcmpQueue,
-);
-
-impl pallet_evm_coder_substrate::Config for Runtime {}
-
-impl pallet_xcm::Config for Runtime {
-	type Event = Event;
-	type SendXcmOrigin = EnsureXcmOrigin<Origin, LocalOriginToLocation>;
-	type XcmRouter = XcmRouter;
-	type ExecuteXcmOrigin = EnsureXcmOrigin<Origin, LocalOriginToLocation>;
-	type XcmExecuteFilter = Everything;
-	type XcmExecutor = XcmExecutor<XcmConfig>;
-	type XcmTeleportFilter = Everything;
-	type XcmReserveTransferFilter = Everything;
-	type Weigher = FixedWeightBounds<UnitWeightCost, Call, MaxInstructions>;
-	type LocationInverter = LocationInverter<Ancestry>;
-	type Origin = Origin;
-	type Call = Call;
-	const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
-	type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion;
-}
-
-impl cumulus_pallet_xcm::Config for Runtime {
-	type Event = Event;
-	type XcmExecutor = XcmExecutor<XcmConfig>;
-}
-
-impl cumulus_pallet_xcmp_queue::Config for Runtime {
-	type WeightInfo = ();
-	type Event = Event;
-	type XcmExecutor = XcmExecutor<XcmConfig>;
-	type ChannelInfo = ParachainSystem;
-	type VersionWrapper = ();
-	type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
-	type ControllerOrigin = EnsureRoot<AccountId>;
-	type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin;
-}
-
-impl cumulus_pallet_dmp_queue::Config for Runtime {
-	type Event = Event;
-	type XcmExecutor = XcmExecutor<XcmConfig>;
-	type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
-}
-
-impl pallet_aura::Config for Runtime {
-	type AuthorityId = AuraId;
-	type DisabledValidators = ();
-	type MaxAuthorities = MaxAuthorities;
-}
-
-parameter_types! {
-	pub TreasuryAccountId: AccountId = TreasuryModuleId::get().into_account_truncating();
-	pub const CollectionCreationPrice: Balance = 2 * UNIQUE;
-}
-
-impl pallet_common::Config for Runtime {
-	type WeightInfo = pallet_common::weights::SubstrateWeight<Self>;
-	type Event = Event;
-	type Currency = Balances;
-	type CollectionCreationPrice = CollectionCreationPrice;
-	type TreasuryAccountId = TreasuryAccountId;
-	type CollectionDispatch = CollectionDispatchT<Self>;
-
-	type EvmTokenAddressMapping = EvmTokenAddressMapping;
-	type CrossTokenAddressMapping = CrossTokenAddressMapping<Self::AccountId>;
-	type ContractAddress = EvmCollectionHelpersAddress;
-}
-
-impl pallet_structure::Config for Runtime {
-	type Event = Event;
-	type Call = Call;
-	type WeightInfo = pallet_structure::weights::SubstrateWeight<Self>;
-}
-
-impl pallet_fungible::Config for Runtime {
-	type WeightInfo = pallet_fungible::weights::SubstrateWeight<Self>;
-}
-impl pallet_refungible::Config for Runtime {
-	type WeightInfo = pallet_refungible::weights::SubstrateWeight<Self>;
-}
-impl pallet_nonfungible::Config for Runtime {
-	type WeightInfo = pallet_nonfungible::weights::SubstrateWeight<Self>;
-}
-
-impl pallet_unique::Config for Runtime {
-	type Event = Event;
-	type WeightInfo = pallet_unique::weights::SubstrateWeight<Self>;
-	type CommonWeightInfo = CommonWeights<Self>;
-	type RefungibleExtensionsWeightInfo = CommonWeights<Self>;
-}
-
-parameter_types! {
-	pub const InflationBlockInterval: BlockNumber = 100; // every time per how many blocks inflation is applied
-}
-
-/// Used for the pallet inflation
-impl pallet_inflation::Config for Runtime {
-	type Currency = Balances;
-	type TreasuryAccountId = TreasuryAccountId;
-	type InflationBlockInterval = InflationBlockInterval;
-	type BlockNumberProvider = RelayChainBlockNumberProvider<Runtime>;
-}
-
-parameter_types! {
-	pub MaximumSchedulerWeight: Weight = Perbill::from_percent(50) *
-		RuntimeBlockWeights::get().max_block;
-	pub const MaxScheduledPerBlock: u32 = 50;
-}
-
-type ChargeTransactionPayment = pallet_charge_transaction::ChargeTransactionPayment<Runtime>;
-use frame_support::traits::NamedReservableCurrency;
-
-fn get_signed_extras(from: <Runtime as frame_system::Config>::AccountId) -> SignedExtraScheduler {
-	(
-		frame_system::CheckSpecVersion::<Runtime>::new(),
-		frame_system::CheckGenesis::<Runtime>::new(),
-		frame_system::CheckEra::<Runtime>::from(Era::Immortal),
-		frame_system::CheckNonce::<Runtime>::from(frame_system::Pallet::<Runtime>::account_nonce(
-			from,
-		)),
-		frame_system::CheckWeight::<Runtime>::new(),
-		// sponsoring transaction logic
-		// pallet_charge_transaction::ChargeTransactionPayment::<Runtime>::new(0),
-	)
-}
-
-pub struct SchedulerPaymentExecutor;
-impl<T: frame_system::Config + pallet_unique_scheduler::Config, SelfContainedSignedInfo>
-	DispatchCall<T, SelfContainedSignedInfo> for SchedulerPaymentExecutor
-where
-	<T as frame_system::Config>::Call: Member
-		+ Dispatchable<Origin = Origin, Info = DispatchInfo>
-		+ SelfContainedCall<SignedInfo = SelfContainedSignedInfo>
-		+ GetDispatchInfo
-		+ From<frame_system::Call<Runtime>>,
-	SelfContainedSignedInfo: Send + Sync + 'static,
-	Call: From<<T as frame_system::Config>::Call>
-		+ From<<T as pallet_unique_scheduler::Config>::Call>
-		+ SelfContainedCall<SignedInfo = SelfContainedSignedInfo>,
-	sp_runtime::AccountId32: From<<T as frame_system::Config>::AccountId>,
-{
-	fn dispatch_call(
-		signer: <T as frame_system::Config>::AccountId,
-		call: <T as pallet_unique_scheduler::Config>::Call,
-	) -> Result<
-		Result<PostDispatchInfo, DispatchErrorWithPostInfo<PostDispatchInfo>>,
-		TransactionValidityError,
-	> {
-		let dispatch_info = call.get_dispatch_info();
-		let extrinsic = fp_self_contained::CheckedExtrinsic::<
-			AccountId,
-			Call,
-			SignedExtraScheduler,
-			SelfContainedSignedInfo,
-		> {
-			signed:
-				CheckedSignature::<AccountId, SignedExtraScheduler, SelfContainedSignedInfo>::Signed(
-					signer.clone().into(),
-					get_signed_extras(signer.into()),
-				),
-			function: call.into(),
-		};
-
-		extrinsic.apply::<Runtime>(&dispatch_info, 0)
-	}
-
-	fn reserve_balance(
-		id: [u8; 16],
-		sponsor: <T as frame_system::Config>::AccountId,
-		call: <T as pallet_unique_scheduler::Config>::Call,
-		count: u32,
-	) -> Result<(), DispatchError> {
-		let dispatch_info = call.get_dispatch_info();
-		let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0)
-			.saturating_mul(count.into());
-
-		<Balances as NamedReservableCurrency<AccountId>>::reserve_named(
-			&id,
-			&(sponsor.into()),
-			weight,
-		)
-	}
-
-	fn pay_for_call(
-		id: [u8; 16],
-		sponsor: <T as frame_system::Config>::AccountId,
-		call: <T as pallet_unique_scheduler::Config>::Call,
-	) -> Result<u128, DispatchError> {
-		let dispatch_info = call.get_dispatch_info();
-		let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0);
-		Ok(
-			<Balances as NamedReservableCurrency<AccountId>>::unreserve_named(
-				&id,
-				&(sponsor.into()),
-				weight,
-			),
-		)
-	}
-
-	fn cancel_reserve(
-		id: [u8; 16],
-		sponsor: <T as frame_system::Config>::AccountId,
-	) -> Result<u128, DispatchError> {
-		Ok(
-			<Balances as NamedReservableCurrency<AccountId>>::unreserve_named(
-				&id,
-				&(sponsor.into()),
-				u128::MAX,
-			),
-		)
-	}
-}
-
-parameter_types! {
-	pub const NoPreimagePostponement: Option<u32> = Some(10);
-	pub const Preimage: Option<u32> = Some(10);
-}
-
-/// Used the compare the privilege of an origin inside the scheduler.
-pub struct OriginPrivilegeCmp;
-
-impl PrivilegeCmp<OriginCaller> for OriginPrivilegeCmp {
-	fn cmp_privilege(_left: &OriginCaller, _right: &OriginCaller) -> Option<Ordering> {
-		Some(Ordering::Equal)
-	}
-}
-
-impl pallet_unique_scheduler::Config for Runtime {
-	type Event = Event;
-	type Origin = Origin;
-	type Currency = Balances;
-	type PalletsOrigin = OriginCaller;
-	type Call = Call;
-	type MaximumWeight = MaximumSchedulerWeight;
-	type ScheduleOrigin = EnsureSigned<AccountId>;
-	type MaxScheduledPerBlock = MaxScheduledPerBlock;
-	type WeightInfo = ();
-	type CallExecutor = SchedulerPaymentExecutor;
-	type OriginPrivilegeCmp = OriginPrivilegeCmp;
-	type PreimageProvider = ();
-	type NoPreimagePostponement = NoPreimagePostponement;
-}
-
-type EvmSponsorshipHandler = (
-	UniqueEthSponsorshipHandler<Runtime>,
-	pallet_evm_contract_helpers::HelpersContractSponsoring<Runtime>,
-);
-type SponsorshipHandler = (
-	UniqueSponsorshipHandler<Runtime>,
-	//pallet_contract_helpers::ContractSponsorshipHandler<Runtime>,
-	pallet_evm_transaction_payment::BridgeSponsorshipHandler<Runtime>,
-);
-
-impl pallet_evm_transaction_payment::Config for Runtime {
-	type EvmSponsorshipHandler = EvmSponsorshipHandler;
-	type Currency = Balances;
-}
-
-impl pallet_charge_transaction::Config for Runtime {
-	type SponsorshipHandler = SponsorshipHandler;
-}
-
-// impl pallet_contract_helpers::Config for Runtime {
-//	 type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit;
-// }
-
-parameter_types! {
-	// 0x842899ECF380553E8a4de75bF534cdf6fBF64049
-	pub const HelpersContractAddress: H160 = H160([
-		0x84, 0x28, 0x99, 0xec, 0xf3, 0x80, 0x55, 0x3e, 0x8a, 0x4d, 0xe7, 0x5b, 0xf5, 0x34, 0xcd, 0xf6, 0xfb, 0xf6, 0x40, 0x49,
-	]);
-
-	// 0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f
-	pub const EvmCollectionHelpersAddress: H160 = H160([
-		0x6c, 0x4e, 0x9f, 0xe1, 0xae, 0x37, 0xa4, 0x1e, 0x93, 0xce, 0xe4, 0x29, 0xe8, 0xe1, 0x88, 0x1a, 0xbd, 0xcb, 0xb5, 0x4f,
-	]);
-}
-
-impl pallet_evm_contract_helpers::Config for Runtime {
-	type ContractAddress = HelpersContractAddress;
-	type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit;
-}
-
-construct_runtime!(
-	pub enum Runtime where
-		Block = Block,
-		NodeBlock = opaque::Block,
-		UncheckedExtrinsic = UncheckedExtrinsic
-	{
-		ParachainSystem: cumulus_pallet_parachain_system::{Pallet, Call, Config, Storage, Inherent, Event<T>, ValidateUnsigned} = 20,
-		ParachainInfo: parachain_info::{Pallet, Storage, Config} = 21,
-
-		Aura: pallet_aura::{Pallet, Config<T>} = 22,
-		AuraExt: cumulus_pallet_aura_ext::{Pallet, Config} = 23,
-
-		Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>} = 30,
-		RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Pallet, Storage} = 31,
-		Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent} = 32,
-		TransactionPayment: pallet_transaction_payment::{Pallet, Storage} = 33,
-		Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event<T>} = 34,
-		Sudo: pallet_sudo::{Pallet, Call, Storage, Config<T>, Event<T>} = 35,
-		System: frame_system::{Pallet, Call, Storage, Config, Event<T>} = 36,
-		Vesting: orml_vesting::{Pallet, Storage, Call, Event<T>, Config<T>} = 37,
-		// Vesting: pallet_vesting::{Pallet, Call, Config<T>, Storage, Event<T>} = 37,
-		// Contracts: pallet_contracts::{Pallet, Call, Storage, Event<T>} = 38,
-
-		// XCM helpers.
-		XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event<T>} = 50,
-		PolkadotXcm: pallet_xcm::{Pallet, Call, Event<T>, Origin} = 51,
-		CumulusXcm: cumulus_pallet_xcm::{Pallet, Call, Event<T>, Origin} = 52,
-		DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event<T>} = 53,
-
-		// Unique Pallets
-		Inflation: pallet_inflation::{Pallet, Call, Storage} = 60,
-		Unique: pallet_unique::{Pallet, Call, Storage, Event<T>} = 61,
-		Scheduler: pallet_unique_scheduler::{Pallet, Call, Storage, Event<T>} = 62,
-		// free = 63
-		Charging: pallet_charge_transaction::{Pallet, Call, Storage } = 64,
-		// ContractHelpers: pallet_contract_helpers::{Pallet, Call, Storage} = 65,
-		Common: pallet_common::{Pallet, Storage, Event<T>} = 66,
-		Fungible: pallet_fungible::{Pallet, Storage} = 67,
-		Refungible: pallet_refungible::{Pallet, Storage} = 68,
-		Nonfungible: pallet_nonfungible::{Pallet, Storage} = 69,
-		Structure: pallet_structure::{Pallet, Call, Storage, Event<T>} = 70,
-
-		// Frontier
-		EVM: pallet_evm::{Pallet, Config, Call, Storage, Event<T>} = 100,
-		Ethereum: pallet_ethereum::{Pallet, Config, Call, Storage, Event, Origin} = 101,
-
-		EvmCoderSubstrate: pallet_evm_coder_substrate::{Pallet, Storage} = 150,
-		EvmContractHelpers: pallet_evm_contract_helpers::{Pallet, Storage} = 151,
-		EvmTransactionPayment: pallet_evm_transaction_payment::{Pallet} = 152,
-		EvmMigration: pallet_evm_migration::{Pallet, Call, Storage} = 153,
-	}
-);
-
-pub struct TransactionConverter;
-
-impl fp_rpc::ConvertTransaction<UncheckedExtrinsic> for TransactionConverter {
-	fn convert_transaction(&self, transaction: pallet_ethereum::Transaction) -> UncheckedExtrinsic {
-		UncheckedExtrinsic::new_unsigned(
-			pallet_ethereum::Call::<Runtime>::transact { transaction }.into(),
-		)
-	}
-}
-
-impl fp_rpc::ConvertTransaction<opaque::UncheckedExtrinsic> for TransactionConverter {
-	fn convert_transaction(
-		&self,
-		transaction: pallet_ethereum::Transaction,
-	) -> opaque::UncheckedExtrinsic {
-		let extrinsic = UncheckedExtrinsic::new_unsigned(
-			pallet_ethereum::Call::<Runtime>::transact { transaction }.into(),
-		);
-		let encoded = extrinsic.encode();
-		opaque::UncheckedExtrinsic::decode(&mut &encoded[..])
-			.expect("Encoded extrinsic is always valid")
-	}
-}
-
-/// The address format for describing accounts.
-pub type Address = sp_runtime::MultiAddress<AccountId, ()>;
-/// Block header type as expected by this runtime.
-pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
-/// Block type as expected by this runtime.
-pub type Block = generic::Block<Header, UncheckedExtrinsic>;
-/// A Block signed with a Justification
-pub type SignedBlock = generic::SignedBlock<Block>;
-/// BlockId type as expected by this runtime.
-pub type BlockId = generic::BlockId<Block>;
-/// The SignedExtension to the basic transaction logic.
-pub type SignedExtra = (
-	frame_system::CheckSpecVersion<Runtime>,
-	// system::CheckTxVersion<Runtime>,
-	frame_system::CheckGenesis<Runtime>,
-	frame_system::CheckEra<Runtime>,
-	frame_system::CheckNonce<Runtime>,
-	frame_system::CheckWeight<Runtime>,
-	pallet_charge_transaction::ChargeTransactionPayment<Runtime>,
-	//pallet_contract_helpers::ContractHelpersExtension<Runtime>,
-	pallet_ethereum::FakeTransactionFinalizer<Runtime>,
-);
-pub type SignedExtraScheduler = (
-	frame_system::CheckSpecVersion<Runtime>,
-	frame_system::CheckGenesis<Runtime>,
-	frame_system::CheckEra<Runtime>,
-	frame_system::CheckNonce<Runtime>,
-	frame_system::CheckWeight<Runtime>,
-	// pallet_charge_transaction::ChargeTransactionPayment<Runtime>,
-);
-/// Unchecked extrinsic type as expected by this runtime.
-pub type UncheckedExtrinsic =
-	fp_self_contained::UncheckedExtrinsic<Address, Call, Signature, SignedExtra>;
-/// Extrinsic type that has already been checked.
-pub type CheckedExtrinsic = fp_self_contained::CheckedExtrinsic<AccountId, Call, SignedExtra, H160>;
-/// Executive: handles dispatch to the various modules.
-pub type Executive = frame_executive::Executive<
-	Runtime,
-	Block,
-	frame_system::ChainContext<Runtime>,
-	Runtime,
-	AllPalletsReversedWithSystemFirst,
->;
-
-impl_opaque_keys! {
-	pub struct SessionKeys {
-		pub aura: Aura,
-	}
-}
-
-impl fp_self_contained::SelfContainedCall for Call {
-	type SignedInfo = H160;
-
-	fn is_self_contained(&self) -> bool {
-		match self {
-			Call::Ethereum(call) => call.is_self_contained(),
-			_ => false,
-		}
-	}
-
-	fn check_self_contained(&self) -> Option<Result<Self::SignedInfo, TransactionValidityError>> {
-		match self {
-			Call::Ethereum(call) => call.check_self_contained(),
-			_ => None,
-		}
-	}
-
-	fn validate_self_contained(
-		&self,
-		info: &Self::SignedInfo,
-		dispatch_info: &DispatchInfoOf<Call>,
-		len: usize,
-	) -> Option<TransactionValidity> {
-		match self {
-			Call::Ethereum(call) => call.validate_self_contained(info, dispatch_info, len),
-			_ => None,
-		}
-	}
-
-	fn pre_dispatch_self_contained(
-		&self,
-		info: &Self::SignedInfo,
-	) -> Option<Result<(), TransactionValidityError>> {
-		match self {
-			Call::Ethereum(call) => call.pre_dispatch_self_contained(info),
-			_ => None,
-		}
-	}
-
-	fn apply_self_contained(
-		self,
-		info: Self::SignedInfo,
-	) -> Option<sp_runtime::DispatchResultWithInfo<PostDispatchInfoOf<Self>>> {
-		match self {
-			call @ Call::Ethereum(pallet_ethereum::Call::transact { .. }) => Some(call.dispatch(
-				Origin::from(pallet_ethereum::RawOrigin::EthereumTransaction(info)),
-			)),
-			_ => None,
-		}
-	}
-}
-
-macro_rules! dispatch_unique_runtime {
-	($collection:ident.$method:ident($($name:ident),*)) => {{
-		let collection = <Runtime as pallet_common::Config>::CollectionDispatch::dispatch(<pallet_common::CollectionHandle<Runtime>>::try_get($collection)?);
-		let dispatch = collection.as_dyn();
-
-		Ok::<_, DispatchError>(dispatch.$method($($name),*))
-	}};
-}
-
-impl_common_runtime_apis! {
-	#![custom_apis]
-
-	impl rmrk_rpc::RmrkApi<
-		Block,
-		AccountId,
-		RmrkCollectionInfo<AccountId>,
-		RmrkInstanceInfo<AccountId>,
-		RmrkResourceInfo,
-		RmrkPropertyInfo,
-		RmrkBaseInfo<AccountId>,
-		RmrkPartType,
-		RmrkTheme
-	> for Runtime {
-		fn last_collection_idx() -> Result<RmrkCollectionId, DispatchError> {
-			Ok(Default::default())
-		}
-
-		fn collection_by_id(_collection_id: RmrkCollectionId) -> Result<Option<RmrkCollectionInfo<AccountId>>, DispatchError> {
-			Ok(Default::default())
-		}
-
-		fn nft_by_id(_collection_id: RmrkCollectionId, _nft_by_id: RmrkNftId) -> Result<Option<RmrkInstanceInfo<AccountId>>, DispatchError> {
-			Ok(Default::default())
-		}
-
-		fn account_tokens(_account_id: AccountId, _collection_id: RmrkCollectionId) -> Result<Vec<RmrkNftId>, DispatchError> {
-			Ok(Default::default())
-		}
-
-		fn nft_children(_collection_id: RmrkCollectionId, _nft_id: RmrkNftId) -> Result<Vec<RmrkNftChild>, DispatchError> {
-			Ok(Default::default())
-		}
-
-		fn collection_properties(_collection_id: RmrkCollectionId, _filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
-			Ok(Default::default())
-		}
-
-		fn nft_properties(_collection_id: RmrkCollectionId, _nft_id: RmrkNftId, _filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
-			Ok(Default::default())
-		}
-
-		fn nft_resources(_collection_id: RmrkCollectionId, _nft_id: RmrkNftId) -> Result<Vec<RmrkResourceInfo>, DispatchError> {
-			Ok(Default::default())
-		}
-
-		fn nft_resource_priority(_collection_id: RmrkCollectionId, _nft_id: RmrkNftId, _resource_id: RmrkResourceId) -> Result<Option<u32>, DispatchError> {
-			Ok(Default::default())
-		}
-
-		fn base(_base_id: RmrkBaseId) -> Result<Option<RmrkBaseInfo<AccountId>>, DispatchError> {
-			Ok(Default::default())
-		}
-
-		fn base_parts(_base_id: RmrkBaseId) -> Result<Vec<RmrkPartType>, DispatchError> {
-			Ok(Default::default())
-		}
-
-		fn theme_names(_base_id: RmrkBaseId) -> Result<Vec<RmrkThemeName>, DispatchError> {
-			Ok(Default::default())
-		}
-
-		fn theme(_base_id: RmrkBaseId, _theme_name: RmrkThemeName, _filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Option<RmrkTheme>, DispatchError> {
-			Ok(Default::default())
-		}
-	}
-}
-
-struct CheckInherents;
-
-impl cumulus_pallet_parachain_system::CheckInherents<Block> for CheckInherents {
-	fn check_inherents(
-		block: &Block,
-		relay_state_proof: &cumulus_pallet_parachain_system::RelayChainStateProof,
-	) -> sp_inherents::CheckInherentsResult {
-		let relay_chain_slot = relay_state_proof
-			.read_slot()
-			.expect("Could not read the relay chain slot from the proof");
-
-		let inherent_data =
-			cumulus_primitives_timestamp::InherentDataProvider::from_relay_chain_slot_and_duration(
-				relay_chain_slot,
-				sp_std::time::Duration::from_secs(6),
-			)
-			.create_inherent_data()
-			.expect("Could not create the timestamp inherent data");
-
-		inherent_data.check_extrinsics(block)
-	}
-}
+impl_common_runtime_apis!();
 
 cumulus_pallet_parachain_system::register_validate_block!(
 	Runtime = Runtime,
modifiedtests/package.jsondiffbeforeafterboth
--- a/tests/package.json
+++ b/tests/package.json
@@ -8,6 +8,7 @@
     "@polkadot/typegen": "8.7.2-15",
     "@types/chai": "^4.3.1",
     "@types/chai-as-promised": "^7.1.5",
+    "@types/chai-like": "^1.1.1",
     "@types/mocha": "^9.1.1",
     "@types/node": "^17.0.35",
     "@typescript-eslint/eslint-plugin": "^5.26.0",
@@ -96,6 +97,7 @@
     "@polkadot/util-crypto": "9.4.1",
     "bignumber.js": "^9.0.2",
     "chai-as-promised": "^7.1.1",
+    "chai-like": "^1.1.1",
     "find-process": "^1.4.7",
     "solc": "0.8.14-fixed",
     "web3": "^1.7.3"
modifiedtests/src/approve.test.tsdiffbeforeafterboth
--- a/tests/src/approve.test.ts
+++ b/tests/src/approve.test.ts
@@ -32,6 +32,8 @@
   getCreatedCollectionCount,
   transferFromExpectSuccess,
   transferFromExpectFail,
+  requirePallets,
+  Pallets,
 } from './util/helpers';
 
 chai.use(chaiAsPromised);
@@ -49,34 +51,44 @@
     });
   });
 
-  it('Execute the extrinsic and check approvedList', async () => {
+  it('[nft] Execute the extrinsic and check approvedList', async () => {
     const nftCollectionId = await createCollectionExpectSuccess();
-    // nft
     const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT');
     await approveExpectSuccess(nftCollectionId, newNftTokenId, alice, bob.address);
-    // fungible
+  });
+
+  it('[fungible] Execute the extrinsic and check approvedList', async () => {
     const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
     const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible');
     await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, alice, bob.address);
-    // reFungible
+  });
+
+  it('[refungible] Execute the extrinsic and check approvedList', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     const reFungibleCollectionId =
       await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
     const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible');
     await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, alice, bob.address);
   });
 
-  it('Remove approval by using 0 amount', async () => {
+  it('[nft] Remove approval by using 0 amount', async () => {
     const nftCollectionId = await createCollectionExpectSuccess();
-    // nft
     const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT');
     await approveExpectSuccess(nftCollectionId, newNftTokenId, alice, bob.address, 1);
     await approveExpectSuccess(nftCollectionId, newNftTokenId, alice, bob.address, 0);
-    // fungible
+  });
+
+  it('[fungible] Remove approval by using 0 amount', async () => {
     const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
     const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible');
     await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, alice, bob.address, 1);
     await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, alice, bob.address, 0);
-    // reFungible
+  });
+
+  it('[refungible] Remove approval by using 0 amount', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     const reFungibleCollectionId =
       await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
     const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible');
@@ -117,7 +129,9 @@
     await approveExpectSuccess(collectionId, itemId, bob, charlie.address);
   });
 
-  it('ReFungible up to an approved amount', async () => {
+  it('ReFungible up to an approved amount', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     const collectionId = await createCollectionExpectSuccess({mode:{type: 'ReFungible'}});
     const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', bob.address);
     await approveExpectSuccess(collectionId, itemId, bob, charlie.address);
@@ -151,7 +165,9 @@
     await transferFromExpectSuccess(collectionId, itemId, charlie, bob, alice, 1, 'Fungible');
   });
 
-  it('ReFungible up to an approved amount', async () => {
+  it('ReFungible up to an approved amount', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     const collectionId = await createCollectionExpectSuccess({mode:{type: 'ReFungible'}});
     const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', bob.address);
     await approveExpectSuccess(collectionId, itemId, bob, charlie.address);
@@ -188,7 +204,9 @@
     await transferFromExpectFail(collectionId, itemId, charlie, bob, alice, 1);
   });
 
-  it('ReFungible up to an approved amount', async () => {
+  it('ReFungible up to an approved amount', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     const collectionId = await createCollectionExpectSuccess({mode:{type: 'ReFungible'}});
     const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', bob.address);
     await approveExpectSuccess(collectionId, itemId, bob, charlie.address);
@@ -250,7 +268,9 @@
     await transferFromExpectFail(fungibleCollectionId, newFungibleTokenId, bob, bob, charlie, 1);
   });
 
-  it('ReFungible', async () => {
+  it('ReFungible', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     const reFungibleCollectionId =
       await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
     const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible');
@@ -285,7 +305,9 @@
     await approveExpectFail(fungibleCollectionId, newFungibleTokenId, bob, charlie, 11);
   });
 
-  it('ReFungible', async () => {
+  it('ReFungible', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
     const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible');
     await approveExpectFail(reFungibleCollectionId, newReFungibleTokenId, bob, charlie, 101);
@@ -325,7 +347,9 @@
     await transferFromExpectSuccess(collectionId, itemId, bob, dave, alice, 1, 'Fungible');
   });
 
-  it('ReFungible up to an approved amount', async () => {
+  it('ReFungible up to an approved amount', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     const collectionId = await createCollectionExpectSuccess({mode:{type: 'ReFungible'}});
     await setCollectionLimitsExpectSuccess(alice, collectionId, {ownerCanTransfer: true});
     const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', charlie.address);
@@ -422,63 +446,87 @@
     });
   });
 
-  it('Approve for a collection that does not exist', async () => {
+  it('[nft] Approve for a collection that does not exist', async () => {
     await usingApi(async (api: ApiPromise) => {
-      // nft
       const nftCollectionCount = await getCreatedCollectionCount(api);
       await approveExpectFail(nftCollectionCount + 1, 1, alice, bob);
-      // fungible
+    });
+  });
+
+  it('[fungible] Approve for a collection that does not exist', async () => {
+    await usingApi(async (api: ApiPromise) => {
       const fungibleCollectionCount = await getCreatedCollectionCount(api);
       await approveExpectFail(fungibleCollectionCount + 1, 0, alice, bob);
-      // reFungible
+    });
+  });
+
+  it('[refungible] Approve for a collection that does not exist', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
+    await usingApi(async (api: ApiPromise) => {
       const reFungibleCollectionCount = await getCreatedCollectionCount(api);
       await approveExpectFail(reFungibleCollectionCount + 1, 1, alice, bob);
     });
   });
 
-  it('Approve for a collection that was destroyed', async () => {
-    // nft
+  it('[nft] Approve for a collection that was destroyed', async () => {
     const nftCollectionId = await createCollectionExpectSuccess();
     await destroyCollectionExpectSuccess(nftCollectionId);
     await approveExpectFail(nftCollectionId, 1, alice, bob);
-    // fungible
+  });
+
+  it('Approve for a collection that was destroyed', async () => {
     const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
     await destroyCollectionExpectSuccess(fungibleCollectionId);
     await approveExpectFail(fungibleCollectionId, 0, alice, bob);
-    // reFungible
+  });
+
+  it('[refungible] Approve for a collection that was destroyed', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     const reFungibleCollectionId =
       await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
     await destroyCollectionExpectSuccess(reFungibleCollectionId);
     await approveExpectFail(reFungibleCollectionId, 1, alice, bob);
   });
 
-  it('Approve transfer of a token that does not exist', async () => {
-    // nft
+  it('[nft] Approve transfer of a token that does not exist', async () => {
     const nftCollectionId = await createCollectionExpectSuccess();
     await approveExpectFail(nftCollectionId, 2, alice, bob);
-    // reFungible
+  });
+
+  it('[refungible] Approve transfer of a token that does not exist', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     const reFungibleCollectionId =
       await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
     await approveExpectFail(reFungibleCollectionId, 2, alice, bob);
   });
 
-  it('Approve using the address that does not own the approved token', async () => {
+  it('[nft] Approve using the address that does not own the approved token', async () => {
     const nftCollectionId = await createCollectionExpectSuccess();
-    // nft
     const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT');
     await approveExpectFail(nftCollectionId, newNftTokenId, bob, alice);
-    // fungible
+  });
+
+  it('[fungible] Approve using the address that does not own the approved token', async () => {
     const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
     const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible');
     await approveExpectFail(fungibleCollectionId, newFungibleTokenId, bob, alice);
-    // reFungible
+  });
+
+  it('[refungible] Approve using the address that does not own the approved token', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     const reFungibleCollectionId =
       await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
     const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible');
     await approveExpectFail(reFungibleCollectionId, newReFungibleTokenId, bob, alice);
   });
 
-  it('should fail if approved more ReFungibles than owned', async () => {
+  it('should fail if approved more ReFungibles than owned', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     const nftCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
     const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'ReFungible');
     await transferExpectSuccess(nftCollectionId, newNftTokenId, alice, bob, 100, 'ReFungible');
modifiedtests/src/burnItem.test.tsdiffbeforeafterboth
--- a/tests/src/burnItem.test.ts
+++ b/tests/src/burnItem.test.ts
@@ -25,6 +25,8 @@
   getBalance,
   setCollectionLimitsExpectSuccess,
   isTokenExists,
+  requirePallets,
+  Pallets,
 } from './util/helpers';
 
 import chai from 'chai';
@@ -80,7 +82,9 @@
     });
   });
 
-  it('Burn item in ReFungible collection', async () => {
+  it('Burn item in ReFungible collection', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     const createMode = 'ReFungible';
     const collectionId = await createCollectionExpectSuccess({mode: {type: createMode}});
     const tokenId = await createItemExpectSuccess(alice, collectionId, createMode);
@@ -99,7 +103,9 @@
     });
   });
 
-  it('Burn owned portion of item in ReFungible collection', async () => {
+  it('Burn owned portion of item in ReFungible collection', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     const createMode = 'ReFungible';
     const collectionId = await createCollectionExpectSuccess({mode: {type: createMode}});
     const tokenId = await createItemExpectSuccess(alice, collectionId, createMode);
@@ -189,7 +195,9 @@
   });
 
   // TODO: burnFrom
-  it('Burn item in ReFungible collection', async () => {
+  it('Burn item in ReFungible collection', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     const createMode = 'ReFungible';
     const collectionId = await createCollectionExpectSuccess({mode: {type: createMode}});
     await setCollectionLimitsExpectSuccess(alice, collectionId, {ownerCanTransfer: true});
modifiedtests/src/confirmSponsorship.test.tsdiffbeforeafterboth
--- a/tests/src/confirmSponsorship.test.ts
+++ b/tests/src/confirmSponsorship.test.ts
@@ -33,6 +33,8 @@
   addCollectionAdminExpectSuccess,
   getCreatedCollectionCount,
   UNIQUE,
+  requirePallets,
+  Pallets,
 } from './util/helpers';
 import {IKeyringPair} from '@polkadot/types/types';
 
@@ -124,7 +126,9 @@
     });
   });
 
-  it('ReFungible: Transfer fees are paid by the sponsor after confirmation', async () => {
+  it('ReFungible: Transfer fees are paid by the sponsor after confirmation', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
     await setCollectionSponsorExpectSuccess(collectionId, bob.address);
     await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
@@ -252,7 +256,9 @@
     });
   });
 
-  it('ReFungible: Sponsoring is rate limited', async () => {
+  it('ReFungible: Sponsoring is rate limited', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
     await setCollectionSponsorExpectSuccess(collectionId, bob.address);
     await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
modifiedtests/src/createCollection.test.tsdiffbeforeafterboth
--- a/tests/src/createCollection.test.ts
+++ b/tests/src/createCollection.test.ts
@@ -16,7 +16,7 @@
 
 import {expect} from 'chai';
 import usingApi, {executeTransaction, submitTransactionAsync} from './substrate/substrate-api';
-import {createCollectionWithPropsExpectFailure, createCollectionExpectFailure, createCollectionExpectSuccess, getCreateCollectionResult, getDetailedCollectionInfo, createCollectionWithPropsExpectSuccess} from './util/helpers';
+import {createCollectionWithPropsExpectFailure, createCollectionExpectFailure, createCollectionExpectSuccess, getCreateCollectionResult, getDetailedCollectionInfo, createCollectionWithPropsExpectSuccess, requirePallets, Pallets} from './util/helpers';
 
 describe('integration test: ext. createCollection():', () => {
   it('Create new NFT collection', async () => {
@@ -34,7 +34,9 @@
   it('Create new Fungible collection', async () => {
     await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
   });
-  it('Create new ReFungible collection', async () => {
+  it('Create new ReFungible collection', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
   });
 
modifiedtests/src/createItem.test.tsdiffbeforeafterboth
--- a/tests/src/createItem.test.ts
+++ b/tests/src/createItem.test.ts
@@ -29,6 +29,8 @@
   itApi,
   normalizeAccountId,
   getCreateItemResult,
+  requirePallets,
+  Pallets,
 } from './util/helpers';
 
 const expect = chai.expect;
@@ -79,7 +81,9 @@
     }
 
   });
-  it('Create new item in ReFungible collection', async () => {
+  it('Create new item in ReFungible collection', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     const createMode = 'ReFungible';
     const newCollectionID = await createCollectionExpectSuccess({mode: {type: createMode}});
     await createItemExpectSuccess(alice, newCollectionID, createMode);
@@ -96,7 +100,9 @@
     await addCollectionAdminExpectSuccess(alice, newCollectionID, bob.address);
     await createItemExpectSuccess(bob, newCollectionID, createMode);
   });
-  it('Create new item in ReFungible collection with collection admin permissions', async () => {
+  it('Create new item in ReFungible collection with collection admin permissions', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     const createMode = 'ReFungible';
     const newCollectionID = await createCollectionExpectSuccess({mode: {type: createMode}});
     await addCollectionAdminExpectSuccess(alice, newCollectionID, bob.address);
@@ -175,7 +181,9 @@
     });
   });
 
-  it('Check total pieces of ReFungible token', async () => {
+  it('Check total pieces of ReFungible token', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await usingApi(async api => {
       const createMode = 'ReFungible';
       const createCollectionResult = await createCollection(api, alice, {mode: {type: createMode}});
@@ -219,7 +227,9 @@
     const newCollectionID = await createCollectionExpectSuccess({mode: {type: createMode, decimalPoints: 0}});
     await expect(createItemExpectSuccess(bob, newCollectionID, createMode)).to.be.rejected;
   });
-  it('Regular user cannot create new item in ReFungible collection', async () => {
+  it('Regular user cannot create new item in ReFungible collection', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     const createMode = 'ReFungible';
     const newCollectionID = await createCollectionExpectSuccess({mode: {type: createMode}});
     await expect(createItemExpectSuccess(bob, newCollectionID, createMode)).to.be.rejected;
@@ -296,7 +306,9 @@
     });
   });
 
-  it('Check total pieces for invalid Refungible token', async () => {
+  it('Check total pieces for invalid Refungible token', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await usingApi(async api => {
       const createCollectionResult = await createCollection(api, alice, {mode: {type: 'ReFungible'}});
       const collectionId  = createCollectionResult.collectionId;
modifiedtests/src/createMultipleItems.test.tsdiffbeforeafterboth
--- a/tests/src/createMultipleItems.test.ts
+++ b/tests/src/createMultipleItems.test.ts
@@ -33,6 +33,9 @@
   createCollectionWithPropsExpectSuccess,
   createMultipleItemsWithPropsExpectSuccess,
   getTokenProperties,
+  requirePallets,
+  Pallets,
+  checkPalletsPresence,
 } from './util/helpers';
 
 chai.use(chaiAsPromised);
@@ -91,7 +94,9 @@
     });
   });
 
-  it('Create 0x31, 0x32, 0x33 items in active ReFungible collection and verify tokens data in chain', async () => {
+  it('Create 0x31, 0x32, 0x33 items in active ReFungible collection and verify tokens data in chain', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await usingApi(async (api, privateKeyWrapper) => {
       const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
       const itemsListIndexBefore = await getLastTokenId(api, collectionId);
@@ -272,7 +277,9 @@
     });
   });
 
-  it('Create 0x31, 0x32, 0x33 items in active ReFungible collection and verify tokens data in chain', async () => {
+  it('Create 0x31, 0x32, 0x33 items in active ReFungible collection and verify tokens data in chain', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await usingApi(async (api: ApiPromise) => {
       const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
       const itemsListIndexBefore = await getLastTokenId(api, collectionId);
@@ -335,7 +342,9 @@
     });
   });
 
-  it('Regular user cannot create items in active ReFungible collection', async () => {
+  it('Regular user cannot create items in active ReFungible collection', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await usingApi(async (api: ApiPromise) => {
       const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
       const itemsListIndexBefore = await getLastTokenId(api, collectionId);
@@ -358,9 +367,8 @@
     });
   });
 
-  it('Create NFT and Re-fungible tokens that has reached the maximum data limit', async () => {
+  it('Create NFTs that has reached the maximum data limit', async function() {
     await usingApi(async (api, privateKeyWrapper) => {
-      // NFT
       const collectionId = await createCollectionWithPropsExpectSuccess({
         propPerm: [{key: 'key', permission: {mutable: true, collectionAdmin: true, tokenOwner: true}}],
       });
@@ -372,8 +380,13 @@
       ];
       const createMultipleItemsTx = api.tx.unique.createMultipleItems(collectionId, normalizeAccountId(alice.address), args);
       await expect(submitTransactionExpectFailAsync(alice, createMultipleItemsTx)).to.be.rejected;
+    });
+  });
 
-      // ReFungible
+  it('Create Refungible tokens that has reached the maximum data limit', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
+    await usingApi(async api => {
       const collectionIdReFungible =
         await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
       {
@@ -402,8 +415,15 @@
   it('Create tokens with different types', async () => {
     await usingApi(async (api: ApiPromise) => {
       const collectionId = await createCollectionExpectSuccess();
+
+      const types = ['NFT', 'Fungible'];
+
+      if (await checkPalletsPresence([Pallets.ReFungible])) {
+        types.push('ReFungible');
+      }
+
       const createMultipleItemsTx = api.tx.unique
-        .createMultipleItems(collectionId, normalizeAccountId(alice.address), ['NFT', 'Fungible', 'ReFungible']);
+        .createMultipleItems(collectionId, normalizeAccountId(alice.address), types);
       await expect(executeTransaction(api, alice, createMultipleItemsTx)).to.be.rejectedWith(/nonfungible\.NotNonfungibleDataUsedToMintFungibleCollectionToken/);
       // garbage collection :-D // lol
       await destroyCollectionExpectSuccess(collectionId);
modifiedtests/src/createMultipleItemsEx.test.tsdiffbeforeafterboth
--- a/tests/src/createMultipleItemsEx.test.ts
+++ b/tests/src/createMultipleItemsEx.test.ts
@@ -16,9 +16,9 @@
 
 import {expect} from 'chai';
 import usingApi, {executeTransaction} from './substrate/substrate-api';
-import {addCollectionAdminExpectSuccess, createCollectionExpectSuccess, createCollectionWithPropsExpectSuccess, getBalance, getLastTokenId, getTokenProperties} from './util/helpers';
+import {addCollectionAdminExpectSuccess, createCollectionExpectSuccess, createCollectionWithPropsExpectSuccess, getBalance, getLastTokenId, getTokenProperties, requirePallets, Pallets} from './util/helpers';
 
-describe.only('Integration Test: createMultipleItemsEx', () => {
+describe('Integration Test: createMultipleItemsEx', () => {
   it('can initialize multiple NFT with different owners', async () => {
     const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
     await usingApi(async (api, privateKeyWrapper) => {
@@ -146,7 +146,9 @@
     });
   });
 
-  it('can initialize an RFT with multiple owners', async () => {
+  it('can initialize an RFT with multiple owners', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await usingApi(async (api, privateKeyWrapper) => {
       const alice = privateKeyWrapper('//Alice');
       const bob = privateKeyWrapper('//Bob');
@@ -178,7 +180,9 @@
     });
   });
 
-  it('can initialize multiple RFTs with the same owner', async () => {
+  it('can initialize multiple RFTs with the same owner', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await usingApi(async (api, privateKeyWrapper) => {
       const alice = privateKeyWrapper('//Alice');
       const collection = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
@@ -386,27 +390,6 @@
       const tokens = await api.query.nonfungible.tokenData.entries(collection);
       const json = tokens.map(([, token]) => token.toJSON());
       expect(json).to.be.deep.equal(data);
-    });
-  });
-
-  it('fails when trying to set multiple owners when creating multiple refungibles', async () => {
-    const collection = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
-
-    await usingApi(async (api, privateKeyWrapper) => {
-      const alice = privateKeyWrapper('//Alice');
-      const bob = privateKeyWrapper('//Bob');
-      // Polkadot requires map, and yet requires keys to be JSON encoded
-      const users = new Map();
-      users.set(JSON.stringify({substrate: alice.address}), 1);
-      users.set(JSON.stringify({substrate: bob.address}), 1);
-
-      // TODO: better error message?
-      await expect(executeTransaction(api, alice, api.tx.unique.createMultipleItemsEx(collection, {
-        RefungibleMultipleItems: [
-          {users},
-          {users},
-        ],
-      }))).to.be.rejectedWith(/^refungible\.NotRefungibleDataUsedToMintFungibleCollectionToken$/);
     });
   });
 });
modifiedtests/src/destroyCollection.test.tsdiffbeforeafterboth
--- a/tests/src/destroyCollection.test.ts
+++ b/tests/src/destroyCollection.test.ts
@@ -25,6 +25,8 @@
   addCollectionAdminExpectSuccess,
   getCreatedCollectionCount,
   createItemExpectSuccess,
+  requirePallets,
+  Pallets,
 } from './util/helpers';
 
 chai.use(chaiAsPromised);
@@ -38,7 +40,9 @@
     const collectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
     await destroyCollectionExpectSuccess(collectionId);
   });
-  it('ReFungible collection can be destroyed', async () => {
+  it('ReFungible collection can be destroyed', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
     await destroyCollectionExpectSuccess(collectionId);
   });
modifiedtests/src/eth/api/CollectionHelpers.soldiffbeforeafterboth
--- a/tests/src/eth/api/CollectionHelpers.sol
+++ b/tests/src/eth/api/CollectionHelpers.sol
@@ -48,7 +48,7 @@
 		string memory name,
 		string memory description,
 		string memory tokenPrefix
-	) external view returns (address);
+	) external returns (address);
 
 	// Selector: createERC721MetadataCompatibleRFTCollection(string,string,string,string) a5596388
 	function createERC721MetadataCompatibleRFTCollection(
modifiedtests/src/eth/api/UniqueFungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueFungible.sol
+++ b/tests/src/eth/api/UniqueFungible.sol
@@ -22,13 +22,7 @@
 	);
 }
 
-// Selector: 79cc6790
-interface ERC20UniqueExtensions is Dummy, ERC165 {
-	// Selector: burnFrom(address,uint256) 79cc6790
-	function burnFrom(address from, uint256 amount) external returns (bool);
-}
-
-// Selector: 7d9262e6
+// Selector: 6cf113cd
 interface Collection is Dummy, ERC165 {
 	// Set collection property.
 	//
@@ -174,6 +168,27 @@
 	//
 	// Selector: setCollectionMintMode(bool) 00018e84
 	function setCollectionMintMode(bool mode) external;
+
+	// Check that account is the owner or admin of the collection
+	//
+	// @param user account to verify
+	// @return "true" if account is the owner or admin
+	//
+	// Selector: verifyOwnerOrAdmin(address) c2282493
+	function verifyOwnerOrAdmin(address user) external view returns (bool);
+
+	// Returns collection type
+	//
+	// @return `Fungible` or `NFT` or `ReFungible`
+	//
+	// Selector: uniqueCollectionType() d34b55b8
+	function uniqueCollectionType() external returns (string memory);
+}
+
+// Selector: 79cc6790
+interface ERC20UniqueExtensions is Dummy, ERC165 {
+	// Selector: burnFrom(address,uint256) 79cc6790
+	function burnFrom(address from, uint256 amount) external returns (bool);
 }
 
 // Selector: 942e8b22
modifiedtests/src/eth/api/UniqueNFT.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -250,33 +250,7 @@
 	function finishMinting() external returns (bool);
 }
 
-// Selector: 780e9d63
-interface ERC721Enumerable is Dummy, ERC165 {
-	// @notice Enumerate valid NFTs
-	// @param index A counter less than `totalSupply()`
-	// @return The token identifier for the `index`th NFT,
-	//  (sort order not specified)
-	//
-	// Selector: tokenByIndex(uint256) 4f6ccce7
-	function tokenByIndex(uint256 index) external view returns (uint256);
-
-	// @dev Not implemented
-	//
-	// Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59
-	function tokenOfOwnerByIndex(address owner, uint256 index)
-		external
-		view
-		returns (uint256);
-
-	// @notice Count NFTs tracked by this contract
-	// @return A count of valid NFTs tracked by this contract, where each one of
-	//  them has an assigned and queryable owner not equal to the zero address
-	//
-	// Selector: totalSupply() 18160ddd
-	function totalSupply() external view returns (uint256);
-}
-
-// Selector: 7d9262e6
+// Selector: 6cf113cd
 interface Collection is Dummy, ERC165 {
 	// Set collection property.
 	//
@@ -422,6 +396,47 @@
 	//
 	// Selector: setCollectionMintMode(bool) 00018e84
 	function setCollectionMintMode(bool mode) external;
+
+	// Check that account is the owner or admin of the collection
+	//
+	// @param user account to verify
+	// @return "true" if account is the owner or admin
+	//
+	// Selector: verifyOwnerOrAdmin(address) c2282493
+	function verifyOwnerOrAdmin(address user) external view returns (bool);
+
+	// Returns collection type
+	//
+	// @return `Fungible` or `NFT` or `ReFungible`
+	//
+	// Selector: uniqueCollectionType() d34b55b8
+	function uniqueCollectionType() external returns (string memory);
+}
+
+// Selector: 780e9d63
+interface ERC721Enumerable is Dummy, ERC165 {
+	// @notice Enumerate valid NFTs
+	// @param index A counter less than `totalSupply()`
+	// @return The token identifier for the `index`th NFT,
+	//  (sort order not specified)
+	//
+	// Selector: tokenByIndex(uint256) 4f6ccce7
+	function tokenByIndex(uint256 index) external view returns (uint256);
+
+	// @dev Not implemented
+	//
+	// Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59
+	function tokenOfOwnerByIndex(address owner, uint256 index)
+		external
+		view
+		returns (uint256);
+
+	// @notice Count NFTs tracked by this contract
+	// @return A count of valid NFTs tracked by this contract, where each one of
+	//  them has an assigned and queryable owner not equal to the zero address
+	//
+	// Selector: totalSupply() 18160ddd
+	function totalSupply() external view returns (uint256);
 }
 
 // Selector: d74d154f
modifiedtests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueRefungible.sol
+++ b/tests/src/eth/api/UniqueRefungible.sol
@@ -248,33 +248,7 @@
 	function finishMinting() external returns (bool);
 }
 
-// Selector: 780e9d63
-interface ERC721Enumerable is Dummy, ERC165 {
-	// @notice Enumerate valid RFTs
-	// @param index A counter less than `totalSupply()`
-	// @return The token identifier for the `index`th NFT,
-	//  (sort order not specified)
-	//
-	// Selector: tokenByIndex(uint256) 4f6ccce7
-	function tokenByIndex(uint256 index) external view returns (uint256);
-
-	// Not implemented
-	//
-	// Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59
-	function tokenOfOwnerByIndex(address owner, uint256 index)
-		external
-		view
-		returns (uint256);
-
-	// @notice Count RFTs tracked by this contract
-	// @return A count of valid RFTs tracked by this contract, where each one of
-	//  them has an assigned and queryable owner not equal to the zero address
-	//
-	// Selector: totalSupply() 18160ddd
-	function totalSupply() external view returns (uint256);
-}
-
-// Selector: 7d9262e6
+// Selector: 6cf113cd
 interface Collection is Dummy, ERC165 {
 	// Set collection property.
 	//
@@ -420,9 +394,50 @@
 	//
 	// Selector: setCollectionMintMode(bool) 00018e84
 	function setCollectionMintMode(bool mode) external;
+
+	// Check that account is the owner or admin of the collection
+	//
+	// @param user account to verify
+	// @return "true" if account is the owner or admin
+	//
+	// Selector: verifyOwnerOrAdmin(address) c2282493
+	function verifyOwnerOrAdmin(address user) external view returns (bool);
+
+	// Returns collection type
+	//
+	// @return `Fungible` or `NFT` or `ReFungible`
+	//
+	// Selector: uniqueCollectionType() d34b55b8
+	function uniqueCollectionType() external returns (string memory);
+}
+
+// Selector: 780e9d63
+interface ERC721Enumerable is Dummy, ERC165 {
+	// @notice Enumerate valid RFTs
+	// @param index A counter less than `totalSupply()`
+	// @return The token identifier for the `index`th NFT,
+	//  (sort order not specified)
+	//
+	// Selector: tokenByIndex(uint256) 4f6ccce7
+	function tokenByIndex(uint256 index) external view returns (uint256);
+
+	// Not implemented
+	//
+	// Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59
+	function tokenOfOwnerByIndex(address owner, uint256 index)
+		external
+		view
+		returns (uint256);
+
+	// @notice Count RFTs tracked by this contract
+	// @return A count of valid RFTs tracked by this contract, where each one of
+	//  them has an assigned and queryable owner not equal to the zero address
+	//
+	// Selector: totalSupply() 18160ddd
+	function totalSupply() external view returns (uint256);
 }
 
-// Selector: d74d154f
+// Selector: 7c3bef89
 interface ERC721UniqueExtensions is Dummy, ERC165 {
 	// @notice Transfer ownership of an RFT
 	// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
@@ -473,6 +488,16 @@
 	function mintBulkWithTokenURI(address to, Tuple0[] memory tokens)
 		external
 		returns (bool);
+
+	// Returns EVM address for refungible token
+	//
+	// @param token ID of the token
+	//
+	// Selector: tokenContractAddress(uint256) ab76fac6
+	function tokenContractAddress(uint256 token)
+		external
+		view
+		returns (address);
 }
 
 interface UniqueRefungible is
modifiedtests/src/eth/api/UniqueRefungibleToken.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueRefungibleToken.sol
+++ b/tests/src/eth/api/UniqueRefungibleToken.sol
@@ -22,6 +22,23 @@
 	);
 }
 
+// Selector: 042f1106
+interface ERC1633UniqueExtensions is Dummy, ERC165 {
+	// Selector: setParentNFT(address,uint256) 042f1106
+	function setParentNFT(address collection, uint256 nftId)
+		external
+		returns (bool);
+}
+
+// Selector: 5755c3f2
+interface ERC1633 is Dummy, ERC165 {
+	// Selector: parentToken() 80a54001
+	function parentToken() external view returns (address);
+
+	// Selector: parentTokenId() d7f083f3
+	function parentTokenId() external view returns (uint256);
+}
+
 // Selector: 942e8b22
 interface ERC20 is Dummy, ERC165, ERC20Events {
 	// @return the name of the token.
@@ -115,5 +132,7 @@
 	Dummy,
 	ERC165,
 	ERC20,
-	ERC20UniqueExtensions
+	ERC20UniqueExtensions,
+	ERC1633,
+	ERC1633UniqueExtensions
 {}
modifiedtests/src/eth/collectionAdmin.test.tsdiffbeforeafterboth
--- a/tests/src/eth/collectionAdmin.test.ts
+++ b/tests/src/eth/collectionAdmin.test.ts
@@ -59,6 +59,22 @@
     expect(adminList[0].asSubstrate.toString().toLocaleLowerCase())
       .to.be.eq(newAdmin.address.toLocaleLowerCase());
   });
+  
+  itWeb3('Verify owner or admin', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const collectionHelper = evmCollectionHelpers(web3, owner);
+        
+    const result = await collectionHelper.methods
+      .createNonfungibleCollection('A', 'B', 'C')
+      .send();
+    const {collectionIdAddress} = await getCollectionAddressFromResult(api, result);
+
+    const newAdmin = createEthAccount(web3);
+    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);
+    expect(await collectionEvm.methods.verifyOwnerOrAdmin(newAdmin).call()).to.be.false;
+    await collectionEvm.methods.addCollectionAdmin(newAdmin).send();
+    expect(await collectionEvm.methods.verifyOwnerOrAdmin(newAdmin).call()).to.be.true;
+  });
 
   itWeb3('(!negative tests!) Add admin by ADMIN is not allowed', async ({api, web3, privateKeyWrapper}) => {
     const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
modifiedtests/src/eth/collectionHelpersAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/collectionHelpersAbi.json
+++ b/tests/src/eth/collectionHelpersAbi.json
@@ -61,7 +61,7 @@
     ],
     "name": "createRefungibleCollection",
     "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
-    "stateMutability": "view",
+    "stateMutability": "nonpayable",
     "type": "function"
   },
   {
modifiedtests/src/eth/createRFTCollection.test.tsdiffbeforeafterboth
--- a/tests/src/eth/createRFTCollection.test.ts
+++ b/tests/src/eth/createRFTCollection.test.ts
@@ -16,7 +16,7 @@
 
 import {evmToAddress} from '@polkadot/util-crypto';
 import {expect} from 'chai';
-import {getCreatedCollectionCount, getDetailedCollectionInfo} from '../util/helpers';
+import {getCreatedCollectionCount, getDetailedCollectionInfo, requirePallets, Pallets} from '../util/helpers';
 import {
   evmCollectionHelpers,
   collectionIdToAddress,
@@ -28,6 +28,10 @@
 } from './util/helpers';
 
 describe('Create RFT collection from EVM', () => {
+  before(async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+  });
+
   itWeb3('Create collection', async ({api, web3, privateKeyWrapper}) => {
     const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const collectionHelper = evmCollectionHelpers(web3, owner);
@@ -146,6 +150,10 @@
 });
 
 describe('(!negative tests!) Create RFT collection from EVM', () => {
+  before(async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+  });
+
   itWeb3('(!negative test!) Create collection (bad lengths)', async ({api, web3, privateKeyWrapper}) => {
     const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const helper = evmCollectionHelpers(web3, owner);
@@ -228,4 +236,4 @@
       .setCollectionLimit('badLimit', 'true')
       .call()).to.be.rejectedWith('unknown boolean limit "badLimit"');
   });
-});
\ No newline at end of file
+});
addedtests/src/eth/fractionalizer/Fractionalizer.bindiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/fractionalizer/Fractionalizer.bin
@@ -0,0 +1 @@
+60c0604052600a60805269526546756e6769626c6560b01b60a0527fcdb72fd4e1d0d6d4eebd7ab142113ec2b4b06ddb24324db5c287ef01ab484d6b60055534801561004a57600080fd5b50600480546001600160a01b0319163317905561137a8061006c6000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c8063115091401461005c5780631b191ea214610071578063d470e60f14610084578063dbc38ad214610097578063eb292412146100aa575b600080fd5b61006f61006a366004610f85565b6100bd565b005b61006f61007f366004610ff2565b61035b565b61006f61009236600461108c565b6104c0565b61006f6100a53660046110b8565b61089d565b61006f6100b8366004611114565b610ee0565b6004546001600160a01b031633146100f05760405162461bcd60e51b81526004016100e79061114d565b60405180910390fd5b6000546001600160a01b0316156101495760405162461bcd60e51b815260206004820152601d60248201527f52465420636f6c6c656374696f6e20697320616c72656164792073657400000060448201526064016100e7565b60008190506000816001600160a01b031663d34b55b86040518163ffffffff1660e01b81526004016000604051808303816000875af1158015610190573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526101b8919081019061118b565b905060055481805190602001201461022f5760405162461bcd60e51b815260206004820152603460248201527f57726f6e6720636f6c6c656374696f6e20747970652e20436f6c6c656374696f604482015273371034b9903737ba103932b33ab733b4b136329760611b60648201526084016100e7565b816001600160a01b03166304a460536040518163ffffffff1660e01b81526004016020604051808303816000875af115801561026f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610293919061125b565b6103055760405162461bcd60e51b815260206004820152603c60248201527f4672616374696f6e616c697a657220636f6e74726163742073686f756c64206260448201527f6520616e2061646d696e206f662074686520636f6c6c656374696f6e0000000060648201526084016100e7565b600080546001600160a01b0319166001600160a01b0385169081179091556040519081527f7186a599bf2297b1f4c8957d30b0965291eee0021a5f9a0aeb54dcbd1ffdceef9060200160405180910390a1505050565b6004546001600160a01b031633146103855760405162461bcd60e51b81526004016100e79061114d565b6000546001600160a01b0316156103de5760405162461bcd60e51b815260206004820152601d60248201527f52465420636f6c6c656374696f6e20697320616c72656164792073657400000060448201526064016100e7565b6040516344a68ad560e01b8152736c4e9fe1ae37a41e93cee429e8e1881abdcbb54f9081906344a68ad590610421908a908a908a908a908a908a906004016112a1565b6020604051808303816000875af1158015610440573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061046491906112ea565b600080546001600160a01b0319166001600160a01b039290921691821790556040519081527f7186a599bf2297b1f4c8957d30b0965291eee0021a5f9a0aeb54dcbd1ffdceef906020015b60405180910390a150505050505050565b6000546001600160a01b03166105145760405162461bcd60e51b81526020600482015260196024820152781491950818dbdb1b1958dd1a5bdb881a5cc81b9bdd081cd95d603a1b60448201526064016100e7565b6000546001600160a01b038381169116146105685760405162461bcd60e51b81526020600482015260146024820152732bb937b7339029232a1031b7b63632b1ba34b7b760611b60448201526064016100e7565b600080546040516355bb7d6360e11b8152600481018490526001600160a01b039091169190829063ab76fac690602401602060405180830381865afa1580156105b5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105d991906112ea565b6001600160a01b03808216600090815260036020908152604091829020825180840190935280549093168083526001909301549082015291925061065f5760405162461bcd60e51b815260206004820181905260248201527f4e6f20636f72726573706f6e64696e67204e465420746f6b656e20666f756e6460448201526064016100e7565b6000829050806001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156106a2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106c69190611307565b6040516370a0823160e01b81523360048201526001600160a01b038316906370a0823190602401602060405180830381865afa15801561070a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061072e9190611307565b1461078a5760405162461bcd60e51b815260206004820152602660248201527f4e6f7420616c6c2070696563657320617265206f776e6564206279207468652060448201526531b0b63632b960d11b60648201526084016100e7565b6040516323b872dd60e01b81526001600160a01b038516906323b872dd906107ba90339030908a90600401611320565b600060405180830381600087803b1580156107d457600080fd5b505af11580156107e8573d6000803e3d6000fd5b5050835160208501516040516323b872dd60e01b81526001600160a01b0390921693506323b872dd92506108229130913391600401611320565b600060405180830381600087803b15801561083c57600080fd5b505af1158015610850573d6000803e3d6000fd5b5050835160208501516040517fe9e9808d24ff79ccc3b1ecf48be7b2d11591adccc452150d0d7947cb48eb0d53945061088d935087929190611320565b60405180910390a1505050505050565b6000546001600160a01b03166108f15760405162461bcd60e51b81526020600482015260196024820152781491950818dbdb1b1958dd1a5bdb881a5cc81b9bdd081cd95d603a1b60448201526064016100e7565b600080546001600160a01b0385811683526001602081905260409093205491169160ff90911615151461098c5760405162461bcd60e51b815260206004820152603c60248201527f4672616374696f6e616c697a6174696f6e206f66207468697320636f6c6c656360448201527f74696f6e206973206e6f7420616c6c6f7765642062792061646d696e0000000060648201526084016100e7565b6040516331a9108f60e11b81526004810184905233906001600160a01b03861690636352211e90602401602060405180830381865afa1580156109d3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109f791906112ea565b6001600160a01b031614610a5d5760405162461bcd60e51b815260206004820152602760248201527f4f6e6c7920746f6b656e206f776e657220636f756c64206672616374696f6e616044820152661b1a5e99481a5d60ca1b60648201526084016100e7565b6040516323b872dd60e01b81526001600160a01b038516906323b872dd90610a8d90339030908890600401611320565b600060405180830381600087803b158015610aa757600080fd5b505af1158015610abb573d6000803e3d6000fd5b505050506001600160a01b0384166000908152600260209081526040808320868452909152812054819081908103610d0757836001600160a01b03166375794a3c6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b2b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b4f9190611307565b6040516340c10f1960e01b8152306004820152602481018290529093506001600160a01b038516906340c10f19906044016020604051808303816000875af1158015610b9f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bc3919061125b565b506040516355bb7d6360e11b8152600481018490526001600160a01b0385169063ab76fac690602401602060405180830381865afa158015610c09573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c2d91906112ea565b6001600160a01b0388811660008181526002602090815260408083208c84528252808320899055805180820182528481528083018d8152878716808652600390945293829020905181546001600160a01b0319169616959095178555915160019094019390935551630217888360e11b81526004810191909152602481018990529193508392509063042f1106906044016020604051808303816000875af1158015610cdd573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d01919061125b565b50610d98565b6001600160a01b0387811660009081526002602090815260408083208a8452909152908190205490516355bb7d6360e11b8152600481018290529094509085169063ab76fac690602401602060405180830381865afa158015610d6e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d9291906112ea565b91508190505b60405163d2418ca760e01b81526001600160801b03861660048201526001600160a01b0382169063d2418ca7906024016020604051808303816000875af1158015610de7573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e0b919061125b565b5060405163a9059cbb60e01b81523360048201526001600160801b03861660248201526001600160a01b0382169063a9059cbb906044016020604051808303816000875af1158015610e61573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e85919061125b565b50604080516001600160a01b03808a168252602082018990528416918101919091526001600160801b03861660608201527f29f372538523984f33874da1b50e596ce0f180a6eb04d7ff22bb2ce80e1576b6906080016104af565b6004546001600160a01b03163314610f0a5760405162461bcd60e51b81526004016100e79061114d565b6001600160a01b038216600081815260016020908152604091829020805460ff19168515159081179091558251938452908301527f6dad0aed33f4b7f07095619b668698e17943fd9f4c83e7cfcc7f6dd880a11588910160405180910390a15050565b6001600160a01b0381168114610f8257600080fd5b50565b600060208284031215610f9757600080fd5b8135610fa281610f6d565b9392505050565b60008083601f840112610fbb57600080fd5b50813567ffffffffffffffff811115610fd357600080fd5b602083019150836020828501011115610feb57600080fd5b9250929050565b6000806000806000806060878903121561100b57600080fd5b863567ffffffffffffffff8082111561102357600080fd5b61102f8a838b01610fa9565b9098509650602089013591508082111561104857600080fd5b6110548a838b01610fa9565b9096509450604089013591508082111561106d57600080fd5b5061107a89828a01610fa9565b979a9699509497509295939492505050565b6000806040838503121561109f57600080fd5b82356110aa81610f6d565b946020939093013593505050565b6000806000606084860312156110cd57600080fd5b83356110d881610f6d565b92506020840135915060408401356001600160801b03811681146110fb57600080fd5b809150509250925092565b8015158114610f8257600080fd5b6000806040838503121561112757600080fd5b823561113281610f6d565b9150602083013561114281611106565b809150509250929050565b6020808252600e908201526d27b7363c9037bbb732b91031b0b760911b604082015260600190565b634e487b7160e01b600052604160045260246000fd5b6000602080838503121561119e57600080fd5b825167ffffffffffffffff808211156111b657600080fd5b818501915085601f8301126111ca57600080fd5b8151818111156111dc576111dc611175565b604051601f8201601f19908116603f0116810190838211818310171561120457611204611175565b81604052828152888684870101111561121c57600080fd5b600093505b8284101561123e5784840186015181850187015292850192611221565b8284111561124f5760008684830101525b98975050505050505050565b60006020828403121561126d57600080fd5b8151610fa281611106565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b6060815260006112b560608301888a611278565b82810360208401526112c8818789611278565b905082810360408401526112dd818587611278565b9998505050505050505050565b6000602082840312156112fc57600080fd5b8151610fa281610f6d565b60006020828403121561131957600080fd5b5051919050565b6001600160a01b03938416815291909216602082015260408101919091526060019056fea2646970667358221220a118fe8c3b83b3933baa7bfe084ed165a014ec509367252e5c99e1a3332d000364736f6c634300080f0033
\ No newline at end of file
addedtests/src/eth/fractionalizer/Fractionalizer.soldiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/fractionalizer/Fractionalizer.sol
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier:  Apache License
+pragma solidity >=0.8.0;
+import {CollectionHelpers} from "../api/CollectionHelpers.sol";
+import {ContractHelpers} from "../api/ContractHelpers.sol";
+import {UniqueRefungibleToken} from "../api/UniqueRefungibleToken.sol";
+import {UniqueRefungible} from "../api/UniqueRefungible.sol";
+import {UniqueNFT} from "../api/UniqueNFT.sol";
+
+/// @dev Fractionalization contract. It stores mappings between NFT and RFT tokens,
+///  stores allowlist of NFT tokens available for fractionalization, has methods
+///  for fractionalization and defractionalization of NFT tokens.
+contract Fractionalizer {
+    struct Token {
+        address _collection;
+        uint256 _tokenId;
+    }
+    address rftCollection;
+    mapping(address => bool) nftCollectionAllowList;
+    mapping(address => mapping(uint256 => uint256)) nft2rftMapping;
+    mapping(address => Token) rft2nftMapping;
+    bytes32 refungibleCollectionType = keccak256(bytes("ReFungible"));
+
+    receive() external payable onlyOwner {}
+
+    /// @dev Method modifier to only allow contract owner to call it.
+    modifier onlyOwner() {
+        address contracthelpersAddress = 0x842899ECF380553E8a4de75bF534cdf6fBF64049;
+        ContractHelpers contractHelpers = ContractHelpers(contracthelpersAddress);
+        address contractOwner = contractHelpers.contractOwner(address(this));
+        require(msg.sender == contractOwner, "Only owner can");
+        _;
+    }
+
+    /// @dev This emits when RFT collection setting is changed.
+    event RFTCollectionSet(address _collection);
+
+    /// @dev This emits when NFT collection is allowed or disallowed.
+    event AllowListSet(address _collection, bool _status);
+
+    /// @dev This emits when NFT token is fractionalized by contract.
+    event Fractionalized(address _collection, uint256 _tokenId, address _rftToken, uint128 _amount);
+
+    /// @dev This emits when NFT token is defractionalized by contract.
+    event Defractionalized(address _rftToken, address _nftCollection, uint256 _nftTokenId);
+
+    /// Set RFT collection that contract will work with. RFT tokens for fractionalized NFT tokens
+    /// would be created in this collection.
+    /// @dev Throws if RFT collection is already configured for this contract.
+    ///  Throws if collection of wrong type (NFT, Fungible) is provided instead
+    ///  of RFT collection.
+    ///  Throws if `msg.sender` is not owner or admin of provided RFT collection.
+    ///  Can only be called by contract owner.
+    /// @param _collection address of RFT collection.
+    function setRFTCollection(address _collection) public onlyOwner {
+        require(
+            rftCollection == address(0),
+            "RFT collection is already set"
+        );
+        UniqueRefungible refungibleContract = UniqueRefungible(_collection);
+        string memory collectionType = refungibleContract.uniqueCollectionType();
+        
+        require(
+            keccak256(bytes(collectionType)) == refungibleCollectionType,
+            "Wrong collection type. Collection is not refungible."
+        );
+        require(
+            refungibleContract.verifyOwnerOrAdmin(address(this)),
+            "Fractionalizer contract should be an admin of the collection"
+        );
+        rftCollection = _collection;
+        emit RFTCollectionSet(rftCollection);
+    }
+
+    /// Creates and sets RFT collection that contract will work with. RFT tokens for fractionalized NFT tokens
+    /// would be created in this collection.
+    /// @dev Throws if RFT collection is already configured for this contract.
+    ///  Can only be called by contract owner.
+    /// @param _name name for created RFT collection.
+    /// @param _description description for created RFT collection.
+    /// @param _tokenPrefix token prefix for created RFT collection.
+    function createAndSetRFTCollection(string calldata _name, string calldata _description, string calldata _tokenPrefix) public onlyOwner {
+        require(
+            rftCollection == address(0),
+            "RFT collection is already set"
+        );
+        address collectionHelpers = 0x6C4E9fE1AE37a41E93CEE429e8E1881aBdcbb54F;
+        rftCollection = CollectionHelpers(collectionHelpers).createRefungibleCollection(_name, _description, _tokenPrefix);
+        emit RFTCollectionSet(rftCollection);
+    }
+
+    /// Allow or disallow NFT collection tokens from being fractionalized by this contract.
+    /// @dev Can only be called by contract owner.
+    /// @param collection NFT token address.
+    /// @param status `true` to allow and `false` to disallow NFT token.
+    function setNftCollectionIsAllowed(address collection, bool status) public onlyOwner {
+        nftCollectionAllowList[collection] = status;
+        emit AllowListSet(collection, status);
+    }
+
+    /// Fractionilize NFT token.
+    /// @dev Takes NFT token from `msg.sender` and transfers RFT token to `msg.sender`
+    ///  instead. Creates new RFT token if provided NFT token never was fractionalized
+    ///  by this contract or existing RFT token if it was.
+    ///  Throws if RFT collection isn't configured for this contract.
+    ///  Throws if fractionalization of provided NFT token is not allowed
+    ///  Throws if `msg.sender` is not owner of provided NFT token
+    /// @param  _collection NFT collection address
+    /// @param  _token id of NFT token to be fractionalized
+    /// @param  _pieces number of pieces new RFT token would have
+    function nft2rft(address _collection, uint256 _token, uint128 _pieces) public {
+        require(
+            rftCollection != address(0),
+            "RFT collection is not set"
+        );
+        UniqueRefungible rftCollectionContract = UniqueRefungible(rftCollection);
+        require(
+            nftCollectionAllowList[_collection] == true,
+            "Fractionalization of this collection is not allowed by admin"
+        );
+        require(
+            UniqueNFT(_collection).ownerOf(_token) == msg.sender,
+            "Only token owner could fractionalize it"
+        );
+        UniqueNFT(_collection).transferFrom(
+            msg.sender,
+            address(this),
+            _token
+        );
+        uint256 rftTokenId;
+        address rftTokenAddress;
+        UniqueRefungibleToken rftTokenContract;
+        if (nft2rftMapping[_collection][_token] == 0) {
+            rftTokenId = rftCollectionContract.nextTokenId();
+            rftCollectionContract.mint(address(this), rftTokenId);
+            rftTokenAddress = rftCollectionContract.tokenContractAddress(rftTokenId);
+            nft2rftMapping[_collection][_token] = rftTokenId;
+            rft2nftMapping[rftTokenAddress] = Token(_collection, _token);
+
+            rftTokenContract = UniqueRefungibleToken(rftTokenAddress);
+            rftTokenContract.setParentNFT(_collection, _token);
+        } else {
+            rftTokenId = nft2rftMapping[_collection][_token];
+            rftTokenAddress = rftCollectionContract.tokenContractAddress(rftTokenId);
+            rftTokenContract = UniqueRefungibleToken(rftTokenAddress);
+        }
+        rftTokenContract.repartition(_pieces);
+        rftTokenContract.transfer(msg.sender, _pieces);
+        emit Fractionalized(_collection, _token, rftTokenAddress, _pieces);
+    }
+
+    /// Defrationalize NFT token.
+    /// @dev Takes RFT token from `msg.sender` and transfers corresponding NFT token
+    ///  to `msg.sender` instead.
+    ///  Throws if RFT collection isn't configured for this contract.
+    ///  Throws if provided RFT token is no from configured RFT collection.
+    ///  Throws if RFT token was not created by this contract.
+    ///  Throws if `msg.sender` isn't owner of all RFT token pieces.
+    /// @param _collection RFT collection address
+    /// @param _token id of RFT token
+    function rft2nft(address _collection, uint256 _token) public {
+        require(
+            rftCollection != address(0),
+            "RFT collection is not set"
+        );
+        require(
+            rftCollection == _collection,
+            "Wrong RFT collection"
+        );
+        UniqueRefungible rftCollectionContract = UniqueRefungible(rftCollection);
+        address rftTokenAddress = rftCollectionContract.tokenContractAddress(_token);
+        Token memory nftToken = rft2nftMapping[rftTokenAddress];
+        require(
+            nftToken._collection != address(0),
+            "No corresponding NFT token found"
+        );
+        UniqueRefungibleToken rftTokenContract = UniqueRefungibleToken(rftTokenAddress);
+        require(
+            rftTokenContract.balanceOf(msg.sender) == rftTokenContract.totalSupply(),
+            "Not all pieces are owned by the caller"
+        );
+        rftCollectionContract.transferFrom(msg.sender, address(this), _token);
+        UniqueNFT(nftToken._collection).transferFrom(
+            address(this),
+            msg.sender,
+            nftToken._tokenId
+        );
+        emit Defractionalized(rftTokenAddress, nftToken._collection, nftToken._tokenId);
+    }
+}
\ No newline at end of file
addedtests/src/eth/fractionalizer/FractionalizerAbi.jsondiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/fractionalizer/FractionalizerAbi.json
@@ -0,0 +1,142 @@
+[
+  { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": false,
+        "internalType": "address",
+        "name": "_collection",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "bool",
+        "name": "_status",
+        "type": "bool"
+      }
+    ],
+    "name": "AllowListSet",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": false,
+        "internalType": "address",
+        "name": "_rftToken",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "address",
+        "name": "_nftCollection",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "_nftTokenId",
+        "type": "uint256"
+      }
+    ],
+    "name": "Defractionalized",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": false,
+        "internalType": "address",
+        "name": "_collection",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "_tokenId",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "address",
+        "name": "_rftToken",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint128",
+        "name": "_amount",
+        "type": "uint128"
+      }
+    ],
+    "name": "Fractionalized",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": false,
+        "internalType": "address",
+        "name": "_collection",
+        "type": "address"
+      }
+    ],
+    "name": "RFTCollectionSet",
+    "type": "event"
+  },
+  {
+    "inputs": [
+      { "internalType": "string", "name": "_name", "type": "string" },
+      { "internalType": "string", "name": "_description", "type": "string" },
+      { "internalType": "string", "name": "_tokenPrefix", "type": "string" }
+    ],
+    "name": "createAndSetRFTCollection",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "_collection", "type": "address" },
+      { "internalType": "uint256", "name": "_token", "type": "uint256" },
+      { "internalType": "uint128", "name": "_pieces", "type": "uint128" }
+    ],
+    "name": "nft2rft",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "_collection", "type": "address" },
+      { "internalType": "uint256", "name": "_token", "type": "uint256" }
+    ],
+    "name": "rft2nft",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "collection", "type": "address" },
+      { "internalType": "bool", "name": "status", "type": "bool" }
+    ],
+    "name": "setNftCollectionIsAllowed",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "_collection", "type": "address" }
+    ],
+    "name": "setRFTCollection",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  }
+]
addedtests/src/eth/fractionalizer/fractionalizer.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/fractionalizer/fractionalizer.test.ts
@@ -0,0 +1,470 @@
+// 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/>.
+
+
+import Web3 from 'web3';
+import {ApiPromise} from '@polkadot/api';
+import {evmToAddress} from '@polkadot/util-crypto';
+import {readFile} from 'fs/promises';
+import {executeTransaction, submitTransactionAsync} from '../../substrate/substrate-api';
+import {getCreateCollectionResult, getCreateItemResult, UNIQUE} from '../../util/helpers';
+import {collectionIdToAddress, CompiledContract, createEthAccountWithBalance, createNonfungibleCollection, createRefungibleCollection, GAS_ARGS, itWeb3, tokenIdFromAddress, uniqueNFT, uniqueRefungible, uniqueRefungibleToken} from '../util/helpers';
+import {Contract} from 'web3-eth-contract';
+import * as solc from 'solc';
+
+import chai from 'chai';
+import chaiLike from 'chai-like';
+import {IKeyringPair} from '@polkadot/types/types';
+chai.use(chaiLike);
+const expect = chai.expect;
+let fractionalizer: CompiledContract;
+
+async function compileFractionalizer() {
+  if (!fractionalizer) {
+    const input = {
+      language: 'Solidity',
+      sources: {
+        ['Fractionalizer.sol']: {
+          content: (await readFile(`${__dirname}/Fractionalizer.sol`)).toString(),
+        },
+      },
+      settings: {
+        outputSelection: {
+          '*': {
+            '*': ['*'],
+          },
+        },
+      },
+    };
+    const json = JSON.parse(solc.compile(JSON.stringify(input), {import: await findImports()}));
+    const out = json.contracts['Fractionalizer.sol']['Fractionalizer'];
+
+    fractionalizer = {
+      abi: out.abi,
+      object: '0x' + out.evm.bytecode.object,
+    };
+  }
+  return fractionalizer;
+}
+
+async function findImports() {
+  const collectionHelpers = (await readFile(`${__dirname}/../api/CollectionHelpers.sol`)).toString();
+  const contractHelpers = (await readFile(`${__dirname}/../api/ContractHelpers.sol`)).toString();
+  const uniqueRefungibleToken = (await readFile(`${__dirname}/../api/UniqueRefungibleToken.sol`)).toString();
+  const uniqueRefungible = (await readFile(`${__dirname}/../api/UniqueRefungible.sol`)).toString();
+  const uniqueNFT = (await readFile(`${__dirname}/../api/UniqueNFT.sol`)).toString();
+
+  return function(path: string) {
+    switch (path) {
+      case 'api/CollectionHelpers.sol': return {contents: `${collectionHelpers}`};
+      case 'api/ContractHelpers.sol': return {contents: `${contractHelpers}`};
+      case 'api/UniqueRefungibleToken.sol': return {contents: `${uniqueRefungibleToken}`};
+      case 'api/UniqueRefungible.sol': return {contents: `${uniqueRefungible}`};
+      case 'api/UniqueNFT.sol': return {contents: `${uniqueNFT}`};
+      default: return {error: 'File not found'};
+    }
+  };
+}
+
+async function deployFractionalizer(web3: Web3, owner: string) {
+  const compiled = await compileFractionalizer();
+  const fractionalizerContract = new web3.eth.Contract(compiled.abi, undefined, {
+    data: compiled.object,
+    from: owner,
+    ...GAS_ARGS,
+  });
+  return await fractionalizerContract.deploy({data: compiled.object}).send({from: owner});
+}
+
+async function initFractionalizer(api: ApiPromise, web3: Web3, privateKeyWrapper: (account: string) => IKeyringPair, owner: string) {
+  const fractionalizer = await deployFractionalizer(web3, owner);
+  const amount = 10n * UNIQUE;
+  await web3.eth.sendTransaction({from: owner, to: fractionalizer.options.address, value: `${amount}`, ...GAS_ARGS});
+  const result = await fractionalizer.methods.createAndSetRFTCollection('A', 'B', 'C').send();
+  const rftCollectionAddress = result.events.RFTCollectionSet.returnValues._collection;
+  return {fractionalizer, rftCollectionAddress};
+}
+
+async function createRFTToken(api: ApiPromise, web3: Web3, owner: string, fractionalizer: Contract, amount: bigint) {
+  const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner);
+  const nftContract = uniqueNFT(web3, nftCollectionAddress, owner);
+  const nftTokenId = await nftContract.methods.nextTokenId().call();
+  await nftContract.methods.mint(owner, nftTokenId).send();
+
+  await fractionalizer.methods.setNftCollectionIsAllowed(nftCollectionAddress, true).send();
+  await nftContract.methods.approve(fractionalizer.options.address, nftTokenId).send();
+  const result = await fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, amount).send();
+  const {_collection, _tokenId, _rftToken} = result.events.Fractionalized.returnValues;
+  return {
+    nftCollectionAddress: _collection,
+    nftTokenId: _tokenId,
+    rftTokenAddress: _rftToken,
+  };
+}
+
+describe('Fractionalizer contract usage', () => {
+  itWeb3('Set RFT collection', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const fractionalizer = await deployFractionalizer(web3, owner);
+    const {collectionIdAddress} = await createRefungibleCollection(api, web3, owner);
+    const refungibleContract = uniqueRefungible(web3, collectionIdAddress, owner);
+    await refungibleContract.methods.addCollectionAdmin(fractionalizer.options.address).send();
+    const result = await fractionalizer.methods.setRFTCollection(collectionIdAddress).send();
+    expect(result.events).to.be.like({
+      RFTCollectionSet: {
+        returnValues: {
+          _collection: collectionIdAddress,
+        },
+      },
+    });
+  });
+
+  itWeb3('Mint RFT collection', async ({api, web3, privateKeyWrapper}) => {
+    const alice = privateKeyWrapper('//Alice');
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const fractionalizer = await deployFractionalizer(web3, owner);
+    const tx = api.tx.balances.transfer(evmToAddress(fractionalizer.options.address), 10n * UNIQUE);
+    await submitTransactionAsync(alice, tx);
+
+    const result = await fractionalizer.methods.createAndSetRFTCollection('A', 'B', 'C').send({from: owner});
+    expect(result.events).to.be.like({
+      RFTCollectionSet: {},
+    });
+    expect(result.events.RFTCollectionSet.returnValues._collection).to.be.ok;
+  });
+
+  itWeb3('Set Allowlist', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, owner);
+    const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner);
+    const result1 = await fractionalizer.methods.setNftCollectionIsAllowed(nftCollectionAddress, true).send({from: owner});
+    expect(result1.events).to.be.like({
+      AllowListSet: {
+        returnValues: {
+          _collection: nftCollectionAddress,
+          _status: true,
+        },
+      },
+    });
+    const result2 = await fractionalizer.methods.setNftCollectionIsAllowed(nftCollectionAddress, false).send({from: owner});
+    expect(result2.events).to.be.like({
+      AllowListSet: {
+        returnValues: {
+          _collection: nftCollectionAddress,
+          _status: false,
+        },
+      },
+    });
+  });
+
+  itWeb3('NFT to RFT', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+
+    const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner);
+    const nftContract = uniqueNFT(web3, nftCollectionAddress, owner);
+    const nftTokenId = await nftContract.methods.nextTokenId().call();
+    await nftContract.methods.mint(owner, nftTokenId).send();
+
+    const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, owner);
+
+    await fractionalizer.methods.setNftCollectionIsAllowed(nftCollectionAddress, true).send();
+    await nftContract.methods.approve(fractionalizer.options.address, nftTokenId).send();
+    const result = await fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, 100).send();
+    expect(result.events).to.be.like({
+      Fractionalized: {
+        returnValues: {
+          _collection: nftCollectionAddress,
+          _tokenId: nftTokenId,
+          _amount: '100',
+        },
+      },
+    });
+    const rftTokenAddress = result.events.Fractionalized.returnValues._rftToken;
+    const rftTokenContract = uniqueRefungibleToken(web3, rftTokenAddress, owner);
+    expect(await rftTokenContract.methods.balanceOf(owner).call()).to.equal('100');
+  });
+
+  itWeb3('RFT to NFT', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+
+    const {fractionalizer, rftCollectionAddress} = await initFractionalizer(api, web3, privateKeyWrapper, owner);
+    const {rftTokenAddress, nftCollectionAddress, nftTokenId} = await createRFTToken(api, web3, owner, fractionalizer, 100n);
+
+    const {collectionId, tokenId} = tokenIdFromAddress(rftTokenAddress);
+    const refungibleAddress = collectionIdToAddress(collectionId);
+    expect(rftCollectionAddress).to.be.equal(refungibleAddress);
+    const refungibleTokenContract = uniqueRefungibleToken(web3, rftTokenAddress, owner);
+    await refungibleTokenContract.methods.approve(fractionalizer.options.address, 100).send();
+    const result = await fractionalizer.methods.rft2nft(refungibleAddress, tokenId).send();
+    expect(result.events).to.be.like({
+      Defractionalized: {
+        returnValues: {
+          _rftToken: rftTokenAddress,
+          _nftCollection: nftCollectionAddress,
+          _nftTokenId: nftTokenId,
+        },
+      },
+    });
+  });
+});
+
+
+
+describe('Negative Integration Tests for fractionalizer', () => {
+  itWeb3('call setRFTCollection twice', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const {collectionIdAddress} = await createRefungibleCollection(api, web3, owner);
+    const refungibleContract = uniqueRefungible(web3, collectionIdAddress, owner);
+
+    const fractionalizer = await deployFractionalizer(web3, owner);
+    await refungibleContract.methods.addCollectionAdmin(fractionalizer.options.address).send();
+    await fractionalizer.methods.setRFTCollection(collectionIdAddress).send();
+
+    await expect(fractionalizer.methods.setRFTCollection(collectionIdAddress).call())
+      .to.be.rejectedWith(/RFT collection is already set$/g);
+  });
+
+  itWeb3('call setRFTCollection with NFT collection', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const {collectionIdAddress} = await createNonfungibleCollection(api, web3, owner);
+    const nftContract = uniqueNFT(web3, collectionIdAddress, owner);
+
+    const fractionalizer = await deployFractionalizer(web3, owner);
+    await nftContract.methods.addCollectionAdmin(fractionalizer.options.address).send();
+
+    await expect(fractionalizer.methods.setRFTCollection(collectionIdAddress).call())
+      .to.be.rejectedWith(/Wrong collection type. Collection is not refungible.$/g);
+  });
+
+  itWeb3('call setRFTCollection while not collection admin', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const fractionalizer = await deployFractionalizer(web3, owner);
+    const {collectionIdAddress} = await createRefungibleCollection(api, web3, owner);
+
+    await expect(fractionalizer.methods.setRFTCollection(collectionIdAddress).call())
+      .to.be.rejectedWith(/Fractionalizer contract should be an admin of the collection$/g);
+  });
+
+  itWeb3('call setRFTCollection after createAndSetRFTCollection', async ({api, web3, privateKeyWrapper}) => {
+    const alice = privateKeyWrapper('//Alice');
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const fractionalizer = await deployFractionalizer(web3, owner);
+    const tx = api.tx.balances.transfer(evmToAddress(fractionalizer.options.address), 10n * UNIQUE);
+    await submitTransactionAsync(alice, tx);
+
+    const result = await fractionalizer.methods.createAndSetRFTCollection('A', 'B', 'C').send({from: owner});
+    const collectionIdAddress = result.events.RFTCollectionSet.returnValues._collection;
+
+    await expect(fractionalizer.methods.setRFTCollection(collectionIdAddress).call())
+      .to.be.rejectedWith(/RFT collection is already set$/g);
+  });
+
+  itWeb3('call nft2rft without setting RFT collection for contract', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+
+    const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner);
+    const nftContract = uniqueNFT(web3, nftCollectionAddress, owner);
+    const nftTokenId = await nftContract.methods.nextTokenId().call();
+    await nftContract.methods.mint(owner, nftTokenId).send();
+
+    const fractionalizer = await deployFractionalizer(web3, owner);
+
+    await expect(fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, 100).call())
+      .to.be.rejectedWith(/RFT collection is not set$/g);
+  });
+
+  itWeb3('call nft2rft while not owner of NFT token', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const nftOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+
+    const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner);
+    const nftContract = uniqueNFT(web3, nftCollectionAddress, owner);
+    const nftTokenId = await nftContract.methods.nextTokenId().call();
+    await nftContract.methods.mint(owner, nftTokenId).send();
+    await nftContract.methods.transfer(nftOwner, 1).send();
+
+
+    const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, owner);
+    await fractionalizer.methods.setNftCollectionIsAllowed(nftCollectionAddress, true).send();
+
+    await expect(fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, 100).call())
+      .to.be.rejectedWith(/Only token owner could fractionalize it$/g);
+  });
+
+  itWeb3('call nft2rft while not in list of allowed accounts', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+
+    const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner);
+    const nftContract = uniqueNFT(web3, nftCollectionAddress, owner);
+    const nftTokenId = await nftContract.methods.nextTokenId().call();
+    await nftContract.methods.mint(owner, nftTokenId).send();
+
+    const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, owner);
+
+    await nftContract.methods.approve(fractionalizer.options.address, nftTokenId).send();
+    await expect(fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, 100).call())
+      .to.be.rejectedWith(/Fractionalization of this collection is not allowed by admin$/g);
+  });
+
+  itWeb3('call nft2rft while fractionalizer doesnt have approval for nft token', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+
+    const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner);
+    const nftContract = uniqueNFT(web3, nftCollectionAddress, owner);
+    const nftTokenId = await nftContract.methods.nextTokenId().call();
+    await nftContract.methods.mint(owner, nftTokenId).send();
+
+    const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, owner);
+
+    await fractionalizer.methods.setNftCollectionIsAllowed(nftCollectionAddress, true).send();
+    await expect(fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, 100).call())
+      .to.be.rejectedWith(/ApprovedValueTooLow$/g);
+  });
+
+  itWeb3('call rft2nft without setting RFT collection for contract', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+
+    const fractionalizer = await deployFractionalizer(web3, owner);
+    const {collectionIdAddress: rftCollectionAddress} = await createRefungibleCollection(api, web3, owner);
+    const refungibleContract = uniqueRefungible(web3, rftCollectionAddress, owner);
+    const rftTokenId = await refungibleContract.methods.nextTokenId().call();
+    await refungibleContract.methods.mint(owner, rftTokenId).send();
+    
+    await expect(fractionalizer.methods.rft2nft(rftCollectionAddress, rftTokenId).call())
+      .to.be.rejectedWith(/RFT collection is not set$/g);
+  });
+
+  itWeb3('call rft2nft for RFT token that is not from configured RFT collection', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+
+    const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, owner);
+    const {collectionIdAddress: rftCollectionAddress} = await createRefungibleCollection(api, web3, owner);
+    const refungibleContract = uniqueRefungible(web3, rftCollectionAddress, owner);
+    const rftTokenId = await refungibleContract.methods.nextTokenId().call();
+    await refungibleContract.methods.mint(owner, rftTokenId).send();
+    
+    await expect(fractionalizer.methods.rft2nft(rftCollectionAddress, rftTokenId).call())
+      .to.be.rejectedWith(/Wrong RFT collection$/g);
+  });
+
+  itWeb3('call rft2nft for RFT token that was not minted by fractionalizer contract', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const {collectionIdAddress: rftCollectionAddress} = await createRefungibleCollection(api, web3, owner);
+
+    const fractionalizer = await deployFractionalizer(web3, owner);
+    const refungibleContract = uniqueRefungible(web3, rftCollectionAddress, owner);
+
+    await refungibleContract.methods.addCollectionAdmin(fractionalizer.options.address).send();
+    await fractionalizer.methods.setRFTCollection(rftCollectionAddress).send();
+
+    const rftTokenId = await refungibleContract.methods.nextTokenId().call();
+    await refungibleContract.methods.mint(owner, rftTokenId).send();
+    
+    await expect(fractionalizer.methods.rft2nft(rftCollectionAddress, rftTokenId).call())
+      .to.be.rejectedWith(/No corresponding NFT token found$/g);
+  });
+
+  itWeb3('call rft2nft without owning all RFT pieces', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const receiver = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+
+    const {fractionalizer, rftCollectionAddress} = await initFractionalizer(api, web3, privateKeyWrapper, owner);
+    const {rftTokenAddress} = await createRFTToken(api, web3, owner, fractionalizer, 100n);
+    
+    const {tokenId} = tokenIdFromAddress(rftTokenAddress);
+    const refungibleTokenContract = uniqueRefungibleToken(web3, rftTokenAddress, owner);
+    await refungibleTokenContract.methods.transfer(receiver, 50).send();
+    await refungibleTokenContract.methods.approve(fractionalizer.options.address, 50).send();
+    await expect(fractionalizer.methods.rft2nft(rftCollectionAddress, tokenId).call())
+      .to.be.rejectedWith(/Not all pieces are owned by the caller$/g);
+  });
+
+  itWeb3('send QTZ/UNQ to contract from non owner', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const payer = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+
+    const fractionalizer = await deployFractionalizer(web3, owner);
+    const amount = 10n * UNIQUE;
+    await expect(web3.eth.sendTransaction({from: payer, to: fractionalizer.options.address, value: `${amount}`, ...GAS_ARGS})).to.be.rejected;
+  });
+
+  itWeb3('fractionalize NFT with NFT transfers disallowed', async ({api, web3, privateKeyWrapper}) => {
+    const alice = privateKeyWrapper('//Alice');
+    let collectionId;
+    {
+      const tx = api.tx.unique.createCollectionEx({name: 'A', description: 'B', tokenPrefix: 'C', mode: 'NFT'});
+      const events = await submitTransactionAsync(alice, tx);
+      const result = getCreateCollectionResult(events);
+      collectionId = result.collectionId;
+    }
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    let nftTokenId;
+    {
+      const createData = {nft: {}};
+      const tx = api.tx.unique.createItem(collectionId, {Ethereum: owner}, createData as any);
+      const events = await executeTransaction(api, alice, tx);
+      const result = getCreateItemResult(events);
+      nftTokenId = result.itemId;
+    }
+    {
+      const tx = api.tx.unique.setTransfersEnabledFlag(collectionId, false);
+      await executeTransaction(api, alice, tx);
+    }
+    const nftCollectionAddress = collectionIdToAddress(collectionId);
+    const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, owner);
+    await fractionalizer.methods.setNftCollectionIsAllowed(nftCollectionAddress, true).send();
+
+    const nftContract = uniqueNFT(web3, nftCollectionAddress, owner);
+    await nftContract.methods.approve(fractionalizer.options.address, nftTokenId).send();
+    await expect(fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, 100).call())
+      .to.be.rejectedWith(/TransferNotAllowed$/g);
+  });
+  
+  itWeb3('fractionalize NFT with RFT transfers disallowed', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const alice = privateKeyWrapper('//Alice');
+
+    let collectionId;
+    {
+      const tx = api.tx.unique.createCollectionEx({name: 'A', description: 'B', tokenPrefix: 'C', mode: 'ReFungible'});
+      const events = await submitTransactionAsync(alice, tx);
+      const result = getCreateCollectionResult(events);
+      collectionId = result.collectionId;
+    }
+    const rftCollectionAddress = collectionIdToAddress(collectionId);
+    const fractionalizer = await deployFractionalizer(web3, owner);
+    {
+      const changeAdminTx = api.tx.unique.addCollectionAdmin(collectionId, {Ethereum: fractionalizer.options.address});
+      await submitTransactionAsync(alice, changeAdminTx);
+    }
+    await fractionalizer.methods.setRFTCollection(rftCollectionAddress).send();
+    {
+      const tx = api.tx.unique.setTransfersEnabledFlag(collectionId, false);
+      await executeTransaction(api, alice, tx);
+    }
+
+    const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner);
+    const nftContract = uniqueNFT(web3, nftCollectionAddress, owner);
+    const nftTokenId = await nftContract.methods.nextTokenId().call();
+    await nftContract.methods.mint(owner, nftTokenId).send();
+
+    await fractionalizer.methods.setNftCollectionIsAllowed(nftCollectionAddress, true).send();
+    await nftContract.methods.approve(fractionalizer.options.address, nftTokenId).send();
+
+    await expect(fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, 100n).call())
+      .to.be.rejectedWith(/TransferNotAllowed$/g);
+  });
+});
\ No newline at end of file
modifiedtests/src/eth/fungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/fungibleAbi.json
+++ b/tests/src/eth/fungibleAbi.json
@@ -301,5 +301,21 @@
     "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
     "stateMutability": "nonpayable",
     "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "uniqueCollectionType",
+    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "user", "type": "address" }
+    ],
+    "name": "verifyOwnerOrAdmin",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "view",
+    "type": "function"
   }
 ]
modifiedtests/src/eth/nonFungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/nonFungibleAbi.json
+++ b/tests/src/eth/nonFungibleAbi.json
@@ -526,5 +526,21 @@
     "outputs": [],
     "stateMutability": "nonpayable",
     "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "uniqueCollectionType",
+    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "user", "type": "address" }
+    ],
+    "name": "verifyOwnerOrAdmin",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "view",
+    "type": "function"
   }
 ]
modifiedtests/src/eth/reFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/reFungible.test.ts
+++ b/tests/src/eth/reFungible.test.ts
@@ -14,12 +14,15 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-import {createCollectionExpectSuccess, UNIQUE} from '../util/helpers';
-import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, evmCollection, evmCollectionHelpers, GAS_ARGS, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, recordEvents, tokenIdToAddress} from './util/helpers';
-import reFungibleTokenAbi from './reFungibleTokenAbi.json';
+import {createCollectionExpectSuccess, UNIQUE, requirePallets, Pallets} from '../util/helpers';
+import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, evmCollection, evmCollectionHelpers, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, recordEvents, tokenIdToAddress, uniqueRefungibleToken} from './util/helpers';
 import {expect} from 'chai';
 
 describe('Refungible: Information getting', () => {
+  before(async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+  });
+
   itWeb3('totalSupply', async ({api, web3, privateKeyWrapper}) => {
     const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const helper = evmCollectionHelpers(web3, caller);
@@ -84,7 +87,7 @@
     await contract.methods.mint(caller, tokenId).send();
 
     const tokenAddress = tokenIdToAddress(collectionId, tokenId);
-    const tokenContract = new web3.eth.Contract(reFungibleTokenAbi as any, tokenAddress, {from: caller, ...GAS_ARGS});
+    const tokenContract = uniqueRefungibleToken(web3, tokenAddress, caller);
 
     await tokenContract.methods.repartition(2).send();
     await tokenContract.methods.transfer(receiver, 1).send();
@@ -108,7 +111,7 @@
     await contract.methods.mint(caller, tokenId).send();
 
     const tokenAddress = tokenIdToAddress(collectionId, tokenId);
-    const tokenContract = new web3.eth.Contract(reFungibleTokenAbi as any, tokenAddress, {from: caller, ...GAS_ARGS});
+    const tokenContract = uniqueRefungibleToken(web3, tokenAddress, caller);
 
     await tokenContract.methods.repartition(2).send();
     await tokenContract.methods.transfer(receiver, 1).send();
@@ -120,6 +123,10 @@
 });
 
 describe('Refungible: Plain calls', () => {
+  before(async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+  });
+
   itWeb3('Can perform mint()', async ({web3, api, privateKeyWrapper}) => {
     const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const helper = evmCollectionHelpers(web3, owner);
@@ -250,7 +257,7 @@
     await contract.methods.mint(caller, tokenId).send();
 
     const address = tokenIdToAddress(collectionId, tokenId);
-    const tokenContract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: caller, ...GAS_ARGS});
+    const tokenContract = uniqueRefungibleToken(web3, address, caller);
     await tokenContract.methods.repartition(15).send();
 
     {
@@ -269,7 +276,7 @@
           },
         ]);
       });
-      
+
       expect(erc20Events).to.include.deep.members([
         {
           address,
@@ -345,12 +352,12 @@
     await contract.methods.mint(caller, tokenId).send();
 
     const tokenAddress = tokenIdToAddress(collectionId, tokenId);
-    const tokenContract = new web3.eth.Contract(reFungibleTokenAbi as any, tokenAddress, {from: caller, ...GAS_ARGS});
+    const tokenContract = uniqueRefungibleToken(web3, tokenAddress, caller);
 
     await tokenContract.methods.repartition(2).send();
     await tokenContract.methods.transfer(receiver, 1).send();
 
-    const events =  await recordEvents(contract, async () => 
+    const events =  await recordEvents(contract, async () =>
       await tokenContract.methods.transfer(receiver, 1).send());
     expect(events).to.deep.equal([
       {
@@ -377,13 +384,13 @@
     await contract.methods.mint(caller, tokenId).send();
 
     const tokenAddress = tokenIdToAddress(collectionId, tokenId);
-    const tokenContract = new web3.eth.Contract(reFungibleTokenAbi as any, tokenAddress, {from: caller, ...GAS_ARGS});
+    const tokenContract = uniqueRefungibleToken(web3, tokenAddress, caller);
 
     await tokenContract.methods.repartition(2).send();
-    
-    const events =  await recordEvents(contract, async () => 
+
+    const events =  await recordEvents(contract, async () =>
       await tokenContract.methods.transfer(receiver, 1).send());
-      
+
     expect(events).to.deep.equal([
       {
         address: collectionIdAddress,
@@ -399,6 +406,10 @@
 });
 
 describe('RFT: Fees', () => {
+  before(async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+  });
+
   itWeb3('transferFrom() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => {
     const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const helper = evmCollectionHelpers(web3, caller);
@@ -435,6 +446,10 @@
 });
 
 describe('Common metadata', () => {
+  before(async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+  });
+
   itWeb3('Returns collection name', async ({api, web3, privateKeyWrapper}) => {
     const collection = await createCollectionExpectSuccess({
       name: 'token name',
@@ -462,4 +477,4 @@
 
     expect(symbol).to.equal('TOK');
   });
-});
\ No newline at end of file
+});
modifiedtests/src/eth/reFungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/reFungibleAbi.json
+++ b/tests/src/eth/reFungibleAbi.json
@@ -482,6 +482,15 @@
   },
   {
     "inputs": [
+      { "internalType": "uint256", "name": "token", "type": "uint256" }
+    ],
+    "name": "tokenContractAddress",
+    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "owner", "type": "address" },
       { "internalType": "uint256", "name": "index", "type": "uint256" }
     ],
@@ -526,5 +535,21 @@
     "outputs": [],
     "stateMutability": "nonpayable",
     "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "uniqueCollectionType",
+    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      { "internalType": "address", "name": "user", "type": "address" }
+    ],
+    "name": "verifyOwnerOrAdmin",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "view",
+    "type": "function"
   }
 ]
modifiedtests/src/eth/reFungibleToken.test.tsdiffbeforeafterboth
--- a/tests/src/eth/reFungibleToken.test.ts
+++ b/tests/src/eth/reFungibleToken.test.ts
@@ -14,9 +14,8 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-import {approve, createCollection, createRefungibleToken, transfer, transferFrom, UNIQUE} from '../util/helpers';
-import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, evmCollection, evmCollectionHelpers, GAS_ARGS, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, tokenIdToAddress, transferBalanceToEth} from './util/helpers';
-import reFungibleTokenAbi from './reFungibleTokenAbi.json';
+import {approve, createCollection, createRefungibleToken, transfer, transferFrom, UNIQUE, requirePallets, Pallets} from '../util/helpers';
+import {collectionIdFromAddress, collectionIdToAddress, createEthAccount, createEthAccountWithBalance, createNonfungibleCollection, createRefungibleCollection, evmCollection, evmCollectionHelpers, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, tokenIdToAddress, transferBalanceToEth, uniqueNFT, uniqueRefungible, uniqueRefungibleToken} from './util/helpers';
 
 import chai from 'chai';
 import chaiAsPromised from 'chai-as-promised';
@@ -24,6 +23,10 @@
 const expect = chai.expect;
 
 describe('Refungible token: Information getting', () => {
+  before(async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+  });
+
   itWeb3('totalSupply', async ({api, web3, privateKeyWrapper}) => {
     const alice = privateKeyWrapper('//Alice');
 
@@ -34,7 +37,7 @@
     const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: caller})).itemId;
 
     const address = tokenIdToAddress(collectionId, tokenId);
-    const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: caller, ...GAS_ARGS});
+    const contract = uniqueRefungibleToken(web3, address, caller);
     const totalSupply = await contract.methods.totalSupply().call();
 
     expect(totalSupply).to.equal('200');
@@ -50,7 +53,7 @@
     const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: caller})).itemId;
 
     const address = tokenIdToAddress(collectionId, tokenId);
-    const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: caller, ...GAS_ARGS});
+    const contract = uniqueRefungibleToken(web3, address, caller);
     const balance = await contract.methods.balanceOf(caller).call();
 
     expect(balance).to.equal('200');
@@ -66,7 +69,7 @@
     const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: caller})).itemId;
 
     const address = tokenIdToAddress(collectionId, tokenId);
-    const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: caller, ...GAS_ARGS});
+    const contract = uniqueRefungibleToken(web3, address, caller);
     const decimals = await contract.methods.decimals().call();
 
     expect(decimals).to.equal('0');
@@ -75,6 +78,10 @@
 
 // FIXME: Need erc721 for ReFubgible.
 describe('Check ERC721 token URI for ReFungible', () => {
+  before(async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+  });
+
   itWeb3('Empty tokenURI', async ({web3, api, privateKeyWrapper}) => {
     const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const helper = evmCollectionHelpers(web3, owner);
@@ -82,7 +89,7 @@
     const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
     const receiver = createEthAccount(web3);
     const contract = evmCollection(web3, owner, collectionIdAddress, {type: 'ReFungible'});
-    
+
     const nextTokenId = await contract.methods.nextTokenId().call();
     expect(nextTokenId).to.be.equal('1');
     result = await contract.methods.mint(
@@ -115,17 +122,17 @@
     const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
     const receiver = createEthAccount(web3);
     const contract = evmCollection(web3, owner, collectionIdAddress, {type: 'ReFungible'});
-    
+
     const nextTokenId = await contract.methods.nextTokenId().call();
     expect(nextTokenId).to.be.equal('1');
     result = await contract.methods.mint(
       receiver,
       nextTokenId,
     ).send();
-    
+
     // Set URL
     await contract.methods.setProperty(nextTokenId, 'url', Buffer.from('Token URI')).send();
-      
+
     const events = normalizeEvents(result.events);
     const address = collectionIdToAddress(collectionId);
 
@@ -151,14 +158,14 @@
     const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
     const receiver = createEthAccount(web3);
     const contract = evmCollection(web3, owner, collectionIdAddress, {type: 'ReFungible'});
-    
+
     const nextTokenId = await contract.methods.nextTokenId().call();
     expect(nextTokenId).to.be.equal('1');
     result = await contract.methods.mint(
       receiver,
       nextTokenId,
     ).send();
-          
+
     const events = normalizeEvents(result.events);
     const address = collectionIdToAddress(collectionId);
 
@@ -184,14 +191,14 @@
     const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
     const receiver = createEthAccount(web3);
     const contract = evmCollection(web3, owner, collectionIdAddress, {type: 'ReFungible'});
-    
+
     const nextTokenId = await contract.methods.nextTokenId().call();
     expect(nextTokenId).to.be.equal('1');
     result = await contract.methods.mint(
       receiver,
       nextTokenId,
     ).send();
-          
+
     // Set suffix
     const suffix = '/some/suffix';
     await contract.methods.setProperty(nextTokenId, 'suffix', Buffer.from(suffix)).send();
@@ -216,6 +223,10 @@
 });
 
 describe('Refungible: Plain calls', () => {
+  before(async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+  });
+
   itWeb3('Can perform approve()', async ({web3, api, privateKeyWrapper}) => {
     const alice = privateKeyWrapper('//Alice');
 
@@ -229,7 +240,7 @@
 
     const spender = createEthAccount(web3);
 
-    const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS});
+    const contract = uniqueRefungibleToken(web3, address, owner);
 
     {
       const result = await contract.methods.approve(spender, 100).send({from: owner});
@@ -270,7 +281,7 @@
     const receiver = createEthAccount(web3);
 
     const address = tokenIdToAddress(collectionId, tokenId);
-    const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS});
+    const contract = uniqueRefungibleToken(web3, address, owner);
 
     await contract.methods.approve(spender, 100).send();
 
@@ -324,7 +335,7 @@
     await transferBalanceToEth(api, alice, receiver);
 
     const address = tokenIdToAddress(collectionId, tokenId);
-    const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS});
+    const contract = uniqueRefungibleToken(web3, address, owner);
 
     {
       const result = await contract.methods.transfer(receiver, 50).send({from: owner});
@@ -367,14 +378,14 @@
     const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n, {Ethereum: owner})).itemId;
 
     const address = tokenIdToAddress(collectionId, tokenId);
-    const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS});
+    const contract = uniqueRefungibleToken(web3, address, owner);
 
     await contract.methods.repartition(200).send({from: owner});
     expect(+await contract.methods.balanceOf(owner).call()).to.be.equal(200);
     await contract.methods.transfer(receiver, 110).send({from: owner});
     expect(+await contract.methods.balanceOf(owner).call()).to.be.equal(90);
     expect(+await contract.methods.balanceOf(receiver).call()).to.be.equal(110);
-    
+
     await expect(contract.methods.repartition(80).send({from: owner})).to.eventually.be.rejected;
 
     await contract.methods.transfer(receiver, 90).send({from: owner});
@@ -397,7 +408,7 @@
     const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n, {Ethereum: owner})).itemId;
 
     const address = tokenIdToAddress(collectionId, tokenId);
-    const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS});
+    const contract = uniqueRefungibleToken(web3, address, owner);
 
     const result = await contract.methods.repartition(200).send();
     const events = normalizeEvents(result.events);
@@ -426,7 +437,7 @@
     const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n, {Ethereum: owner})).itemId;
 
     const address = tokenIdToAddress(collectionId, tokenId);
-    const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS});
+    const contract = uniqueRefungibleToken(web3, address, owner);
 
     const result = await contract.methods.repartition(50).send();
     const events = normalizeEvents(result.events);
@@ -456,11 +467,11 @@
 
     const address = tokenIdToAddress(collectionId, tokenId);
 
-    const tokenContract =  new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: caller, ...GAS_ARGS});
+    const tokenContract =  uniqueRefungibleToken(web3, address, caller);
     await tokenContract.methods.repartition(2).send();
     await tokenContract.methods.transfer(receiver, 1).send();
 
-    const events =  await recordEvents(contract, async () => 
+    const events =  await recordEvents(contract, async () =>
       await tokenContract.methods.burnFrom(caller, 1).send());
     expect(events).to.deep.equal([
       {
@@ -477,6 +488,10 @@
 });
 
 describe('Refungible: Fees', () => {
+  before(async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+  });
+
   itWeb3('approve() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => {
     const alice = privateKeyWrapper('//Alice');
 
@@ -488,7 +503,7 @@
     const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: owner})).itemId;
 
     const address = tokenIdToAddress(collectionId, tokenId);
-    const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS});
+    const contract = uniqueRefungibleToken(web3, address, owner);
 
     const cost = await recordEthFee(api, owner, () => contract.methods.approve(spender, 100).send({from: owner}));
     expect(cost < BigInt(0.2 * Number(UNIQUE)));
@@ -505,7 +520,7 @@
     const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: owner})).itemId;
 
     const address = tokenIdToAddress(collectionId, tokenId);
-    const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS});
+    const contract = uniqueRefungibleToken(web3, address, owner);
 
     await contract.methods.approve(spender, 100).send({from: owner});
 
@@ -524,7 +539,7 @@
     const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: owner})).itemId;
 
     const address = tokenIdToAddress(collectionId, tokenId);
-    const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS});
+    const contract = uniqueRefungibleToken(web3, address, owner);
 
     const cost = await recordEthFee(api, owner, () => contract.methods.transfer(receiver, 100).send({from: owner}));
     expect(cost < BigInt(0.2 * Number(UNIQUE)));
@@ -532,6 +547,10 @@
 });
 
 describe('Refungible: Substrate calls', () => {
+  before(async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+  });
+
   itWeb3('Events emitted for approve()', async ({web3, api, privateKeyWrapper}) => {
     const alice = privateKeyWrapper('//Alice');
 
@@ -542,7 +561,7 @@
     const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n)).itemId;
 
     const address = tokenIdToAddress(collectionId, tokenId);
-    const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address);
+    const contract = uniqueRefungibleToken(web3, address);
 
     const events = await recordEvents(contract, async () => {
       expect(await approve(api, collectionId, tokenId, alice, {Ethereum: receiver}, 100n)).to.be.true;
@@ -573,7 +592,7 @@
     expect(await approve(api, collectionId, tokenId, alice, bob.address, 100n)).to.be.true;
 
     const address = tokenIdToAddress(collectionId, tokenId);
-    const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address);
+    const contract = uniqueRefungibleToken(web3, address);
 
     const events = await recordEvents(contract, async () => {
       expect(await transferFrom(api, collectionId, tokenId, bob, alice, {Ethereum: receiver},  51n)).to.be.true;
@@ -611,7 +630,7 @@
     const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n)).itemId;
 
     const address = tokenIdToAddress(collectionId, tokenId);
-    const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address);
+    const contract = uniqueRefungibleToken(web3, address);
 
     const events = await recordEvents(contract, async () => {
       expect(await transfer(api, collectionId, tokenId, alice, {Ethereum: receiver},  51n)).to.be.true;
@@ -630,3 +649,47 @@
     ]);
   });
 });
+
+describe('ERC 1633 implementation', () => {
+  itWeb3('Parent NFT token address and id', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+
+    const {collectionIdAddress:  nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner);
+    const nftContract = uniqueNFT(web3, nftCollectionAddress, owner);
+    const nftTokenId = await nftContract.methods.nextTokenId().call();
+    await nftContract.methods.mint(owner, nftTokenId).send();
+    const nftCollectionId = collectionIdFromAddress(nftCollectionAddress);
+
+    const {collectionIdAddress, collectionId} = await createRefungibleCollection(api, web3, owner);
+    const refungibleContract = uniqueRefungible(web3, collectionIdAddress, owner);
+    const refungibleTokenId = await refungibleContract.methods.nextTokenId().call();
+    await refungibleContract.methods.mint(owner, refungibleTokenId).send();
+
+    const rftTokenAddress = tokenIdToAddress(collectionId, refungibleTokenId);
+    const refungibleTokenContract = uniqueRefungibleToken(web3, rftTokenAddress, owner);
+    await refungibleTokenContract.methods.setParentNFT(nftCollectionAddress, nftTokenId).send();
+
+    const tokenAddress = await refungibleTokenContract.methods.parentToken().call();
+    const tokenId = await refungibleTokenContract.methods.parentTokenId().call();
+    const nftTokenAddress = tokenIdToAddress(nftCollectionId, nftTokenId);
+    expect(tokenAddress).to.be.equal(nftTokenAddress);
+    expect(tokenId).to.be.equal(nftTokenId);
+  });
+
+  itWeb3('Default parent token address and id', async ({api, web3, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+
+    const {collectionIdAddress, collectionId} = await createRefungibleCollection(api, web3, owner);
+    const refungibleContract = uniqueRefungible(web3, collectionIdAddress, owner);
+    const refungibleTokenId = await refungibleContract.methods.nextTokenId().call();
+    await refungibleContract.methods.mint(owner, refungibleTokenId).send();
+
+    const rftTokenAddress = tokenIdToAddress(collectionId, refungibleTokenId);
+    const refungibleTokenContract = uniqueRefungibleToken(web3, rftTokenAddress, owner);
+
+    const tokenAddress = await refungibleTokenContract.methods.parentToken().call();
+    const tokenId = await refungibleTokenContract.methods.parentTokenId().call();
+    expect(tokenAddress).to.be.equal(rftTokenAddress);
+    expect(tokenId).to.be.equal(refungibleTokenId);
+  });
+});
modifiedtests/src/eth/reFungibleTokenAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/reFungibleTokenAbi.json
+++ b/tests/src/eth/reFungibleTokenAbi.json
@@ -103,6 +103,20 @@
     "type": "function"
   },
   {
+    "inputs": [],
+    "name": "parentToken",
+    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "parentTokenId",
+    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
     "inputs": [
       { "internalType": "uint256", "name": "amount", "type": "uint256" }
     ],
@@ -113,6 +127,16 @@
   },
   {
     "inputs": [
+      { "internalType": "address", "name": "collection", "type": "address" },
+      { "internalType": "uint256", "name": "nftId", "type": "uint256" }
+    ],
+    "name": "setParentNFT",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }
     ],
     "name": "supportsInterface",
modifiedtests/src/eth/scheduling.test.tsdiffbeforeafterboth
--- a/tests/src/eth/scheduling.test.ts
+++ b/tests/src/eth/scheduling.test.ts
@@ -16,9 +16,13 @@
 
 import {expect} from 'chai';
 import {createEthAccountWithBalance, deployFlipper, GAS_ARGS, itWeb3, subToEth, transferBalanceToEth} from './util/helpers';
-import {scheduleExpectSuccess, waitNewBlocks} from '../util/helpers';
+import {scheduleExpectSuccess, waitNewBlocks, requirePallets, Pallets} from '../util/helpers';
 
 describe('Scheduing EVM smart contracts', () => {
+  before(async function() {
+    await requirePallets(this, [Pallets.Scheduler]);
+  });
+
   itWeb3('Successfully schedules and periodically executes an EVM contract', async ({api, web3, privateKeyWrapper}) => {
     const deployer = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
     const flipper = await deployFlipper(web3, deployer);
@@ -51,4 +55,4 @@
       expect(await flipper.methods.getValue().call()).to.be.equal(initialValue);
     }
   });
-});
\ No newline at end of file
+});
modifiedtests/src/eth/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/eth/util/helpers.ts
+++ b/tests/src/eth/util/helpers.ts
@@ -32,6 +32,7 @@
 import fungibleAbi from '../fungibleAbi.json';
 import nonFungibleAbi from '../nonFungibleAbi.json';
 import refungibleAbi from '../reFungibleAbi.json';
+import refungibleTokenAbi from '../reFungibleTokenAbi.json';
 import contractHelpersAbi from './contractHelpersAbi.json';
 
 export const GAS_ARGS = {gas: 2500000};
@@ -101,6 +102,18 @@
   ]);
   return Web3.utils.toChecksumAddress('0x' + buf.toString('hex'));
 }
+
+export function tokenIdFromAddress(address: string) {
+  if (!address.startsWith('0x'))
+    throw 'address not starts with "0x"';
+  if (address.length > 42)
+    throw 'address length is more than 20 bytes';
+  return {
+    collectionId: Number('0x' + address.substring(address.length - 16, address.length - 8)),
+    tokenId: Number('0x' + address.substring(address.length - 8)),
+  };
+}
+
 export function tokenIdToCross(collection: number, token: number): CrossAccountId {
   return {
     Ethereum: tokenIdToAddress(collection, token),
@@ -128,6 +141,44 @@
   expect(result.success).to.be.true;
 }
 
+export async function createRefungibleCollection(api: ApiPromise, web3: Web3, owner: string) {
+  const collectionHelper = evmCollectionHelpers(web3, owner);
+  const result = await collectionHelper.methods
+    .createRefungibleCollection('A', 'B', 'C')
+    .send();
+  return await getCollectionAddressFromResult(api, result);
+}
+
+
+export async function createNonfungibleCollection(api: ApiPromise, web3: Web3, owner: string) {
+  const collectionHelper = evmCollectionHelpers(web3, owner);
+  const result = await collectionHelper.methods
+    .createNonfungibleCollection('A', 'B', 'C')
+    .send();
+  return await getCollectionAddressFromResult(api, result);
+}
+
+export function uniqueNFT(web3: Web3, address: string, owner: string) {
+  return new web3.eth.Contract(nonFungibleAbi as any, address, {
+    from: owner,
+    ...GAS_ARGS,
+  });
+}
+
+export function uniqueRefungible(web3: Web3, collectionAddress: string, owner: string) {
+  return new web3.eth.Contract(refungibleAbi as any, collectionAddress, {
+    from: owner,
+    ...GAS_ARGS,
+  });
+}
+
+export function uniqueRefungibleToken(web3: Web3, tokenAddress: string, owner: string | undefined = undefined) {
+  return new web3.eth.Contract(refungibleTokenAbi as any, tokenAddress, {
+    from: owner,
+    ...GAS_ARGS,
+  });
+}
+
 export async function itWeb3(name: string, cb: (apis: { web3: Web3, api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any, opts: { only?: boolean, skip?: boolean } = {}) {
   let i: any = it;
   if (opts.only) i = i.only;
@@ -199,7 +250,12 @@
   return Web3.utils.toChecksumAddress(subToEthLowercase(eth));
 }
 
-export function compileContract(name: string, src: string) {
+export interface CompiledContract {
+  abi: any,
+  object: string,
+}
+
+export function compileContract(name: string, src: string) : CompiledContract {
   const out = JSON.parse(solc.compile(JSON.stringify({
     language: 'Solidity',
     sources: {
modifiedtests/src/fungible.test.tsdiffbeforeafterboth
--- a/tests/src/fungible.test.ts
+++ b/tests/src/fungible.test.ts
@@ -14,25 +14,10 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-import {default as usingApi} from './substrate/substrate-api';
 import {IKeyringPair} from '@polkadot/types/types';
-import {
-  getBalance,
-  createMultipleItemsExpectSuccess,
-  isTokenExists,
-  getLastTokenId,
-  getAllowance,
-  approve,
-  transferFrom,
-  createCollection,
-  transfer,
-  burnItem,
-  normalizeAccountId,
-  CrossAccountId,
-  createFungibleItemExpectSuccess,
-  U128_MAX,
-  burnFromExpectSuccess,
-} from './util/helpers';
+import {U128_MAX} from './util/helpers';
+
+import {usingPlaygrounds} from './util/playgrounds';
 
 import chai from 'chai';
 import chaiAsPromised from 'chai-as-promised';
@@ -44,141 +29,137 @@
 
 describe('integration test: Fungible functionality:', () => {
   before(async () => {
-    await usingApi(async (api, privateKeyWrapper) => {
-      alice = privateKeyWrapper('//Alice');
-      bob = privateKeyWrapper('//Bob');
+    await usingPlaygrounds(async (helper, privateKey) => {
+      alice = privateKey('//Alice');
+      bob = privateKey('//Bob');
     });
   });
 
   it('Create fungible collection and token', async () => {
-    await usingApi(async api => {
-      const createCollectionResult = await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}});
-      expect(createCollectionResult.success).to.be.true;
-      const collectionId  = createCollectionResult.collectionId;
-      const defaultTokenId = await getLastTokenId(api, collectionId);
-      const aliceTokenId = await createFungibleItemExpectSuccess(alice, collectionId, {Value: U128_MAX}, alice.address);
-      const aliceBalance = await getBalance(api, collectionId, alice, aliceTokenId); 
-      const itemCountAfter = await getLastTokenId(api, collectionId);
-      
-      // What to expect
-      // tslint:disable-next-line:no-unused-expression
+    await usingPlaygrounds(async helper => {
+      const collection = await helper.ft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'trest'});
+      const defaultTokenId = await collection.getLastTokenId();
+      expect(defaultTokenId).to.be.equal(0);
+
+      await collection.mint(alice, {Substrate: alice.address}, U128_MAX);
+      const aliceBalance = await collection.getBalance({Substrate: alice.address});
+      const itemCountAfter = await collection.getLastTokenId();
+
       expect(itemCountAfter).to.be.equal(defaultTokenId);
       expect(aliceBalance).to.be.equal(U128_MAX);
     });
   });
   
   it('RPC method tokenOnewrs for fungible collection and token', async () => {
-    await usingApi(async (api, privateKeyWrapper) => {
+    await usingPlaygrounds(async (helper, privateKey) => {
       const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'};
-      const facelessCrowd = Array.from(Array(7).keys()).map(i => normalizeAccountId(privateKeyWrapper(i.toString())));
+      const facelessCrowd = Array(7).fill(0).map((_, i) => ({Substrate: privateKey(`//Alice+${i}`).address}));
+
+      const collection = await helper.ft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+
+      await collection.mint(alice, {Substrate: alice.address}, U128_MAX);
+
+      await collection.transfer(alice, {Substrate: bob.address}, 1000n);
+      await collection.transfer(alice, ethAcc, 900n);
       
-      const createCollectionResult = await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}});
-      const collectionId = createCollectionResult.collectionId;
-      const aliceTokenId = await createFungibleItemExpectSuccess(alice, collectionId, {Value: U128_MAX}, alice.address);
-     
-      await transfer(api, collectionId, aliceTokenId, alice, bob, 1000n);
-      await transfer(api, collectionId, aliceTokenId, alice, ethAcc, 900n);
-            
       for (let i = 0; i < 7; i++) {
-        await transfer(api, collectionId, aliceTokenId, alice, facelessCrowd[i], 1);
+        await collection.transfer(alice, facelessCrowd[i], 1n);
       } 
-      
-      const owners = await api.rpc.unique.tokenOwners(collectionId, aliceTokenId);
-      const ids = (owners.toJSON() as CrossAccountId[]).map(s => normalizeAccountId(s));
-      const aliceID = normalizeAccountId(alice);
-      const bobId = normalizeAccountId(bob);
 
+      const owners = await collection.getTop10Owners();
+
       // What to expect
-      // tslint:disable-next-line:no-unused-expression
-      expect(ids).to.deep.include.members([aliceID, ethAcc, bobId, ...facelessCrowd]);
-      expect(owners.length == 10).to.be.true;
+      expect(owners).to.deep.include.members([{Substrate: alice.address}, ethAcc, {Substrate: bob.address}, ...facelessCrowd]);
+      expect(owners.length).to.be.equal(10);
       
-      const eleven = privateKeyWrapper('11');
-      expect(await transfer(api, collectionId, aliceTokenId, alice, eleven, 10n)).to.be.true;
-      expect((await api.rpc.unique.tokenOwners(collectionId, aliceTokenId)).length).to.be.equal(10);
+      const eleven = privateKey('//ALice+11');
+      expect(await collection.transfer(alice, {Substrate: eleven.address}, 10n)).to.be.true;
+      expect((await collection.getTop10Owners()).length).to.be.equal(10);
     });
   });
   
   it('Transfer token', async () => {
-    await usingApi(async api => {
+    await usingPlaygrounds(async helper => {
       const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'};
-      const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId;
-      const tokenId = await createFungibleItemExpectSuccess(alice, collectionId, {Value: 500n}, alice.address);
+      const collection = await helper.ft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+      await collection.mint(alice, {Substrate: alice.address}, 500n);
 
-      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(500n);
-      expect(await transfer(api, collectionId, tokenId, alice, bob, 60n)).to.be.true;
-      expect(await transfer(api, collectionId, tokenId, alice, ethAcc, 140n)).to.be.true;
+      expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(500n);
+      expect(await collection.transfer(alice, {Substrate: bob.address}, 60n)).to.be.true;
+      expect(await collection.transfer(alice, ethAcc, 140n)).to.be.true;
 
-      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(300n);
-      expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(60n);
-      expect(await getBalance(api, collectionId, ethAcc, tokenId)).to.be.equal(140n);
-      await expect(transfer(api, collectionId, tokenId, alice, bob, 350n)).to.eventually.be.rejected;
+      expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(300n);
+      expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(60n);
+      expect(await collection.getBalance(ethAcc)).to.be.equal(140n);
+
+      await expect(collection.transfer(alice, {Substrate: bob.address}, 350n)).to.eventually.be.rejected;
     });
   });
 
   it('Tokens multiple creation', async () => {
-    await usingApi(async api => {
-      const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId;
-      
-      const args = [
-        {Fungible: {Value: 500n}},
-        {Fungible: {Value: 400n}},
-        {Fungible: {Value: 300n}},
-      ];
-      
-      await createMultipleItemsExpectSuccess(alice, collectionId, args);
-      expect(await getBalance(api, collectionId, alice, 0)).to.be.equal(1200n);
-    });   
+    await usingPlaygrounds(async helper => {
+      const collection = await helper.ft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+
+      await collection.mintWithOneOwner(alice, {Substrate: alice.address}, [
+        {value: 500n},
+        {value: 400n},
+        {value: 300n},
+      ]);
+
+      expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(1200n);
+    });
   });
 
   it('Burn some tokens ', async () => {
-    await usingApi(async api => {   
-      const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId;
-      const tokenId = (await createFungibleItemExpectSuccess(alice, collectionId, {Value: 500n}, alice.address));
-      expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
-      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(500n);
-      expect(await burnItem(api, alice, collectionId, tokenId, 499n)).to.be.true;
-      expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
-      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(1n);
+    await usingPlaygrounds(async helper => {
+      const collection = await helper.ft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+      await collection.mint(alice, {Substrate: alice.address}, 500n);
+
+      expect(await collection.isTokenExists(0)).to.be.true;
+      expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(500n);
+      expect(await collection.burnTokens(alice, 499n)).to.be.true;
+      expect(await collection.isTokenExists(0)).to.be.true;
+      expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(1n);
     });
   });
   
   it('Burn all tokens ', async () => {
-    await usingApi(async api => {   
-      const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId;
-      const tokenId = (await createFungibleItemExpectSuccess(alice, collectionId, {Value: 500n}, alice.address));
-      expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
-      expect(await burnItem(api, alice, collectionId, tokenId, 500n)).to.be.true;
-      expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
-      
-      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(0n);
-      expect((await api.rpc.unique.totalPieces(collectionId, tokenId)).value.toBigInt()).to.be.equal(0n);
+    await usingPlaygrounds(async helper => {
+      const collection = await helper.ft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+      await collection.mint(alice, {Substrate: alice.address}, 500n);
+
+      expect(await collection.isTokenExists(0)).to.be.true;
+      expect(await collection.burnTokens(alice, 500n)).to.be.true;
+      expect(await collection.isTokenExists(0)).to.be.true;
+
+      expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(0n);
+      expect(await collection.getTotalPieces()).to.be.equal(0n);
     });
   });
 
   it('Set allowance for token', async () => {
-    await usingApi(async api => {
-      const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId;
+    await usingPlaygrounds(async helper => {
+      const collection = await helper.ft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
       const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'};
+      await collection.mint(alice, {Substrate: alice.address}, 100n);
+
+      expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(100n);
       
-      const tokenId = (await createFungibleItemExpectSuccess(alice, collectionId, {Value: 100n}, alice.address));
-      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n);
+      expect(await collection.approveTokens(alice, {Substrate: bob.address}, 60n)).to.be.true;
+      expect(await collection.getApprovedTokens({Substrate: alice.address}, {Substrate: bob.address})).to.be.equal(60n);
+      expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(0n);
+
+      expect(await collection.transferFrom(bob, {Substrate: alice.address}, {Substrate: bob.address}, 20n)).to.be.true;
+      expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(80n);
+      expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(20n);
+      expect(await collection.getApprovedTokens({Substrate: alice.address}, {Substrate: bob.address})).to.be.equal(40n);
 
-      expect(await approve(api, collectionId, tokenId, alice, bob, 60n)).to.be.true;
-      expect(await getAllowance(api, collectionId, alice, bob, tokenId)).to.be.equal(60n);
-      expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(0n);
-      
-      expect(await transferFrom(api, collectionId, tokenId, bob, alice, bob,  20n)).to.be.true;
-      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(80n);
-      expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(20n);
-      expect(await getAllowance(api, collectionId, alice, bob, tokenId)).to.be.equal(40n);
-      
-      await burnFromExpectSuccess(bob, alice, collectionId, tokenId, 10n);
-     
-      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(70n);
-      expect(await getAllowance(api, collectionId, alice, bob, tokenId)).to.be.equal(30n);
-      expect(await transferFrom(api, collectionId, tokenId, bob, alice, ethAcc,  10n)).to.be.true;
-      expect(await getBalance(api, collectionId, ethAcc, tokenId)).to.be.equal(10n);
+      await collection.burnTokensFrom(bob, {Substrate: alice.address}, 10n);
+
+      expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(70n);
+      expect(await collection.getApprovedTokens({Substrate: alice.address}, {Substrate: bob.address})).to.be.equal(30n);
+      expect(await collection.transferFrom(bob, {Substrate: alice.address}, ethAcc, 10n)).to.be.true;
+      expect(await collection.getBalance(ethAcc)).to.be.equal(10n);
     });
   });
 });
modifiedtests/src/interfaces/augment-api-query.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-query.ts
+++ b/tests/src/interfaces/augment-api-query.ts
@@ -266,7 +266,7 @@
        * 
        * Currently used to store RMRK data.
        **/
-      tokenAuxProperties: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array, arg3: UpDataStructsPropertyScope | 'None' | 'Rmrk' | number | Uint8Array, arg4: Bytes | string | Uint8Array) => Observable<Option<Bytes>>, [u32, u32, UpDataStructsPropertyScope, Bytes]> & QueryableStorageEntry<ApiType, [u32, u32, UpDataStructsPropertyScope, Bytes]>;
+      tokenAuxProperties: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array, arg3: UpDataStructsPropertyScope | 'None' | 'Rmrk' | 'Eth' | number | Uint8Array, arg4: Bytes | string | Uint8Array) => Observable<Option<Bytes>>, [u32, u32, UpDataStructsPropertyScope, Bytes]> & QueryableStorageEntry<ApiType, [u32, u32, UpDataStructsPropertyScope, Bytes]>;
       /**
        * Used to enumerate token's children.
        **/
modifiedtests/src/interfaces/default/types.tsdiffbeforeafterboth
--- a/tests/src/interfaces/default/types.ts
+++ b/tests/src/interfaces/default/types.ts
@@ -2521,7 +2521,8 @@
 export interface UpDataStructsPropertyScope extends Enum {
   readonly isNone: boolean;
   readonly isRmrk: boolean;
-  readonly type: 'None' | 'Rmrk';
+  readonly isEth: boolean;
+  readonly type: 'None' | 'Rmrk' | 'Eth';
 }
 
 /** @name UpDataStructsRpcCollection */
modifiedtests/src/interfaces/lookup.tsdiffbeforeafterboth
--- a/tests/src/interfaces/lookup.ts
+++ b/tests/src/interfaces/lookup.ts
@@ -2978,7 +2978,7 @@
    * Lookup397: up_data_structs::PropertyScope
    **/
   UpDataStructsPropertyScope: {
-    _enum: ['None', 'Rmrk']
+    _enum: ['None', 'Rmrk', 'Eth']
   },
   /**
    * Lookup399: pallet_nonfungible::pallet::Error<T>
modifiedtests/src/interfaces/types-lookup.tsdiffbeforeafterboth
--- a/tests/src/interfaces/types-lookup.ts
+++ b/tests/src/interfaces/types-lookup.ts
@@ -3126,7 +3126,8 @@
   export interface UpDataStructsPropertyScope extends Enum {
     readonly isNone: boolean;
     readonly isRmrk: boolean;
-    readonly type: 'None' | 'Rmrk';
+    readonly isEth: boolean;
+    readonly type: 'None' | 'Rmrk' | 'Eth';
   }
 
   /** @name PalletNonfungibleError (399) */
modifiedtests/src/limits.test.tsdiffbeforeafterboth
--- a/tests/src/limits.test.ts
+++ b/tests/src/limits.test.ts
@@ -27,6 +27,8 @@
   transferExpectSuccess,
   getFreeBalance,
   waitNewBlocks, burnItemExpectSuccess,
+  requirePallets,
+  Pallets,
 } from './util/helpers';
 import {expect} from 'chai';
 
@@ -67,7 +69,9 @@
 describe('Number of tokens per address (ReFungible)', () => {
   let alice: IKeyringPair;
 
-  before(async () => {
+  before(async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await usingApi(async (api, privateKeyWrapper) => {
       alice = privateKeyWrapper('//Alice');
     });
@@ -367,7 +371,9 @@
   let bob: IKeyringPair;
   let charlie: IKeyringPair;
 
-  before(async () => {
+  before(async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await usingApi(async (api, privateKeyWrapper) => {
       alice = privateKeyWrapper('//Alice');
       bob = privateKeyWrapper('//Bob');
modifiedtests/src/nesting/nest.test.tsdiffbeforeafterboth
--- a/tests/src/nesting/nest.test.ts
+++ b/tests/src/nesting/nest.test.ts
@@ -17,6 +17,8 @@
   transferExpectSuccess,
   transferFromExpectSuccess,
   setCollectionLimitsExpectSuccess,
+  requirePallets,
+  Pallets,
 } from '../util/helpers';
 import {IKeyringPair} from '@polkadot/types/types';
 
@@ -311,7 +313,9 @@
 
   // ---------- Re-Fungible ----------
 
-  it('ReFungible: allows an Owner to nest/unnest their token', async () => {
+  it('ReFungible: allows an Owner to nest/unnest their token', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await usingApi(async api => {
       const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
       await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}});
@@ -333,7 +337,9 @@
     });
   });
 
-  it('ReFungible: allows an Owner to nest/unnest their token (Restricted nesting)', async () => {
+  it('ReFungible: allows an Owner to nest/unnest their token (Restricted nesting)', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await usingApi(async api => {
       const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
       const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});
@@ -715,7 +721,9 @@
 
   // ---------- Re-Fungible ----------
 
-  it('ReFungible: disallows to nest token if nesting is disabled', async () => {
+  it('ReFungible: disallows to nest token if nesting is disabled', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await usingApi(async api => {
       const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
       await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {}});
@@ -745,7 +753,9 @@
     });
   });
 
-  it('ReFungible: disallows a non-Owner to nest someone else\'s token', async () => {
+  it('ReFungible: disallows a non-Owner to nest someone else\'s token', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await usingApi(async api => {
       const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
       await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}});
@@ -773,7 +783,9 @@
     });
   });
 
-  it('ReFungible: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async () => {
+  it('ReFungible: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await usingApi(async api => {
       const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
       await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);
@@ -800,7 +812,9 @@
     });
   });
 
-  it('ReFungible: disallows to nest token to an unlisted collection', async () => {
+  it('ReFungible: disallows to nest token to an unlisted collection', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await usingApi(async api => {
       const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
       await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[]}});
modifiedtests/src/nesting/properties.test.tsdiffbeforeafterboth
--- a/tests/src/nesting/properties.test.ts
+++ b/tests/src/nesting/properties.test.ts
@@ -8,6 +8,8 @@
   createItemExpectSuccess,
   getCreateCollectionResult,
   transferExpectSuccess,
+  requirePallets,
+  Pallets,
 } from '../util/helpers';
 import {IKeyringPair} from '@polkadot/types/types';
 import {tokenIdToAddress} from '../eth/util/helpers';
@@ -64,7 +66,9 @@
     await testMakeSureSuppliesRequired({type: 'NFT'});
   });
 
-  it('Makes sure collectionById supplies required fields for ReFungible', async () => {
+  it('Makes sure collectionById supplies required fields for ReFungible', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await testMakeSureSuppliesRequired({type: 'ReFungible'});
   });
 });
@@ -120,7 +124,9 @@
   it('Sets properties for a NFT collection', async () => {
     await testSetsPropertiesForCollection('NFT');
   });
-  it('Sets properties for a ReFungible collection', async () => {
+  it('Sets properties for a ReFungible collection', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await testSetsPropertiesForCollection('ReFungible');
   });
 
@@ -178,7 +184,9 @@
   it('Check valid names for NFT collection properties keys', async () => {
     await testCheckValidNames('NFT');
   });
-  it('Check valid names for ReFungible collection properties keys', async () => {
+  it('Check valid names for ReFungible collection properties keys', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await testCheckValidNames('ReFungible');
   });
 
@@ -209,7 +217,9 @@
   it('Changes properties of a NFT collection', async () => {
     await testChangesProperties({type: 'NFT'});
   });
-  it('Changes properties of a ReFungible collection', async () => {
+  it('Changes properties of a ReFungible collection', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await testChangesProperties({type: 'ReFungible'});
   });
 
@@ -238,7 +248,9 @@
   it('Deletes properties of a NFT collection', async () => {
     await testDeleteProperties({type: 'NFT'});
   });
-  it('Deletes properties of a ReFungible collection', async () => {
+  it('Deletes properties of a ReFungible collection', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await testDeleteProperties({type: 'ReFungible'});
   });
 });
@@ -269,7 +281,9 @@
   it('Fails to set properties in a NFT collection if not its onwer/administrator', async () => {
     await testFailsSetPropertiesIfNotOwnerOrAdmin({type: 'NFT'});
   });
-  it('Fails to set properties in a ReFungible collection if not its onwer/administrator', async () => {
+  it('Fails to set properties in a ReFungible collection if not its onwer/administrator', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await testFailsSetPropertiesIfNotOwnerOrAdmin({type: 'ReFungible'});
   });
   
@@ -307,7 +321,9 @@
   it('Fails to set properties that exceed the limits (NFT)', async () => {
     await testFailsSetPropertiesThatExeedLimits({type: 'NFT'});
   });
-  it('Fails to set properties that exceed the limits (ReFungible)', async () => {
+  it('Fails to set properties that exceed the limits (ReFungible)', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await testFailsSetPropertiesThatExeedLimits({type: 'ReFungible'});
   });
   
@@ -337,7 +353,9 @@
   it('Fails to set more properties than it is allowed (NFT)', async () => {
     await testFailsSetMorePropertiesThanAllowed({type: 'NFT'});
   });
-  it('Fails to set more properties than it is allowed (ReFungible)', async () => {
+  it('Fails to set more properties than it is allowed (ReFungible)', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await testFailsSetMorePropertiesThanAllowed({type: 'ReFungible'});
   });
   
@@ -392,7 +410,9 @@
   it('Fails to set properties with invalid names (NFT)', async () => {
     await testFailsSetPropertiesWithInvalidNames({type: 'NFT'});
   });
-  it('Fails to set properties with invalid names (ReFungible)', async () => {
+  it('Fails to set properties with invalid names (ReFungible)', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await testFailsSetPropertiesWithInvalidNames({type: 'ReFungible'});
   });
 });
@@ -443,7 +463,9 @@
   it('Sets access rights to properties of a collection (NFT)', async () => {
     await testSetsAccessRightsToProperties({type: 'NFT'});
   });
-  it('Sets access rights to properties of a collection (ReFungible)', async () => {
+  it('Sets access rights to properties of a collection (ReFungible)', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await testSetsAccessRightsToProperties({type: 'ReFungible'});
   });
   
@@ -472,7 +494,9 @@
   it('Changes access rights to properties of a NFT collection', async () => {
     await testChangesAccessRightsToProperty({type: 'NFT'});
   });
-  it('Changes access rights to properties of a ReFungible collection', async () => {
+  it('Changes access rights to properties of a ReFungible collection', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await testChangesAccessRightsToProperty({type: 'ReFungible'});
   });
 });
@@ -502,7 +526,9 @@
   it('Prevents from setting access rights to properties of a NFT collection if not an onwer/admin', async () => {
     await testPreventsFromSettingAccessRightsNotAdminOrOwner({type: 'NFT'});
   });
-  it('Prevents from setting access rights to properties of a ReFungible collection if not an onwer/admin', async () => {
+  it('Prevents from setting access rights to properties of a ReFungible collection if not an onwer/admin', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await testPreventsFromSettingAccessRightsNotAdminOrOwner({type: 'ReFungible'});
   });
 
@@ -531,7 +557,9 @@
   it('Prevents from adding too many possible properties (NFT)', async () => {
     await testPreventFromAddingTooManyPossibleProperties({type: 'NFT'});
   });
-  it('Prevents from adding too many possible properties (ReFungible)', async () => {
+  it('Prevents from adding too many possible properties (ReFungible)', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await testPreventFromAddingTooManyPossibleProperties({type: 'ReFungible'});
   });
 
@@ -560,7 +588,9 @@
   it('Prevents access rights to be modified if constant (NFT)', async () => {
     await testPreventAccessRightsModifiedIfConstant({type: 'NFT'});
   });
-  it('Prevents access rights to be modified if constant (ReFungible)', async () => {
+  it('Prevents access rights to be modified if constant (ReFungible)', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await testPreventAccessRightsModifiedIfConstant({type: 'ReFungible'});
   });
 
@@ -608,7 +638,9 @@
   it('Prevents adding properties with invalid names (NFT)', async () => {
     await testPreventsAddingPropertiesWithInvalidNames({type: 'NFT'});
   });
-  it('Prevents adding properties with invalid names (ReFungible)', async () => {
+  it('Prevents adding properties with invalid names (ReFungible)', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await testPreventsAddingPropertiesWithInvalidNames({type: 'ReFungible'});
   });
 });
@@ -651,7 +683,9 @@
   it('Reads yet empty properties of a token (NFT)', async () => {
     await testReadsYetEmptyProperties({type: 'NFT'});
   });
-  it('Reads yet empty properties of a token (ReFungible)', async () => {
+  it('Reads yet empty properties of a token (ReFungible)', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await testReadsYetEmptyProperties({type: 'ReFungible'});
   });
 
@@ -696,7 +730,9 @@
   it('Assigns properties to a token according to permissions (NFT)', async () => {
     await testAssignPropertiesAccordingToPermissions({type: 'NFT'}, 1);
   });
-  it('Assigns properties to a token according to permissions (ReFungible)', async () => {
+  it('Assigns properties to a token according to permissions (ReFungible)', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await testAssignPropertiesAccordingToPermissions({type: 'ReFungible'}, 100);
   });
 
@@ -749,7 +785,9 @@
   it('Changes properties of a token according to permissions (NFT)', async () => {
     await testChangesPropertiesAccordingPermission({type: 'NFT'}, 1);
   });
-  it('Changes properties of a token according to permissions (ReFungible)', async () => {
+  it('Changes properties of a token according to permissions (ReFungible)', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await testChangesPropertiesAccordingPermission({type: 'ReFungible'}, 100);
   });
 
@@ -802,7 +840,9 @@
   it('Deletes properties of a token according to permissions (NFT)', async () => {
     await testDeletePropertiesAccordingPermission({type: 'NFT'}, 1);
   });
-  it('Deletes properties of a token according to permissions (ReFungible)', async () => {
+  it('Deletes properties of a token according to permissions (ReFungible)', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await testDeletePropertiesAccordingPermission({type: 'ReFungible'}, 100);
   });
 
@@ -1029,7 +1069,9 @@
   it('Forbids changing/deleting properties of a token if the user is outside of permissions (NFT)', async () => {
     await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions({type: 'NFT'}, 1);
   });
-  it('Forbids changing/deleting properties of a token if the user is outside of permissions (ReFungible)', async () => {
+  it('Forbids changing/deleting properties of a token if the user is outside of permissions (ReFungible)', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions({type: 'ReFungible'}, 100);
   });
 
@@ -1062,7 +1104,9 @@
   it('Forbids changing/deleting properties of a token if the property is permanent (immutable) (NFT)', async () => {
     await testForbidsChangingDeletingPropertiesIfPropertyImmutable({type: 'NFT'}, 1);
   });
-  it('Forbids changing/deleting properties of a token if the property is permanent (immutable) (ReFungible)', async () => {
+  it('Forbids changing/deleting properties of a token if the property is permanent (immutable) (ReFungible)', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await testForbidsChangingDeletingPropertiesIfPropertyImmutable({type: 'ReFungible'}, 100);
   });
 
@@ -1096,7 +1140,9 @@
   it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (NFT)', async () => {
     await testForbidsAddingPropertiesIfPropertyNotDeclared({type: 'NFT'}, 1);
   });
-  it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (ReFungible)', async () => {
+  it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (ReFungible)', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await testForbidsAddingPropertiesIfPropertyNotDeclared({type: 'ReFungible'}, 100);
   });
 
@@ -1140,7 +1186,9 @@
   it('Forbids adding too many properties to a token (NFT)', async () => {
     await testForbidsAddingTooManyProperties({type: 'NFT'}, 1);
   });
-  it('Forbids adding too many properties to a token (ReFungible)', async () => {
+  it('Forbids adding too many properties to a token (ReFungible)', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await testForbidsAddingTooManyProperties({type: 'ReFungible'}, 100);
   });
 });
@@ -1149,7 +1197,9 @@
   let collection: number;
   let token: number;
 
-  before(async () => {
+  before(async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await usingApi(async (api, privateKeyWrapper) => {
       alice = privateKeyWrapper('//Alice');
       bob = privateKeyWrapper('//Bob');
modifiedtests/src/nesting/rules-smoke.test.tsdiffbeforeafterboth
--- a/tests/src/nesting/rules-smoke.test.ts
+++ b/tests/src/nesting/rules-smoke.test.ts
@@ -1,7 +1,7 @@
 import {expect} from 'chai';
 import {tokenIdToAddress} from '../eth/util/helpers';
 import usingApi, {executeTransaction} from '../substrate/substrate-api';
-import {createCollectionExpectSuccess, createFungibleItemExpectSuccess, createItemExpectSuccess, CrossAccountId, getCreateCollectionResult} from '../util/helpers';
+import {createCollectionExpectSuccess, createFungibleItemExpectSuccess, createItemExpectSuccess, CrossAccountId, getCreateCollectionResult, requirePallets, Pallets} from '../util/helpers';
 import {IKeyringPair} from '@polkadot/types/types';
 
 describe('nesting check', () => {
@@ -47,7 +47,9 @@
     });
   });
 
-  it('called for refungible', async () => {
+  it('called for refungible', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await usingApi(async api => {
       const collection = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
       await expect(executeTransaction(api, alice, api.tx.unique.createItem(collection, nestTarget, {ReFungible: {}})))
modifiedtests/src/nesting/unnest.test.tsdiffbeforeafterboth
--- a/tests/src/nesting/unnest.test.ts
+++ b/tests/src/nesting/unnest.test.ts
@@ -10,6 +10,8 @@
   setCollectionPermissionsExpectSuccess,
   transferExpectSuccess,
   transferFromExpectSuccess,
+  requirePallets,
+  Pallets,
 } from '../util/helpers';
 import {IKeyringPair} from '@polkadot/types/types';
 
@@ -80,7 +82,9 @@
     });
   });
 
-  it('ReFungible: allows the owner to successfully unnest a token', async () => {
+  it('ReFungible: allows the owner to successfully unnest a token', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await usingApi(async api => {
       const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
       await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});
modifiedtests/src/nextSponsoring.test.tsdiffbeforeafterboth
--- a/tests/src/nextSponsoring.test.ts
+++ b/tests/src/nextSponsoring.test.ts
@@ -27,6 +27,8 @@
   transferExpectSuccess,
   normalizeAccountId,
   getNextSponsored,
+  requirePallets,
+  Pallets,
 } from './util/helpers';
 
 chai.use(chaiAsPromised);
@@ -89,7 +91,9 @@
     });
   });
 
-  it('ReFungible', async () => {
+  it('ReFungible', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await usingApi(async (api: ApiPromise) => {
 
       const createMode = 'ReFungible';
modifiedtests/src/pallet-presence.test.tsdiffbeforeafterboth
--- a/tests/src/pallet-presence.test.ts
+++ b/tests/src/pallet-presence.test.ts
@@ -49,8 +49,6 @@
   'inflation',
   'unique',
   'nonfungible',
-  'refungible',
-  'scheduler',
   'charging',
 ];
 
@@ -66,8 +64,16 @@
     await usingApi(async api => {
       const chain = await api.rpc.system.chain();
 
-      if (!chain.eq('UNIQUE')) {
-        requiredPallets.push(...['rmrkcore', 'rmrkequip']);
+      const refungible = 'refungible';
+      const scheduler = 'scheduler';
+      const rmrkPallets = ['rmrkcore', 'rmrkequip'];
+
+      if (chain.eq('OPAL by UNIQUE')) {
+        requiredPallets.push(refungible, scheduler, ...rmrkPallets);
+      } else if (chain.eq('QUARTZ by UNIQUE')) {
+        // Insert Quartz additional pallets here
+      } else if (chain.eq('UNIQUE')) {
+        // Insert Unique additional pallets here
       }
     });
   });
modifiedtests/src/refungible.test.tsdiffbeforeafterboth
--- a/tests/src/refungible.test.ts
+++ b/tests/src/refungible.test.ts
@@ -14,28 +14,13 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-import {default as usingApi, submitTransactionAsync} from './substrate/substrate-api';
 import {IKeyringPair} from '@polkadot/types/types';
+
+import { usingPlaygrounds } from './util/playgrounds';
 import {
-  createCollectionExpectSuccess,
-  getBalance,
-  createMultipleItemsExpectSuccess,
-  isTokenExists,
-  getLastTokenId,
-  getAllowance,
-  approve,
-  transferFrom,
-  createCollection,
-  createRefungibleToken,
-  transfer,
-  burnItem,
-  repartitionRFT,
-  createCollectionWithPropsExpectSuccess,
-  getDetailedCollectionInfo,
-  normalizeAccountId,
-  CrossAccountId,
-  getCreateItemsResult,
-  getDestroyItemsResult,
+  getModuleNames,
+  Pallets,
+  requirePallets,
 } from './util/helpers';
 
 import chai from 'chai';
@@ -46,249 +31,241 @@
 let alice: IKeyringPair;
 let bob: IKeyringPair;
 
-describe('integration test: Refungible functionality:', () => {
-  before(async () => {
-    await usingApi(async (api, privateKeyWrapper) => {
-      alice = privateKeyWrapper('//Alice');
-      bob = privateKeyWrapper('//Bob');
+describe('integration test: Refungible functionality:', async () => {
+  before(async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
+    await usingPlaygrounds(async (helper, privateKey) => {
+      alice = privateKey('//Alice');
+      bob = privateKey('//Bob');
+      if (!getModuleNames(helper.api!).includes(Pallets.ReFungible)) this.skip();
     });
   });
-
+  
   it('Create refungible collection and token', async () => {
-    await usingApi(async api => {
-      const createCollectionResult = await createCollection(api, alice, {mode: {type: 'ReFungible'}});
-      expect(createCollectionResult.success).to.be.true;
-      const collectionId  = createCollectionResult.collectionId;
+    await usingPlaygrounds(async helper => {
+      const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+
+      const itemCountBefore = await collection.getLastTokenId();
+      const token = await collection.mintToken(alice, {Substrate: alice.address}, 100n);
       
-      const itemCountBefore = await getLastTokenId(api, collectionId);
-      const result = await createRefungibleToken(api, alice, collectionId, 100n);
+      const itemCountAfter = await collection.getLastTokenId();
       
-      const itemCountAfter = await getLastTokenId(api, collectionId);
-      
       // What to expect
-      // tslint:disable-next-line:no-unused-expression
-      expect(result.success).to.be.true;
+      expect(token?.tokenId).to.be.gte(itemCountBefore);
       expect(itemCountAfter).to.be.equal(itemCountBefore + 1);
-      expect(collectionId).to.be.equal(result.collectionId);
-      expect(itemCountAfter.toString()).to.be.equal(result.itemId.toString());
+      expect(itemCountAfter.toString()).to.be.equal(token?.tokenId.toString());
     });
   });
   
   it('RPC method tokenOnewrs for refungible collection and token', async () => {
-    await usingApi(async (api, privateKeyWrapper) => {
+    await usingPlaygrounds(async (helper, privateKey) => {
       const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'};
-      const facelessCrowd = Array.from(Array(7).keys()).map(i => normalizeAccountId(privateKeyWrapper(i.toString())));
-      
-      const createCollectionResult = await createCollection(api, alice, {mode: {type: 'ReFungible'}});
-      const collectionId = createCollectionResult.collectionId;
-      
-      const result = await createRefungibleToken(api, alice, collectionId, 10_000n);
-      const aliceTokenId = result.itemId;
-      
-      
-      await transfer(api, collectionId, aliceTokenId, alice, bob, 1000n);
-      await transfer(api, collectionId, aliceTokenId, alice, ethAcc, 900n);
+      const facelessCrowd = Array(7).fill(0).map((_, i) => ({Substrate: privateKey(`//Alice+${i}`).address}));
+
+      const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+
+      const token = await collection.mintToken(alice, {Substrate: alice.address}, 10_000n);
+
+      await token.transfer(alice, {Substrate: bob.address}, 1000n);
+      await token.transfer(alice, ethAcc, 900n);
       
       for (let i = 0; i < 7; i++) {
-        await transfer(api, collectionId, aliceTokenId, alice, facelessCrowd[i], 50*(i+1));
+        await token.transfer(alice, facelessCrowd[i], 50n * BigInt(i + 1));
       } 
-      
-      const owners = await api.rpc.unique.tokenOwners(collectionId, aliceTokenId);
-      const ids = (owners.toJSON() as CrossAccountId[]).map(s => normalizeAccountId(s));
-      
-      const aliceID = normalizeAccountId(alice);
-      const bobId = normalizeAccountId(bob);
-      
+
+      const owners = await token.getTop10Owners();
+
       // What to expect
-      // tslint:disable-next-line:no-unused-expression
-      expect(ids).to.deep.include.members([aliceID, ethAcc, bobId, ...facelessCrowd]);
+      expect(owners).to.deep.include.members([{Substrate: alice.address}, ethAcc, {Substrate: bob.address}, ...facelessCrowd]);
       expect(owners.length).to.be.equal(10);
       
-      const eleven = privateKeyWrapper('11');
-      expect(await transfer(api, collectionId, aliceTokenId, alice, eleven, 10n)).to.be.true;
-      expect((await api.rpc.unique.tokenOwners(collectionId, aliceTokenId)).length).to.be.equal(10);
+      const eleven = privateKey('//ALice+11');
+      expect(await token.transfer(alice, {Substrate: eleven.address}, 10n)).to.be.true;
+      expect((await token.getTop10Owners()).length).to.be.equal(10);
     });
   });
   
   it('Transfer token pieces', async () => {
-    await usingApi(async api => {
-      const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId;
-      const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId;
+    await usingPlaygrounds(async helper => {
+      const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+      const token = await collection.mintToken(alice, {Substrate: alice.address}, 100n);
 
-      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n);
-      expect(await transfer(api, collectionId, tokenId, alice, bob, 60n)).to.be.true;
-
-      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(40n);
-      expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(60n);
-      await expect(transfer(api, collectionId, tokenId, alice, bob, 41n)).to.eventually.be.rejected;
+      expect(await token.getBalance({Substrate: alice.address})).to.be.equal(100n);
+      expect(await token.transfer(alice, {Substrate: bob.address}, 60n)).to.be.true;
+      
+      expect(await token.getBalance({Substrate: alice.address})).to.be.equal(40n);
+      expect(await token.getBalance({Substrate: bob.address})).to.be.equal(60n);
+      
+      await expect(token.transfer(alice, {Substrate: bob.address}, 41n)).to.eventually.be.rejected;
     });
   });
 
   it('Create multiple tokens', async () => {
-    const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
-    const args = [
-      {ReFungible: {pieces: 1}},
-      {ReFungible: {pieces: 2}},
-      {ReFungible: {pieces: 100}},
-    ];
-    await createMultipleItemsExpectSuccess(alice, collectionId, args);
-
-    await usingApi(async api => {      
-      const tokenId = await getLastTokenId(api, collectionId);
-      expect(tokenId).to.be.equal(3);
-      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n);
+    await usingPlaygrounds(async helper => {
+      const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+      // TODO: fix mintMultipleTokens
+      // await collection.mintMultipleTokens(alice, [
+      //   {owner: {Substrate: alice.address}, pieces: 1n},
+      //   {owner: {Substrate: alice.address}, pieces: 2n},
+      //   {owner: {Substrate: alice.address}, pieces: 100n},
+      // ]);
+      await helper.rft.mintMultipleTokensWithOneOwner(alice, collection.collectionId, {Substrate: alice.address}, [
+        {pieces: 1n}, 
+        {pieces: 2n}, 
+        {pieces: 100n},
+      ]);
+      const lastTokenId = await collection.getLastTokenId();
+      expect(lastTokenId).to.be.equal(3);
+      expect(await collection.getTokenBalance(lastTokenId, {Substrate: alice.address})).to.be.equal(100n);
     });
   });
 
   it('Burn some pieces', async () => {
-    await usingApi(async api => {   
-      const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId;
-      const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId;
-      expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
-      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n);
-      expect(await burnItem(api, alice, collectionId, tokenId, 99n)).to.be.true;
-      expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
-      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(1n);
+    await usingPlaygrounds(async helper => {
+      const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+      const token = await collection.mintToken(alice, {Substrate: alice.address}, 100n);
+      expect(await collection.isTokenExists(token.tokenId)).to.be.true;
+      expect(await token.getBalance({Substrate: alice.address})).to.be.equal(100n);
+      expect((await token.burn(alice, 99n)).success).to.be.true;
+      expect(await collection.isTokenExists(token.tokenId)).to.be.true;
+      expect(await token.getBalance({Substrate: alice.address})).to.be.equal(1n);
     });
   });
 
   it('Burn all pieces', async () => {
-    await usingApi(async api => {   
-      const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId;
-      const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId;
-      expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
-      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n);
-      expect(await burnItem(api, alice, collectionId, tokenId, 100n)).to.be.true;
-      expect(await isTokenExists(api, collectionId, tokenId)).to.be.false;
+    await usingPlaygrounds(async helper => {   
+      const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+      const token = await collection.mintToken(alice, {Substrate: alice.address}, 100n);
+      
+      expect(await collection.isTokenExists(token.tokenId)).to.be.true;
+      expect(await token.getBalance({Substrate: alice.address})).to.be.equal(100n);
+
+      expect((await token.burn(alice, 100n)).success).to.be.true;
+      expect(await collection.isTokenExists(token.tokenId)).to.be.false;
     });
   });
 
   it('Burn some pieces for multiple users', async () => {
-    await usingApi(async api => {   
-      const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId;
-      const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId;
-      expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
+    await usingPlaygrounds(async helper => {
+      const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+      const token = await collection.mintToken(alice, {Substrate: alice.address}, 100n);
 
-      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n);
-      expect(await transfer(api, collectionId, tokenId, alice, bob, 60n)).to.be.true;
+      expect(await collection.isTokenExists(token.tokenId)).to.be.true;
+      
+      expect(await token.getBalance({Substrate: alice.address})).to.be.equal(100n);
+      expect(await token.transfer(alice, {Substrate: bob.address}, 60n)).to.be.true;
 
-      
-      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(40n);
-      expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(60n);
-      expect(await burnItem(api, alice, collectionId, tokenId, 40n)).to.be.true;
+      expect(await token.getBalance({Substrate: alice.address})).to.be.equal(40n);
+      expect(await token.getBalance({Substrate: bob.address})).to.be.equal(60n);
+
+      expect((await token.burn(alice, 40n)).success).to.be.true;
+
+      expect(await collection.isTokenExists(token.tokenId)).to.be.true;
+      expect(await token.getBalance({Substrate: alice.address})).to.be.equal(0n);
+
+      expect((await token.burn(bob, 59n)).success).to.be.true;
+
+      expect(await token.getBalance({Substrate: bob.address})).to.be.equal(1n);
+      expect(await collection.isTokenExists(token.tokenId)).to.be.true;
 
-      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(0n);
-      expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
-      expect(await burnItem(api, bob, collectionId, tokenId, 59n)).to.be.true;
+      expect((await token.burn(bob, 1n)).success).to.be.true;
 
-      expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(1n);
-      expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
-      expect(await burnItem(api, bob, collectionId, tokenId, 1n)).to.be.true;
-      
-      expect(await isTokenExists(api, collectionId, tokenId)).to.be.false;
+      expect(await collection.isTokenExists(token.tokenId)).to.be.false;
     });
   });
 
   it('Set allowance for token', async () => {
-    await usingApi(async api => {
-      const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId;
-      const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId;
+    await usingPlaygrounds(async helper => {
+      const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+      const token = await collection.mintToken(alice, {Substrate: alice.address}, 100n);
+      
+      expect(await token.getBalance({Substrate: alice.address})).to.be.equal(100n);
 
-      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n);
+      expect(await token.approve(alice, {Substrate: bob.address}, 60n)).to.be.true;
+      expect(await token.getApprovedPieces({Substrate: alice.address}, {Substrate: bob.address})).to.be.equal(60n);
 
-      expect(await approve(api, collectionId, tokenId, alice, bob, 60n)).to.be.true;
-      expect(await getAllowance(api, collectionId, alice, bob, tokenId)).to.be.equal(60n);
-
-      expect(await transferFrom(api, collectionId, tokenId, bob, alice, bob,  20n)).to.be.true;
-      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(80n);
-      expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(20n);
-      expect(await getAllowance(api, collectionId, alice, bob, tokenId)).to.be.equal(40n);
+      expect(await token.transferFrom(bob, {Substrate: alice.address}, {Substrate: bob.address}, 20n)).to.be.true;
+      expect(await token.getBalance({Substrate: alice.address})).to.be.equal(80n);
+      expect(await token.getBalance({Substrate: bob.address})).to.be.equal(20n);
+      expect(await token.getApprovedPieces({Substrate: alice.address}, {Substrate: bob.address})).to.be.equal(40n);
     });
   });
 
   it('Repartition', async () => {
-    await usingApi(async api => {
-      const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId;
-      const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId;
+    await usingPlaygrounds(async helper => {
+      const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+      const token = await collection.mintToken(alice, {Substrate: alice.address}, 100n);
 
-      expect(await repartitionRFT(api, collectionId, alice, tokenId, 200n)).to.be.true;
-      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(200n);
-
-      expect(await transfer(api, collectionId, tokenId, alice, bob, 110n)).to.be.true;
-      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(90n);
-      expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(110n);
-
-      await expect(repartitionRFT(api, collectionId, alice, tokenId, 80n)).to.eventually.be.rejected;
+      expect(await token.repartition(alice, 200n)).to.be.true;
+      expect(await token.getBalance({Substrate: alice.address})).to.be.equal(200n);
+      expect(await token.getTotalPieces()).to.be.equal(200n);
+      
+      expect(await token.transfer(alice, {Substrate: bob.address}, 110n)).to.be.true;
+      expect(await token.getBalance({Substrate: alice.address})).to.be.equal(90n);
+      expect(await token.getBalance({Substrate: bob.address})).to.be.equal(110n);
+      
+      await expect(token.repartition(alice, 80n)).to.eventually.be.rejected;
+      
+      expect(await token.transfer(alice, {Substrate: bob.address}, 90n)).to.be.true;
+      expect(await token.getBalance({Substrate: alice.address})).to.be.equal(0n);
+      expect(await token.getBalance({Substrate: bob.address})).to.be.equal(200n);
 
-      expect(await transfer(api, collectionId, tokenId, alice, bob, 90n)).to.be.true;
-      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(0n);
-      expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(200n);
+      expect(await token.repartition(bob, 150n)).to.be.true;
+      await expect(token.transfer(bob, {Substrate: alice.address}, 160n)).to.eventually.be.rejected;
 
-      expect(await repartitionRFT(api, collectionId, bob, tokenId, 150n)).to.be.true;
-      await expect(transfer(api, collectionId, tokenId, bob, alice, 160n)).to.eventually.be.rejected;
     });
   });
 
   it('Repartition with increased amount', async () => {
-    await usingApi(async api => {
-      const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId;
-      const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId;
-
-      const tx = api.tx.unique.repartition(collectionId, tokenId, 200n);
-      const events = await submitTransactionAsync(alice, tx);
-      const substrateEvents = getCreateItemsResult(events);
-      expect(substrateEvents).to.include.deep.members([
-        {
-          success: true,
-          collectionId,
-          itemId: tokenId,
-          recipient: {Substrate: alice.address},
-          amount: 100,
-        },
-      ]);
+    await usingPlaygrounds(async helper => {
+      const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+      const token = await collection.mintToken(alice, {Substrate: alice.address}, 100n);
+      await token.repartition(alice, 200n);
+      const chainEvents = helper.chainLog.slice(-1)[0].events.map((x: any) => x.event);
+      expect(chainEvents).to.include.deep.members([{
+        method: 'ItemCreated',
+        section: 'common',
+        index: '0x4202',
+        data: [ 
+          collection.collectionId.toString(), 
+          token.tokenId.toString(), 
+          {Substrate: alice.address}, 
+          '100',
+        ],
+      }]);
     });
   });
 
   it('Repartition with decreased amount', async () => {
-    await usingApi(async api => {
-      const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId;
-      const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId;
-
-      const tx = api.tx.unique.repartition(collectionId, tokenId, 50n);
-      const events = await submitTransactionAsync(alice, tx);
-      const substrateEvents = getDestroyItemsResult(events);
-      expect(substrateEvents).to.include.deep.members([
-        {
-          success: true,
-          collectionId,
-          itemId: tokenId,
-          owner: {Substrate: alice.address},
-          amount: 50,
-        },
-      ]);
-    });
-  });
-});
-
-describe('Test Refungible properties:', () => {
-  before(async () => {
-    await usingApi(async (api, privateKeyWrapper) => {
-      alice = privateKeyWrapper('//Alice');
-      bob = privateKeyWrapper('//Bob');
+    await usingPlaygrounds(async helper => {
+      const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+      const token = await collection.mintToken(alice, {Substrate: alice.address}, 100n);
+      await token.repartition(alice, 50n);
+      const chainEvents = helper.chainLog.slice(-1)[0].events.map((x: any) => x.event);
+      expect(chainEvents).to.include.deep.members([{
+        method: 'ItemDestroyed',
+        section: 'common',
+        index: '0x4203',
+        data: [ 
+          collection.collectionId.toString(), 
+          token.tokenId.toString(), 
+          {Substrate: alice.address}, 
+          '50',
+        ],
+      }]);
     });
   });
   
-  it('Сreate new collection with properties', async () => {
-    await usingApi(async api => {
+  it('Create new collection with properties', async () => {
+    await usingPlaygrounds(async helper => {
       const properties = [{key: 'key1', value: 'val1'}];
-      const propertyPermissions = [{key: 'key1', permission: {tokenOwner: true, mutable: false, collectionAdmin: true}}];
-      const collectionId = await createCollectionWithPropsExpectSuccess({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'ReFungible'},
-        properties: properties,
-        propPerm: propertyPermissions, 
-      });
-      const collection = (await getDetailedCollectionInfo(api, collectionId))!;
-      expect(collection.properties.toHuman()).to.be.deep.equal(properties);
-      expect(collection.tokenPropertyPermissions.toHuman()).to.be.deep.equal(propertyPermissions);
+      const tokenPropertyPermissions = [{key: 'key1', permission: {tokenOwner: true, mutable: false, collectionAdmin: true}}];
+      const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test', properties, tokenPropertyPermissions});
+      const info = await collection.getData();
+      expect(info?.raw.properties).to.be.deep.equal(properties);
+      expect(info?.raw.tokenPropertyPermissions).to.be.deep.equal(tokenPropertyPermissions);
     });
   });
 });
+
modifiedtests/src/removeCollectionAdmin.test.tsdiffbeforeafterboth
--- a/tests/src/removeCollectionAdmin.test.ts
+++ b/tests/src/removeCollectionAdmin.test.ts
@@ -16,123 +16,112 @@
 
 import chai from 'chai';
 import chaiAsPromised from 'chai-as-promised';
-import {default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync} from './substrate/substrate-api';
-import {createCollectionExpectSuccess, destroyCollectionExpectSuccess, getAdminList, normalizeAccountId, queryCollectionExpectSuccess} from './util/helpers';
+import {usingPlaygrounds} from './util/playgrounds';
 
 chai.use(chaiAsPromised);
 const expect = chai.expect;
 
 describe('Integration Test removeCollectionAdmin(collection_id, account_id):', () => {
   it('Remove collection admin.', async () => {
-    await usingApi(async (api, privateKeyWrapper) => {
-      const collectionId = await createCollectionExpectSuccess();
-      const alice = privateKeyWrapper('//Alice');
-      const bob = privateKeyWrapper('//Bob');
-      const collection = await queryCollectionExpectSuccess(api, collectionId);
-      expect(collection.owner.toString()).to.be.deep.eq(alice.address);
+    await usingPlaygrounds(async (helper, privateKey) => {
+      const alice = privateKey('//Alice');
+      const bob = privateKey('//Bob');
+      const collection = await helper.nft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+      const collectionInfo = await collection.getData();
+      expect(collectionInfo?.raw.owner.toString()).to.be.deep.eq(alice.address);
       // first - add collection admin Bob
-      const addAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(bob.address));
-      await submitTransactionAsync(alice, addAdminTx);
+      await collection.addAdmin(alice, {Substrate: bob.address});
 
-      const adminListAfterAddAdmin = await getAdminList(api, collectionId);
-      expect(adminListAfterAddAdmin).to.be.deep.contains(normalizeAccountId(bob.address));
+      const adminListAfterAddAdmin = await collection.getAdmins();
+      expect(adminListAfterAddAdmin).to.be.deep.contains({Substrate: helper.address.normalizeSubstrate(bob.address)});
 
       // then remove bob from admins of collection
-      const removeAdminTx = api.tx.unique.removeCollectionAdmin(collectionId, normalizeAccountId(bob.address));
-      await submitTransactionAsync(alice, removeAdminTx);
+      await collection.removeAdmin(alice, {Substrate: bob.address});
 
-      const adminListAfterRemoveAdmin = await getAdminList(api, collectionId);
-      expect(adminListAfterRemoveAdmin).not.to.be.deep.contains(normalizeAccountId(bob.address));
+      const adminListAfterRemoveAdmin = await collection.getAdmins();
+      expect(adminListAfterRemoveAdmin).not.to.be.deep.contains({Substrate: helper.address.normalizeSubstrate(bob.address)});
     });
   });
 
   it('Remove admin from collection that has no admins', async () => {
-    await usingApi(async (api, privateKeyWrapper) => {
-      const alice = privateKeyWrapper('//Alice');
-      const collectionId = await createCollectionExpectSuccess();
+    await usingPlaygrounds(async (helper, privateKey) => {
+      const alice = privateKey('//Alice');
+      const collection = await helper.nft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
 
-      const adminListBeforeAddAdmin = await getAdminList(api, collectionId);
+      const adminListBeforeAddAdmin = await collection.getAdmins();
       expect(adminListBeforeAddAdmin).to.have.lengthOf(0);
 
-      const tx = api.tx.unique.removeCollectionAdmin(collectionId, normalizeAccountId(alice.address));
-      await submitTransactionAsync(alice, tx);
+      // await expect(collection.removeAdmin(alice, {Substrate: alice.address})).to.be.rejectedWith('Unable to remove collection admin');
+      await collection.removeAdmin(alice, {Substrate: alice.address});
     });
   });
 });
 
 describe('Negative Integration Test removeCollectionAdmin(collection_id, account_id):', () => {
   it('Can\'t remove collection admin from not existing collection', async () => {
-    await usingApi(async (api, privateKeyWrapper) => {
+    await usingPlaygrounds(async (helper, privateKey) => {
       // tslint:disable-next-line: no-bitwise
       const collectionId = (1 << 32) - 1;
-      const alice = privateKeyWrapper('//Alice');
-      const bob = privateKeyWrapper('//Bob');
+      const alice = privateKey('//Alice');
+      const bob = privateKey('//Bob');
 
-      const changeOwnerTx = api.tx.unique.removeCollectionAdmin(collectionId, normalizeAccountId(bob.address));
-      await expect(submitTransactionExpectFailAsync(alice, changeOwnerTx)).to.be.rejected;
+      await expect(helper.collection.removeAdmin(alice, collectionId, {Substrate: bob.address})).to.be.rejected;
 
       // Verifying that nothing bad happened (network is live, new collections can be created, etc.)
-      await createCollectionExpectSuccess();
+      await helper.nft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
     });
   });
 
   it('Can\'t remove collection admin from deleted collection', async () => {
-    await usingApi(async (api, privateKeyWrapper) => {
-      // tslint:disable-next-line: no-bitwise
-      const collectionId = await createCollectionExpectSuccess();
-      const alice = privateKeyWrapper('//Alice');
-      const bob = privateKeyWrapper('//Bob');
+    await usingPlaygrounds(async (helper, privateKey) => {
+      const alice = privateKey('//Alice');
+      const bob = privateKey('//Bob');
+      const collection = await helper.nft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
 
-      await destroyCollectionExpectSuccess(collectionId);
+      expect(await collection.burn(alice)).to.be.true;
 
-      const changeOwnerTx = api.tx.unique.removeCollectionAdmin(collectionId, normalizeAccountId(bob.address));
-      await expect(submitTransactionExpectFailAsync(alice, changeOwnerTx)).to.be.rejected;
+      await expect(helper.collection.removeAdmin(alice, collection.collectionId, {Substrate: bob.address})).to.be.rejected;
 
       // Verifying that nothing bad happened (network is live, new collections can be created, etc.)
-      await createCollectionExpectSuccess();
+      await helper.nft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
     });
   });
 
   it('Regular user can\'t remove collection admin', async () => {
-    await usingApi(async (api, privateKeyWrapper) => {
-      const collectionId = await createCollectionExpectSuccess();
-      const alice = privateKeyWrapper('//Alice');
-      const bob = privateKeyWrapper('//Bob');
-      const charlie = privateKeyWrapper('//Charlie');
+    await usingPlaygrounds(async (helper, privateKey) => {
+      const alice = privateKey('//Alice');
+      const bob = privateKey('//Bob');
+      const charlie = privateKey('//Charlie');
+      const collection = await helper.nft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
 
-      const addAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(bob.address));
-      await submitTransactionAsync(alice, addAdminTx);
+      await collection.addAdmin(alice, {Substrate: bob.address});
 
-      const changeOwnerTx = api.tx.unique.removeCollectionAdmin(collectionId, normalizeAccountId(bob.address));
-      await expect(submitTransactionExpectFailAsync(charlie, changeOwnerTx)).to.be.rejected;
+      await expect(collection.removeAdmin(charlie, {Substrate: bob.address})).to.be.rejected;
 
       // Verifying that nothing bad happened (network is live, new collections can be created, etc.)
-      await createCollectionExpectSuccess();
+      await helper.nft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
     });
   });
 
   it('Admin can\'t remove collection admin.', async () => {
-    await usingApi(async (api, privateKeyWrapper) => {
-      const collectionId = await createCollectionExpectSuccess();
-      const alice = privateKeyWrapper('//Alice');
-      const bob = privateKeyWrapper('//Bob');
-      const charlie = privateKeyWrapper('//Charlie');
-
-      const addBobAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(bob.address));
-      await submitTransactionAsync(alice, addBobAdminTx);
-      const addCharlieAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(charlie.address));
-      await submitTransactionAsync(alice, addCharlieAdminTx);
+    await usingPlaygrounds(async (helper, privateKey) => {
+      const alice = privateKey('//Alice');
+      const bob = privateKey('//Bob');
+      const charlie = privateKey('//Charlie');
+      const collection = await helper.nft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+      
+      await collection.addAdmin(alice, {Substrate: bob.address});
+      await collection.addAdmin(alice, {Substrate: charlie.address});
 
-      const adminListAfterAddAdmin = await getAdminList(api, collectionId);
-      expect(adminListAfterAddAdmin).to.be.deep.contains(normalizeAccountId(bob.address));
-      expect(adminListAfterAddAdmin).to.be.deep.contains(normalizeAccountId(charlie.address));
+      const adminListAfterAddAdmin = await collection.getAdmins();
+      expect(adminListAfterAddAdmin).to.be.deep.contains({Substrate: helper.address.normalizeSubstrate(bob.address)});
+      expect(adminListAfterAddAdmin).to.be.deep.contains({Substrate: helper.address.normalizeSubstrate(charlie.address)});
 
-      const removeAdminTx = api.tx.unique.removeCollectionAdmin(collectionId, normalizeAccountId(bob.address));
-      await expect(submitTransactionExpectFailAsync(charlie, removeAdminTx)).to.be.rejected;
+      await expect(collection.removeAdmin(charlie, {Substrate: bob.address})).to.be.rejected;
 
-      const adminListAfterRemoveAdmin = await getAdminList(api, collectionId);
-      expect(adminListAfterRemoveAdmin).to.be.deep.contains(normalizeAccountId(bob.address));
-      expect(adminListAfterRemoveAdmin).to.be.deep.contains(normalizeAccountId(charlie.address));
+      const adminListAfterRemoveAdmin = await collection.getAdmins();
+      expect(adminListAfterRemoveAdmin).to.be.deep.contains({Substrate: helper.address.normalizeSubstrate(bob.address)});
+      expect(adminListAfterRemoveAdmin).to.be.deep.contains({Substrate: helper.address.normalizeSubstrate(charlie.address)});
     });
   });
 });
modifiedtests/src/rmrk/acceptNft.test.tsdiffbeforeafterboth
--- a/tests/src/rmrk/acceptNft.test.ts
+++ b/tests/src/rmrk/acceptNft.test.ts
@@ -8,14 +8,19 @@
 } from './util/tx';
 import {NftIdTuple} from './util/fetch';
 import {isNftChildOfAnother, expectTxFailure} from './util/helpers';
+import {requirePallets, Pallets} from '../util/helpers';
 
 describe('integration test: accept NFT', () => {
   let api: any;
-  before(async () => { api = await getApiConnection(); });
-
+  before(async function() {
+    api = await getApiConnection();
+    await requirePallets(this, [Pallets.RmrkCore]);
+  });
+  
+  
   const alice = '//Alice';
   const bob = '//Bob';
-
+  
   const createTestCollection = async (issuerUri: string) => {
     return await createCollection(
       api,
modifiedtests/src/rmrk/addResource.test.tsdiffbeforeafterboth
--- a/tests/src/rmrk/addResource.test.ts
+++ b/tests/src/rmrk/addResource.test.ts
@@ -12,6 +12,7 @@
   addNftComposableResource,
 } from './util/tx';
 import {RmrkTraitsResourceResourceInfo as ResourceInfo} from '@polkadot/types/lookup';
+import {requirePallets, Pallets} from '../util/helpers';
 
 describe('integration test: add NFT resource', () => {
   const Alice = '//Alice';
@@ -24,8 +25,9 @@
   const nonexistentId = 99999;
 
   let api: any;
-  before(async () => {
+  before(async function() {
     api = await getApiConnection();
+    await requirePallets(this, [Pallets.RmrkCore]);
   });
 
   it('add resource', async () => {
modifiedtests/src/rmrk/addTheme.test.tsdiffbeforeafterboth
--- a/tests/src/rmrk/addTheme.test.ts
+++ b/tests/src/rmrk/addTheme.test.ts
@@ -3,10 +3,14 @@
 import {createBase, addTheme} from './util/tx';
 import {expectTxFailure} from './util/helpers';
 import {getThemeNames} from './util/fetch';
+import {requirePallets, Pallets} from '../util/helpers';
 
 describe('integration test: add Theme to Base', () => {
   let api: any;
-  before(async () => { api = await getApiConnection(); });
+  before(async function() {
+    api = await getApiConnection();
+    await requirePallets(this, [Pallets.RmrkEquip]);
+  });
 
   const alice = '//Alice';
   const bob = '//Bob';
modifiedtests/src/rmrk/burnNft.test.tsdiffbeforeafterboth
--- a/tests/src/rmrk/burnNft.test.ts
+++ b/tests/src/rmrk/burnNft.test.ts
@@ -5,6 +5,7 @@
 
 import chai from 'chai';
 import chaiAsPromised from 'chai-as-promised';
+import {requirePallets, Pallets} from '../util/helpers';
 
 chai.use(chaiAsPromised);
 const expect = chai.expect;
@@ -14,10 +15,12 @@
   const Bob = '//Bob';
 
   let api: any;
-  before(async () => {
+  before(async function() {
     api = await getApiConnection();
+    await requirePallets(this, [Pallets.RmrkCore]);
   });
 
+
   it('burn nft', async () => {
     await createCollection(
       api,
modifiedtests/src/rmrk/changeCollectionIssuer.test.tsdiffbeforeafterboth
--- a/tests/src/rmrk/changeCollectionIssuer.test.ts
+++ b/tests/src/rmrk/changeCollectionIssuer.test.ts
@@ -1,4 +1,5 @@
 import {getApiConnection} from '../substrate/substrate-api';
+import {requirePallets, Pallets} from '../util/helpers';
 import {expectTxFailure} from './util/helpers';
 import {
   changeIssuer,
@@ -10,10 +11,13 @@
   const Bob = '//Bob';
 
   let api: any;
-  before(async () => {
+  before(async function() {
     api = await getApiConnection();
+    await requirePallets(this, [Pallets.RmrkCore]);
   });
 
+
+
   it('change collection issuer', async () => {
     await createCollection(
       api,
modifiedtests/src/rmrk/createBase.test.tsdiffbeforeafterboth
--- a/tests/src/rmrk/createBase.test.ts
+++ b/tests/src/rmrk/createBase.test.ts
@@ -1,9 +1,13 @@
 import {getApiConnection} from '../substrate/substrate-api';
+import {requirePallets, Pallets} from '../util/helpers';
 import {createCollection, createBase} from './util/tx';
 
 describe('integration test: create new Base', () => {
   let api: any;
-  before(async () => { api = await getApiConnection(); });
+  before(async function() {
+    api = await getApiConnection();
+    await requirePallets(this, [Pallets.RmrkCore, Pallets.RmrkEquip]);
+  });
 
   const alice = '//Alice';
 
modifiedtests/src/rmrk/createCollection.test.tsdiffbeforeafterboth
--- a/tests/src/rmrk/createCollection.test.ts
+++ b/tests/src/rmrk/createCollection.test.ts
@@ -1,9 +1,15 @@
 import {getApiConnection} from '../substrate/substrate-api';
+import {requirePallets, Pallets} from '../util/helpers';
 import {createCollection} from './util/tx';
 
 describe('Integration test: create new collection', () => {
   let api: any;
-  before(async () => { api = await getApiConnection(); });
+  before(async function () {
+    api = await getApiConnection();
+    await requirePallets(this, [Pallets.RmrkCore]);
+  });
+
+
 
   const alice = '//Alice';
 
modifiedtests/src/rmrk/deleteCollection.test.tsdiffbeforeafterboth
--- a/tests/src/rmrk/deleteCollection.test.ts
+++ b/tests/src/rmrk/deleteCollection.test.ts
@@ -1,11 +1,13 @@
 import {getApiConnection} from '../substrate/substrate-api';
+import {requirePallets, Pallets} from '../util/helpers';
 import {expectTxFailure} from './util/helpers';
 import {createCollection, deleteCollection} from './util/tx';
 
 describe('integration test: delete collection', () => {
   let api: any;
-  before(async () => {
+  before(async function () {
     api = await getApiConnection();
+    await requirePallets(this, [Pallets.RmrkCore]);
   });
 
   const Alice = '//Alice';
modifiedtests/src/rmrk/equipNft.test.tsdiffbeforeafterboth
--- a/tests/src/rmrk/equipNft.test.ts
+++ b/tests/src/rmrk/equipNft.test.ts
@@ -1,6 +1,7 @@
 import {ApiPromise} from '@polkadot/api';
 import {expect} from 'chai';
 import {getApiConnection} from '../substrate/substrate-api';
+import {requirePallets, Pallets} from '../util/helpers';
 import {getNft, getParts, NftIdTuple} from './util/fetch';
 import {expectTxFailure} from './util/helpers';
 import {
@@ -122,8 +123,10 @@
 describe.skip('integration test: Equip NFT', () => {
 
   let api: any;
-  before(async () => {
+  
+  before(async function () {
     api = await getApiConnection();
+    await requirePallets(this, [Pallets.RmrkCore, Pallets.RmrkEquip]);
   });
 
   it('equip nft', async () => {
modifiedtests/src/rmrk/getOwnedNfts.test.tsdiffbeforeafterboth
--- a/tests/src/rmrk/getOwnedNfts.test.ts
+++ b/tests/src/rmrk/getOwnedNfts.test.ts
@@ -1,11 +1,17 @@
 import {expect} from 'chai';
 import {getApiConnection} from '../substrate/substrate-api';
+import {requirePallets, Pallets} from '../util/helpers';
 import {getOwnedNfts} from './util/fetch';
 import {mintNft, createCollection} from './util/tx';
 
 describe('integration test: get owned NFTs', () => {
   let api: any;
-  before(async () => { api = await getApiConnection(); });
+  
+  before(async function () {
+    api = await getApiConnection();
+    await requirePallets(this, [Pallets.RmrkCore]);
+  });
+
 
   const alice = '//Alice';
 
modifiedtests/src/rmrk/lockCollection.test.tsdiffbeforeafterboth
--- a/tests/src/rmrk/lockCollection.test.ts
+++ b/tests/src/rmrk/lockCollection.test.ts
@@ -1,4 +1,5 @@
 import {getApiConnection} from '../substrate/substrate-api';
+import {requirePallets, Pallets} from '../util/helpers';
 import {expectTxFailure} from './util/helpers';
 import {createCollection, lockCollection, mintNft} from './util/tx';
 
@@ -8,8 +9,9 @@
   const Max = 5;
 
   let api: any;
-  before(async () => {
+  before(async function () {
     api = await getApiConnection();
+    await requirePallets(this, [Pallets.RmrkCore]);
   });
 
   it('lock collection', async () => {
modifiedtests/src/rmrk/mintNft.test.tsdiffbeforeafterboth
--- a/tests/src/rmrk/mintNft.test.ts
+++ b/tests/src/rmrk/mintNft.test.ts
@@ -1,12 +1,18 @@
 import {expect} from 'chai';
 import {getApiConnection} from '../substrate/substrate-api';
+import {requirePallets, Pallets} from '../util/helpers';
 import {getNft} from './util/fetch';
 import {expectTxFailure} from './util/helpers';
 import {createCollection, mintNft} from './util/tx';
 
 describe('integration test: mint new NFT', () => {
   let api: any;
-  before(async () => { api = await getApiConnection(); });
+ 
+  before(async function () {
+    api = await getApiConnection();
+    await requirePallets(this, [Pallets.RmrkCore]);
+  });
+
 
   const alice = '//Alice';
   const bob = '//Bob';
modifiedtests/src/rmrk/rejectNft.test.tsdiffbeforeafterboth
--- a/tests/src/rmrk/rejectNft.test.ts
+++ b/tests/src/rmrk/rejectNft.test.ts
@@ -8,10 +8,16 @@
 } from './util/tx';
 import {getChildren, NftIdTuple} from './util/fetch';
 import {isNftChildOfAnother, expectTxFailure} from './util/helpers';
+import {requirePallets, Pallets} from '../util/helpers';
 
 describe('integration test: reject NFT', () => {
   let api: any;
-  before(async () => { api = await getApiConnection(); });
+  before(async function () {
+    api = await getApiConnection();
+    await requirePallets(this, [Pallets.RmrkCore]);
+  });
+
+
 
   const alice = '//Alice';
   const bob = '//Bob';
modifiedtests/src/rmrk/removeResource.test.tsdiffbeforeafterboth
--- a/tests/src/rmrk/removeResource.test.ts
+++ b/tests/src/rmrk/removeResource.test.ts
@@ -1,6 +1,7 @@
 import {expect} from 'chai';
 import privateKey from '../substrate/privateKey';
 import {executeTransaction, getApiConnection} from '../substrate/substrate-api';
+import {requirePallets, Pallets} from '../util/helpers';
 import {getNft, NftIdTuple} from './util/fetch';
 import {expectTxFailure} from './util/helpers';
 import {
@@ -16,9 +17,10 @@
 describe('Integration test: remove nft resource', () => {
   let api: any;
   let ss58Format: string;
-  before(async () => {
+  before(async function() {
     api = await getApiConnection();
     ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;
+    await requirePallets(this, [Pallets.RmrkCore]);
   });
 
   const Alice = '//Alice';
modifiedtests/src/rmrk/rmrkIsolation.test.tsdiffbeforeafterboth
--- a/tests/src/rmrk/rmrkIsolation.test.ts
+++ b/tests/src/rmrk/rmrkIsolation.test.ts
@@ -6,7 +6,9 @@
   getCreateCollectionResult,
   getDetailedCollectionInfo,
   getGenericResult,
+  requirePallets,
   normalizeAccountId,
+  Pallets,
 } from '../util/helpers';
 import {IKeyringPair} from '@polkadot/types/types';
 import {ApiPromise} from '@polkadot/api';
@@ -59,11 +61,10 @@
 describe('RMRK External Integration Test', async () => {
   const it_rmrk = (await isUnique() ? it : it.skip);
 
-  before(async () => {
+  before(async function() {
     await usingApi(async (api, privateKeyWrapper) => {
       alice = privateKeyWrapper('//Alice');
-
-      
+      await requirePallets(this, [Pallets.RmrkCore]);
     });
   });
 
modifiedtests/src/rmrk/sendNft.test.tsdiffbeforeafterboth
--- a/tests/src/rmrk/sendNft.test.ts
+++ b/tests/src/rmrk/sendNft.test.ts
@@ -3,10 +3,14 @@
 import {createCollection, mintNft, sendNft} from './util/tx';
 import {NftIdTuple} from './util/fetch';
 import {isNftChildOfAnother, expectTxFailure} from './util/helpers';
+import {requirePallets, Pallets} from '../util/helpers';
 
 describe('integration test: send NFT', () => {
   let api: any;
-  before(async () => { api = await getApiConnection(); });
+  before(async function () {
+    api = await getApiConnection();
+    await requirePallets(this, [Pallets.RmrkCore]);
+  });
 
   const maxNftId = 0xFFFFFFFF;
 
modifiedtests/src/rmrk/setCollectionProperty.test.tsdiffbeforeafterboth
--- a/tests/src/rmrk/setCollectionProperty.test.ts
+++ b/tests/src/rmrk/setCollectionProperty.test.ts
@@ -1,4 +1,5 @@
 import {getApiConnection} from '../substrate/substrate-api';
+import {requirePallets, Pallets} from '../util/helpers';
 import {expectTxFailure} from './util/helpers';
 import {createCollection, setPropertyCollection} from './util/tx';
 
@@ -7,8 +8,9 @@
   const Bob = '//Bob';
 
   let api: any;
-  before(async () => {
+  before(async function () {
     api = await getApiConnection();
+    await requirePallets(this, [Pallets.RmrkCore]);
   });
 
   it('set collection property', async () => {
modifiedtests/src/rmrk/setEquippableList.test.tsdiffbeforeafterboth
--- a/tests/src/rmrk/setEquippableList.test.ts
+++ b/tests/src/rmrk/setEquippableList.test.ts
@@ -1,10 +1,14 @@
 import {getApiConnection} from '../substrate/substrate-api';
+import {requirePallets, Pallets} from '../util/helpers';
 import {expectTxFailure} from './util/helpers';
 import {createCollection, createBase, setEquippableList} from './util/tx';
 
 describe("integration test: set slot's Equippable List", () => {
   let api: any;
-  before(async () => { api = await getApiConnection(); });
+  before(async function () {
+    api = await getApiConnection();
+    await requirePallets(this, [Pallets.RmrkCore]);
+  });
 
   const alice = '//Alice';
   const bob = '//Bob';
modifiedtests/src/rmrk/setNftProperty.test.tsdiffbeforeafterboth
--- a/tests/src/rmrk/setNftProperty.test.ts
+++ b/tests/src/rmrk/setNftProperty.test.ts
@@ -1,11 +1,15 @@
 import {getApiConnection} from '../substrate/substrate-api';
+import {requirePallets, Pallets} from '../util/helpers';
 import {NftIdTuple} from './util/fetch';
 import {expectTxFailure} from './util/helpers';
 import {createCollection, mintNft, sendNft, setNftProperty} from './util/tx';
 
 describe('integration test: set NFT property', () => {
   let api: any;
-  before(async () => { api = await getApiConnection(); });
+  before(async function () {
+    api = await getApiConnection();
+    await requirePallets(this, [Pallets.RmrkCore]);
+  });
 
   const alice = '//Alice';
   const bob = '//Bob';
modifiedtests/src/rmrk/setResourcePriorities.test.tsdiffbeforeafterboth
--- a/tests/src/rmrk/setResourcePriorities.test.ts
+++ b/tests/src/rmrk/setResourcePriorities.test.ts
@@ -1,10 +1,14 @@
 import {getApiConnection} from '../substrate/substrate-api';
+import {requirePallets, Pallets} from '../util/helpers';
 import {expectTxFailure} from './util/helpers';
 import {mintNft, createCollection, setResourcePriorities} from './util/tx';
 
 describe('integration test: set NFT resource priorities', () => {
   let api: any;
-  before(async () => { api = await getApiConnection(); });
+  before(async function () {
+    api = await getApiConnection();
+    await requirePallets(this, [Pallets.RmrkCore]);
+  });
 
   const alice = '//Alice';
   const bob = '//Bob';
modifiedtests/src/setCollectionSponsor.test.tsdiffbeforeafterboth
--- a/tests/src/setCollectionSponsor.test.ts
+++ b/tests/src/setCollectionSponsor.test.ts
@@ -23,6 +23,8 @@
   setCollectionSponsorExpectFailure,
   addCollectionAdminExpectSuccess,
   getCreatedCollectionCount,
+  requirePallets,
+  Pallets,
 } from './util/helpers';
 import {IKeyringPair} from '@polkadot/types/types';
 
@@ -50,7 +52,9 @@
     const collectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
     await setCollectionSponsorExpectSuccess(collectionId, bob.address);
   });
-  it('Set ReFungible collection sponsor', async () => {
+  it('Set ReFungible collection sponsor', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
     await setCollectionSponsorExpectSuccess(collectionId, bob.address);
   });
modifiedtests/src/transfer.test.tsdiffbeforeafterboth
--- a/tests/src/transfer.test.ts
+++ b/tests/src/transfer.test.ts
@@ -35,11 +35,14 @@
   getBalance as getTokenBalance,
   transferFromExpectSuccess,
   transferFromExpectFail,
+  requirePallets,
+  Pallets,
 } from './util/helpers';
 import {
   subToEth,
   itWeb3, 
 } from './eth/util/helpers';
+import {request} from 'https';
 
 let alice: IKeyringPair;
 let bob: IKeyringPair;
@@ -89,56 +92,61 @@
     });
   });
 
-  it('User can transfer owned token', async () => {
-    await usingApi(async (api, privateKeyWrapper) => {
-      // nft
-      const nftCollectionId = await createCollectionExpectSuccess();
-      const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT');
-      await transferExpectSuccess(nftCollectionId, newNftTokenId, alice, bob, 1, 'NFT');
-      // fungible
-      const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
-      const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible');
-      await transferExpectSuccess(fungibleCollectionId, newFungibleTokenId, alice, bob, 1, 'Fungible');
-      // reFungible
-      const reFungibleCollectionId = await
-      createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
-      const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible');
-      await transferExpectSuccess(
-        reFungibleCollectionId,
-        newReFungibleTokenId,
-        alice,
-        bob,
-        100,
-        'ReFungible',
-      );
-    });
+  it('[nft] User can transfer owned token', async () => {
+    const nftCollectionId = await createCollectionExpectSuccess();
+    const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT');
+    await transferExpectSuccess(nftCollectionId, newNftTokenId, alice, bob, 1, 'NFT');
+  });
+
+  it('[fungible] User can transfer owned token', async () => {
+    const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
+    const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible');
+    await transferExpectSuccess(fungibleCollectionId, newFungibleTokenId, alice, bob, 1, 'Fungible');
+  });
+
+  it('[refungible] User can transfer owned token', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
+    const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
+    const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible');
+    await transferExpectSuccess(
+      reFungibleCollectionId,
+      newReFungibleTokenId,
+      alice,
+      bob,
+      100,
+      'ReFungible',
+    );
+  });
+
+  it('[nft] Collection admin can transfer owned token', async () => {
+    const nftCollectionId = await createCollectionExpectSuccess();
+    await addCollectionAdminExpectSuccess(alice, nftCollectionId, bob.address);
+    const newNftTokenId = await createItemExpectSuccess(bob, nftCollectionId, 'NFT', bob.address);
+    await transferExpectSuccess(nftCollectionId, newNftTokenId, bob, alice, 1, 'NFT');
+  });
+
+  it('[fungible] Collection admin can transfer owned token', async () => {
+    const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
+    await addCollectionAdminExpectSuccess(alice, fungibleCollectionId, bob.address);
+    const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible', bob.address);
+    await transferExpectSuccess(fungibleCollectionId, newFungibleTokenId, bob, alice, 1, 'Fungible');
   });
 
-  it('Collection admin can transfer owned token', async () => {
-    await usingApi(async (api, privateKeyWrapper) => {
-      // nft
-      const nftCollectionId = await createCollectionExpectSuccess();
-      await addCollectionAdminExpectSuccess(alice, nftCollectionId, bob.address);
-      const newNftTokenId = await createItemExpectSuccess(bob, nftCollectionId, 'NFT', bob.address);
-      await transferExpectSuccess(nftCollectionId, newNftTokenId, bob, alice, 1, 'NFT');
-      // fungible
-      const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
-      await addCollectionAdminExpectSuccess(alice, fungibleCollectionId, bob.address);
-      const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible', bob.address);
-      await transferExpectSuccess(fungibleCollectionId, newFungibleTokenId, bob, alice, 1, 'Fungible');
-      // reFungible
-      const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
-      await addCollectionAdminExpectSuccess(alice, reFungibleCollectionId, bob.address);
-      const newReFungibleTokenId = await createItemExpectSuccess(bob, reFungibleCollectionId, 'ReFungible', bob.address);
-      await transferExpectSuccess(
-        reFungibleCollectionId,
-        newReFungibleTokenId,
-        bob,
-        alice,
-        100,
-        'ReFungible',
-      );
-    });
+  it('[refungible] Collection admin can transfer owned token', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
+    const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
+    await addCollectionAdminExpectSuccess(alice, reFungibleCollectionId, bob.address);
+    const newReFungibleTokenId = await createItemExpectSuccess(bob, reFungibleCollectionId, 'ReFungible', bob.address);
+    await transferExpectSuccess(
+      reFungibleCollectionId,
+      newReFungibleTokenId,
+      bob,
+      alice,
+      100,
+      'ReFungible',
+    );
   });
 });
 
@@ -150,33 +158,49 @@
       charlie = privateKeyWrapper('//Charlie');
     });
   });
-  it('Transfer with not existed collection_id', async () => {
+
+  it('[nft] Transfer with not existed collection_id', async () => {
     await usingApi(async (api) => {
-      // nft
       const nftCollectionCount = await getCreatedCollectionCount(api);
       await transferExpectFailure(nftCollectionCount + 1, 1, alice, bob, 1);
-      // fungible
+    });
+  });
+
+  it('[fungible] Transfer with not existed collection_id', async () => {
+    await usingApi(async (api) => {
       const fungibleCollectionCount = await getCreatedCollectionCount(api);
       await transferExpectFailure(fungibleCollectionCount + 1, 0, alice, bob, 1);
-      // reFungible
+    });
+  });
+
+  it('[refungible] Transfer with not existed collection_id', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
+    await usingApi(async (api) => {
       const reFungibleCollectionCount = await getCreatedCollectionCount(api);
       await transferExpectFailure(reFungibleCollectionCount + 1, 1, alice, bob, 1);
     });
   });
-  it('Transfer with deleted collection_id', async () => {
-    // nft
+
+  it('[nft] Transfer with deleted collection_id', async () => {
     const nftCollectionId = await createCollectionExpectSuccess();
     const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT');
     await burnItemExpectSuccess(alice, nftCollectionId, newNftTokenId);
     await destroyCollectionExpectSuccess(nftCollectionId);
     await transferExpectFailure(nftCollectionId, newNftTokenId, alice, bob, 1);
-    // fungible
+  });
+
+  it('[fungible] Transfer with deleted collection_id', async () => {
     const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
     const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible');
     await burnItemExpectSuccess(alice, fungibleCollectionId, newFungibleTokenId, 10);
     await destroyCollectionExpectSuccess(fungibleCollectionId);
     await transferExpectFailure(fungibleCollectionId, newFungibleTokenId, alice, bob, 1);
-    // reFungible
+  });
+
+  it('[refungible] Transfer with deleted collection_id', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     const reFungibleCollectionId = await
     createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
     const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible');
@@ -190,16 +214,21 @@
       1,
     );
   });
-  it('Transfer with not existed item_id', async () => {
-    // nft
+
+  it('[nft] Transfer with not existed item_id', async () => {
     const nftCollectionId = await createCollectionExpectSuccess();
     await transferExpectFailure(nftCollectionId, 2, alice, bob, 1);
-    // fungible
+  });
+
+  it('[fungible] Transfer with not existed item_id', async () => {
     const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
     await transferExpectFailure(fungibleCollectionId, 2, alice, bob, 1);
-    // reFungible
-    const reFungibleCollectionId = await
-    createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
+  });
+
+  it('[refungible] Transfer with not existed item_id', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
+    const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
     await transferExpectFailure(
       reFungibleCollectionId,
       2,
@@ -208,18 +237,24 @@
       1,
     );
   });
-  it('Transfer with deleted item_id', async () => {
-    // nft
+
+  it('[nft] Transfer with deleted item_id', async () => {
     const nftCollectionId = await createCollectionExpectSuccess();
     const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT');
     await burnItemExpectSuccess(alice, nftCollectionId, newNftTokenId, 1);
     await transferExpectFailure(nftCollectionId, newNftTokenId, alice, bob, 1);
-    // fungible
+  });
+
+  it('[fungible] Transfer with deleted item_id', async () => {
     const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
     const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible');
     await burnItemExpectSuccess(alice, fungibleCollectionId, newFungibleTokenId, 10);
     await transferExpectFailure(fungibleCollectionId, newFungibleTokenId, alice, bob, 1);
-    // reFungible
+  });
+
+  it('[refungible] Transfer with deleted item_id', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     const reFungibleCollectionId = await
     createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
     const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible');
@@ -232,18 +267,23 @@
       1,
     );
   });
-  it('Transfer with recipient that is not owner', async () => {
-    // nft
+
+  it('[nft] Transfer with recipient that is not owner', async () => {
     const nftCollectionId = await createCollectionExpectSuccess();
     const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT');
     await transferExpectFailure(nftCollectionId, newNftTokenId, charlie, bob, 1);
-    // fungible
+  });
+
+  it('[fungible] Transfer with recipient that is not owner', async () => {
     const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
     const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible');
     await transferExpectFailure(fungibleCollectionId, newFungibleTokenId, charlie, bob, 1);
-    // reFungible
-    const reFungibleCollectionId = await
-    createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
+  });
+
+  it('[refungible] Transfer with recipient that is not owner', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
+    const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
     const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible');
     await transferExpectFailure(
       reFungibleCollectionId,
@@ -276,7 +316,9 @@
     });
   });
 
-  it('RFT', async () => {
+  it('RFT', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await usingApi(async (api: ApiPromise) => {
       const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
       const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible');
modifiedtests/src/transferFrom.test.tsdiffbeforeafterboth
--- a/tests/src/transferFrom.test.ts
+++ b/tests/src/transferFrom.test.ts
@@ -31,6 +31,8 @@
   burnItemExpectSuccess,
   setCollectionLimitsExpectSuccess,
   getCreatedCollectionCount,
+  requirePallets,
+  Pallets,
 } from './util/helpers';
 
 chai.use(chaiAsPromised);
@@ -49,36 +51,36 @@
     });
   });
 
-  it('Execute the extrinsic and check nftItemList - owner of token', async () => {
-    await usingApi(async () => {
-      // nft
-      const nftCollectionId = await createCollectionExpectSuccess();
-      const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT');
-      await approveExpectSuccess(nftCollectionId, newNftTokenId, alice, bob.address);
+  it('[nft] Execute the extrinsic and check nftItemList - owner of token', async () => {
+    const nftCollectionId = await createCollectionExpectSuccess();
+    const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT');
+    await approveExpectSuccess(nftCollectionId, newNftTokenId, alice, bob.address);
 
-      await transferFromExpectSuccess(nftCollectionId, newNftTokenId, bob, alice, charlie, 1, 'NFT');
+    await transferFromExpectSuccess(nftCollectionId, newNftTokenId, bob, alice, charlie, 1, 'NFT');
+  });
 
-      // fungible
-      const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
-      const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible');
-      await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, alice, bob.address);
-      await transferFromExpectSuccess(fungibleCollectionId, newFungibleTokenId, bob, alice, charlie, 1, 'Fungible');
+  it('[fungible] Execute the extrinsic and check nftItemList - owner of token', async () => {
+    const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
+    const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible');
+    await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, alice, bob.address);
+    await transferFromExpectSuccess(fungibleCollectionId, newFungibleTokenId, bob, alice, charlie, 1, 'Fungible');
+  });
 
-      // reFungible
-      const reFungibleCollectionId = await
-      createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
-      const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible');
-      await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, alice, bob.address, 100);
-      await transferFromExpectSuccess(
-        reFungibleCollectionId,
-        newReFungibleTokenId,
-        bob,
-        alice,
-        charlie,
-        100,
-        'ReFungible',
-      );
-    });
+  it('[refungible] Execute the extrinsic and check nftItemList - owner of token', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
+    const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
+    const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible');
+    await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, alice, bob.address, 100);
+    await transferFromExpectSuccess(
+      reFungibleCollectionId,
+      newReFungibleTokenId,
+      bob,
+      alice,
+      charlie,
+      100,
+      'ReFungible',
+    );
   });
 
   it('Should reduce allowance if value is big', async () => {
@@ -119,20 +121,28 @@
     });
   });
 
-  it('transferFrom for a collection that does not exist', async () => {
+  it('[nft] transferFrom for a collection that does not exist', async () => {
     await usingApi(async (api: ApiPromise) => {
-      // nft
       const nftCollectionCount = await getCreatedCollectionCount(api);
       await approveExpectFail(nftCollectionCount + 1, 1, alice, bob);
 
       await transferFromExpectFail(nftCollectionCount + 1, 1, bob, alice, charlie, 1);
+    });
+  });
 
-      // fungible
+  it('[fungible] transferFrom for a collection that does not exist', async () => {
+    await usingApi(async (api: ApiPromise) => {
       const fungibleCollectionCount = await getCreatedCollectionCount(api);
       await approveExpectFail(fungibleCollectionCount + 1, 0, alice, bob);
 
       await transferFromExpectFail(fungibleCollectionCount + 1, 0, bob, alice, charlie, 1);
-      // reFungible
+    });
+  });
+
+  it('[refungible] transferFrom for a collection that does not exist', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
+    await usingApi(async (api: ApiPromise) => {
       const reFungibleCollectionCount = await getCreatedCollectionCount(api);
       await approveExpectFail(reFungibleCollectionCount + 1, 1, alice, bob);
 
@@ -158,67 +168,70 @@
     });
   }); */
 
-  it('transferFrom for not approved address', async () => {
-    await usingApi(async () => {
-      // nft
-      const nftCollectionId = await createCollectionExpectSuccess();
-      const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT');
+  it('[nft] transferFrom for not approved address', async () => {
+    const nftCollectionId = await createCollectionExpectSuccess();
+    const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT');
 
-      await transferFromExpectFail(nftCollectionId, newNftTokenId, bob, alice, charlie, 1);
+    await transferFromExpectFail(nftCollectionId, newNftTokenId, bob, alice, charlie, 1);
+  });
 
-      // fungible
-      const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
-      const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible');
-      await transferFromExpectFail(fungibleCollectionId, newFungibleTokenId, bob, alice, charlie, 1);
-      // reFungible
-      const reFungibleCollectionId = await
-      createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
-      const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible');
-      await transferFromExpectFail(
-        reFungibleCollectionId,
-        newReFungibleTokenId,
-        bob,
-        alice,
-        charlie,
-        1,
-      );
-    });
+  it('[fungible] transferFrom for not approved address', async () => {
+    const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
+    const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible');
+    await transferFromExpectFail(fungibleCollectionId, newFungibleTokenId, bob, alice, charlie, 1);
+  });
+
+  it('[refungible] transferFrom for not approved address', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
+    const reFungibleCollectionId = await
+    createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
+    const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible');
+    await transferFromExpectFail(
+      reFungibleCollectionId,
+      newReFungibleTokenId,
+      bob,
+      alice,
+      charlie,
+      1,
+    );
+  });
+
+  it('[nft] transferFrom incorrect token count', async () => {
+    const nftCollectionId = await createCollectionExpectSuccess();
+    const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT');
+    await approveExpectSuccess(nftCollectionId, newNftTokenId, alice, bob.address);
+
+    await transferFromExpectFail(nftCollectionId, newNftTokenId, bob, alice, charlie, 2);
   });
 
-  it('transferFrom incorrect token count', async () => {
-    await usingApi(async () => {
-      // nft
-      const nftCollectionId = await createCollectionExpectSuccess();
-      const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT');
-      await approveExpectSuccess(nftCollectionId, newNftTokenId, alice, bob.address);
+  it('[fungible] transferFrom incorrect token count', async () => {
+    const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
+    const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible');
+    await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, alice, bob.address);
+    await transferFromExpectFail(fungibleCollectionId, newFungibleTokenId, bob, alice, charlie, 2);
+  });
 
-      await transferFromExpectFail(nftCollectionId, newNftTokenId, bob, alice, charlie, 2);
+  it('[refungible] transferFrom incorrect token count', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
 
-      // fungible
-      const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
-      const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible');
-      await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, alice, bob.address);
-      await transferFromExpectFail(fungibleCollectionId, newFungibleTokenId, bob, alice, charlie, 2);
-      // reFungible
-      const reFungibleCollectionId = await
-      createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
-      const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible');
-      await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, alice, bob.address);
-      await transferFromExpectFail(
-        reFungibleCollectionId,
-        newReFungibleTokenId,
-        bob,
-        alice,
-        charlie,
-        2,
-      );
-    });
+    const reFungibleCollectionId = await
+    createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
+    const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible');
+    await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, alice, bob.address);
+    await transferFromExpectFail(
+      reFungibleCollectionId,
+      newReFungibleTokenId,
+      bob,
+      alice,
+      charlie,
+      2,
+    );
   });
 
-  it('execute transferFrom from account that is not owner of collection', async () => {
+  it('[nft] execute transferFrom from account that is not owner of collection', async () => {
     await usingApi(async (api, privateKeyWrapper) => {
       const dave = privateKeyWrapper('//Dave');
-      // nft
       const nftCollectionId = await createCollectionExpectSuccess();
       const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT');
       try {
@@ -230,8 +243,13 @@
       }
 
       // await transferFromExpectFail(nftCollectionId, newNftTokenId, Dave, Alice, Charlie, 1);
+    });
+  });
 
-      // fungible
+  it('[fungible] execute transferFrom from account that is not owner of collection', async () => {
+    await usingApi(async (api, privateKeyWrapper) => {
+      const dave = privateKeyWrapper('//Dave');
+
       const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
       const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible');
       try {
@@ -241,7 +259,14 @@
         // tslint:disable-next-line:no-unused-expression
         expect(e).to.be.exist;
       }
-      // reFungible
+    });
+  });
+
+  it('[refungible] execute transferFrom from account that is not owner of collection', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
+    await usingApi(async (api, privateKeyWrapper) => {
+      const dave = privateKeyWrapper('//Dave');
       const reFungibleCollectionId = await
       createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
       const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible');
@@ -276,7 +301,9 @@
 
     });
   });
-  it('transferFrom burnt token before approve ReFungible', async () => {
+  it('transferFrom burnt token before approve ReFungible', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await usingApi(async () => {
       const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
       await setCollectionLimitsExpectSuccess(alice, reFungibleCollectionId, {ownerCanTransfer: true});
@@ -308,7 +335,9 @@
 
     });
   });
-  it('transferFrom burnt token after approve ReFungible', async () => {
+  it('transferFrom burnt token after approve ReFungible', async function() {
+    await requirePallets(this, [Pallets.ReFungible]);
+
     await usingApi(async () => {
       const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
       const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible');
modifiedtests/src/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -28,6 +28,7 @@
 import {hexToStr, strToUTF16, utf16ToStr} from './util';
 import {UpDataStructsRpcCollection, UpDataStructsCreateItemData, UpDataStructsProperty} from '@polkadot/types/lookup';
 import {UpDataStructsTokenChild} from '../interfaces';
+import {Context} from 'mocha';
 
 chai.use(chaiAsPromised);
 const expect = chai.expect;
@@ -38,6 +39,66 @@
   Ethereum: string,
 };
 
+
+export enum Pallets {
+  Inflation = 'inflation',
+  RmrkCore = 'rmrkcore',
+  RmrkEquip = 'rmrkequip',
+  ReFungible = 'refungible',
+  Fungible = 'fungible',
+  NFT = 'nonfungible',
+  Scheduler = 'scheduler',
+}
+
+export async function isUnique(): Promise<boolean> {
+  return usingApi(async api => {
+    const chain = await api.rpc.system.chain();
+
+    return chain.eq('UNIQUE');
+  });
+}
+
+export async function isQuartz(): Promise<boolean> {
+  return usingApi(async api => {
+    const chain = await api.rpc.system.chain();
+    
+    return chain.eq('QUARTZ');
+  });
+}
+
+let modulesNames: any;
+export function getModuleNames(api: ApiPromise): string[] {
+  if (typeof modulesNames === 'undefined') 
+    modulesNames = api.runtimeMetadata.asLatest.pallets.map(m => m.name.toString().toLowerCase());
+  return modulesNames;
+}
+
+export async function missingRequiredPallets(requiredPallets: string[]): Promise<string[]> {
+  return await usingApi(async api => {
+    const pallets = getModuleNames(api);
+
+    return requiredPallets.filter(p => !pallets.includes(p));
+  });
+}
+
+export async function checkPalletsPresence(requiredPallets: string[]): Promise<boolean> {
+  return (await missingRequiredPallets(requiredPallets)).length == 0;
+}
+
+export async function requirePallets(mocha: Context, requiredPallets: string[]) {
+  const missingPallets = await missingRequiredPallets(requiredPallets);
+
+  if (missingPallets.length > 0) {
+    const skippingTestMsg = `\tSkipping test "${mocha.test?.title}".`;
+    const missingPalletsMsg = `\tThe following pallets are missing:\n\t- ${missingPallets.join('\n\t- ')}`;
+    const skipMsg = `${skippingTestMsg}\n${missingPalletsMsg}`;
+
+    console.error('\x1b[38:5:208m%s\x1b[0m', skipMsg);
+
+    mocha.skip();
+  }
+}
+
 export function normalizeAccountId(input: string | AccountId | CrossAccountId | IKeyringPair): CrossAccountId {
   if (typeof input === 'string') {
     if (input.length >= 47) {
addedtests/src/util/playgrounds/index.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/util/playgrounds/index.ts
@@ -0,0 +1,93 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+import {IKeyringPair} from '@polkadot/types/types';
+import {UniqueHelper} from './unique';
+import config from '../../config';
+import '../../interfaces/augment-api-events';
+import * as defs from '../../interfaces/definitions';
+import {ApiPromise, WsProvider} from '@polkadot/api';
+
+
+class SilentLogger {
+  log(msg: any, level: any): void {}
+  level = {
+    ERROR: 'ERROR' as const,
+    WARNING: 'WARNING' as const,
+    INFO: 'INFO' as const,
+  };
+}
+
+
+class DevUniqueHelper extends UniqueHelper {
+  async connect(wsEndpoint: string, listeners?: any): Promise<void> {
+    const wsProvider = new WsProvider(wsEndpoint);
+    this.api = new ApiPromise({
+      provider: wsProvider, 
+      signedExtensions: {
+        ContractHelpers: {
+          extrinsic: {},
+          payload: {},
+        },
+        FakeTransactionFinalizer: {
+          extrinsic: {},
+          payload: {},
+        },
+      },
+      rpc: {
+        unique: defs.unique.rpc,
+        rmrk: defs.rmrk.rpc,
+        eth: {
+          feeHistory: {
+            description: 'Dummy',
+            params: [],
+            type: 'u8',
+          },
+          maxPriorityFeePerGas: {
+            description: 'Dummy',
+            params: [],
+            type: 'u8',
+          },
+        },
+      },
+    });
+    await this.api.isReadyOrError;
+    this.network = await UniqueHelper.detectNetwork(this.api);
+  }
+}
+
+export const usingPlaygrounds = async (code: (helper: UniqueHelper, privateKey: (seed: string) => IKeyringPair) => Promise<void>) => {
+  // TODO: Remove, this is temporary: Filter unneeded API output
+  // (Jaco promised it will be removed in the next version)
+  const consoleErr = console.error;
+  const consoleLog = console.log;
+  const consoleWarn = console.warn;
+
+  const outFn = (printer: any) => (...args: any[]) => {
+    for (const arg of args) {
+      if (typeof arg !== 'string')
+        continue;
+      if (arg.includes('1000:: Normal connection closure' || arg === 'Normal connection closure'))
+        return;
+    }
+    printer(...args);
+  };
+
+  console.error = outFn(consoleErr.bind(console));
+  console.log = outFn(consoleLog.bind(console));
+  console.warn = outFn(consoleWarn.bind(console));
+  const helper = new DevUniqueHelper(new SilentLogger());
+
+  try {
+    await helper.connect(config.substrateUrl);
+    const ss58Format = helper.chain.getChainProperties().ss58Format;
+    const privateKey = (seed: string) => helper.util.fromSeed(seed, ss58Format);
+    await code(helper, privateKey);
+  } 
+  finally {
+    await helper.disconnect();
+    console.error = consoleErr;
+    console.log = consoleLog;
+    console.warn = consoleWarn;
+  }
+};
\ No newline at end of file
addedtests/src/util/playgrounds/unique.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/util/playgrounds/unique.ts
@@ -0,0 +1,2490 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+/* eslint-disable @typescript-eslint/no-var-requires */
+/* eslint-disable function-call-argument-newline */
+/* eslint-disable no-prototype-builtins */
+
+import {ApiPromise, WsProvider, Keyring} from '@polkadot/api';
+import {ApiInterfaceEvents} from '@polkadot/api/types';
+import {IKeyringPair} from '@polkadot/types/types';
+import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm} from '@polkadot/util-crypto';
+
+
+const crossAccountIdFromLower = (lowerAddress: ICrossAccountIdLower): ICrossAccountId => {
+  const address = {} as ICrossAccountId;
+  if(lowerAddress.substrate) address.Substrate = lowerAddress.substrate;
+  if(lowerAddress.ethereum) address.Ethereum = lowerAddress.ethereum;
+  return address;
+};
+
+
+const nesting = {
+  toChecksumAddress(address: string): string {
+    if (typeof address === 'undefined') return '';
+
+    if(!/^(0x)?[0-9a-f]{40}$/i.test(address)) throw new Error(`Given address "${address}" is not a valid Ethereum address.`);
+
+    address = address.toLowerCase().replace(/^0x/i,'');
+    const addressHash = keccakAsHex(address).replace(/^0x/i,'');
+    const checksumAddress = ['0x'];
+
+    for (let i = 0; i < address.length; i++) {
+      // If ith character is 8 to f then make it uppercase
+      if (parseInt(addressHash[i], 16) > 7) {
+        checksumAddress.push(address[i].toUpperCase());
+      } else {
+        checksumAddress.push(address[i]);
+      }
+    }
+    return checksumAddress.join('');
+  },
+  tokenIdToAddress(collectionId: number, tokenId: number) {
+    return this.toChecksumAddress(`0xf8238ccfff8ed887463fd5e0${collectionId.toString(16).padStart(8, '0')}${tokenId.toString(16).padStart(8, '0')}`);
+  },
+};
+
+
+interface IChainEvent {
+  data: any;
+  method: string;
+  section: string;
+}
+
+interface ITransactionResult {
+    status: 'Fail' | 'Success';
+    result: {
+        events: {
+          event: IChainEvent
+        }[];
+    },
+    moduleError?: string;
+}
+
+interface ILogger {
+  log: (msg: any, level?: string) => void;
+  level: {
+    ERROR: 'ERROR';
+    WARNING: 'WARNING';
+    INFO: 'INFO';
+    [key: string]: string;
+  }
+}
+
+interface IUniqueHelperLog {
+  executedAt: number;
+  executionTime: number;
+  type: 'extrinsic' | 'rpc';
+  status: 'Fail' | 'Success';
+  call: string;
+  params: any[];
+  moduleError?: string;
+  events?: any;
+}
+
+interface IApiListeners {
+  connected?: (...args: any[]) => any;
+  disconnected?: (...args: any[]) => any;
+  error?: (...args: any[]) => any;
+  ready?: (...args: any[]) => any; 
+  decorated?: (...args: any[]) => any;
+}
+
+interface ICrossAccountId {
+  Substrate?: TSubstrateAccount;
+  Ethereum?: TEthereumAccount;
+}
+
+interface ICrossAccountIdLower {
+  substrate?: TSubstrateAccount;
+  ethereum?: TEthereumAccount;
+}
+
+interface ICollectionLimits {
+  accountTokenOwnershipLimit?: number | null;
+  sponsoredDataSize?: number | null;
+  sponsoredDataRateLimit?: {blocks: number} | {sponsoringDisabled: null} | null;
+  tokenLimit?: number | null;
+  sponsorTransferTimeout?: number | null;
+  sponsorApproveTimeout?: number | null;
+  ownerCanTransfer?: boolean | null;
+  ownerCanDestroy?: boolean | null;
+  transfersEnabled?: boolean | null;
+}
+
+interface INestingPermissions {
+  tokenOwner?: boolean;
+  collectionAdmin?: boolean;
+  restricted?: number[] | null;
+}
+
+interface ICollectionPermissions {
+  access?: 'Normal' | 'AllowList';
+  mintMode?: boolean;
+  nesting?: INestingPermissions;
+}
+
+interface IProperty {
+  key: string;
+  value: string;
+}
+
+interface ITokenPropertyPermission {
+  key: string;
+  permission: {
+    mutable: boolean;
+    tokenOwner: boolean;
+    collectionAdmin: boolean;
+  }
+}
+
+interface IToken {
+  collectionId: number;
+  tokenId: number;
+}
+
+interface ICollectionCreationOptions {
+  name: string | number[];
+  description: string | number[];
+  tokenPrefix: string | number[];
+  mode?: {
+    nft?: null;
+    refungible?: null;
+    fungible?: number;
+  }
+  permissions?: ICollectionPermissions;
+  properties?: IProperty[];
+  tokenPropertyPermissions?: ITokenPropertyPermission[];
+  limits?: ICollectionLimits;
+  pendingSponsor?: TSubstrateAccount;
+}
+
+interface IChainProperties {
+  ss58Format: number;
+  tokenDecimals: number[];
+  tokenSymbol: string[]
+}
+
+type TSubstrateAccount = string;
+type TEthereumAccount = string;
+type TApiAllowedListeners = 'connected' | 'disconnected' | 'error' | 'ready' | 'decorated';
+type TUniqueNetworks = 'opal' | 'quartz' | 'unique';
+type TSigner = IKeyringPair; // | 'string'
+
+class UniqueUtil {
+  static transactionStatus = {
+    NOT_READY: 'NotReady',
+    FAIL: 'Fail',
+    SUCCESS: 'Success',
+  };
+
+  static chainLogType = {
+    EXTRINSIC: 'extrinsic',
+    RPC: 'rpc',
+  };
+
+  static getNestingTokenAddress(collectionId: number, tokenId: number) {
+    return nesting.tokenIdToAddress(collectionId, tokenId);
+  }
+
+  static getDefaultLogger(): ILogger {
+    return {
+      log(msg: any, level = 'INFO') {
+        console[level.toLocaleLowerCase() === 'error' ? 'error' : 'log'](...(Array.isArray(msg) ? msg : [msg]));
+      },
+      level: {
+        ERROR: 'ERROR',
+        WARNING: 'WARNING',
+        INFO: 'INFO',
+      },
+    };
+  }
+
+  static vec2str(arr: string[] | number[]) {
+    return arr.map(x => String.fromCharCode(parseInt(x.toString()))).join('');
+  }
+
+  static str2vec(string: string) {
+    if (typeof string !== 'string') return string;
+    return Array.from(string).map(x => x.charCodeAt(0));
+  }
+
+  static fromSeed(seed: string, ss58Format = 42) {
+    const keyring = new Keyring({type: 'sr25519', ss58Format});
+    return keyring.addFromUri(seed);
+  }
+
+  static normalizeSubstrateAddress(address: string, ss58Format = 42) {
+    return encodeAddress(decodeAddress(address), ss58Format);
+  }
+
+  static extractCollectionIdFromCreationResult(creationResult: ITransactionResult, label = 'new collection') {
+    if (creationResult.status !== this.transactionStatus.SUCCESS) {
+      throw Error(`Unable to create collection for ${label}`);
+    }
+
+    let collectionId = null;
+    creationResult.result.events.forEach(({event: {data, method, section}}) => {
+      if ((section === 'common') && (method === 'CollectionCreated')) {
+        collectionId = parseInt(data[0].toString(), 10);
+      }
+    });
+
+    if (collectionId === null) {
+      throw Error(`No CollectionCreated event for ${label}`);
+    }
+
+    return collectionId;
+  }
+
+  static extractTokensFromCreationResult(creationResult: ITransactionResult, label = 'new tokens') {
+    if (creationResult.status !== this.transactionStatus.SUCCESS) {
+      throw Error(`Unable to create tokens for ${label}`);
+    }
+    let success = false;
+    const tokens = [] as any;
+    creationResult.result.events.forEach(({event: {data, method, section}}) => {
+      if (method === 'ExtrinsicSuccess') {
+        success = true;
+      } else if ((section === 'common') && (method === 'ItemCreated')) {
+        tokens.push({
+          collectionId: parseInt(data[0].toString(), 10),
+          tokenId: parseInt(data[1].toString(), 10),
+          owner: data[2].toJSON(),
+        });
+      }
+    });
+    return {success, tokens};
+  }
+
+  static extractTokensFromBurnResult(burnResult: ITransactionResult, label = 'burned tokens') {
+    if (burnResult.status !== this.transactionStatus.SUCCESS) {
+      throw Error(`Unable to burn tokens for ${label}`);
+    }
+    let success = false;
+    const tokens = [] as any;
+    burnResult.result.events.forEach(({event: {data, method, section}}) => {
+      if (method === 'ExtrinsicSuccess') {
+        success = true;
+      } else if ((section === 'common') && (method === 'ItemDestroyed')) {
+        tokens.push({
+          collectionId: parseInt(data[0].toString(), 10),
+          tokenId: parseInt(data[1].toString(), 10),
+          owner: data[2].toJSON(),
+        });
+      }
+    });
+    return {success, tokens};
+  }
+
+  static findCollectionInEvents(events: {event: IChainEvent}[], collectionId: number, expectedSection: string, expectedMethod: string, label?: string) {
+    let eventId = null;
+    events.forEach(({event: {data, method, section}}) => {
+      if ((section === expectedSection) && (method === expectedMethod)) {
+        eventId = parseInt(data[0].toString(), 10);
+      }
+    });
+
+    if (eventId === null) {
+      throw Error(`No ${expectedMethod} event for ${label}`);
+    }
+    return eventId === collectionId;
+  }
+
+  static isTokenTransferSuccess(events: {event: IChainEvent}[], collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) {
+    const normalizeAddress = (address: string | ICrossAccountId) => {
+      if(typeof address === 'string') return address;
+      const obj = {} as any;
+      Object.keys(address).forEach(k => {
+        obj[k.toLocaleLowerCase()] = address[k as 'Substrate' | 'Ethereum'];
+      });
+      if(obj.substrate) return {Substrate: this.normalizeSubstrateAddress(obj.substrate)};
+      if(obj.ethereum) return {Ethereum: obj.ethereum.toLocaleLowerCase()};
+      return address;
+    };
+    let transfer = {collectionId: null, tokenId: null, from: null, to: null, amount: 1} as any;
+    events.forEach(({event: {data, method, section}}) => {
+      if ((section === 'common') && (method === 'Transfer')) {
+        const hData = (data as any).toJSON();
+        transfer = {
+          collectionId: hData[0],
+          tokenId: hData[1],
+          from: normalizeAddress(hData[2]),
+          to: normalizeAddress(hData[3]),
+          amount: BigInt(hData[4]),
+        };
+      }
+    });
+    let isSuccess = parseInt(collectionId.toString()) === transfer.collectionId && parseInt(tokenId.toString()) === transfer.tokenId;
+    isSuccess = isSuccess && JSON.stringify(normalizeAddress(fromAddressObj)) === JSON.stringify(transfer.from);
+    isSuccess = isSuccess && JSON.stringify(normalizeAddress(toAddressObj)) === JSON.stringify(transfer.to);
+    isSuccess = isSuccess && amount === transfer.amount;
+    return isSuccess;
+  }
+}
+
+
+class ChainHelperBase {
+  transactionStatus = UniqueUtil.transactionStatus;
+  chainLogType = UniqueUtil.chainLogType;
+  util: typeof UniqueUtil;
+  logger: ILogger;
+  api: ApiPromise | null;
+  forcedNetwork: TUniqueNetworks | null;
+  network: TUniqueNetworks | null;
+  chainLog: IUniqueHelperLog[];
+
+  constructor(logger?: ILogger) {
+    this.util = UniqueUtil;
+    if (typeof logger == 'undefined') logger = this.util.getDefaultLogger();
+    this.logger = logger;
+    this.api = null;
+    this.forcedNetwork = null;
+    this.network = null;
+    this.chainLog = [];
+  }
+
+  clearChainLog(): void {
+    this.chainLog = [];
+  }
+
+  forceNetwork(value: TUniqueNetworks): void {
+    this.forcedNetwork = value;
+  }
+
+  async connect(wsEndpoint: string, listeners?: IApiListeners) {
+    if (this.api !== null) throw Error('Already connected');
+    const {api, network} = await ChainHelperBase.createConnection(wsEndpoint, listeners, this.forcedNetwork);
+    this.api = api;
+    this.network = network;
+  }
+
+  async disconnect() {
+    if (this.api === null) return;
+    await this.api.disconnect();
+    this.api = null;
+    this.network = null;
+  }
+
+  static async detectNetwork(api: ApiPromise): Promise<TUniqueNetworks> {
+    const spec = (await api.query.system.lastRuntimeUpgrade()).toJSON() as any;
+    if(['quartz', 'unique'].indexOf(spec.specName) > -1) return spec.specName;
+    return 'opal';
+  }
+
+  static async detectNetworkByWsEndpoint(wsEndpoint: string): Promise<TUniqueNetworks> {
+    const api = new ApiPromise({provider: new WsProvider(wsEndpoint)});
+    await api.isReady;
+
+    const network = await this.detectNetwork(api);
+
+    await api.disconnect();
+
+    return network;
+  }
+
+  static async createConnection(wsEndpoint: string, listeners?: IApiListeners, network?: TUniqueNetworks | null): Promise<{ 
+    api: ApiPromise; 
+    network: TUniqueNetworks; 
+  }> {
+    if(typeof network === 'undefined' || network === null) network = 'opal';
+    const supportedRPC = {
+      opal: {
+        unique: require('@unique-nft/opal-testnet-types/definitions').unique.rpc,
+      },
+      quartz: {
+        unique: require('@unique-nft/quartz-mainnet-types/definitions').unique.rpc,
+      },
+      unique: {
+        unique: require('@unique-nft/unique-mainnet-types/definitions').unique.rpc,
+      },
+    };
+    if(!supportedRPC.hasOwnProperty(network)) network = await this.detectNetworkByWsEndpoint(wsEndpoint);
+    const rpc = supportedRPC[network];
+
+    // TODO: investigate how to replace rpc in runtime
+    // api._rpcCore.addUserInterfaces(rpc);
+
+    const api = new ApiPromise({provider: new WsProvider(wsEndpoint), rpc});
+
+    await api.isReadyOrError;
+
+    if (typeof listeners === 'undefined') listeners = {};
+    for (const event of ['connected', 'disconnected', 'error', 'ready', 'decorated']) {
+      if (!listeners.hasOwnProperty(event) || typeof listeners[event as TApiAllowedListeners] === 'undefined') continue;
+      api.on(event as ApiInterfaceEvents, listeners[event as TApiAllowedListeners] as (...args: any[]) => any);
+    }
+
+    return {api, network};
+  }
+
+  getTransactionStatus(data: {events: {event: IChainEvent}[], status: any}) {
+    const {events, status} = data;
+    if (status.isReady) {
+      return this.transactionStatus.NOT_READY;
+    }
+    if (status.isBroadcast) {
+      return this.transactionStatus.NOT_READY;
+    }
+    if (status.isInBlock || status.isFinalized) {
+      const errors = events.filter(e => e.event.data.method === 'ExtrinsicFailed');
+      if (errors.length > 0) {
+        return this.transactionStatus.FAIL;
+      }
+      if (events.filter(e => e.event.data.method === 'ExtrinsicSuccess').length > 0) {
+        return this.transactionStatus.SUCCESS;
+      }
+    }
+
+    return this.transactionStatus.FAIL;
+  }
+
+  signTransaction(sender: TSigner, transaction: any, label = 'transaction', options = null) {
+    const sign = (callback: any) => {
+      if(options !== null) return transaction.signAndSend(sender, options, callback);
+      return transaction.signAndSend(sender, callback);
+    };
+    return new Promise(async (resolve, reject) => {
+      try {
+        const unsub = await sign((result: any) => {
+          const status = this.getTransactionStatus(result);
+
+          if (status === this.transactionStatus.SUCCESS) {
+            this.logger.log(`${label} successful`);
+            unsub();
+            resolve({result, status});
+          } else if (status === this.transactionStatus.FAIL) {
+            let moduleError = null;
+
+            if (result.hasOwnProperty('dispatchError')) {
+              const dispatchError = result['dispatchError'];
+
+              if (dispatchError && dispatchError.isModule) {
+                const modErr = dispatchError.asModule;
+                const errorMeta = dispatchError.registry.findMetaError(modErr);
+
+                moduleError = `${errorMeta.section}.${errorMeta.name}`;
+              }
+              else {
+                this.logger.log(result, this.logger.level.ERROR);
+              }
+            }
+
+            this.logger.log(`Something went wrong with ${label}. Status: ${status}`, this.logger.level.ERROR);
+            unsub();
+            reject({status, moduleError, result});
+          }
+        });
+      } catch (e) {
+        this.logger.log(e, this.logger.level.ERROR);
+        reject(e);
+      }
+    });
+  }
+
+  constructApiCall(apiCall: string, params: any[]) {
+    if(!apiCall.startsWith('api.')) throw Error(`Invalid api call: ${apiCall}`);
+    let call = this.api as any;
+    for(const part of apiCall.slice(4).split('.')) {
+      call = call[part];
+    }
+    return call(...params);
+  }
+
+  async executeExtrinsic(sender: TSigner, extrinsic: string, params: any[], expectSuccess=false, failureMessage='expected success') {
+    if(this.api === null) throw Error('API not initialized');
+    if(!extrinsic.startsWith('api.tx.')) throw Error(`${extrinsic} is not transaction`);
+
+    const startTime = (new Date()).getTime();
+    let result: ITransactionResult;
+    let events = [];
+    try {
+      result = await this.signTransaction(sender, this.constructApiCall(extrinsic, params), extrinsic) as ITransactionResult;
+      events = result.result.events.map((x: any) => x.toHuman());
+    }
+    catch(e) {
+      if(!(e as object).hasOwnProperty('status')) throw e;
+      result = e as ITransactionResult;
+    }
+
+    const endTime = (new Date()).getTime();
+
+    const log = {
+      executedAt: endTime,
+      executionTime: endTime - startTime,
+      type: this.chainLogType.EXTRINSIC,
+      status: result.status,
+      call: extrinsic,
+      params,
+    } as IUniqueHelperLog;
+
+    if(result.status !== this.transactionStatus.SUCCESS && result.moduleError) log.moduleError = result.moduleError;
+    if(events.length > 0) log.events = events;
+
+    this.chainLog.push(log);
+
+    if(expectSuccess && result.status !== this.transactionStatus.SUCCESS) throw Error(failureMessage);
+    return result;
+  }
+
+  async callRpc(rpc: string, params?: any[]) {
+    if(typeof params === 'undefined') params = [];
+    if(this.api === null) throw Error('API not initialized');
+    if(!rpc.startsWith('api.rpc.') && !rpc.startsWith('api.query.')) throw Error(`${rpc} is not RPC call`);
+
+    const startTime = (new Date()).getTime();
+    let result;
+    let error = null;
+    const log = {
+      type: this.chainLogType.RPC,
+      call: rpc,
+      params,
+    } as IUniqueHelperLog;
+
+    try {
+      result = await this.constructApiCall(rpc, params);
+    }
+    catch(e) {
+      error = e;
+    }
+
+    const endTime = (new Date()).getTime();
+
+    log.executedAt = endTime;
+    log.status = (error === null ? this.transactionStatus.SUCCESS : this.transactionStatus.FAIL) as 'Fail' | 'Success';
+    log.executionTime = endTime - startTime;
+
+    this.chainLog.push(log);
+
+    if(error !== null) throw error;
+
+    return result;
+  }
+
+  getSignerAddress(signer: IKeyringPair | string): string {
+    if(typeof signer === 'string') return signer;
+    return signer.address;
+  }
+}
+
+
+class HelperGroup {
+  helper: UniqueHelper;
+
+  constructor(uniqueHelper: UniqueHelper) {
+    this.helper = uniqueHelper;
+  }
+}
+
+
+class CollectionGroup extends HelperGroup {
+  /**
+ * Get number of blocks when sponsored transaction is available.
+ *
+ * @param collectionId ID of collection
+ * @param tokenId ID of token
+ * @param addressObj address for which the sponsorship is checked
+ * @example await getTokenNextSponsored(1, 2, {Substrate: '5DfhbVfww7ThF8q6f3...'});
+ * @returns number of blocks or null if sponsorship hasn't been set
+ */
+  async getTokenNextSponsored(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<number | null> {
+    return (await this.helper.callRpc('api.rpc.unique.nextSponsored', [collectionId, addressObj, tokenId])).toJSON();
+  }
+
+  /**
+   * Get the number of created collections.
+   * 
+   * @returns number of created collections
+   */
+  async getTotalCount(): Promise<number> {
+    return (await this.helper.callRpc('api.rpc.unique.collectionStats')).created.toNumber();
+  }
+
+  /**
+   * Get information about the collection with additional data, including the number of tokens it contains, its administrators, the normalized address of the collection's owner, and decoded name and description.
+   * 
+   * @param collectionId ID of collection
+   * @example await getData(2)
+   * @returns collection information object
+   */
+  async getData(collectionId: number): Promise<{
+    id: number;
+    name: string;
+    description: string;
+    tokensCount: number;
+    admins: ICrossAccountId[];
+    normalizedOwner: TSubstrateAccount;
+    raw: any
+  } | null> {
+    const collection = await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId]);
+    const humanCollection = collection.toHuman(), collectionData = {
+      id: collectionId, name: null, description: null, tokensCount: 0, admins: [],
+      raw: humanCollection,
+    } as any, jsonCollection = collection.toJSON();
+    if (humanCollection === null) return null;
+    collectionData.raw.limits = jsonCollection.limits;
+    collectionData.raw.permissions = jsonCollection.permissions;
+    collectionData.normalizedOwner = this.helper.address.normalizeSubstrate(collectionData.raw.owner);
+    for (const key of ['name', 'description']) {
+      collectionData[key] = this.helper.util.vec2str(humanCollection[key]);
+    }
+
+    collectionData.tokensCount = (['RFT', 'NFT'].includes(humanCollection.mode)) ? await this.helper[humanCollection.mode.toLocaleLowerCase() as 'nft' | 'rft'].getLastTokenId(collectionId) : 0;
+    collectionData.admins = await this.getAdmins(collectionId);
+
+    return collectionData;
+  }
+
+  /**
+   * Get the normalized addresses of the collection's administrators.
+   * 
+   * @param collectionId ID of collection
+   * @example await getAdmins(1)
+   * @returns array of administrators
+   */
+  async getAdmins(collectionId: number): Promise<ICrossAccountId[]> {
+    const normalized = [];
+    for(const admin of (await this.helper.callRpc('api.rpc.unique.adminlist', [collectionId])).toHuman()) {
+      if(admin.Substrate) normalized.push({Substrate: this.helper.address.normalizeSubstrate(admin.Substrate)});
+      else normalized.push(admin);
+    }
+    return normalized;
+  }
+
+  /**
+   * Get the effective limits of the collection instead of null for default values
+   * 
+   * @param collectionId ID of collection
+   * @example await getEffectiveLimits(2)
+   * @returns object of collection limits
+   */
+  async getEffectiveLimits(collectionId: number): Promise<ICollectionLimits> {
+    return (await this.helper.callRpc('api.rpc.unique.effectiveCollectionLimits', [collectionId])).toJSON();
+  }
+
+  /**
+   * Burns the collection if the signer has sufficient permissions and collection is empty.
+   * 
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param label extra label for log
+   * @example await helper.collection.burn(aliceKeyring, 3);
+   * @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async burn(signer: TSigner, collectionId: number, label?: string): Promise<boolean> {
+    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+    const result = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.destroyCollection', [collectionId],
+      true, `Unable to burn collection for ${label}`,
+    );
+
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionDestroyed', label);
+  }
+
+  /**
+   * Sets the sponsor for the collection (Requires the Substrate address).
+   * 
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param sponsorAddress Sponsor substrate address
+   * @param label extra label for log
+   * @example setSponsor(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")
+   * @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async setSponsor(signer: TSigner, collectionId: number, sponsorAddress: TSubstrateAccount, label?: string): Promise<boolean> {
+    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+    const result = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.setCollectionSponsor', [collectionId, sponsorAddress],
+      true, `Unable to set collection sponsor for ${label}`,
+    );
+
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionSponsorSet', label);
+  }
+
+  /**
+   * Confirms consent to sponsor the collection on behalf of the signer.
+   * 
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param label extra label for log
+   * @example confirmSponsorship(aliceKeyring, 10)
+   * @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async confirmSponsorship(signer: TSigner, collectionId: number, label?: string): Promise<boolean> {
+    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+    const result = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.confirmSponsorship', [collectionId],
+      true, `Unable to confirm collection sponsorship for ${label}`,
+    );
+
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'SponsorshipConfirmed', label);
+  }
+
+  /**
+   * Sets the limits of the collection. At least one limit must be specified for a correct call.
+   * 
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param limits collection limits object
+   * @param label extra label for log
+   * @example
+   * await setLimits(
+   *   aliceKeyring,
+   *   10,
+   *   {
+   *     sponsorTransferTimeout: 0,
+   *     ownerCanDestroy: false
+   *   }
+   * )
+   * @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async setLimits(signer: TSigner, collectionId: number, limits: ICollectionLimits, label?: string): Promise<boolean> {
+    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+    const result = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.setCollectionLimits', [collectionId, limits],
+      true, `Unable to set collection limits for ${label}`,
+    );
+
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionLimitSet', label);
+  }
+
+  /**
+   * Changes the owner of the collection to the new Substrate address.
+   * 
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param ownerAddress substrate address of new owner
+   * @param label extra label for log
+   * @example changeOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...")
+   * @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async changeOwner(signer: TSigner, collectionId: number, ownerAddress: TSubstrateAccount, label?: string): Promise<boolean> {
+    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+    const result = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.changeCollectionOwner', [collectionId, ownerAddress],
+      true, `Unable to change collection owner for ${label}`,
+    );
+
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionOwnedChanged', label);
+  }
+
+  /**
+   * Adds a collection administrator. 
+   * 
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param adminAddressObj Administrator address (substrate or ethereum)
+   * @param label extra label for log
+   * @example addAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})
+   * @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async addAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId, label?: string): Promise<boolean> {
+    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+    const result = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.addCollectionAdmin', [collectionId, adminAddressObj],
+      true, `Unable to add collection admin for ${label}`,
+    );
+
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminAdded', label);
+  }
+
+  /**
+   * Removes a collection administrator.
+   * 
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param adminAddressObj Administrator address (substrate or ethereum)
+   * @param label extra label for log
+   * @example removeAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."})
+   * @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async removeAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId, label?: string): Promise<boolean> {
+    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+    const result = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.removeCollectionAdmin', [collectionId, adminAddressObj],
+      true, `Unable to remove collection admin for ${label}`,
+    );
+
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminRemoved', label);
+  }
+
+  /**
+   * Sets onchain permissions for selected collection.
+   * 
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param permissions collection permissions object
+   * @param label extra label for log
+   * @example setPermissions(aliceKeyring, 10, {access:'AllowList', mintMode: true, nesting: {collectionAdmin: true, tokenOwner: true}});
+   * @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async setPermissions(signer: TSigner, collectionId: number, permissions: ICollectionPermissions, label?: string): Promise<boolean> {
+    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+    const result = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.setCollectionPermissions', [collectionId, permissions],
+      true, `Unable to set collection permissions for ${label}`,
+    );
+
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionPermissionSet', label);
+  }
+
+  /**
+   * Enables nesting for selected collection. If `restricted` set, you can nest only tokens from specified collections.
+   * 
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param permissions nesting permissions object
+   * @param label extra label for log
+   * @example enableNesting(aliceKeyring, 10, {collectionAdmin: true, tokenOwner: true});
+   * @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async enableNesting(signer: TSigner, collectionId: number, permissions: INestingPermissions, label?: string): Promise<boolean> {
+    return await this.setPermissions(signer, collectionId, {nesting: permissions}, label);
+  }
+
+  /**
+   * Disables nesting for selected collection.
+   * 
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param label extra label for log
+   * @example disableNesting(aliceKeyring, 10);
+   * @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async disableNesting(signer: TSigner, collectionId: number, label?: string): Promise<boolean> {
+    return await this.setPermissions(signer, collectionId, {nesting: {tokenOwner: false, collectionAdmin: false}}, label);
+  }
+
+  /**
+   * Sets onchain properties to the collection.
+   * 
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param properties array of property objects
+   * @param label extra label for log
+   * @example setProperties(aliceKeyring, 10, [{key: "gender", value: "male"}]);
+   * @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async setProperties(signer: TSigner, collectionId: number, properties: IProperty[], label?: string): Promise<boolean> {
+    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+    const result = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.setCollectionProperties', [collectionId, properties],
+      true, `Unable to set collection properties for ${label}`,
+    );
+
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertySet', label);
+  }
+
+  /**
+   * Deletes onchain properties from the collection.
+   * 
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param propertyKeys array of property keys to delete
+   * @param label
+   * @example deleteProperties(aliceKeyring, 10, ["gender", "age"]);
+   * @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async deleteProperties(signer: TSigner, collectionId: number, propertyKeys: string[], label?: string): Promise<boolean> {
+    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+    const result = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.deleteCollectionProperties', [collectionId, propertyKeys],
+      true, `Unable to delete collection properties for ${label}`,
+    );
+
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertyDeleted', label);
+  }
+
+  /**
+   * Changes the owner of the token.
+   * 
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param tokenId ID of token
+   * @param addressObj address of a new owner
+   * @param amount amount of tokens to be transfered. For NFT must be set to 1n
+   * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})
+   * @returns true if the token success, otherwise false
+   */
+  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=1n): Promise<boolean> {
+    const result = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.transfer', [addressObj, collectionId, tokenId, amount],
+      true, `Unable to transfer token #${tokenId} from collection #${collectionId}`,
+    );
+
+    return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, {Substrate: typeof signer === 'string' ? signer : signer.address}, addressObj, amount);
+  }
+
+  /**
+   * 
+   * Change ownership of a token(s) on behalf of the owner. 
+   * 
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param tokenId ID of token
+   * @param fromAddressObj address on behalf of which the token will be sent
+   * @param toAddressObj new token owner
+   * @param amount amount of tokens to be transfered. For NFT must be set to 1n
+   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg"}, {Ethereum: "0x9F0583DbB85..."})
+   * @returns true if the token success, otherwise false
+   */
+  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n): Promise<boolean> {
+    const result = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.transferFrom', [fromAddressObj, toAddressObj, collectionId, tokenId, amount],
+      true, `Unable to transfer token #${tokenId} from collection #${collectionId}`,
+    );
+    return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, fromAddressObj, toAddressObj, amount);
+  }
+
+  /**
+   * 
+   * Destroys a concrete instance of NFT/RFT or burns a specified amount of fungible tokens.
+   * 
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param tokenId ID of token
+   * @param label 
+   * @param amount amount of tokens to be burned. For NFT must be set to 1n
+   * @example burnToken(aliceKeyring, 10, 5);
+   * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null```
+   */
+  async burnToken(signer: TSigner, collectionId: number, tokenId: number, label?: string, amount=1n): Promise<{
+    success: boolean,
+    token: number | null
+  }> {
+    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+    const burnResult = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.burnItem', [collectionId, tokenId, amount],
+      true, `Unable to burn token for ${label}`,
+    );
+    const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult, label);
+    if (burnedTokens.tokens.length > 1) throw Error('Burned multiple tokens');
+    return {success: burnedTokens.success, token: burnedTokens.tokens.length > 0 ? burnedTokens.tokens[0] : null};
+  }
+
+  /**
+   * Destroys a concrete instance of NFT on behalf of the owner
+   * 
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param fromAddressObj address on behalf of which the token will be burnt
+   * @param tokenId ID of token
+   * @param label 
+   * @param amount amount of tokens to be burned. For NFT must be set to 1n
+   * @example burnTokenFrom(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}, 5, {Ethereum: "0x9F0583DbB85..."})
+   * @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async burnTokenFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, tokenId: number, label?: string, amount=1n): Promise<boolean> {
+    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+    const burnResult = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.burnFrom', [collectionId, fromAddressObj, tokenId, amount],
+      true, `Unable to burn token from for ${label}`,
+    );
+    const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult, label);
+    return burnedTokens.success && burnedTokens.tokens.length > 0;
+  }
+
+  /**
+   * Set, change, or remove approved address to transfer the ownership of the NFT.
+   * 
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param tokenId ID of token
+   * @param toAddressObj 
+   * @param label 
+   * @param amount amount of token to be approved. For NFT must be set to 1n
+   * @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string, amount=1n) {
+    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+    const approveResult = await this.helper.executeExtrinsic(
+      signer, 
+      'api.tx.unique.approve', [toAddressObj, collectionId, tokenId, amount],
+      true, `Unable to approve token for ${label}`,
+    );
+
+    return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved', label);
+  }
+
+  /**
+   * Get the amount of token pieces approved to transfer
+   * @param collectionId ID of collection
+   * @param tokenId ID of token
+   * @param toAccountObj 
+   * @param fromAccountObj
+   * @example getTokenApprovedPieces(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5ERZNF88Mm7UGfPP3mdG..."})
+   * @returns number of approved to transfer pieces
+   */
+  async getTokenApprovedPieces(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId, fromAccountObj: ICrossAccountId): Promise<bigint> {
+    return (await this.helper.callRpc('api.rpc.unique.allowance', [collectionId, fromAccountObj, toAccountObj, tokenId])).toBigInt();
+  }
+
+  /**
+   * Get the last created token id
+   * @param collectionId ID of collection
+   * @example getLastTokenId(10);
+   * @returns id of the last created token
+   */
+  async getLastTokenId(collectionId: number): Promise<number> {
+    return (await this.helper.callRpc('api.rpc.unique.lastTokenId', [collectionId])).toNumber();
+  }
+
+  /**
+   * Check if token exists
+   * @param collectionId ID of collection
+   * @param tokenId ID of token
+   * @example isTokenExists(10, 20);
+   * @returns true if the token exists, otherwise false
+   */
+  async isTokenExists(collectionId: number, tokenId: number): Promise<boolean> {
+    return (await this.helper.callRpc('api.rpc.unique.tokenExists', [collectionId, tokenId])).toJSON();
+  }
+}
+
+class NFTnRFT extends CollectionGroup {
+  /**
+   * Get tokens owned by account
+   * 
+   * @param collectionId ID of collection
+   * @param addressObj tokens owner
+   * @example getTokensByAddress(10, {Substrate: "5DyN4Y92vZCjv38fg..."})
+   * @returns array of token ids owned by account
+   */
+  async getTokensByAddress(collectionId: number, addressObj: ICrossAccountId): Promise<number[]> {
+    return (await this.helper.callRpc('api.rpc.unique.accountTokens', [collectionId, addressObj])).toJSON();
+  }
+
+  /**
+   * Get token data
+   * @param collectionId ID of collection
+   * @param tokenId ID of token
+   * @param blockHashAt 
+   * @param propertyKeys
+   * @example getToken(10, 5);
+   * @returns human readable token data 
+   */
+  async getToken(collectionId: number, tokenId: number, blockHashAt?: string, propertyKeys?: string[]): Promise<{
+    properties: IProperty[];
+    owner: ICrossAccountId;
+    normalizedOwner: ICrossAccountId;
+  }| null> {
+    let tokenData;
+    if(typeof blockHashAt === 'undefined') {
+      tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId]);
+    }
+    else {
+      if(typeof propertyKeys === 'undefined') {
+        const collection = (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();
+        if(!collection) return null;
+        propertyKeys = collection.tokenPropertyPermissions.map((x: ITokenPropertyPermission) => x.key);
+      }
+      tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId, propertyKeys, blockHashAt]);
+    }
+    tokenData = tokenData.toHuman();
+    if (tokenData === null || tokenData.owner === null) return null;
+    const owner = {} as any;
+    for (const key of Object.keys(tokenData.owner)) {
+      owner[key.toLocaleLowerCase()] = key.toLocaleLowerCase() === 'substrate' ? this.helper.address.normalizeSubstrate(tokenData.owner[key]) : tokenData.owner[key];
+    }
+    tokenData.normalizedOwner = crossAccountIdFromLower(owner);
+    return tokenData;
+  }
+
+  /**
+   * Set permissions to change token properties
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param permissions permissions to change a property by the collection owner or admin
+   * @param label 
+   * @example setTokenPropertyPermissions(
+   *   aliceKeyring, 10, [{key: "gender", permission: {tokenOwner: true, mutable: true, collectionAdmin: true}}]
+   * )
+   * @returns true if extrinsic success otherwise false
+   */
+  async setTokenPropertyPermissions(signer: TSigner, collectionId: number, permissions: ITokenPropertyPermission[], label?: string): Promise<boolean> {
+    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+    const result = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.setTokenPropertyPermissions', [collectionId, permissions],
+      true, `Unable to set token property permissions for ${label}`,
+    );
+
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'PropertyPermissionSet', label);
+  }
+
+  /**
+   * Set token properties
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param tokenId ID of token
+   * @param properties 
+   * @param label 
+   * @example setTokenProperties(aliceKeyring, 10, 5, [{key: "gender", value: "female"}, {key: "age", value: "23"}])
+   * @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async setTokenProperties(signer: TSigner, collectionId: number, tokenId: number, properties: IProperty[], label?: string): Promise<boolean> {
+    if(typeof label === 'undefined') label = `token #${tokenId} from collection #${collectionId}`;
+    const result = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.setTokenProperties', [collectionId, tokenId, properties],
+      true, `Unable to set token properties for ${label}`,
+    );
+
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertySet', label);
+  }
+
+  /**
+   * Delete the provided properties of a token
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param tokenId ID of token
+   * @param propertyKeys property keys to be deleted 
+   * @param label 
+   * @example deleteTokenProperties(aliceKeyring, 10, 5, ["gender", "age"])
+   * @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async deleteTokenProperties(signer: TSigner, collectionId: number, tokenId: number, propertyKeys: string[], label?: string): Promise<boolean> {
+    if(typeof label === 'undefined') label = `token #${tokenId} from collection #${collectionId}`;
+    const result = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.deleteTokenProperties', [collectionId, tokenId, propertyKeys],
+      true, `Unable to delete token properties for ${label}`,
+    );
+
+    return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertyDeleted', label);
+  }
+
+  /**
+   * Mint new collection
+   * @param signer keyring of signer
+   * @param collectionOptions basic collection options and properties 
+   * @param mode NFT or RFT type of a collection
+   * @param errorLabel 
+   * @example mintCollection(aliceKeyring, {name: 'New', description: "New collection", tokenPrefix: "NEW"}, "NFT")
+   * @returns object of the created collection
+   */
+  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT', errorLabel = 'Unable to mint collection'): Promise<UniqueCollectionBase> {
+    collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object
+    collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null};
+    for (const key of ['name', 'description', 'tokenPrefix']) {
+      if (typeof collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] === 'string') collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] = this.helper.util.str2vec(collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] as string);
+    }
+    const creationResult = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.createCollectionEx', [collectionOptions],
+      true, errorLabel,
+    );
+    return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult, errorLabel));
+  }
+
+  getCollectionObject(collectionId: number): any {
+    return null;
+  }
+
+  getTokenObject(collectionId: number, tokenId: number): any {
+    return null;
+  }
+}
+
+
+class NFTGroup extends NFTnRFT {
+  /**
+   * Get collection object
+   * @param collectionId ID of collection
+   * @example getCollectionObject(2);
+   * @returns instance of UniqueNFTCollection
+   */
+  getCollectionObject(collectionId: number): UniqueNFTCollection {
+    return new UniqueNFTCollection(collectionId, this.helper);
+  }
+
+  /**
+   * Get token object
+   * @param collectionId ID of collection
+   * @param tokenId ID of token
+   * @example getTokenObject(10, 5);
+   * @returns instance of UniqueNFTToken
+   */
+  getTokenObject(collectionId: number, tokenId: number): UniqueNFTToken {
+    return new UniqueNFTToken(tokenId, this.getCollectionObject(collectionId));
+  }
+
+  /**
+   * Get token's owner
+   * @param collectionId ID of collection
+   * @param tokenId ID of token
+   * @param blockHashAt 
+   * @example getTokenOwner(10, 5);
+   * @returns Address in CrossAccountId format, e.g. {Substrate: "5DnSF6RRjwteE3BrCj..."}
+   */
+  async getTokenOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<ICrossAccountId> {
+    let owner;
+    if (typeof blockHashAt === 'undefined') {
+      owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId]);
+    } else {
+      owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId, blockHashAt]);
+    }
+    return crossAccountIdFromLower(owner.toJSON());
+  }
+
+  /**
+   * Is token approved to transfer
+   * @param collectionId ID of collection
+   * @param tokenId ID of token
+   * @param toAccountObj address to be approved
+   * @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async isTokenApproved(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId): Promise<boolean> {
+    return (await this.getTokenApprovedPieces(collectionId, tokenId, toAccountObj, await this.getTokenOwner(collectionId, tokenId))) === 1n;
+  }
+
+  /**
+   * Changes the owner of the token.
+   * 
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param tokenId ID of token
+   * @param addressObj address of a new owner
+   * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})
+   * @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<boolean> {
+    return await super.transferToken(signer, collectionId, tokenId, addressObj, 1n);
+  }
+
+  /**
+   * 
+   * Change ownership of a NFT on behalf of the owner. 
+   * 
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param tokenId ID of token
+   * @param fromAddressObj address on behalf of which the token will be sent
+   * @param toAddressObj new token owner
+   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Ethereum: "0x9F0583DbB85..."})
+   * @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId): Promise<boolean> {
+    return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, 1n);
+  }
+
+  /**
+   * Recursively find the address that owns the token
+   * @param collectionId ID of collection
+   * @param tokenId ID of token
+   * @param blockHashAt 
+   * @example getTokenTopmostOwner(10, 5);
+   * @returns address in CrossAccountId format, e.g. {Substrate: "5DyN4Y92vZCjv38fg..."}
+   */
+  async getTokenTopmostOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<ICrossAccountId | null> {
+    let owner;
+    if (typeof blockHashAt === 'undefined') {
+      owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId]);
+    } else {
+      owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId, blockHashAt]);
+    }
+
+    if (owner === null) return null;
+
+    owner = owner.toHuman();
+
+    return owner.Substrate ? {Substrate: this.helper.address.normalizeSubstrate(owner.Substrate)} : owner;
+  }
+
+  /**
+   * Get tokens nested in the provided token
+   * @param collectionId ID of collection
+   * @param tokenId ID of token
+   * @param blockHashAt 
+   * @example getTokenChildren(10, 5);
+   * @returns tokens whose depth of nesting is <= 5 
+   */
+  async getTokenChildren(collectionId: number, tokenId: number, blockHashAt?: string): Promise<IToken[]> {
+    let children;
+    if(typeof blockHashAt === 'undefined') {
+      children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId]);
+    } else {
+      children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId, blockHashAt]);
+    }
+
+    return children.toJSON().map((x: any) => {
+      return {collectionId: x.collection, tokenId: x.token};
+    });
+  }
+
+  /**
+   * Nest one token into another
+   * @param signer keyring of signer
+   * @param tokenObj token to be nested
+   * @param rootTokenObj token to be parent
+   * @param label 
+   * @example nestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4});
+   * @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, label='nest token'): Promise<boolean> {
+    const rootTokenAddress = {Ethereum: this.helper.util.getNestingTokenAddress(rootTokenObj.collectionId, rootTokenObj.tokenId)};
+    const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);
+    if(!result) {
+      throw Error(`Unable to nest token for ${label}`);
+    }
+    return result;
+  }
+
+  /**
+   * Remove token from nested state
+   * @param signer keyring of signer
+   * @param tokenObj token to unnest
+   * @param rootTokenObj parent of a token
+   * @param toAddressObj address of a new token owner 
+   * @param label 
+   * @example unnestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4}, {Substrate: "5DyN4Y92vZCjv38fg..."});
+   * @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId, label='unnest token'): Promise<boolean> {
+    const rootTokenAddress = {Ethereum: this.helper.util.getNestingTokenAddress(rootTokenObj.collectionId, rootTokenObj.tokenId)};
+    const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);
+    if(!result) {
+      throw Error(`Unable to unnest token for ${label}`);
+    }
+    return result;
+  }
+
+  /**
+   * Mint new collection
+   * @param signer keyring of signer
+   * @param collectionOptions Collection options
+   * @param label 
+   * @example 
+   * mintCollection(aliceKeyring, {
+   *   name: 'New',
+   *   description: 'New collection',
+   *   tokenPrefix: 'NEW',
+   * })
+   * @returns object of the created collection
+   */
+  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, label = 'new collection'): Promise<UniqueNFTCollection> {
+    return await super.mintCollection(signer, collectionOptions, 'NFT', `Unable to mint NFT collection for ${label}`) as UniqueNFTCollection;
+  }
+
+  /**
+   * Mint new token
+   * @param signer keyring of signer
+   * @param data token data
+   * @param label 
+   * @returns created token object
+   */
+  async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }, label?: string): Promise<UniqueNFTToken> {
+    if(typeof label === 'undefined') label = `collection #${data.collectionId}`;
+    const creationResult = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {
+        nft: {
+          properties: data.properties,
+        },
+      }],
+      true, `Unable to mint NFT token for ${label}`,
+    );
+    const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult, label);
+    if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');
+    if (createdTokens.tokens.length < 1) throw Error('No tokens minted');
+    return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);
+  }
+
+  /**
+   * Mint multiple NFT tokens
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param tokens array of tokens with owner and properties
+   * @param label 
+   * @example 
+   * mintMultipleTokens(aliceKeyring, 10, [{
+   *     owner: {Substrate: "5DyN4Y92vZCjv38fg..."},
+   *     properties: [{key: "gender", value: "male"},{key: "age", value: "45"}],
+   *   },{
+   *     owner: {Ethereum: "0x9F0583DbB855d..."},
+   *     properties: [{key: "gender", value: "female"},{key: "age", value: "22"}],
+   * }]);
+   * @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[], label?: string): Promise<UniqueNFTToken[]> {
+    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+    const creationResult = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}],
+      true, `Unable to mint NFT tokens for ${label}`,
+    );
+    const collection = this.getCollectionObject(collectionId);
+    return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));
+  }
+
+  /**
+   * Mint multiple NFT tokens with one owner
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param owner tokens owner
+   * @param tokens array of tokens with owner and properties
+   * @param label 
+   * @example
+   * mintMultipleTokensWithOneOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...", [{
+   *   properties: [{
+   *   key: "gender",
+   *   value: "female",
+   *  },{
+   *   key: "age",
+   *   value: "33",
+   *  }],
+   * }]);
+   * @returns array of newly created tokens
+   */
+  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {properties?: IProperty[]}[], label?: string): Promise<UniqueNFTToken[]> {
+    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+    const rawTokens = [];
+    for (const token of tokens) {
+      const raw = {NFT: {properties: token.properties}};
+      rawTokens.push(raw);
+    }
+    const creationResult = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],
+      true, `Unable to mint NFT tokens for ${label}`,
+    );
+    const collection = this.getCollectionObject(collectionId);
+    return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));
+  }
+
+  /**
+   * Destroys a concrete instance of NFT.
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param tokenId ID of token
+   * @param label 
+   * @example burnToken(aliceKeyring, 10, 5);
+   * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null```
+   */
+  async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, label?: string): Promise<{ success: boolean; token: number | null; }> {
+    return await super.burnToken(signer, collectionId, tokenId, label, 1n);
+  }
+
+  /**
+   * Set, change, or remove approved address to transfer the ownership of the NFT.
+   * 
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param tokenId ID of token
+   * @param toAddressObj address to approve
+   * @param label 
+   * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."})
+   * @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string) {
+    return super.approveToken(signer, collectionId, tokenId, toAddressObj, label, 1n);
+  }
+}
+
+
+class RFTGroup extends NFTnRFT {
+  /**
+   * Get collection object
+   * @param collectionId ID of collection
+   * @example getCollectionObject(2);
+   * @returns instance of UniqueRFTCollection
+   */
+  getCollectionObject(collectionId: number): UniqueRFTCollection {
+    return new UniqueRFTCollection(collectionId, this.helper);
+  }
+
+  /**
+   * Get token object
+   * @param collectionId ID of collection
+   * @param tokenId ID of token
+   * @example getTokenObject(10, 5);
+   * @returns instance of UniqueNFTToken
+   */
+  getTokenObject(collectionId: number, tokenId: number): UniqueRFTToken {
+    return new UniqueRFTToken(tokenId, this.getCollectionObject(collectionId));
+  }
+
+  /**
+   * Get top 10 token owners with the largest number of pieces 
+   * @param collectionId ID of collection
+   * @param tokenId ID of token
+   * @example getTokenTop10Owners(10, 5);
+   * @returns array of top 10 owners
+   */
+  async getTokenTop10Owners(collectionId: number, tokenId: number): Promise<ICrossAccountId[]> {
+    return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, tokenId])).toJSON().map(crossAccountIdFromLower);
+  }
+
+  /**
+   * Get number of pieces owned by address
+   * @param collectionId ID of collection
+   * @param tokenId ID of token
+   * @param addressObj address token owner
+   * @example getTokenBalance(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."});
+   * @returns number of pieces ownerd by address
+   */
+  async getTokenBalance(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise<bigint> {
+    return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, tokenId])).toBigInt();
+  }
+
+  /**
+   * Transfer pieces of token to another address
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param tokenId ID of token
+   * @param addressObj address of a new owner
+   * @param amount number of pieces to be transfered
+   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2000n)
+   * @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=100n): Promise<boolean> {
+    return await super.transferToken(signer, collectionId, tokenId, addressObj, amount);
+  }
+
+  /**
+   * Change ownership of some pieces of RFT on behalf of the owner. 
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param tokenId ID of token
+   * @param fromAddressObj address on behalf of which the token will be sent
+   * @param toAddressObj new token owner
+   * @param amount number of pieces to be transfered
+   * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5DfhbVfww7ThF8q6f3i..."}, 2000n)
+   * @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n): Promise<boolean> {
+    return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, amount);
+  }
+
+  /**
+   * Mint new collection
+   * @param signer keyring of signer
+   * @param collectionOptions Collection options
+   * @param label 
+   * @example
+   * mintCollection(aliceKeyring, {
+   *   name: 'New',
+   *   description: 'New collection',
+   *   tokenPrefix: 'NEW',
+   * })
+   * @returns object of the created collection
+   */
+  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, label = 'new collection'): Promise<UniqueRFTCollection> {
+    return await super.mintCollection(signer, collectionOptions, 'RFT', `Unable to mint RFT collection for ${label}`) as UniqueRFTCollection;
+  }
+
+  /**
+   * Mint new token
+   * @param signer keyring of signer
+   * @param data token data
+   * @param label 
+   * @example mintToken(aliceKeyring, {collectionId: 10, owner: {Substrate: '5GHoZe9c73RYbVzq...'}, pieces: 10000n});
+   * @returns created token object
+   */
+  async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }, label?: string): Promise<UniqueRFTToken> {
+    if(typeof label === 'undefined') label = `collection #${data.collectionId}`;
+    const creationResult = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, {
+        refungible: {
+          pieces: data.pieces,
+          properties: data.properties,
+        },
+      }],
+      true, `Unable to mint RFT token for ${label}`,
+    );
+    const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult, label);
+    if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens');
+    if (createdTokens.tokens.length < 1) throw Error('No tokens minted');
+    return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId);
+  }
+
+  async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[], label?: string): Promise<UniqueRFTToken[]> {
+    throw Error('Not implemented');
+    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+    const creationResult = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.createMultipleItemsEx', [collectionId, {RefungibleMultipleOwners: tokens}],
+      true, `Unable to mint RFT tokens for ${label}`,
+    );
+    const collection = this.getCollectionObject(collectionId);
+    return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));
+  }
+
+  /**
+   * Mint multiple RFT tokens with one owner
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param owner tokens owner
+   * @param tokens array of tokens with properties and pieces
+   * @param label 
+   * @example mintMultipleTokensWithOneOwner(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, [{pieces: 100000n, properties: [{key: "gender", value: "male"}]}]);
+   * @returns array of newly created RFT tokens
+   */
+  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {pieces: bigint, properties?: IProperty[]}[], label?: string): Promise<UniqueRFTToken[]> {
+    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+    const rawTokens = [];
+    for (const token of tokens) {
+      const raw = {ReFungible: {pieces: token.pieces, properties: token.properties}};
+      rawTokens.push(raw);
+    }
+    const creationResult = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],
+      true, `Unable to mint RFT tokens for ${label}`,
+    );
+    const collection = this.getCollectionObject(collectionId);
+    return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId));
+  }
+
+  /**
+   * Destroys a concrete instance of RFT.
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param tokenId ID of token
+   * @param label 
+   * @param amount number of pieces to be burnt
+   * @example burnToken(aliceKeyring, 10, 5);
+   * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null```
+   */
+  async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, label?: string, amount=100n): Promise<{ success: boolean; token: number | null; }> {
+    return await super.burnToken(signer, collectionId, tokenId, label, amount);
+  }
+
+  /**
+   * Set, change, or remove approved address to transfer the ownership of the RFT.
+   * 
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param tokenId ID of token
+   * @param toAddressObj address to approve
+   * @param label 
+   * @param amount number of pieces to be approved
+   * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5GHoZe9c73RYbVzq..."}, "", 10000n);
+   * @returns true if the token success, otherwise false
+   */
+  async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string, amount=100n) {
+    return super.approveToken(signer, collectionId, tokenId, toAddressObj, label, amount);
+  }
+
+  /**
+   * Get total number of pieces
+   * @param collectionId ID of collection
+   * @param tokenId ID of token
+   * @example getTokenTotalPieces(10, 5);
+   * @returns number of pieces
+   */
+  async getTokenTotalPieces(collectionId: number, tokenId: number): Promise<bigint> {
+    return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, tokenId])).unwrap().toBigInt();
+  }
+
+  /**
+   * Change number of token pieces. Signer must be the owner of all token pieces.
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param tokenId ID of token
+   * @param amount new number of pieces
+   * @param label 
+   * @example repartitionToken(aliceKeyring, 10, 5, 12345n);
+   * @returns true if the repartion was success, otherwise false
+   */
+  async repartitionToken(signer: TSigner, collectionId: number, tokenId: number, amount: bigint, label?: string): Promise<boolean> {
+    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+    const currentAmount = await this.getTokenTotalPieces(collectionId, tokenId);
+    const repartitionResult = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.repartition', [collectionId, tokenId, amount],
+      true, `Unable to repartition RFT token for ${label}`,
+    );
+    if(currentAmount < amount) return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemCreated', label);
+    return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemDestroyed', label);
+  }
+}
+
+
+class FTGroup extends CollectionGroup {
+  /**
+   * Get collection object
+   * @param collectionId ID of collection
+   * @example getCollectionObject(2);
+   * @returns instance of UniqueFTCollection
+   */
+  getCollectionObject(collectionId: number): UniqueFTCollection {
+    return new UniqueFTCollection(collectionId, this.helper);
+  }
+
+  /**
+   * Mint new fungible collection
+   * @param signer keyring of signer
+   * @param collectionOptions Collection options
+   * @param decimalPoints number of token decimals 
+   * @param errorLabel 
+   * @example
+   * mintCollection(aliceKeyring, {
+   *   name: 'New',
+   *   description: 'New collection',
+   *   tokenPrefix: 'NEW',
+   * }, 18)
+   * @returns newly created fungible collection
+   */
+  async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, decimalPoints = 0, errorLabel = 'Unable to mint collection'): Promise<UniqueFTCollection> {
+    collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object
+    if(collectionOptions.tokenPropertyPermissions) throw Error('Fungible collections has no tokenPropertyPermissions');
+    collectionOptions.mode = {fungible: decimalPoints};
+    for (const key of ['name', 'description', 'tokenPrefix']) {
+      if (typeof collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] === 'string') collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] = this.helper.util.str2vec(collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] as string);
+    }
+    const creationResult = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.createCollectionEx', [collectionOptions],
+      true, errorLabel,
+    );
+    return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult, errorLabel));
+  }
+
+  /**
+   * Mint tokens
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param owner address owner of new tokens
+   * @param amount amount of tokens to be meanted
+   * @param label 
+   * @example mintTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq"}, 1000n);
+   * @returns ```true``` if extrinsic success, otherwise ```false``` 
+   */
+  async mintTokens(signer: TSigner, collectionId: number, owner: ICrossAccountId | string, amount: bigint, label?: string): Promise<boolean> {
+    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+    const creationResult = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.createItem', [collectionId, (typeof owner === 'string') ? {Substrate: owner} : owner, {
+        fungible: {
+          value: amount,
+        },
+      }],
+      true, `Unable to mint fungible tokens for ${label}`,
+    );
+    return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated', label);
+  }
+
+  /**
+   * Mint multiple Fungible tokens with one owner
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param owner tokens owner
+   * @param tokens array of tokens with properties and pieces
+   * @param label 
+   * @returns ```true``` if extrinsic success, otherwise ```false``` 
+   */
+  async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {value: bigint}[], label?: string): Promise<boolean> {
+    if(typeof label === 'undefined') label = `collection #${collectionId}`;
+    const rawTokens = [];
+    for (const token of tokens) {
+      const raw = {Fungible: {Value: token.value}};
+      rawTokens.push(raw);
+    }
+    const creationResult = await this.helper.executeExtrinsic(
+      signer,
+      'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens],
+      true, `Unable to mint RFT tokens for ${label}`,
+    );
+    return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated', label);
+  }
+
+  /**
+   * Get the top 10 owners with the largest balance for the Fungible collection 
+   * @param collectionId ID of collection
+   * @example getTop10Owners(10);
+   * @returns array of ```ICrossAccountId```
+   */
+  async getTop10Owners(collectionId: number): Promise<ICrossAccountId[]> {
+    return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, 0])).toJSON().map(crossAccountIdFromLower);
+  }
+
+  /**
+   * Get account balance
+   * @param collectionId ID of collection
+   * @param addressObj address of owner
+   * @example getBalance(10, {Substrate: "5GHoZe9c73RYbVzq..."})
+   * @returns amount of fungible tokens owned by address
+   */
+  async getBalance(collectionId: number, addressObj: ICrossAccountId): Promise<bigint> {
+    return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, 0])).toBigInt();
+  }
+
+  /**
+   * Transfer tokens to address
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param toAddressObj address recepient
+   * @param amount amount of tokens to be sent
+   * @example transfer(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);
+   * @returns ```true``` if extrinsic success, otherwise ```false``` 
+   */
+  async transfer(signer: TSigner, collectionId: number, toAddressObj: ICrossAccountId, amount: bigint) {
+    return await super.transferToken(signer, collectionId, 0, toAddressObj, amount);
+  }
+
+  /**
+   * Transfer some tokens on behalf of the owner.
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param fromAddressObj address on behalf of which tokens will be sent
+   * @param toAddressObj address where token to be sent
+   * @param amount number of tokens to be sent
+   * @example transferFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, {Substrate: "5DfhbVfww7ThF8q6f3ij..."}, 10000n);
+   * @returns ```true``` if extrinsic success, otherwise ```false``` 
+   */
+  async transferFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount: bigint) {
+    return await super.transferTokenFrom(signer, collectionId, 0, fromAddressObj, toAddressObj, amount);
+  }
+
+  /**
+   * Destroy some amount of tokens
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param amount amount of tokens to be destroyed
+   * @param label 
+   * @example burnTokens(aliceKeyring, 10, 1000n);
+   * @returns ```true``` if extrinsic success, otherwise ```false``` 
+   */
+  async burnTokens(signer: IKeyringPair, collectionId: number, amount=100n, label?: string): Promise<boolean> {
+    return (await super.burnToken(signer, collectionId, 0, label, amount)).success;
+  }
+
+  /**
+   * Burn some tokens on behalf of the owner.
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param fromAddressObj address on behalf of which tokens will be burnt
+   * @param amount amount of tokens to be burnt
+   * @param label 
+   * @example burnTokensFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n);
+   * @returns ```true``` if extrinsic success, otherwise ```false``` 
+   */
+  async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount=100n, label?: string): Promise<boolean> {
+    return await super.burnTokenFrom(signer, collectionId, fromAddressObj, 0, label, amount);
+  }
+
+  /**
+   * Get total collection supply
+   * @param collectionId 
+   * @returns 
+   */
+  async getTotalPieces(collectionId: number): Promise<bigint> {
+    return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, 0])).unwrap().toBigInt();
+  }
+
+  /**
+   * Set, change, or remove approved address to transfer tokens.
+   * 
+   * @param signer keyring of signer
+   * @param collectionId ID of collection
+   * @param toAddressObj address to be approved
+   * @param amount amount of tokens to be approved
+   * @param label 
+   * @example approveTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n)
+   * @returns ```true``` if extrinsic success, otherwise ```false``` 
+   */
+  async approveTokens(signer: IKeyringPair, collectionId: number, toAddressObj: ICrossAccountId, amount=100n, label?: string) {
+    return super.approveToken(signer, collectionId, 0, toAddressObj, label, amount);
+  }
+
+  /**
+   * Get amount of fungible tokens approved to transfer
+   * @param collectionId ID of collection
+   * @param fromAddressObj owner of tokens
+   * @param toAddressObj the address approved for the transfer of tokens on behalf of the owner
+   * @returns number of tokens approved for the transfer
+   */
+  async getApprovedTokens(collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {
+    return super.getTokenApprovedPieces(collectionId, 0, toAddressObj, fromAddressObj);
+  }
+}
+
+
+class ChainGroup extends HelperGroup {
+  /**
+   * Get system properties of a chain
+   * @example getChainProperties();
+   * @returns ss58Format, token decimals, and token symbol
+   */
+  getChainProperties(): IChainProperties {
+    const properties = (this.helper.api as any).registry.getChainProperties().toJSON();
+    return {
+      ss58Format: properties.ss58Format.toJSON(),
+      tokenDecimals: properties.tokenDecimals.toJSON(),
+      tokenSymbol: properties.tokenSymbol.toJSON(),
+    };
+  }
+
+  /**
+   * Get chain header
+   * @example getLatestBlockNumber();
+   * @returns the number of the last block
+   */
+  async getLatestBlockNumber(): Promise<number> {
+    return (await this.helper.callRpc('api.rpc.chain.getHeader')).number.toNumber();
+  }
+
+  /**
+   * Get block hash by block number
+   * @param blockNumber number of block
+   * @example getBlockHashByNumber(12345);
+   * @returns hash of a block
+   */
+  async getBlockHashByNumber(blockNumber: number): Promise<string | null> {
+    const blockHash = (await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])).toJSON();
+    if(blockHash === '0x0000000000000000000000000000000000000000000000000000000000000000') return null;
+    return blockHash;
+  }
+
+  /**
+   * Get account nonce
+   * @param address substrate address
+   * @example getNonce("5GrwvaEF5zXb26Fz...");
+   * @returns number, account's nonce
+   */
+  async getNonce(address: TSubstrateAccount): Promise<number> {
+    return (await (this.helper.api as any).query.system.account(address)).nonce.toNumber();
+  }
+}
+
+
+class BalanceGroup extends HelperGroup {
+  /**
+   * Representation of the native token in the smallest unit
+   * @example getOneTokenNominal()
+   * @returns ```BigInt``` representation of the native token in the smallest unit, e.g. ```1_000_000_000_000_000_000n``` for QTZ.
+   */
+  getOneTokenNominal(): bigint {
+    const chainProperties = this.helper.chain.getChainProperties();
+    return 10n ** BigInt((chainProperties.tokenDecimals || [18])[0]);
+  }
+
+  /**
+   * Get substrate address balance
+   * @param address substrate address
+   * @example getSubstrate("5GrwvaEF5zXb26Fz...")
+   * @returns amount of tokens on address
+   */
+  async getSubstrate(address: TSubstrateAccount): Promise<bigint> {
+    return (await this.helper.callRpc('api.query.system.account', [address])).data.free.toBigInt();
+  }
+
+  /**
+   * Get ethereum address balance
+   * @param address ethereum address
+   * @example getEthereum("0x9F0583DbB855d...")
+   * @returns amount of tokens on address
+   */
+  async getEthereum(address: TEthereumAccount): Promise<bigint> {
+    return (await this.helper.callRpc('api.rpc.eth.getBalance', [address])).toBigInt();
+  }
+
+  /**
+   * Transfer tokens to substrate address
+   * @param signer keyring of signer
+   * @param address substrate address of a recepient
+   * @param amount amount of tokens to be transfered
+   * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n);
+   * @returns ```true``` if extrinsic success, otherwise ```false```
+   */
+  async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise<boolean> {
+    const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transfer', [address, amount], true, `Unable to transfer balance from ${this.helper.getSignerAddress(signer)} to ${address}`);
+
+    let transfer = {from: null, to: null, amount: 0n} as any;
+    result.result.events.forEach(({event: {data, method, section}}) => {
+      if ((section === 'balances') && (method === 'Transfer')) {
+        transfer = {
+          from: this.helper.address.normalizeSubstrate(data[0]),
+          to: this.helper.address.normalizeSubstrate(data[1]),
+          amount: BigInt(data[2]),
+        };
+      }
+    });
+    let isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from;
+    isSuccess = isSuccess && this.helper.address.normalizeSubstrate(address) === transfer.to;
+    isSuccess = isSuccess && BigInt(amount) === transfer.amount;
+    return isSuccess;
+  }
+}
+
+
+class AddressGroup extends HelperGroup {
+  /**
+   * Normalizes the address to the specified ss58 format, by default ```42```.
+   * @param address substrate address
+   * @param ss58Format format for address conversion, by default ```42```
+   * @example normalizeSubstrate("unjKJQJrRd238pkUZZvzDQrfKuM39zBSnQ5zjAGAGcdRhaJTx") // returns 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
+   * @returns substrate address converted to normalized (i.e., starting with 5) or specified explicitly representation
+   */
+  normalizeSubstrate(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount {
+    return this.helper.util.normalizeSubstrateAddress(address, ss58Format);
+  }
+
+  /**
+   * Get address in the connected chain format
+   * @param address substrate address
+   * @example normalizeSubstrateToChainFormat("5GrwvaEF5zXb26Fz...") // returns unjKJQJrRd238pkUZZ... for Unique Network
+   * @returns address in chain format
+   */
+  async normalizeSubstrateToChainFormat(address: TSubstrateAccount): Promise<TSubstrateAccount> {
+    const info = this.helper.chain.getChainProperties();
+    return encodeAddress(decodeAddress(address), info.ss58Format);
+  }
+
+  /**
+   * Get substrate mirror of an ethereum address
+   * @param ethAddress ethereum address
+   * @param toChainFormat false for normalized account
+   * @example ethToSubstrate('0x9F0583DbB855d...')
+   * @returns substrate mirror of a provided ethereum address
+   */
+  async ethToSubstrate(ethAddress: TEthereumAccount, toChainFormat=false): Promise<TSubstrateAccount> {
+    if(!toChainFormat) return evmToAddress(ethAddress);
+    const info = this.helper.chain.getChainProperties();
+    return evmToAddress(ethAddress, info.ss58Format);
+  }
+
+  /**
+   * Get ethereum mirror of a substrate address
+   * @param subAddress substrate account
+   * @example substrateToEth("5DnSF6RRjwteE3BrC...")
+   * @returns ethereum mirror of a provided substrate address
+   */
+  substrateToEth(subAddress: TSubstrateAccount): TEthereumAccount {
+    return nesting.toChecksumAddress('0x' + Array.from(addressToEvm(subAddress), i => i.toString(16).padStart(2, '0')).join(''));
+  }
+}
+
+
+export class UniqueHelper extends ChainHelperBase {
+  chain: ChainGroup;
+  balance: BalanceGroup;
+  address: AddressGroup;
+  collection: CollectionGroup;
+  nft: NFTGroup;
+  rft: RFTGroup;
+  ft: FTGroup;
+
+  constructor(logger?: ILogger) {
+    super(logger);
+    this.chain = new ChainGroup(this);
+    this.balance = new BalanceGroup(this);
+    this.address = new AddressGroup(this);
+    this.collection = new CollectionGroup(this);
+    this.nft = new NFTGroup(this);
+    this.rft = new RFTGroup(this);
+    this.ft = new FTGroup(this);
+  }  
+}
+
+
+class UniqueCollectionBase {
+  helper: UniqueHelper;
+  collectionId: number;
+
+  constructor(collectionId: number, uniqueHelper: UniqueHelper) {
+    this.collectionId = collectionId;
+    this.helper = uniqueHelper;
+  }
+
+  async getData() {
+    return await this.helper.collection.getData(this.collectionId);
+  }
+
+  async getLastTokenId() {
+    return await this.helper.collection.getLastTokenId(this.collectionId);
+  }
+
+  async isTokenExists(tokenId: number) {
+    return await this.helper.collection.isTokenExists(this.collectionId, tokenId);
+  }
+
+  async getAdmins() {
+    return await this.helper.collection.getAdmins(this.collectionId);
+  }
+
+  async getEffectiveLimits() {
+    return await this.helper.collection.getEffectiveLimits(this.collectionId);
+  }
+
+  async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount, label?: string) {
+    return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress, label);
+  }
+
+  async confirmSponsorship(signer: TSigner, label?: string) {
+    return await this.helper.collection.confirmSponsorship(signer, this.collectionId, label);
+  }
+
+  async setLimits(signer: TSigner, limits: ICollectionLimits, label?: string) {
+    return await this.helper.collection.setLimits(signer, this.collectionId, limits, label);
+  }
+
+  async changeOwner(signer: TSigner, ownerAddress: TSubstrateAccount, label?: string) {
+    return await this.helper.collection.changeOwner(signer, this.collectionId, ownerAddress, label);
+  }
+
+  async addAdmin(signer: TSigner, adminAddressObj: ICrossAccountId, label?: string) {
+    return await this.helper.collection.addAdmin(signer, this.collectionId, adminAddressObj, label);
+  }
+
+  async removeAdmin(signer: TSigner, adminAddressObj: ICrossAccountId, label?: string) {
+    return await this.helper.collection.removeAdmin(signer, this.collectionId, adminAddressObj, label);
+  }
+
+  async setProperties(signer: TSigner, properties: IProperty[], label?: string) {
+    return await this.helper.collection.setProperties(signer, this.collectionId, properties, label);
+  }
+
+  async deleteProperties(signer: TSigner, propertyKeys: string[], label?: string) {
+    return await this.helper.collection.deleteProperties(signer, this.collectionId, propertyKeys, label);
+  }
+
+  async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {
+    return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);
+  }
+
+  async setPermissions(signer: TSigner, permissions: ICollectionPermissions, label?: string) {
+    return await this.helper.collection.setPermissions(signer, this.collectionId, permissions, label);
+  }
+
+  async enableNesting(signer: TSigner, permissions: INestingPermissions, label?: string) {
+    return await this.helper.collection.enableNesting(signer, this.collectionId, permissions, label);
+  }
+
+  async disableNesting(signer: TSigner, label?: string) {
+    return await this.helper.collection.disableNesting(signer, this.collectionId, label);
+  }
+
+  async burn(signer: TSigner, label?: string) {
+    return await this.helper.collection.burn(signer, this.collectionId, label);
+  }
+}
+
+
+class UniqueNFTCollection extends UniqueCollectionBase {
+  getTokenObject(tokenId: number) {
+    return new UniqueNFTToken(tokenId, this);
+  }
+
+  async getTokensByAddress(addressObj: ICrossAccountId) {
+    return await this.helper.nft.getTokensByAddress(this.collectionId, addressObj);
+  }
+
+  async getToken(tokenId: number, blockHashAt?: string) {
+    return await this.helper.nft.getToken(this.collectionId, tokenId, blockHashAt);
+  }
+
+  async getTokenOwner(tokenId: number, blockHashAt?: string) {
+    return await this.helper.nft.getTokenOwner(this.collectionId, tokenId, blockHashAt);
+  }
+
+  async getTokenTopmostOwner(tokenId: number, blockHashAt?: string) {
+    return await this.helper.nft.getTokenTopmostOwner(this.collectionId, tokenId, blockHashAt);
+  }
+
+  async getTokenChildren(tokenId: number, blockHashAt?: string) {
+    return await this.helper.nft.getTokenChildren(this.collectionId, tokenId, blockHashAt);
+  }
+
+  async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId) {
+    return await this.helper.nft.transferToken(signer, this.collectionId, tokenId, addressObj);
+  }
+
+  async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {
+    return await this.helper.nft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj);
+  }
+
+  async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, label?: string) {
+    return await this.helper.nft.approveToken(signer, this.collectionId, tokenId, toAddressObj, label);
+  }
+
+  async isTokenApproved(tokenId: number, toAddressObj: ICrossAccountId) {
+    return await this.helper.nft.isTokenApproved(this.collectionId, tokenId, toAddressObj);
+  }
+
+  async mintToken(signer: TSigner, owner: ICrossAccountId, properties?: IProperty[], label?: string) {
+    return await this.helper.nft.mintToken(signer, {collectionId: this.collectionId, owner, properties}, label);
+  }
+
+  async mintMultipleTokens(signer: TSigner, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[], label?: string) {
+    return await this.helper.nft.mintMultipleTokens(signer, this.collectionId, tokens, label);
+  }
+
+  async burnToken(signer: TSigner, tokenId: number, label?: string) {
+    return await this.helper.nft.burnToken(signer, this.collectionId, tokenId, label);
+  }
+
+  async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[], label?: string) {
+    return await this.helper.nft.setTokenProperties(signer, this.collectionId, tokenId, properties, label);
+  }
+
+  async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[], label?: string) {
+    return await this.helper.nft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys, label);
+  }
+
+  async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[], label?: string) {
+    return await this.helper.nft.setTokenPropertyPermissions(signer, this.collectionId, permissions, label);
+  }
+
+  async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken, label?: string) {
+    return await this.helper.nft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj, label);
+  }
+
+  async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId, label?: string) {
+    return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj, label);
+  }
+}
+
+
+class UniqueRFTCollection extends UniqueCollectionBase {
+  getTokenObject(tokenId: number) {
+    return new UniqueRFTToken(tokenId, this);
+  }
+
+  async getTokensByAddress(addressObj: ICrossAccountId) {
+    return await this.helper.rft.getTokensByAddress(this.collectionId, addressObj);
+  }
+
+  async getTop10TokenOwners(tokenId: number) {
+    return await this.helper.rft.getTokenTop10Owners(this.collectionId, tokenId);
+  }
+
+  async getTokenBalance(tokenId: number, addressObj: ICrossAccountId) {
+    return await this.helper.rft.getTokenBalance(this.collectionId, tokenId, addressObj);
+  }
+
+  async getTokenTotalPieces(tokenId: number) {
+    return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId);
+  }
+
+  async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount=100n) {
+    return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount);
+  }
+
+  async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n) {
+    return await this.helper.rft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj, amount);
+  }
+
+  async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, amount=100n, label?: string) {
+    return await this.helper.rft.approveToken(signer, this.collectionId, tokenId, toAddressObj, label, amount);
+  }
+
+  async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {
+    return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj);
+  }
+
+  async repartitionToken(signer: TSigner, tokenId: number, amount: bigint, label?: string) {
+    return await this.helper.rft.repartitionToken(signer, this.collectionId, tokenId, amount, label);
+  }
+
+  async mintToken(signer: TSigner, owner: ICrossAccountId, pieces=100n, properties?: IProperty[], label?: string) {
+    return await this.helper.rft.mintToken(signer, {collectionId: this.collectionId, owner, pieces, properties}, label);
+  }
+
+  async mintMultipleTokens(signer: TSigner, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[], label?: string) {
+    return await this.helper.rft.mintMultipleTokens(signer, this.collectionId, tokens, label);
+  }
+
+  async burnToken(signer: TSigner, tokenId: number, amount=100n, label?: string) {
+    return await this.helper.rft.burnToken(signer, this.collectionId, tokenId, label, amount);
+  }
+
+  async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[], label?: string) {
+    return await this.helper.rft.setTokenProperties(signer, this.collectionId, tokenId, properties, label);
+  }
+
+  async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[], label?: string) {
+    return await this.helper.rft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys, label);
+  }
+
+  async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[], label?: string) {
+    return await this.helper.rft.setTokenPropertyPermissions(signer, this.collectionId, permissions, label);
+  }
+}
+
+
+class UniqueFTCollection extends UniqueCollectionBase {
+  async mint(signer: TSigner, owner: ICrossAccountId, amount: bigint, label?: string) {
+    return await this.helper.ft.mintTokens(signer, this.collectionId, owner, amount, label);
+  }
+
+  async mintWithOneOwner(signer: TSigner, owner: ICrossAccountId, tokens: {value: bigint}[], label?: string) {
+    return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, owner, tokens, label);
+  }
+
+  async getBalance(addressObj: ICrossAccountId) {
+    return await this.helper.ft.getBalance(this.collectionId, addressObj);
+  }
+
+  async getTop10Owners() {
+    return await this.helper.ft.getTop10Owners(this.collectionId);
+  }
+
+  async transfer(signer: TSigner, toAddressObj: ICrossAccountId, amount: bigint) {
+    return await this.helper.ft.transfer(signer, this.collectionId, toAddressObj, amount);
+  }
+
+  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount: bigint) {
+    return await this.helper.ft.transferFrom(signer, this.collectionId, fromAddressObj, toAddressObj, amount);
+  }
+
+  async burnTokens(signer: TSigner, amount: bigint, label?: string) {
+    return await this.helper.ft.burnTokens(signer, this.collectionId, amount, label);
+  }
+
+  async burnTokensFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount: bigint, label?: string) {
+    return await this.helper.ft.burnTokensFrom(signer, this.collectionId, fromAddressObj, amount, label);
+  }
+
+  async getTotalPieces() {
+    return await this.helper.ft.getTotalPieces(this.collectionId);
+  }
+
+  async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount=100n, label?: string) {
+    return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount, label);
+  }
+
+  async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {
+    return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj);
+  }
+}
+
+
+class UniqueTokenBase implements IToken {
+  collection: UniqueNFTCollection | UniqueRFTCollection;
+  collectionId: number;
+  tokenId: number;
+
+  constructor(tokenId: number, collection: UniqueNFTCollection | UniqueRFTCollection) {
+    this.collection = collection;
+    this.collectionId = collection.collectionId;
+    this.tokenId = tokenId;
+  }
+
+  async getNextSponsored(addressObj: ICrossAccountId) {
+    return await this.collection.getTokenNextSponsored(this.tokenId, addressObj);
+  }
+
+  async setProperties(signer: TSigner, properties: IProperty[], label?: string) {
+    return await this.collection.setTokenProperties(signer, this.tokenId, properties, label);
+  }
+
+  async deleteProperties(signer: TSigner, propertyKeys: string[], label?: string) {
+    return await this.collection.deleteTokenProperties(signer, this.tokenId, propertyKeys, label);
+  }
+}
+
+
+class UniqueNFTToken extends UniqueTokenBase {
+  collection: UniqueNFTCollection;
+
+  constructor(tokenId: number, collection: UniqueNFTCollection) {
+    super(tokenId, collection);
+    this.collection = collection;
+  }
+
+  async getData(blockHashAt?: string) {
+    return await this.collection.getToken(this.tokenId, blockHashAt);
+  }
+
+  async getOwner(blockHashAt?: string) {
+    return await this.collection.getTokenOwner(this.tokenId, blockHashAt);
+  }
+
+  async getTopmostOwner(blockHashAt?: string) {
+    return await this.collection.getTokenTopmostOwner(this.tokenId, blockHashAt);
+  }
+
+  async getChildren(blockHashAt?: string) {
+    return await this.collection.getTokenChildren(this.tokenId, blockHashAt);
+  }
+
+  async nest(signer: TSigner, toTokenObj: IToken, label?: string) {
+    return await this.collection.nestToken(signer, this.tokenId, toTokenObj, label);
+  }
+
+  async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId, label?: string) {
+    return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj, label);
+  }
+
+  async transfer(signer: TSigner, addressObj: ICrossAccountId) {
+    return await this.collection.transferToken(signer, this.tokenId, addressObj);
+  }
+
+  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) {
+    return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj);
+  }
+
+  async approve(signer: TSigner, toAddressObj: ICrossAccountId, label?: string) {
+    return await this.collection.approveToken(signer, this.tokenId, toAddressObj, label);
+  }
+
+  async isApproved(toAddressObj: ICrossAccountId) {
+    return await this.collection.isTokenApproved(this.tokenId, toAddressObj);
+  }
+
+  async burn(signer: TSigner, label?: string) {
+    return await this.collection.burnToken(signer, this.tokenId, label);
+  }
+}
+
+class UniqueRFTToken extends UniqueTokenBase {
+  collection: UniqueRFTCollection;
+
+  constructor(tokenId: number, collection: UniqueRFTCollection) {
+    super(tokenId, collection);
+    this.collection = collection;
+  }
+
+  async getTop10Owners() {
+    return await this.collection.getTop10TokenOwners(this.tokenId);
+  }
+
+  async getBalance(addressObj: ICrossAccountId) {
+    return await this.collection.getTokenBalance(this.tokenId, addressObj);
+  }
+
+  async getTotalPieces() {
+    return await this.collection.getTokenTotalPieces(this.tokenId);
+  }
+
+  async transfer(signer: TSigner, addressObj: ICrossAccountId, amount=100n) {
+    return await this.collection.transferToken(signer, this.tokenId, addressObj, amount);
+  }
+
+  async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n) {
+    return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj, amount);
+  }
+
+  async approve(signer: TSigner, toAddressObj: ICrossAccountId, amount=100n, label?: string) {
+    return await this.collection.approveToken(signer, this.tokenId, toAddressObj, amount, label);
+  }
+
+  async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) {
+    return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj);
+  }
+
+  async repartition(signer: TSigner, amount: bigint, label?: string) {
+    return await this.collection.repartitionToken(signer, this.tokenId, amount, label);
+  }
+
+  async burn(signer: TSigner, amount=100n, label?: string) {
+    return await this.collection.burnToken(signer, this.tokenId, amount, label);
+  }
+}
\ No newline at end of file
modifiedtests/yarn.lockdiffbeforeafterboth
--- a/tests/yarn.lock
+++ b/tests/yarn.lock
@@ -963,6 +963,13 @@
   dependencies:
     "@types/chai" "*"
 
+"@types/chai-like@^1.1.1":
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/@types/chai-like/-/chai-like-1.1.1.tgz#c454039b0a2f92664fb5b7b7a2a66c3358783ae7"
+  integrity sha512-s46EZsupBuVhLn66DbRee5B0SELLmL4nFXVrBiV29BxLGm9Sh7Bful623j3AfiQRu2zAP4cnlZ3ETWB3eWc4bA==
+  dependencies:
+    "@types/chai" "*"
+
 "@types/chai@*", "@types/chai@^4.3.1":
   version "4.3.1"
   resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.1.tgz#e2c6e73e0bdeb2521d00756d099218e9f5d90a04"
@@ -1545,6 +1552,11 @@
   dependencies:
     check-error "^1.0.2"
 
+chai-like@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/chai-like/-/chai-like-1.1.1.tgz#8c558a414c34514e814d497c772547ceb7958f64"
+  integrity sha512-VKa9z/SnhXhkT1zIjtPACFWSoWsqVoaz1Vg+ecrKo5DCKVlgL30F/pEyEvXPBOVwCgLZcWUleCM/C1okaKdTTA==
+
 chai@^4.3.6:
   version "4.3.6"
   resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.6.tgz#ffe4ba2d9fa9d6680cc0b370adae709ec9011e9c"