git.delta.rocks / unique-network / refs/commits / 97d7053c9b0e

difftreelog

Merge branch 'develop' into feature/contract-sponsoring-modes

Igor Kozyrev2022-02-17parents: #e7ebf1d #71137c9.patch.diff
in: master

34 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -382,9 +382,9 @@
 
 [[package]]
 name = "autocfg"
-version = "1.0.1"
+version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
 [[package]]
 name = "backtrace"
@@ -634,9 +634,9 @@
 
 [[package]]
 name = "block-buffer"
-version = "0.10.1"
+version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03588e54c62ae6d763e2a80090d50353b785795361b4ff5b3bf0a5097fc31c0b"
+checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
 dependencies = [
  "generic-array 0.14.5",
 ]
@@ -1097,9 +1097,9 @@
 
 [[package]]
 name = "core-foundation"
-version = "0.9.2"
+version = "0.9.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3"
+checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
 dependencies = [
  "core-foundation-sys",
  "libc",
@@ -1229,9 +1229,9 @@
 
 [[package]]
 name = "crc32fast"
-version = "1.3.1"
+version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2209c310e29876f7f0b2721e7e26b84aff178aa3da5d091f9bfbf47669e60e3"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
 dependencies = [
  "cfg-if 1.0.0",
 ]
@@ -1288,9 +1288,9 @@
 
 [[package]]
 name = "crypto-common"
-version = "0.1.1"
+version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "683d6b536309245c849479fba3da410962a43ed8e51c26b729208ec0ac2798d0"
+checksum = "a4600d695eb3f6ce1cd44e6e291adceb2cc3ab12f20a33777ecd0bf6eba34e06"
 dependencies = [
  "generic-array 0.14.5",
 ]
@@ -1898,13 +1898,12 @@
 
 [[package]]
 name = "digest"
-version = "0.10.1"
+version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b697d66081d42af4fba142d56918a3cb21dc8eb63372c6b85d14f44fb9c5979b"
+checksum = "8cb780dce4f9a8f5c087362b3a4595936b2019e7c8b30f2c3e9a7e94e6ae9837"
 dependencies = [
- "block-buffer 0.10.1",
+ "block-buffer 0.10.2",
  "crypto-common",
- "generic-array 0.14.5",
 ]
 
 [[package]]
@@ -3241,9 +3240,9 @@
 
 [[package]]
 name = "httparse"
-version = "1.5.1"
+version = "1.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503"
+checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4"
 
 [[package]]
 name = "httpdate"
@@ -4664,9 +4663,9 @@
 
 [[package]]
 name = "memmap2"
-version = "0.5.2"
+version = "0.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe3179b85e1fd8b14447cbebadb75e45a1002f541b925f0bfec366d56a81c56d"
+checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f"
 dependencies = [
  "libc",
 ]
@@ -8805,7 +8804,7 @@
 source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.16#4aeb95f7f38fcd519e2628f32f79044a8fef99d5"
 dependencies = [
  "impl-trait-for-tuples",
- "memmap2 0.5.2",
+ "memmap2 0.5.3",
  "parity-scale-codec",
  "sc-chain-spec-derive",
  "sc-network",
@@ -9912,7 +9911,7 @@
 dependencies = [
  "cfg-if 1.0.0",
  "cpufeatures 0.2.1",
- "digest 0.10.1",
+ "digest 0.10.2",
 ]
 
 [[package]]
@@ -11644,7 +11643,7 @@
 
 [[package]]
 name = "unique-node"
-version = "0.9.14"
+version = "0.9.16"
 dependencies = [
  "cumulus-client-cli",
  "cumulus-client-collator",
modifiedDockerfile-parachaindiffbeforeafterboth
--- a/Dockerfile-parachain
+++ b/Dockerfile-parachain
@@ -90,7 +90,7 @@
       nvm install v15.5.0 && \
       nvm use v15.5.0
 
-RUN git clone https://github.com/PureStake/polkadot-launch -b jlm-use-para-id-in-specs
+RUN git clone https://github.com/paritytech/polkadot-launch
 
 RUN export NVM_DIR="$HOME/.nvm" && \
     [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && \
modifiedREADME.mddiffbeforeafterboth
--- a/README.md
+++ b/README.md
@@ -29,26 +29,11 @@
 
 Please see our [walk-thorugh instructions](doc/hackusama_walk_through.md) to try everything out!
 
-## Hackusama Update
-
-During the Kusama Hackaphon the following changes were made:
-
--   Enabled Smart Contracts Pallet
--   Enabled integration between Smart Contracts and Unique Pallet (required special edition of RC4 Substrate version)
--   Fixed misc. bugs in Unique Pallet
--   Deployed Unique TestNet. Public node available at wss://unique.usetech.com, custom UI types - see below in this README.
--   New Features:
-    -   Re-Fungible Token Mode
-    -   Off-Chain Schema to store token image URLs
-    -   Alternative economic model
-    -   Allow Lists and Public Mint Permission
--   Use example: [SubstraPunks Game](https://github.com/usetech-llc/substrapunks), fully hosted on IPFS and Unique Testnet
-    Blockchain.
-
 ## Application Development
 
 If you are building an application that operates NFT tokens, use [this document](doc/application_development.md).
 
+
 ## Building
 
 Building Unique chain requires special versions of Rust and toolchain. We don't use the most recent versions of everything
@@ -57,8 +42,8 @@
 1. Install Rust:
 
 ```bash
+sudo apt-get install git curl libssl-dev llvm pkg-config libclang-dev clang
 curl https://sh.rustup.rs -sSf | sh
-sudo apt-get install libssl-dev pkg-config libclang-dev clang
 ```
 
 2. Remove all installed toolchains with `rustup toolchain list` and `rustup toolchain uninstall <toolchain>`.
@@ -86,111 +71,52 @@
 cargo build --release
 ```
 
-## Run
-
-You can start a development chain with:
-
-```bash
-cargo run -- --dev
-```
-
-Detailed logs may be shown by running the node with the following environment variables set:
-`RUST_LOG=debug RUST_BACKTRACE=1 cargo run -- --dev`.
+## Building as Parachain locally
 
-If you want to see the multi-node consensus algorithm in action locally, then you can create a local testnet with two
-validator nodes for Alice and Bob, who are the initial authorities of the genesis chain that have been endowed with
-testnet units. Give each node a name and expose them so they are listed on the Polkadot
-[telemetry site](https://telemetry.polkadot.io/#/Local%20Testnet). You'll need two terminal windows open.
+Note: checkout this project and all related projects (see below) in the sibling folders (both under the same folder)
 
-We'll start Alice's substrate node first on default TCP port 30333 with her chain database stored locally at
-`/tmp/alice`. The bootnode ID of her node is `QmQZ8TjTqeDj3ciwr93EJ95hxfDsb9pEYDizUAbWpigtQN`, which is generated from
-the `--node-key` value that we specify below:
+### Polkadot launch utility
 
-```bash
-cargo run -- \
-  --base-path /tmp/alice \
-  --chain=local \
-  --alice \
-  --node-key 0000000000000000000000000000000000000000000000000000000000000001 \
-  --telemetry-url ws://telemetry.polkadot.io:1024 \
-  --validator
 ```
-
-In the second terminal, we'll start Bob's substrate node on a different TCP port of 30334, and with his chain database
-stored locally at `/tmp/bob`. We'll specify a value for the `--bootnodes` option that will connect his node to Alice's
-bootnode ID on TCP port 30333:
-
-```bash
-cargo run -- \
-  --base-path /tmp/bob \
-  --bootnodes /ip4/127.0.0.1/tcp/30333/p2p/QmQZ8TjTqeDj3ciwr93EJ95hxfDsb9pEYDizUAbWpigtQN \
-  --chain=local \
-  --bob \
-  --port 30334 \
-  --telemetry-url ws://telemetry.polkadot.io:1024 \
-  --validator
+git clone https://github.com/paritytech/polkadot-launch
 ```
-
-Additional CLI usage options are available and may be shown by running `cargo run -- --help`.
-
-## Building and Running as Parachain locally
-
-Rust toolchain: nightly-2021-11-11
-Note: checkout this project and polkadot project (see below) in the sibling folders (both under the same folder)
 
 ### Build relay
 
 ```
 git clone https://github.com/paritytech/polkadot.git
 cd polkadot
-git checkout release-v0.9.9
+git checkout release-v0.9.16
 cargo build --release
 ```
 
-### Build parachain
+### Build Unique parachain
 
 Run in the root of this project:
 ```
-cargo --build
+cargo build --release
 ```
 
-### Run 4-node Relay
-
-1. Download `rococo-custom-4.json` chain spec here: https://substrate.dev/cumulus-workshop/shared/chainspecs/rococo-custom-4.json
-2. Use these instructions to launch 4 nodes: https://substrate.dev/cumulus-workshop/#/en/2-relay-chain/1-launch
+### Build Acala parachain (optional, full config only)
 
-Example (Run in polkadot folder. Replace `12D3KooWNLAmKcyee3oqSgTMthaQVXaAcXeo8RrGCzMfMVA3B5on` with output from Alice node):
 ```
-./target/release/polkadot --alice --validator --base-path ./cumulus_relay0 --chain rococo-custom-4.json --port 50555 --ws-port 9944
-./target/release/polkadot --bob --validator --base-path ./cumulus_relay1 --chain rococo-custom-4.json --bootnodes /ip4/127.0.0.1/tcp/30333/p2p/12D3KooWNLAmKcyee3oqSgTMthaQVXaAcXeo8RrGCzMfMVA3B5on --port 50556 --ws-port 9945
-./target/release/polkadot --charlie --validator --base-path ./cumulus_relay1 --chain rococo-custom-4.json --bootnodes /ip4/127.0.0.1/tcp/30333/p2p/12D3KooWNLAmKcyee3oqSgTMthaQVXaAcXeo8RrGCzMfMVA3B5on --port 50557 --ws-port 9946
-./target/release/polkadot --dave --validator --base-path ./cumulus_relay1 --chain rococo-custom-4.json --bootnodes /ip4/127.0.0.1/tcp/30333/p2p/12D3KooWNLAmKcyee3oqSgTMthaQVXaAcXeo8RrGCzMfMVA3B5on --port 50558 --ws-port 9947
-
+git clone https://github.com/AcalaNetwork/Acala
+cd Acala
+git checkout 54db3acd409a0b787f116f20e163a3b24101ce38
+make build-release
 ```
 
-3. Export genesis state and runtime wasm from Unique parachain:
+## Running as Parachain locally
 
-Run from this project root:
 ```
-./target/release/unique-collator export-genesis-state --parachain-id 2000 > ./resources/para-2000-genesis
-./target/release/unique-collator export-genesis-wasm > ./resources/para-2000-wasm
+./launch-testnet.sh
 ```
 
-4. Run two parachain nodes:
-
-Replace `12D3KooWN1ah2bFQSysEFnwZqcmcVpDDR8UedXyo6xfzV1zDNMNg` with Alice or Bob relay ID
-
-Run from this project root:
+Optional, full setup with Acala and Statemint
 ```
-./target/release/unique-collator --alice --collator --force-authoring --base-path ./tmp/parachain-alice --parachain-id 2000 --port 40333 --ws-port 9844  -- --execution wasm --chain ../polkadot/rococo-custom-4.json --port 30343 --ws-port 9977
-./target/release/unique-collator --bob --collator --force-authoring --parachain-id 2000 --base-path ./tmp/parachain/bob --port 40334 --ws-port 9845 -- --execution wasm --chain ../polkadot/rococo-custom-4.json --port 30344 --ws-port 9978 --bootnodes /ip4/127.0.0.1/tcp/50556/p2p/12D3KooWN1ah2bFQSysEFnwZqcmcVpDDR8UedXyo6xfzV1zDNMNg
+./launch-testnet-full.sh
 ```
-
-4. Reserve parachain ID as described here: https://substrate.dev/cumulus-workshop/#/en/2-relay-chain/2-reserve
 
-5. Register parachain in relay as described here: https://substrate.dev/cumulus-workshop/#/en/3-parachains/2-register
-
-
 ## Run Integration Tests
 
 1. Install all needed dependecies
@@ -202,40 +128,11 @@
 2. Run tests
 ```
 yarn test
-```
-
-## Benchmarks
-
-First of all, add rust toolchain and make it default.
-```bash
-rustup target add wasm32-unknown-unknown --toolchain nightly-2021-11-11
-```
-
-Then in "/node/src" run build command below
-```bash
-cargo +nightly-2021-11-11 build --release --features runtime-benchmarks
-```
-
-Run benchmark
-```bash
-target/release/unique-collator benchmark --chain dev --pallet "pallet_unique" --extrinsic "*" --repeat 1
 ```
-
-## UI custom types
-
-Moved to [runtime_types.json](./runtime_types.json).
-
-## Running Integration Tests
 
-See [tests/README.md](./tests/README.md).
 
 ## Code Formatting
 
-### Get formatter and linter settings into your branch (if you forked before they were introduced)
-```bash
-git cherry-pick -n 8ff77c21b0d30b2a4648fa35dbf61dfa9d3948a7
-```
-
 ### Apply formatting and clippy fixes
 ```bash
 cargo clippy
@@ -252,84 +149,57 @@
 cd tests && yarn eslint --ext .ts,.js src/
 ```
 
-## Re-Enabling Ink! Contracts
 
-Uncomment following lies:
-1. In node/rpc/Cargo.toml
-```
-# pallet-contracts-rpc = { version = "3.0", git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.9' }
-```
+## Karura token transfer
 
-2. In node/rpc/src/lib.rs
-```
-// C::Api: pallet_contracts_rpc::ContractsRuntimeApi<Block, AccountId, Balance, BlockNumber, Hash>,
-...
-// use pallet_contracts_rpc::{Contracts, ContractsApi};
-...
-// io.extend_with(ContractsApi::to_delegate(Contracts::new(client.clone())));
+To get started, you need to open inbound and outbound hrmp channels.
 
+### Next, we need to register our asset at Karura.
 ```
+assetRegistry -> registerForeignAsset(location, metadata)
+location:
+	V0(X2(Parent, Parachain(PARA_ID))) 
+metadata:
+	name         OPL
+	symbol       OPL
+	decimals     18
+minimalBalance	 1
+```
 
-3. In runtime/Cargo.toml
+### Next, we can send tokens from Opal to Karura:
 ```
-    # 'pallet-contracts/std',
-    # 'pallet-contracts-primitives/std',
-    # 'pallet-contracts-rpc-runtime-api/std',
-    # 'pallet-contract-helpers/std',
-...
-    # [dependencies.pallet-contracts]
-    # git = 'https://github.com/paritytech/substrate.git'
-    # default-features = false
-    # branch = 'polkadot-v0.9.9'
-    # version = '3.0.0'
+polkadotXcm -> reserveTransferAssets
+dest:
+	V0(X2(Parent, Parachain(<KARURA_PARA_ID>))) 
+beneficiary:
+	X1(AccountId(Any, <ACCOUNT>))
+assets:
+	V1(Concrete(0,Here), Fungible(<AMOUNT>))
+feeAssetItem: 
+	0	
+weightLimit:
+	<LIMIT>
+```	
 
-    # [dependencies.pallet-contracts-primitives]
-    # git = 'https://github.com/paritytech/substrate.git'
-    # default-features = false
-    # branch = 'polkadot-v0.9.9'
-    # version = '3.0.0'
+The result will be displayed in ChainState   
+tokens -> accounts	
 
-    # [dependencies.pallet-contracts-rpc-runtime-api]
-    # git = 'https://github.com/paritytech/substrate.git'
-    # default-features = false
-    # branch = 'polkadot-v0.9.9'
-    # version = '3.0.0'
-...
-    # pallet-contract-helpers = { path = '../pallets/contract-helpers', default-features = false, version = '0.1.0' }
+### To send tokens from Karura to Opal:
 ```
+xtokens -> transfer
 
-4. runtime/src/lib.rs
-```
-// use pallet_contracts::weights::WeightInfo;
-...
-// pub use pallet_timestamp::Call as TimestampCall;
-...
-// mod chain_extension;
-// use crate::chain_extension::{NFTExtension, Imbalance};
-...
-/*
-parameter_types! {
-	pub TombstoneDeposit: Balance = deposit(
-  ...
-}
-*/
-...
-//pallet_contract_helpers::ContractSponsorshipHandler<Runtime>,
-...
-// impl pallet_contract_helpers::Config for Runtime {}
-...
-// Contracts: pallet_contracts::{Pallet, Call, Storage, Event<T>},
-...
-// ContractHelpers: pallet_contract_helpers::{Pallet, Call, Storage},
-...
-//pallet_contract_helpers::ContractHelpersExtension<Runtime>,
-...
-/*
-	impl pallet_contracts_rpc_runtime_api::ContractsApi<Block, AccountId, Balance, BlockNumber, Hash>
-		for Runtime
-	{
-    ...
-	}
-*/
+currencyId:
+	ForeingAsset
+		<TOKEN_ID>
 
-```
+amount:
+		<AMOUNT>
+dest:
+	V1
+	(
+		Parents:1, 
+		X2(Parachain(<KARURA_PARA_ID>), AccountId(Any, <ACCOUNT>)
+	)
+destWeight:
+	<WEIGHT>
+```
\ No newline at end of file
modifiedclient/rpc/src/lib.rsdiffbeforeafterboth
--- a/client/rpc/src/lib.rs
+++ b/client/rpc/src/lib.rs
@@ -4,7 +4,7 @@
 use jsonrpc_core::{Error as RpcError, ErrorCode, Result};
 use jsonrpc_derive::rpc;
 use up_data_structs::{Collection, CollectionId, CollectionStats, TokenId};
-use sp_api::{BlockId, BlockT, ProvideRuntimeApi};
+use sp_api::{BlockId, BlockT, ProvideRuntimeApi, ApiExt};
 use sp_blockchain::HeaderBackend;
 use up_rpc::UniqueApi as UniqueRuntimeApi;
 
@@ -132,7 +132,10 @@
 }
 
 macro_rules! pass_method {
-	($method_name:ident($($name:ident: $ty:ty),* $(,)?) -> $result:ty $(=> $mapper:expr)?) => {
+	(
+		$method_name:ident($($name:ident: $ty:ty),* $(,)?) -> $result:ty $(=> $mapper:expr)?
+		$(; changed_in $ver:expr, $changed_method_name:ident ($($changed_name:expr), * $(,)?) => $fixer:expr)*
+	) => {
 		fn $method_name(
 			&self,
 			$(
@@ -142,8 +145,25 @@
 		) -> Result<$result> {
 			let api = self.client.runtime_api();
 			let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash));
+			let _api_version = if let Ok(Some(api_version)) =
+				api.api_version::<dyn UniqueRuntimeApi<Block, CrossAccountId, AccountId>>(&at)
+			{
+				api_version
+			} else {
+				// unreachable for our runtime
+				return Err(RpcError {
+					code: ErrorCode::InvalidParams,
+					message: "Api is not available".into(),
+					data: None,
+				})
+			};
 
-			let result = api.$method_name(&at, $($name),*).map_err(|e| RpcError {
+			let result = $(if _api_version < $ver {
+				api.$changed_method_name(&at, $($changed_name),*).map(|r| r.map($fixer))
+			} else)*
+			{ api.$method_name(&at, $($name),*) };
+
+			let result = result.map_err(|e| RpcError {
 				code: ErrorCode::ServerError(Error::RuntimeError.into()),
 				message: "Unable to query".into(),
 				data: Some(format!("{:?}", e).into()),
@@ -168,7 +188,10 @@
 {
 	pass_method!(account_tokens(collection: CollectionId, account: CrossAccountId) -> Vec<TokenId>);
 	pass_method!(token_exists(collection: CollectionId, token: TokenId) -> bool);
-	pass_method!(token_owner(collection: CollectionId, token: TokenId) -> Option<CrossAccountId>);
+	pass_method!(
+		token_owner(collection: CollectionId, token: TokenId) -> Option<CrossAccountId>;
+		changed_in 2, token_owner_before_version_2(collection, token) => |u| Some(u)
+	);
 	pass_method!(const_metadata(collection: CollectionId, token: TokenId) -> Vec<u8>);
 	pass_method!(variable_metadata(collection: CollectionId, token: TokenId) -> Vec<u8>);
 	pass_method!(collection_tokens(collection: CollectionId) -> u32);
addeddoc/hackusama_update.mddiffbeforeafterboth
--- /dev/null
+++ b/doc/hackusama_update.md
@@ -0,0 +1,15 @@
+## Hackusama Update
+
+During the Kusama Hackaphon the following changes were made:
+
+-   Enabled Smart Contracts Pallet
+-   Enabled integration between Smart Contracts and Unique Pallet (required special edition of RC4 Substrate version)
+-   Fixed misc. bugs in Unique Pallet
+-   Deployed Unique TestNet. Public node available at wss://unique.usetech.com, custom UI types - see below in this README.
+-   New Features:
+    -   Re-Fungible Token Mode
+    -   Off-Chain Schema to store token image URLs
+    -   Alternative economic model
+    -   Allow Lists and Public Mint Permission
+-   Use example: [SubstraPunks Game](https://github.com/usetech-llc/substrapunks), fully hosted on IPFS and Unique Testnet
+    Blockchain.
\ No newline at end of file
addeddoc/re-enable_ink_contracts.mddiffbeforeafterboth
--- /dev/null
+++ b/doc/re-enable_ink_contracts.md
@@ -0,0 +1,81 @@
+## Re-Enabling Ink! Contracts
+
+Uncomment following lies:
+1. In node/rpc/Cargo.toml
+```
+# pallet-contracts-rpc = { version = "3.0", git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.9' }
+```
+
+2. In node/rpc/src/lib.rs
+```
+// C::Api: pallet_contracts_rpc::ContractsRuntimeApi<Block, AccountId, Balance, BlockNumber, Hash>,
+...
+// use pallet_contracts_rpc::{Contracts, ContractsApi};
+...
+// io.extend_with(ContractsApi::to_delegate(Contracts::new(client.clone())));
+
+```
+
+3. In runtime/Cargo.toml
+```
+    # 'pallet-contracts/std',
+    # 'pallet-contracts-primitives/std',
+    # 'pallet-contracts-rpc-runtime-api/std',
+    # 'pallet-contract-helpers/std',
+...
+    # [dependencies.pallet-contracts]
+    # git = 'https://github.com/paritytech/substrate.git'
+    # default-features = false
+    # branch = 'polkadot-v0.9.9'
+    # version = '3.0.0'
+
+    # [dependencies.pallet-contracts-primitives]
+    # git = 'https://github.com/paritytech/substrate.git'
+    # default-features = false
+    # branch = 'polkadot-v0.9.9'
+    # version = '3.0.0'
+
+    # [dependencies.pallet-contracts-rpc-runtime-api]
+    # git = 'https://github.com/paritytech/substrate.git'
+    # default-features = false
+    # branch = 'polkadot-v0.9.9'
+    # version = '3.0.0'
+...
+    # pallet-contract-helpers = { path = '../pallets/contract-helpers', default-features = false, version = '0.1.0' }
+```
+
+4. runtime/src/lib.rs
+```
+// use pallet_contracts::weights::WeightInfo;
+...
+// pub use pallet_timestamp::Call as TimestampCall;
+...
+// mod chain_extension;
+// use crate::chain_extension::{NFTExtension, Imbalance};
+...
+/*
+parameter_types! {
+	pub TombstoneDeposit: Balance = deposit(
+  ...
+}
+*/
+...
+//pallet_contract_helpers::ContractSponsorshipHandler<Runtime>,
+...
+// impl pallet_contract_helpers::Config for Runtime {}
+...
+// Contracts: pallet_contracts::{Pallet, Call, Storage, Event<T>},
+...
+// ContractHelpers: pallet_contract_helpers::{Pallet, Call, Storage},
+...
+//pallet_contract_helpers::ContractHelpersExtension<Runtime>,
+...
+/*
+	impl pallet_contracts_rpc_runtime_api::ContractsApi<Block, AccountId, Balance, BlockNumber, Hash>
+		for Runtime
+	{
+    ...
+	}
+*/
+
+```
\ No newline at end of file
addedlaunch-config-full.jsondiffbeforeafterboth
--- /dev/null
+++ b/launch-config-full.json
@@ -0,0 +1,135 @@
+{
+    "relaychain": {
+        "bin": "../polkadot/target/release/polkadot",
+        "chain": "rococo-local",
+        "nodes": [
+            {
+                "name": "alice",
+                "wsPort": 9844,
+                "rpcPort": 9843,
+                "port": 30444,
+                "flags": [
+                    "-lparachain::candidate_validation=debug",
+                    "-lxcm=trace"
+                ]
+            },
+            {
+                "name": "bob",
+                "wsPort": 9855,
+                "rpcPort": 9854,
+                "port": 30555,
+                "flags": [
+                    "-lparachain::candidate_validation=debug",
+                    "-lxcm=trace"
+                ]
+            },
+            {
+                "name": "charlie",
+                "wsPort": 9866,
+                "rpcPort": 9865,
+                "port": 30666,
+                "flags": [
+                    "-lparachain::candidate_validation=debug"
+                ]
+            },
+            {
+                "name": "dave",
+                "wsPort": 9877,
+                "rpcPort": 9876,
+                "port": 30777,
+                "flags": [
+                    "-lparachain::candidate_validation=debug"
+                ]
+            },
+            {
+                "name": "eve",
+                "wsPort": 9888,
+                "rpcPort": 9887,
+                "port": 30888,
+                "flags": [
+                    "-lparachain::candidate_validation=debug"
+                ]
+            }
+        ],
+        "genesis": {
+            "runtime": {
+                "runtime_genesis_config": {
+                    "parachainsConfiguration": {
+                        "config": {
+                            "validation_upgrade_frequency": 1,
+                            "validation_upgrade_delay": 1
+                        }
+                    }
+                }
+            }
+        }
+    },
+    "parachains": [
+        {
+            "bin": "../unique-chain/target/release/unique-collator",
+            "id": "1000",
+            "balance": "1000000000000000000000000",
+            "nodes": [
+                {
+                    "port": 31200,
+                    "wsPort": 9944,
+                    "rpcPort": 9933,
+                    "name": "alice",
+                    "flags": [
+                        "--rpc-cors=all",
+                        "--unsafe-rpc-external",
+                        "--unsafe-ws-external",
+                        "-lxcm=trace"
+                    ]
+                },
+                {
+                    "port": 31201,
+                    "wsPort": 9945,
+                    "rpcPort": 9934,
+                    "name": "bob",
+                    "flags": [
+                        "--rpc-cors=all",
+                        "--unsafe-rpc-external",
+                        "--unsafe-ws-external",
+                        "-lxcm=trace"
+                    ]
+                }
+            ]
+        },
+        {
+            "bin": "../Acala/target/release/acala",
+            "id": "2000",
+            "chain":  "karura-dev",
+			"balance": "1000000000000000000000",
+			"nodes": [
+				{
+					"wsPort": 9946,
+					"port": 31202,
+					"name": "alice",
+                    "flags": [
+                        "--rpc-cors=all",
+                        "--unsafe-rpc-external",
+                        "--unsafe-ws-external",
+                        "-lxcm=trace"
+                    ]
+				}
+			]
+		}
+    ],
+    "simpleParachains": [],
+    "hrmpChannels": [
+        {
+            "sender": 2000,
+            "recipient": 1000,
+            "maxCapacity": 8,
+            "maxMessageSize": 512
+        },
+        {
+            "sender": 1000,
+            "recipient": 2000,
+            "maxCapacity": 8,
+            "maxMessageSize": 512
+        }
+    ],
+    "finalization": false
+}
deletedlaunch-config-westend.jsondiffbeforeafterboth
--- a/launch-config-westend.json
+++ /dev/null
@@ -1,141 +0,0 @@
-{
-    "relaychain": {
-        "bin": "../polkadot/target/release/polkadot",
-        "chain": "westend-local",
-        "nodes": [
-            {
-                "name": "alice",
-                "wsPort": 9844,
-                "rpcPort": 9843,
-                "port": 30444,
-                "flags": [
-                    "-lparachain::candidate_validation=debug"
-                ]
-            },
-            {
-                "name": "bob",
-                "wsPort": 9855,
-                "rpcPort": 9854,
-                "port": 30555,
-                "flags": [
-                    "-lparachain::candidate_validation=debug"
-                ]
-            },
-            {
-                "name": "charlie",
-                "wsPort": 9866,
-                "rpcPort": 9865,
-                "port": 30666,
-                "flags": [
-                    "-lparachain::candidate_validation=debug"
-                ]
-            },
-            {
-                "name": "dave",
-                "wsPort": 9877,
-                "rpcPort": 9876,
-                "port": 30777,
-                "flags": [
-                    "-lparachain::candidate_validation=debug"
-                ]
-            },
-            {
-                "name": "eve",
-                "wsPort": 9888,
-                "rpcPort": 9886,
-                "port": 30888,
-                "flags": [
-                    "-lparachain::candidate_validation=debug"
-                ]
-            },
-            {
-                "name": "ferdie",
-                "wsPort": 9897,
-                "rpcPort": 9896,
-                "port": 30999,
-                "flags": [
-                    "-lparachain::candidate_validation=debug"
-                ]
-            }
-        ],
-        "genesis": {
-            "runtime": {
-                "runtime_genesis_config": {
-                    "parachainsConfiguration": {
-                        "config": {
-                            "validation_upgrade_frequency": 1,
-                            "validation_upgrade_delay": 1
-                        }
-                    }
-                }
-            }
-        }
-    },
-    "parachains": [
-        {
-            "bin": "../unique-chain/target/release/unique-collator",
-            "chain": "westend-local",
-            "balance": "1000000000000000000000000",
-            "nodes": [
-                {
-                    "port": 31200,
-                    "wsPort": 9944,
-                    "rpcPort": 9933,
-                    "name": "alice",
-                    "flags": [
-                        "--rpc-cors=all",
-                        "--unsafe-rpc-external",
-                        "--unsafe-ws-external"
-                    ]
-                },
-                {
-                    "port": 31201,
-                    "wsPort": 9945,
-                    "rpcPort": 9934,
-                    "name": "bob",
-                    "flags": [
-                        "--rpc-cors=all",
-                        "--unsafe-rpc-external",
-                        "--unsafe-ws-external"
-                    ]
-                },
-                {
-                    "port": 31202,
-                    "wsPort": 9946,
-                    "rpcPort": 9935,
-                    "name": "charlie",
-                    "flags": [
-                        "--rpc-cors=all",
-                        "--unsafe-rpc-external",
-                        "--unsafe-ws-external"
-                    ]
-                },
-                {
-                    "port": 31203,
-                    "wsPort": 9947,
-                    "rpcPort": 9936,
-                    "name": "dave",
-                    "flags": [
-                        "--rpc-cors=all",
-                        "--unsafe-rpc-external",
-                        "--unsafe-ws-external"
-                    ]
-                },
-                {
-                    "port": 31204,
-                    "wsPort": 9948,
-                    "rpcPort": 9937,
-                    "name": "eve",
-                    "flags": [
-                        "--rpc-cors=all",
-                        "--unsafe-rpc-external",
-                        "--unsafe-ws-external"
-                    ]
-                }
-            ]
-        }
-    ],
-    "simpleParachains": [],
-    "hrmpChannels": [],
-    "finalization": false
-}
modifiedlaunch-config.jsondiffbeforeafterboth
--- a/launch-config.json
+++ b/launch-config.json
@@ -9,7 +9,8 @@
                 "rpcPort": 9843,
                 "port": 30444,
                 "flags": [
-                    "-lparachain::candidate_validation=debug"
+                    "-lparachain::candidate_validation=debug",
+                    "-lxcm=trace"
                 ]
             },
             {
@@ -18,7 +19,8 @@
                 "rpcPort": 9854,
                 "port": 30555,
                 "flags": [
-                    "-lparachain::candidate_validation=debug"
+                    "-lparachain::candidate_validation=debug",
+                    "-lxcm=trace"
                 ]
             },
             {
@@ -38,6 +40,15 @@
                 "flags": [
                     "-lparachain::candidate_validation=debug"
                 ]
+            },
+            {
+                "name": "eve",
+                "wsPort": 9888,
+                "rpcPort": 9887,
+                "port": 30888,
+                "flags": [
+                    "-lparachain::candidate_validation=debug"
+                ]
             }
         ],
         "genesis": {
@@ -67,7 +78,8 @@
                     "flags": [
                         "--rpc-cors=all",
                         "--unsafe-rpc-external",
-                        "--unsafe-ws-external"
+                        "--unsafe-ws-external",
+                        "-lxcm=trace"
                     ]
                 },
                 {
@@ -78,7 +90,8 @@
                     "flags": [
                         "--rpc-cors=all",
                         "--unsafe-rpc-external",
-                        "--unsafe-ws-external"
+                        "--unsafe-ws-external",
+                        "-lxcm=trace"
                     ]
                 }
             ]
deletedlaunch-test-env.shdiffbeforeafterboth
--- a/launch-test-env.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-cp launch-config.json ../polkadot-launch/launch-config.json
-
-cd ../polkadot-launch
-yarn install
-yarn build
-yarn start launch-config.json
addedlaunch-testnet-full.shdiffbeforeafterboth
--- /dev/null
+++ b/launch-testnet-full.sh
@@ -0,0 +1,6 @@
+cp launch-config-full.json ../polkadot-launch/launch-config-full.json
+
+cd ../polkadot-launch
+yarn install
+yarn build
+yarn start launch-config-full.json
addedlaunch-testnet.shdiffbeforeafterboth
--- /dev/null
+++ b/launch-testnet.sh
@@ -0,0 +1,6 @@
+cp launch-config.json ../polkadot-launch/launch-config.json
+
+cd ../polkadot-launch
+yarn install
+yarn build
+yarn start launch-config.json
modifiednode/cli/Cargo.tomldiffbeforeafterboth
--- a/node/cli/Cargo.toml
+++ b/node/cli/Cargo.toml
@@ -257,7 +257,7 @@
 license = 'All Rights Reserved'
 name = 'unique-node'
 repository = 'https://github.com/UniqueNetwork/unique-chain'
-version = '0.9.14'
+version = '0.9.16'
 
 [[bin]]
 name = 'unique-collator'
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -14,7 +14,10 @@
 use up_data_structs::{
 	COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, ExistenceRequirement,
 	MAX_TOKEN_PREFIX_LENGTH, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, Pays, PostDispatchInfo,
-	TokenId, Weight, WithdrawReasons, CollectionStats, CustomDataLimit,
+	TokenId, Weight, WithdrawReasons, CollectionStats, MAX_TOKEN_OWNERSHIP, CollectionMode,
+	NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+	REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,
+	CustomDataLimit, CreateCollectionData, SponsorshipState,
 };
 pub use pallet::*;
 use sp_core::H160;
@@ -285,6 +288,10 @@
 		TokenVariableDataLimitExceeded,
 		/// Exceeded max admin count
 		CollectionAdminCountExceeded,
+		/// Collection limit bounds per collection exceeded
+		CollectionLimitBoundsExceeded,
+		/// Tried to enable permissions which are only permitted to be disabled
+		OwnerPermissionsCantBeReverted,
 
 		/// Collection settings not allowing items transferring
 		TransferNotAllowed,
@@ -300,7 +307,7 @@
 		/// Item balance not enough.
 		TokenValueTooLow,
 		/// Requested value more than approved.
-		TokenValueNotEnough,
+		ApprovedValueTooLow,
 		/// Tried to approve more than owned
 		CantApproveMoreThanOwned,
 
@@ -395,7 +402,10 @@
 }
 
 impl<T: Config> Pallet<T> {
-	pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
+	pub fn init_collection(
+		owner: T::AccountId,
+		data: CreateCollectionData<T::AccountId>,
+	) -> Result<CollectionId, DispatchError> {
 		{
 			ensure!(
 				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,
@@ -418,6 +428,29 @@
 
 		// =========
 
+		let collection = Collection {
+			owner: owner.clone(),
+			name: data.name,
+			mode: data.mode.clone(),
+			mint_mode: false,
+			access: data.access.unwrap_or_default(),
+			description: data.description,
+			token_prefix: data.token_prefix,
+			offchain_schema: data.offchain_schema,
+			schema_version: data.schema_version.unwrap_or_default(),
+			sponsorship: data
+				.pending_sponsor
+				.map(SponsorshipState::Unconfirmed)
+				.unwrap_or_default(),
+			variable_on_chain_schema: data.variable_on_chain_schema,
+			const_on_chain_schema: data.const_on_chain_schema,
+			limits: data
+				.limits
+				.map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))
+				.unwrap_or_else(|| Ok(CollectionLimits::default()))?,
+			meta_update_permission: data.meta_update_permission.unwrap_or_default(),
+		};
+
 		// Take a (non-refundable) deposit of collection creation
 		{
 			let mut imbalance =
@@ -429,7 +462,7 @@
 				),
 			);
 			<T as Config>::Currency::settle(
-				&data.owner,
+				&owner,
 				imbalance,
 				WithdrawReasons::TRANSFER,
 				ExistenceRequirement::KeepAlive,
@@ -438,12 +471,8 @@
 		}
 
 		<CreatedCollectionCount<T>>::put(created_count);
-		<Pallet<T>>::deposit_event(Event::CollectionCreated(
-			id,
-			data.mode.id(),
-			data.owner.clone(),
-		));
-		<CollectionById<T>>::insert(id, data);
+		<Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));
+		<CollectionById<T>>::insert(id, collection);
 		Ok(id)
 	}
 
@@ -527,6 +556,61 @@
 
 		Ok(())
 	}
+
+	pub fn clamp_limits(
+		mode: CollectionMode,
+		old_limit: &CollectionLimits,
+		mut new_limit: CollectionLimits,
+	) -> Result<CollectionLimits, DispatchError> {
+		macro_rules! limit_default {
+				($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{
+					$(
+						if let Some($new) = $new.$field {
+							let $old = $old.$field($($arg)?);
+							let _ = $new;
+							let _ = $old;
+							$check
+						} else {
+							$new.$field = $old.$field
+						}
+					)*
+				}};
+			}
+
+		limit_default!(old_limit, new_limit,
+			account_token_ownership_limit => ensure!(
+				new_limit <= MAX_TOKEN_OWNERSHIP,
+				<Error<T>>::CollectionLimitBoundsExceeded,
+			),
+			sponsor_transfer_timeout(match mode {
+				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,
+				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+			}) => ensure!(
+				new_limit <= MAX_SPONSOR_TIMEOUT,
+				<Error<T>>::CollectionLimitBoundsExceeded,
+			),
+			sponsored_data_size => ensure!(
+				new_limit <= CUSTOM_DATA_LIMIT,
+				<Error<T>>::CollectionLimitBoundsExceeded,
+			),
+			token_limit => ensure!(
+				old_limit >= new_limit && new_limit > 0,
+				<Error<T>>::CollectionTokenLimitExceeded
+			),
+			owner_can_transfer => ensure!(
+				old_limit || !new_limit,
+				<Error<T>>::OwnerPermissionsCantBeReverted,
+			),
+			owner_can_destroy => ensure!(
+				old_limit || !new_limit,
+				<Error<T>>::OwnerPermissionsCantBeReverted,
+			),
+			sponsored_data_rate_limit => {},
+			transfers_enabled => {},
+		);
+		Ok(new_limit)
+	}
 }
 
 #[macro_export]
modifiedpallets/fungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -2,7 +2,7 @@
 
 use core::ops::Deref;
 use frame_support::{ensure};
-use up_data_structs::{AccessMode, Collection, CollectionId, TokenId};
+use up_data_structs::{AccessMode, CollectionId, TokenId, CreateCollectionData};
 use pallet_common::{
 	Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, account::CrossAccountId,
 };
@@ -100,8 +100,11 @@
 }
 
 impl<T: Config> Pallet<T> {
-	pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
-		<PalletCommon<T>>::init_collection(data)
+	pub fn init_collection(
+		owner: T::AccountId,
+		data: CreateCollectionData<T::AccountId>,
+	) -> Result<CollectionId, DispatchError> {
+		<PalletCommon<T>>::init_collection(owner, data)
 	}
 	pub fn destroy_collection(
 		collection: FungibleHandle<T>,
@@ -343,7 +346,7 @@
 		if allowance.is_none() {
 			ensure!(
 				collection.ignores_allowance(spender),
-				<CommonError<T>>::TokenValueNotEnough
+				<CommonError<T>>::ApprovedValueTooLow
 			);
 		}
 
@@ -374,7 +377,7 @@
 		if allowance.is_none() {
 			ensure!(
 				collection.ignores_allowance(spender),
-				<CommonError<T>>::TokenValueNotEnough
+				<CommonError<T>>::ApprovedValueTooLow
 			);
 		}
 
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -2,7 +2,7 @@
 
 use erc::ERC721Events;
 use frame_support::{BoundedVec, ensure};
-use up_data_structs::{AccessMode, Collection, CollectionId, CustomDataLimit, TokenId};
+use up_data_structs::{AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData};
 use pallet_common::{
 	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, account::CrossAccountId,
 };
@@ -140,8 +140,11 @@
 
 // unchecked calls skips any permission checks
 impl<T: Config> Pallet<T> {
-	pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
-		<PalletCommon<T>>::init_collection(data)
+	pub fn init_collection(
+		owner: T::AccountId,
+		data: CreateCollectionData<T::AccountId>,
+	) -> Result<CollectionId, DispatchError> {
+		<PalletCommon<T>>::init_collection(owner, data)
 	}
 	pub fn destroy_collection(
 		collection: NonfungibleHandle<T>,
@@ -492,7 +495,7 @@
 		if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {
 			ensure!(
 				collection.ignores_allowance(spender),
-				<CommonError<T>>::TokenValueNotEnough
+				<CommonError<T>>::ApprovedValueTooLow
 			);
 		}
 
@@ -520,7 +523,7 @@
 		if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {
 			ensure!(
 				collection.ignores_allowance(spender),
-				<CommonError<T>>::TokenValueNotEnough
+				<CommonError<T>>::ApprovedValueTooLow
 			);
 		}
 
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -2,7 +2,7 @@
 
 use frame_support::{ensure, BoundedVec};
 use up_data_structs::{
-	AccessMode, Collection, CollectionId, CustomDataLimit, MAX_REFUNGIBLE_PIECES, TokenId,
+	AccessMode, CollectionId, CustomDataLimit, MAX_REFUNGIBLE_PIECES, TokenId, CreateCollectionData,
 };
 use pallet_common::{
 	Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, account::CrossAccountId,
@@ -155,8 +155,11 @@
 
 // unchecked calls skips any permission checks
 impl<T: Config> Pallet<T> {
-	pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
-		<PalletCommon<T>>::init_collection(data)
+	pub fn init_collection(
+		owner: T::AccountId,
+		data: CreateCollectionData<T::AccountId>,
+	) -> Result<CollectionId, DispatchError> {
+		<PalletCommon<T>>::init_collection(owner, data)
 	}
 	pub fn destroy_collection(
 		collection: RefungibleHandle<T>,
@@ -528,7 +531,7 @@
 		if allowance.is_none() {
 			ensure!(
 				collection.ignores_allowance(spender),
-				<CommonError<T>>::TokenValueNotEnough
+				<CommonError<T>>::ApprovedValueTooLow
 			);
 		}
 
@@ -561,7 +564,7 @@
 		if allowance.is_none() {
 			ensure!(
 				collection.ignores_allowance(spender),
-				<CommonError<T>>::TokenValueNotEnough
+				<CommonError<T>>::ApprovedValueTooLow
 			);
 		}
 
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -36,13 +36,11 @@
 use frame_system::{self as system, ensure_signed};
 use sp_runtime::{sp_std::prelude::Vec};
 use up_data_structs::{
-	MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, CUSTOM_DATA_LIMIT,
-	VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT,
-	FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
-	NFT_SPONSOR_TRANSFER_TIMEOUT, MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH,
-	MAX_TOKEN_PREFIX_LENGTH, AccessMode, Collection, CreateItemData, CollectionLimits,
-	CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,
-	CustomDataLimit,
+	MAX_DECIMAL_POINTS, VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT,
+	OFFCHAIN_SCHEMA_LIMIT, MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH,
+	MAX_TOKEN_PREFIX_LENGTH, AccessMode, CreateItemData, CollectionLimits, CollectionId,
+	CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,
+	CreateCollectionData, CustomDataLimit,
 };
 use pallet_common::{
 	account::CrossAccountId, CollectionHandle, Pallet as PalletCommon, Error as CommonError,
@@ -84,10 +82,6 @@
 		ConfirmUnsetSponsorFail,
 		/// Length of items properties must be greater than 0.
 		EmptyArgument,
-		/// Collection limit bounds per collection exceeded
-		CollectionLimitBoundsExceeded,
-		/// Tried to enable permissions which are only permitted to be disabled
-		OwnerPermissionsCantBeReverted,
 	}
 }
 
@@ -321,42 +315,39 @@
 		// returns collection ID
 		#[weight = <SelfWeightOf<T>>::create_collection()]
 		#[transactional]
+		#[deprecated]
 		pub fn create_collection(origin,
 								 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,
 								 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,
 								 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,
-								 mode: CollectionMode) -> DispatchResult {
-
-			// Anyone can create a collection
-			let who = ensure_signed(origin)?;
-
-			// Create new collection
-			let new_collection = Collection {
-				owner: who,
+								 mode: CollectionMode) -> DispatchResult  {
+			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {
 				name: collection_name,
-				mode: mode.clone(),
-				mint_mode: false,
-				access: AccessMode::Normal,
 				description: collection_description,
 				token_prefix,
-				offchain_schema: BoundedVec::default(),
-				schema_version: SchemaVersion::ImageURL,
-				sponsorship: SponsorshipState::Disabled,
-				variable_on_chain_schema: BoundedVec::default(),
-				const_on_chain_schema: BoundedVec::default(),
-				limits: Default::default(),
-				meta_update_permission: Default::default(),
+				mode,
+				..Default::default()
 			};
+			Self::create_collection_ex(origin, data)
+		}
 
-			let _id = match mode {
-				CollectionMode::NFT => {<PalletNonfungible<T>>::init_collection(new_collection)?},
+		/// This method creates a collection
+		///
+		/// Prefer it to deprecated [`created_collection`] method
+		#[weight = <SelfWeightOf<T>>::create_collection()]
+		#[transactional]
+		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {
+			let owner = ensure_signed(origin)?;
+
+			let _id = match data.mode {
+				CollectionMode::NFT => {<PalletNonfungible<T>>::init_collection(owner, data)?},
 				CollectionMode::Fungible(decimal_points) => {
 					// check params
 					ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);
-					<PalletFungible<T>>::init_collection(new_collection)?
+					<PalletFungible<T>>::init_collection(owner, data)?
 				}
 				CollectionMode::ReFungible => {
-					<PalletRefungible<T>>::init_collection(new_collection)?
+					<PalletRefungible<T>>::init_collection(owner, data)?
 				}
 			};
 
@@ -1093,61 +1084,12 @@
 			collection_id: CollectionId,
 			new_limit: CollectionLimits,
 		) -> DispatchResult {
-			let mut new_limit = new_limit;
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
 			target_collection.check_is_owner(&sender)?;
 			let old_limit = &target_collection.limits;
 
-			macro_rules! limit_default {
-				($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{
-					$(
-						if let Some($new) = $new.$field {
-							let $old = $old.$field($($arg)?);
-							let _ = $new;
-							let _ = $old;
-							$check
-						} else {
-							$new.$field = $old.$field
-						}
-					)*
-				}};
-			}
-
-			limit_default!(old_limit, new_limit,
-				account_token_ownership_limit => ensure!(
-					new_limit <= MAX_TOKEN_OWNERSHIP,
-					<Error<T>>::CollectionLimitBoundsExceeded,
-				),
-				sponsor_transfer_timeout(match target_collection.mode {
-					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,
-					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
-					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
-				}) => ensure!(
-					new_limit <= MAX_SPONSOR_TIMEOUT,
-					<Error<T>>::CollectionLimitBoundsExceeded,
-				),
-				sponsored_data_size => ensure!(
-					new_limit <= CUSTOM_DATA_LIMIT,
-					<Error<T>>::CollectionLimitBoundsExceeded,
-				),
-				token_limit => ensure!(
-					old_limit >= new_limit && new_limit > 0,
-					<CommonError<T>>::CollectionTokenLimitExceeded
-				),
-				owner_can_transfer => ensure!(
-					old_limit || !new_limit,
-					<Error<T>>::OwnerPermissionsCantBeReverted,
-				),
-				owner_can_destroy => ensure!(
-					old_limit || !new_limit,
-					<Error<T>>::OwnerPermissionsCantBeReverted,
-				),
-				sponsored_data_rate_limit => {},
-				transfers_enabled => {},
-			);
-
-			target_collection.limits = new_limit;
+			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;
 
 			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(
 				collection_id
modifiedpallets/unique/src/tests.rsdiffbeforeafterboth
--- a/pallets/unique/src/tests.rs
+++ b/pallets/unique/src/tests.rs
@@ -5,7 +5,7 @@
 use up_data_structs::{
 	COLLECTION_NUMBER_LIMIT, CollectionId, CreateItemData, CreateFungibleData, CreateNftData,
 	CreateReFungibleData, MAX_DECIMAL_POINTS, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission,
-	TokenId,
+	TokenId, MAX_TOKEN_OWNERSHIP,
 };
 use frame_support::{assert_noop, assert_ok};
 use sp_std::convert::TryInto;
@@ -601,7 +601,7 @@
 				1
 			)
 			.map_err(|e| e.error),
-			CommonError::<Test>::TokenValueNotEnough
+			CommonError::<Test>::ApprovedValueTooLow
 		);
 
 		// do approve
@@ -916,7 +916,7 @@
 				4
 			)
 			.map_err(|e| e.error),
-			CommonError::<Test>::TokenValueNotEnough
+			CommonError::<Test>::ApprovedValueTooLow
 		);
 	});
 }
modifiedprimitives/data-structs/src/lib.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -263,6 +263,31 @@
 	pub meta_update_permission: MetaUpdatePermission,
 }
 
+#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, Debug, Derivative, MaxEncodedLen)]
+#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
+#[derivative(Default(bound = ""))]
+pub struct CreateCollectionData<AccountId> {
+	#[derivative(Default(value = "CollectionMode::NFT"))]
+	pub mode: CollectionMode,
+	pub access: Option<AccessMode>,
+	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	pub name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,
+	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	pub description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,
+	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	pub token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,
+	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	pub offchain_schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,
+	pub schema_version: Option<SchemaVersion>,
+	pub pending_sponsor: Option<AccountId>,
+	pub limits: Option<CollectionLimits>,
+	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	pub variable_on_chain_schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>,
+	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	pub const_on_chain_schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>,
+	pub meta_update_permission: Option<MetaUpdatePermission>,
+}
+
 #[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo)]
 #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
 pub struct NftItemType<AccountId> {
modifiedprimitives/rpc/src/lib.rsdiffbeforeafterboth
--- a/primitives/rpc/src/lib.rs
+++ b/primitives/rpc/src/lib.rs
@@ -9,10 +9,14 @@
 type Result<T> = core::result::Result<T, DispatchError>;
 
 sp_api::decl_runtime_apis! {
+	#[api_version(2)]
 	pub trait UniqueApi<CrossAccountId, AccountId> where
 		AccountId: Decode,
 		CrossAccountId: pallet_common::account::CrossAccountId<AccountId>,
 	{
+		#[changed_in(2)]
+		fn token_owner(collection: CollectionId, token: TokenId) -> Result<CrossAccountId>;
+
 		fn account_tokens(collection: CollectionId, account: CrossAccountId) -> Result<Vec<TokenId>>;
 		fn token_exists(collection: CollectionId, token: TokenId) -> Result<bool>;
 
modifiedruntime/src/lib.rsdiffbeforeafterboth
--- a/runtime/src/lib.rs
+++ b/runtime/src/lib.rs
@@ -23,7 +23,8 @@
 use sp_runtime::{
 	Permill, Perbill, Percent, create_runtime_str, generic, impl_opaque_keys,
 	traits::{
-		AccountIdLookup, BlakeTwo256, Block as BlockT, IdentifyAccount, Verify, AccountIdConversion,
+		AccountIdLookup, BlakeTwo256, Block as BlockT, IdentifyAccount, Verify,
+		AccountIdConversion, Zero,
 	},
 	transaction_validity::{TransactionSource, TransactionValidity},
 	ApplyExtrinsicResult, MultiSignature, RuntimeAppPublic,
@@ -45,8 +46,9 @@
 	dispatch::DispatchResult,
 	PalletId, parameter_types, StorageValue, ConsensusEngineId,
 	traits::{
-		Everything, Currency, ExistenceRequirement, Get, IsInVec, KeyOwnerProofSystem,
-		LockIdentifier, OnUnbalanced, Randomness, FindAuthor,
+		tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT, Everything,
+		Currency, ExistenceRequirement, Get, IsInVec, KeyOwnerProofSystem, LockIdentifier,
+		OnUnbalanced, Randomness, FindAuthor,
 	},
 	weights::{
 		constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},
@@ -69,8 +71,9 @@
 use pallet_evm::{Account as EVMAccount, FeeCalculator, GasWeightMapping, OnMethodCall};
 use fp_rpc::TransactionStatus;
 use sp_runtime::{
-	traits::{BlockNumberProvider, Dispatchable, PostDispatchInfoOf},
+	traits::{BlockNumberProvider, Dispatchable, PostDispatchInfoOf, Saturating},
 	transaction_validity::TransactionValidityError,
+	SaturatedConversion,
 };
 
 // pub use pallet_timestamp::Call as TimestampCall;
@@ -82,13 +85,24 @@
 use xcm::v1::{BodyId, Junction::*, MultiLocation, NetworkId, Junctions::*};
 use xcm_builder::{
 	AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter,
-	EnsureXcmOrigin, FixedWeightBounds, IsConcrete, LocationInverter, NativeAsset,
-	ParentAsSuperuser, ParentIsDefault, RelayChainAsNative, SiblingParachainAsNative,
-	SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32,
-	SovereignSignedViaLocation, TakeWeightCredit, UsingComponents,
+	EnsureXcmOrigin, FixedWeightBounds, LocationInverter, NativeAsset, ParentAsSuperuser,
+	ParentIsDefault, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia,
+	SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit,
 };
-use xcm_executor::{Config, XcmExecutor};
+use xcm_executor::{Config, XcmExecutor, Assets};
+use sp_std::{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;
+use sp_runtime::traits::CheckedConversion;
+
 // mod chain_extension;
 // use crate::chain_extension::{NFTExtension, Imbalance};
 
@@ -146,10 +160,10 @@
 	spec_name: create_runtime_str!("opal"),
 	impl_name: create_runtime_str!("opal"),
 	authoring_version: 1,
-	spec_version: 914000,
-	impl_version: 1,
+	spec_version: 916001,
+	impl_version: 0,
 	apis: RUNTIME_API_VERSIONS,
-	transaction_version: 0,
+	transaction_version: 1,
 	state_version: 0,
 };
 
@@ -616,12 +630,22 @@
 	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:
-	IsConcrete<RelayLocation>,
+	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):
@@ -677,6 +701,88 @@
 	// ^^^ 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::calc(&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::calc(&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;
@@ -689,7 +795,13 @@
 	type LocationInverter = LocationInverter<Ancestry>;
 	type Barrier = Barrier;
 	type Weigher = FixedWeightBounds<UnitWeightCost, Call, MaxInstructions>;
-	type Trader = UsingComponents<IdentityFee<Balance>, RelayLocation, AccountId, Balances, ()>;
+	type Trader = UsingOnlySelfCurrencyComponents<
+		IdentityFee<Balance>,
+		RelayLocation,
+		AccountId,
+		Balances,
+		(),
+	>;
 	type ResponseHandler = (); // Don't handle responses for now.
 	type SubscriptionService = PolkadotXcm;
 
modifiedtests/package.jsondiffbeforeafterboth
--- a/tests/package.json
+++ b/tests/package.json
@@ -64,12 +64,14 @@
     "testOverflow": "mocha --timeout 9999999 -r ts-node/register ./**/overflow.test.ts",
     "testSetVariableMetadataSponsoringRateLimit": "mocha --timeout 9999999 -r ts-node/register ./**/setVariableMetadataSponsoringRateLimit.test.ts",
     "testInflation": "mocha --timeout 9999999 -r ts-node/register ./**/inflation.test.ts",
+    "testXcmTransfer": "mocha --timeout 9999999 -r ts-node/register ./**/xcmTransfer.test.ts",
     "testPalletPresence": "mocha --timeout 9999999 -r ts-node/register ./**/pallet-presence.test.ts",
     "testBlockProduction": "mocha --timeout 9999999 -r ts-node/register ./**/block-production.test.ts",
     "testEnableDisableTransfers": "mocha --timeout 9999999 -r ts-node/register ./**/enableDisableTransfer.test.ts",
+    "polkadot-types-fetch-metadata": "curl -H 'Content-Type: application/json' -d '{\"id\":\"1\", \"jsonrpc\":\"2.0\", \"method\": \"state_getMetadata\", \"params\":[]}' http://localhost:9933 > src/interfaces/metadata.json",
     "polkadot-types-from-defs": "ts-node ./node_modules/.bin/polkadot-types-from-defs --input src/interfaces/ --package .",
-    "polkadot-types-from-chain": "ts-node ./node_modules/.bin/polkadot-types-from-chain --endpoint ws://localhost:9944 --output src/interfaces/ --package .",
-    "polkadot-types": "yarn polkadot-types-from-defs && yarn polkadot-types-from-chain"
+    "polkadot-types-from-chain": "ts-node ./node_modules/.bin/polkadot-types-from-chain --endpoint src/interfaces/metadata.json --output src/interfaces/ --package .",
+    "polkadot-types": "yarn polkadot-types-fetch-metadata && yarn polkadot-types-from-defs && yarn polkadot-types-from-chain"
   },
   "author": "",
   "license": "SEE LICENSE IN ../LICENSE",
modifiedtests/src/check-event/createCollectionEvent.test.tsdiffbeforeafterboth
--- a/tests/src/check-event/createCollectionEvent.test.ts
+++ b/tests/src/check-event/createCollectionEvent.test.ts
@@ -27,7 +27,7 @@
   });
   it('Check event from createCollection(): ', async () => {
     await usingApi(async (api: ApiPromise) => {
-      const tx = api.tx.unique.createCollection([0x31], [0x32], '0x33', 'NFT');
+      const tx = api.tx.unique.createCollectionEx({name: [0x31], description: [0x32], tokenPrefix: '0x33', mode: 'NFT'});
       const events = await submitTransactionAsync(alice, tx);
       const msg = JSON.stringify(uniqueEventMessage(events));
       expect(msg).to.be.contain(checkSection);
modifiedtests/src/createCollection.test.tsdiffbeforeafterboth
--- a/tests/src/createCollection.test.ts
+++ b/tests/src/createCollection.test.ts
@@ -3,12 +3,11 @@
 // file 'LICENSE', which is part of this source code package.
 //
 
-import chai from 'chai';
-import chaiAsPromised from 'chai-as-promised';
-import {createCollectionExpectFailure, createCollectionExpectSuccess} from './util/helpers';
+import {expect} from 'chai';
+import privateKey from './substrate/privateKey';
+import usingApi, {executeTransaction, submitTransactionAsync} from './substrate/substrate-api';
+import {createCollectionExpectFailure, createCollectionExpectSuccess, getCreateCollectionResult, getDetailedCollectionInfo} from './util/helpers';
 
-chai.use(chaiAsPromised);
-
 describe('integration test: ext. createCollection():', () => {
   it('Create new NFT collection', async () => {
     await createCollectionExpectSuccess({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'NFT'}});
@@ -28,6 +27,45 @@
   it('Create new ReFungible collection', async () => {
     await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
   });
+  it('Create new collection with extra fields', async () => {
+    await usingApi(async api => {
+      const alice = privateKey('//Alice');
+      const bob = privateKey('//Bob');
+      const tx = api.tx.unique.createCollectionEx({
+        mode: {Fungible: 8},
+        access: 'AllowList',
+        name: [1],
+        description: [2],
+        tokenPrefix: '0x000000',
+        offchainSchema: '0x111111',
+        schemaVersion: 'Unique',
+        pendingSponsor: bob.address,
+        limits: {
+          accountTokenOwnershipLimit: 3,
+        },
+        variableOnChainSchema: '0x222222',
+        constOnChainSchema: '0x333333',
+        metaUpdatePermission: 'Admin',
+      });
+      const events = await submitTransactionAsync(alice, tx);
+      const result = getCreateCollectionResult(events);
+
+      const collection = (await getDetailedCollectionInfo(api, result.collectionId))!;
+      expect(collection.owner.toString()).to.equal(alice.address);
+      expect(collection.mode.asFungible.toNumber()).to.equal(8);
+      expect(collection.access.isAllowList).to.be.true;
+      expect(collection.name.map(v => v.toNumber())).to.deep.equal([1]);
+      expect(collection.description.map(v => v.toNumber())).to.deep.equal([2]);
+      expect(collection.tokenPrefix.toString()).to.equal('0x000000');
+      expect(collection.offchainSchema.toString()).to.equal('0x111111');
+      expect(collection.schemaVersion.isUnique).to.be.true;
+      expect(collection.sponsorship.asUnconfirmed.toString()).to.equal(bob.address);
+      expect(collection.limits.accountTokenOwnershipLimit.unwrap().toNumber()).to.equal(3);
+      expect(collection.variableOnChainSchema.toString()).to.equal('0x222222');
+      expect(collection.constOnChainSchema.toString()).to.equal('0x333333');
+      expect(collection.metaUpdatePermission.isAdmin).to.be.true;
+    });
+  });
 });
 
 describe('(!negative test!) integration test: ext. createCollection():', () => {
@@ -40,4 +78,11 @@
   it('(!negative test!) create new NFT collection whith incorrect data (token_prefix)', async () => {
     await createCollectionExpectFailure({tokenPrefix: 'A'.repeat(17), mode: {type: 'NFT'}});
   });
+  it('fails when bad limits are set', async () => {
+    await usingApi(async api => {
+      const alice = privateKey('//Alice');
+      const tx = api.tx.unique.createCollectionEx({mode: 'NFT', limits: {tokenLimit: 0}});
+      await expect(executeTransaction(api, alice, tx)).to.be.rejectedWith(/^common.CollectionTokenLimitExceeded$/);
+    });
+  });
 });
addedtests/src/interfaces/.gitignorediffbeforeafterboth
--- /dev/null
+++ b/tests/src/interfaces/.gitignore
@@ -0,0 +1 @@
+metadata.json
modifiedtests/src/interfaces/augment-api-errors.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-errors.ts
+++ b/tests/src/interfaces/augment-api-errors.ts
@@ -69,6 +69,10 @@
        **/
       CollectionDescriptionLimitExceeded: AugmentedError<ApiType>;
       /**
+       * Collection limit bounds per collection exceeded
+       **/
+      CollectionLimitBoundsExceeded: AugmentedError<ApiType>;
+      /**
        * Collection name can not be longer than 63 char.
        **/
       CollectionNameLimitExceeded: AugmentedError<ApiType>;
@@ -97,6 +101,10 @@
        **/
       NoPermission: AugmentedError<ApiType>;
       /**
+       * Tried to enable permissions which are only permitted to be disabled
+       **/
+      OwnerPermissionsCantBeReverted: AugmentedError<ApiType>;
+      /**
        * Collection is not in mint mode.
        **/
       PublicMintingNotAllowed: AugmentedError<ApiType>;
@@ -107,7 +115,7 @@
       /**
        * Requested value more than approved.
        **/
-      TokenValueNotEnough: AugmentedError<ApiType>;
+      ApprovedValueTooLow: AugmentedError<ApiType>;
       /**
        * Item balance not enough.
        **/
@@ -436,10 +444,6 @@
        * Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.
        **/
       CollectionDecimalPointLimitExceeded: AugmentedError<ApiType>;
-      /**
-       * Collection limit bounds per collection exceeded
-       **/
-      CollectionLimitBoundsExceeded: AugmentedError<ApiType>;
       /**
        * This address is not set as sponsor, use setCollectionSponsor first.
        **/
@@ -448,10 +452,6 @@
        * Length of items properties must be greater than 0.
        **/
       EmptyArgument: AugmentedError<ApiType>;
-      /**
-       * Tried to enable permissions which are only permitted to be disabled
-       **/
-      OwnerPermissionsCantBeReverted: AugmentedError<ApiType>;
       /**
        * Generic error
        **/
modifiedtests/src/interfaces/augment-api-tx.tsdiffbeforeafterboth
before · tests/src/interfaces/augment-api-tx.ts
1// Auto-generated via `yarn polkadot-types-from-chain`, do not edit2/* eslint-disable */34import type { CumulusPrimitivesParachainInherentParachainInherentData } from './polkadot';5import type { PalletCommonAccountBasicCrossAccountIdRepr, UpDataStructsAccessMode, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCreateItemData, UpDataStructsMetaUpdatePermission, UpDataStructsSchemaVersion } from './unique';6import type { ApiTypes, SubmittableExtrinsic } from '@polkadot/api/types';7import type { Bytes, Compact, Option, U256, Vec, bool, u128, u16, u32, u64 } from '@polkadot/types';8import type { Extrinsic } from '@polkadot/types/interfaces/extrinsics';9import type { AccountId32, Call, H160, H256, MultiAddress, Perbill } from '@polkadot/types/interfaces/runtime';10import type { XcmV1MultiLocation, XcmV2WeightLimit, XcmVersionedMultiAssets, XcmVersionedMultiLocation, XcmVersionedXcm } from '@polkadot/types/lookup';11import type { AnyNumber, ITuple } from '@polkadot/types/types';1213declare module '@polkadot/api/types/submittable' {14  export interface AugmentedSubmittables<ApiType> {15    balances: {16      /**17       * Exactly as `transfer`, except the origin must be root and the source account may be18       * specified.19       * # <weight>20       * - Same as transfer, but additional read and write because the source account is not21       * assumed to be in the overlay.22       * # </weight>23       **/24      forceTransfer: AugmentedSubmittable<(source: MultiAddress | { Id: any } | { Index: any } | { Raw: any } | { Address32: any } | { Address20: any } | string | Uint8Array, dest: MultiAddress | { Id: any } | { Index: any } | { Raw: any } | { Address32: any } | { Address20: any } | string | Uint8Array, value: Compact<u128> | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [MultiAddress, MultiAddress, Compact<u128>]>;25      /**26       * Unreserve some balance from a user by force.27       * 28       * Can only be called by ROOT.29       **/30      forceUnreserve: AugmentedSubmittable<(who: MultiAddress | { Id: any } | { Index: any } | { Raw: any } | { Address32: any } | { Address20: any } | string | Uint8Array, amount: u128 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [MultiAddress, u128]>;31      /**32       * Set the balances of a given account.33       * 34       * This will alter `FreeBalance` and `ReservedBalance` in storage. it will35       * also alter the total issuance of the system (`TotalIssuance`) appropriately.36       * If the new free or reserved balance is below the existential deposit,37       * it will reset the account nonce (`frame_system::AccountNonce`).38       * 39       * The dispatch origin for this call is `root`.40       **/41      setBalance: AugmentedSubmittable<(who: MultiAddress | { Id: any } | { Index: any } | { Raw: any } | { Address32: any } | { Address20: any } | string | Uint8Array, newFree: Compact<u128> | AnyNumber | Uint8Array, newReserved: Compact<u128> | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [MultiAddress, Compact<u128>, Compact<u128>]>;42      /**43       * Transfer some liquid free balance to another account.44       * 45       * `transfer` will set the `FreeBalance` of the sender and receiver.46       * If the sender's account is below the existential deposit as a result47       * of the transfer, the account will be reaped.48       * 49       * The dispatch origin for this call must be `Signed` by the transactor.50       * 51       * # <weight>52       * - Dependent on arguments but not critical, given proper implementations for input config53       * types. See related functions below.54       * - It contains a limited number of reads and writes internally and no complex55       * computation.56       * 57       * Related functions:58       * 59       * - `ensure_can_withdraw` is always called internally but has a bounded complexity.60       * - Transferring balances to accounts that did not exist before will cause61       * `T::OnNewAccount::on_new_account` to be called.62       * - Removing enough funds from an account will trigger `T::DustRemoval::on_unbalanced`.63       * - `transfer_keep_alive` works the same way as `transfer`, but has an additional check64       * that the transfer will not kill the origin account.65       * ---------------------------------66       * - Origin account is already in memory, so no DB operations for them.67       * # </weight>68       **/69      transfer: AugmentedSubmittable<(dest: MultiAddress | { Id: any } | { Index: any } | { Raw: any } | { Address32: any } | { Address20: any } | string | Uint8Array, value: Compact<u128> | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [MultiAddress, Compact<u128>]>;70      /**71       * Transfer the entire transferable balance from the caller account.72       * 73       * NOTE: This function only attempts to transfer _transferable_ balances. This means that74       * any locked, reserved, or existential deposits (when `keep_alive` is `true`), will not be75       * transferred by this function. To ensure that this function results in a killed account,76       * you might need to prepare the account by removing any reference counters, storage77       * deposits, etc...78       * 79       * The dispatch origin of this call must be Signed.80       * 81       * - `dest`: The recipient of the transfer.82       * - `keep_alive`: A boolean to determine if the `transfer_all` operation should send all83       * of the funds the account has, causing the sender account to be killed (false), or84       * transfer everything except at least the existential deposit, which will guarantee to85       * keep the sender account alive (true). # <weight>86       * - O(1). Just like transfer, but reading the user's transferable balance first.87       * #</weight>88       **/89      transferAll: AugmentedSubmittable<(dest: MultiAddress | { Id: any } | { Index: any } | { Raw: any } | { Address32: any } | { Address20: any } | string | Uint8Array, keepAlive: bool | boolean | Uint8Array) => SubmittableExtrinsic<ApiType>, [MultiAddress, bool]>;90      /**91       * Same as the [`transfer`] call, but with a check that the transfer will not kill the92       * origin account.93       * 94       * 99% of the time you want [`transfer`] instead.95       * 96       * [`transfer`]: struct.Pallet.html#method.transfer97       **/98      transferKeepAlive: AugmentedSubmittable<(dest: MultiAddress | { Id: any } | { Index: any } | { Raw: any } | { Address32: any } | { Address20: any } | string | Uint8Array, value: Compact<u128> | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [MultiAddress, Compact<u128>]>;99      /**100       * Generic tx101       **/102      [key: string]: SubmittableExtrinsicFunction<ApiType>;103    };104    charging: {105      /**106       * Generic tx107       **/108      [key: string]: SubmittableExtrinsicFunction<ApiType>;109    };110    cumulusXcm: {111      /**112       * Generic tx113       **/114      [key: string]: SubmittableExtrinsicFunction<ApiType>;115    };116    dmpQueue: {117      /**118       * Service a single overweight message.119       * 120       * - `origin`: Must pass `ExecuteOverweightOrigin`.121       * - `index`: The index of the overweight message to service.122       * - `weight_limit`: The amount of weight that message execution may take.123       * 124       * Errors:125       * - `Unknown`: Message of `index` is unknown.126       * - `OverLimit`: Message execution may use greater than `weight_limit`.127       * 128       * Events:129       * - `OverweightServiced`: On success.130       **/131      serviceOverweight: AugmentedSubmittable<(index: u64 | AnyNumber | Uint8Array, weightLimit: u64 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u64, u64]>;132      /**133       * Generic tx134       **/135      [key: string]: SubmittableExtrinsicFunction<ApiType>;136    };137    ethereum: {138      /**139       * Transact an Ethereum transaction.140       **/141      transact: AugmentedSubmittable<(transaction: EthereumTransactionTransactionV2 | { Legacy: any } | { EIP2930: any } | { EIP1559: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [EthereumTransactionTransactionV2]>;142      /**143       * Generic tx144       **/145      [key: string]: SubmittableExtrinsicFunction<ApiType>;146    };147    evm: {148      /**149       * Issue an EVM call operation. This is similar to a message call transaction in Ethereum.150       **/151      call: AugmentedSubmittable<(source: H160 | string | Uint8Array, target: H160 | string | Uint8Array, input: Bytes | string | Uint8Array, value: U256 | AnyNumber | Uint8Array, gasLimit: u64 | AnyNumber | Uint8Array, maxFeePerGas: U256 | AnyNumber | Uint8Array, maxPriorityFeePerGas: Option<U256> | null | object | string | Uint8Array, nonce: Option<U256> | null | object | string | Uint8Array, accessList: Vec<ITuple<[H160, Vec<H256>]>> | ([H160 | string | Uint8Array, Vec<H256> | (H256 | string | Uint8Array)[]])[]) => SubmittableExtrinsic<ApiType>, [H160, H160, Bytes, U256, u64, U256, Option<U256>, Option<U256>, Vec<ITuple<[H160, Vec<H256>]>>]>;152      /**153       * Issue an EVM create operation. This is similar to a contract creation transaction in154       * Ethereum.155       **/156      create: AugmentedSubmittable<(source: H160 | string | Uint8Array, init: Bytes | string | Uint8Array, value: U256 | AnyNumber | Uint8Array, gasLimit: u64 | AnyNumber | Uint8Array, maxFeePerGas: U256 | AnyNumber | Uint8Array, maxPriorityFeePerGas: Option<U256> | null | object | string | Uint8Array, nonce: Option<U256> | null | object | string | Uint8Array, accessList: Vec<ITuple<[H160, Vec<H256>]>> | ([H160 | string | Uint8Array, Vec<H256> | (H256 | string | Uint8Array)[]])[]) => SubmittableExtrinsic<ApiType>, [H160, Bytes, U256, u64, U256, Option<U256>, Option<U256>, Vec<ITuple<[H160, Vec<H256>]>>]>;157      /**158       * Issue an EVM create2 operation.159       **/160      create2: AugmentedSubmittable<(source: H160 | string | Uint8Array, init: Bytes | string | Uint8Array, salt: H256 | string | Uint8Array, value: U256 | AnyNumber | Uint8Array, gasLimit: u64 | AnyNumber | Uint8Array, maxFeePerGas: U256 | AnyNumber | Uint8Array, maxPriorityFeePerGas: Option<U256> | null | object | string | Uint8Array, nonce: Option<U256> | null | object | string | Uint8Array, accessList: Vec<ITuple<[H160, Vec<H256>]>> | ([H160 | string | Uint8Array, Vec<H256> | (H256 | string | Uint8Array)[]])[]) => SubmittableExtrinsic<ApiType>, [H160, Bytes, H256, U256, u64, U256, Option<U256>, Option<U256>, Vec<ITuple<[H160, Vec<H256>]>>]>;161      /**162       * Withdraw balance from EVM into currency/balances pallet.163       **/164      withdraw: AugmentedSubmittable<(address: H160 | string | Uint8Array, value: u128 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [H160, u128]>;165      /**166       * Generic tx167       **/168      [key: string]: SubmittableExtrinsicFunction<ApiType>;169    };170    evmMigration: {171      begin: AugmentedSubmittable<(address: H160 | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [H160]>;172      finish: AugmentedSubmittable<(address: H160 | string | Uint8Array, code: Bytes | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [H160, Bytes]>;173      setData: AugmentedSubmittable<(address: H160 | string | Uint8Array, data: Vec<ITuple<[H256, H256]>> | ([H256 | string | Uint8Array, H256 | string | Uint8Array])[]) => SubmittableExtrinsic<ApiType>, [H160, Vec<ITuple<[H256, H256]>>]>;174      /**175       * Generic tx176       **/177      [key: string]: SubmittableExtrinsicFunction<ApiType>;178    };179    inflation: {180      /**181       * This method sets the inflation start date. Can be only called once.182       * Inflation start block can be backdated and will catch up. The method will create Treasury183       * account if it does not exist and perform the first inflation deposit.184       * 185       * # Permissions186       * 187       * * Root188       * 189       * # Arguments190       * 191       * * inflation_start_relay_block: The relay chain block at which inflation should start192       **/193      startInflation: AugmentedSubmittable<(inflationStartRelayBlock: u32 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32]>;194      /**195       * Generic tx196       **/197      [key: string]: SubmittableExtrinsicFunction<ApiType>;198    };199    parachainSystem: {200      authorizeUpgrade: AugmentedSubmittable<(codeHash: H256 | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [H256]>;201      enactAuthorizedUpgrade: AugmentedSubmittable<(code: Bytes | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [Bytes]>;202      /**203       * Set the current validation data.204       * 205       * This should be invoked exactly once per block. It will panic at the finalization206       * phase if the call was not invoked.207       * 208       * The dispatch origin for this call must be `Inherent`209       * 210       * As a side effect, this function upgrades the current validation function211       * if the appropriate time has come.212       **/213      setValidationData: AugmentedSubmittable<(data: CumulusPrimitivesParachainInherentParachainInherentData | { validationData?: any; relayChainState?: any; downwardMessages?: any; horizontalMessages?: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [CumulusPrimitivesParachainInherentParachainInherentData]>;214      sudoSendUpwardMessage: AugmentedSubmittable<(message: Bytes | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [Bytes]>;215      /**216       * Generic tx217       **/218      [key: string]: SubmittableExtrinsicFunction<ApiType>;219    };220    polkadotXcm: {221      /**222       * Execute an XCM message from a local, signed, origin.223       * 224       * An event is deposited indicating whether `msg` could be executed completely or only225       * partially.226       * 227       * No more than `max_weight` will be used in its attempted execution. If this is less than the228       * maximum amount of weight that the message could take to be executed, then no execution229       * attempt will be made.230       * 231       * NOTE: A successful return to this does *not* imply that the `msg` was executed successfully232       * to completion; only that *some* of it was executed.233       **/234      execute: AugmentedSubmittable<(message: XcmVersionedXcm | { V0: any } | { V1: any } | { V2: any } | string | Uint8Array, maxWeight: u64 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [XcmVersionedXcm, u64]>;235      /**236       * Set a safe XCM version (the version that XCM should be encoded with if the most recent237       * version a destination can accept is unknown).238       * 239       * - `origin`: Must be Root.240       * - `maybe_xcm_version`: The default XCM encoding version, or `None` to disable.241       **/242      forceDefaultXcmVersion: AugmentedSubmittable<(maybeXcmVersion: Option<u32> | null | object | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [Option<u32>]>;243      /**244       * Ask a location to notify us regarding their XCM version and any changes to it.245       * 246       * - `origin`: Must be Root.247       * - `location`: The location to which we should subscribe for XCM version notifications.248       **/249      forceSubscribeVersionNotify: AugmentedSubmittable<(location: XcmVersionedMultiLocation | { V0: any } | { V1: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [XcmVersionedMultiLocation]>;250      /**251       * Require that a particular destination should no longer notify us regarding any XCM252       * version changes.253       * 254       * - `origin`: Must be Root.255       * - `location`: The location to which we are currently subscribed for XCM version256       * notifications which we no longer desire.257       **/258      forceUnsubscribeVersionNotify: AugmentedSubmittable<(location: XcmVersionedMultiLocation | { V0: any } | { V1: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [XcmVersionedMultiLocation]>;259      /**260       * Extoll that a particular destination can be communicated with through a particular261       * version of XCM.262       * 263       * - `origin`: Must be Root.264       * - `location`: The destination that is being described.265       * - `xcm_version`: The latest version of XCM that `location` supports.266       **/267      forceXcmVersion: AugmentedSubmittable<(location: XcmV1MultiLocation | { parents?: any; interior?: any } | string | Uint8Array, xcmVersion: u32 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [XcmV1MultiLocation, u32]>;268      /**269       * Transfer some assets from the local chain to the sovereign account of a destination chain and forward270       * a notification XCM.271       * 272       * Fee payment on the destination side is made from the first asset listed in the `assets` vector.273       * 274       * - `origin`: Must be capable of withdrawing the `assets` and executing XCM.275       * - `dest`: Destination context for the assets. Will typically be `X2(Parent, Parachain(..))` to send276       * from parachain to parachain, or `X1(Parachain(..))` to send from relay to parachain.277       * - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will generally be278       * an `AccountId32` value.279       * - `assets`: The assets to be withdrawn. This should include the assets used to pay the fee on the280       * `dest` side.281       * - `fee_asset_item`: The index into `assets` of the item which should be used to pay282       * fees.283       * - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase.284       **/285      limitedReserveTransferAssets: AugmentedSubmittable<(dest: XcmVersionedMultiLocation | { V0: any } | { V1: any } | string | Uint8Array, beneficiary: XcmVersionedMultiLocation | { V0: any } | { V1: any } | string | Uint8Array, assets: XcmVersionedMultiAssets | { V0: any } | { V1: any } | string | Uint8Array, feeAssetItem: u32 | AnyNumber | Uint8Array, weightLimit: XcmV2WeightLimit | { Unlimited: any } | { Limited: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [XcmVersionedMultiLocation, XcmVersionedMultiLocation, XcmVersionedMultiAssets, u32, XcmV2WeightLimit]>;286      /**287       * Teleport some assets from the local chain to some destination chain.288       * 289       * Fee payment on the destination side is made from the first asset listed in the `assets` vector.290       * 291       * - `origin`: Must be capable of withdrawing the `assets` and executing XCM.292       * - `dest`: Destination context for the assets. Will typically be `X2(Parent, Parachain(..))` to send293       * from parachain to parachain, or `X1(Parachain(..))` to send from relay to parachain.294       * - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will generally be295       * an `AccountId32` value.296       * - `assets`: The assets to be withdrawn. The first item should be the currency used to to pay the fee on the297       * `dest` side. May not be empty.298       * - `fee_asset_item`: The index into `assets` of the item which should be used to pay299       * fees.300       * - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase.301       **/302      limitedTeleportAssets: AugmentedSubmittable<(dest: XcmVersionedMultiLocation | { V0: any } | { V1: any } | string | Uint8Array, beneficiary: XcmVersionedMultiLocation | { V0: any } | { V1: any } | string | Uint8Array, assets: XcmVersionedMultiAssets | { V0: any } | { V1: any } | string | Uint8Array, feeAssetItem: u32 | AnyNumber | Uint8Array, weightLimit: XcmV2WeightLimit | { Unlimited: any } | { Limited: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [XcmVersionedMultiLocation, XcmVersionedMultiLocation, XcmVersionedMultiAssets, u32, XcmV2WeightLimit]>;303      /**304       * Transfer some assets from the local chain to the sovereign account of a destination chain and forward305       * a notification XCM.306       * 307       * Fee payment on the destination side is made from the first asset listed in the `assets` vector and308       * fee-weight is calculated locally and thus remote weights are assumed to be equal to309       * local weights.310       * 311       * - `origin`: Must be capable of withdrawing the `assets` and executing XCM.312       * - `dest`: Destination context for the assets. Will typically be `X2(Parent, Parachain(..))` to send313       * from parachain to parachain, or `X1(Parachain(..))` to send from relay to parachain.314       * - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will generally be315       * an `AccountId32` value.316       * - `assets`: The assets to be withdrawn. This should include the assets used to pay the fee on the317       * `dest` side.318       * - `fee_asset_item`: The index into `assets` of the item which should be used to pay319       * fees.320       **/321      reserveTransferAssets: AugmentedSubmittable<(dest: XcmVersionedMultiLocation | { V0: any } | { V1: any } | string | Uint8Array, beneficiary: XcmVersionedMultiLocation | { V0: any } | { V1: any } | string | Uint8Array, assets: XcmVersionedMultiAssets | { V0: any } | { V1: any } | string | Uint8Array, feeAssetItem: u32 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [XcmVersionedMultiLocation, XcmVersionedMultiLocation, XcmVersionedMultiAssets, u32]>;322      send: AugmentedSubmittable<(dest: XcmVersionedMultiLocation | { V0: any } | { V1: any } | string | Uint8Array, message: XcmVersionedXcm | { V0: any } | { V1: any } | { V2: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [XcmVersionedMultiLocation, XcmVersionedXcm]>;323      /**324       * Teleport some assets from the local chain to some destination chain.325       * 326       * Fee payment on the destination side is made from the first asset listed in the `assets` vector and327       * fee-weight is calculated locally and thus remote weights are assumed to be equal to328       * local weights.329       * 330       * - `origin`: Must be capable of withdrawing the `assets` and executing XCM.331       * - `dest`: Destination context for the assets. Will typically be `X2(Parent, Parachain(..))` to send332       * from parachain to parachain, or `X1(Parachain(..))` to send from relay to parachain.333       * - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will generally be334       * an `AccountId32` value.335       * - `assets`: The assets to be withdrawn. The first item should be the currency used to to pay the fee on the336       * `dest` side. May not be empty.337       * - `fee_asset_item`: The index into `assets` of the item which should be used to pay338       * fees.339       **/340      teleportAssets: AugmentedSubmittable<(dest: XcmVersionedMultiLocation | { V0: any } | { V1: any } | string | Uint8Array, beneficiary: XcmVersionedMultiLocation | { V0: any } | { V1: any } | string | Uint8Array, assets: XcmVersionedMultiAssets | { V0: any } | { V1: any } | string | Uint8Array, feeAssetItem: u32 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [XcmVersionedMultiLocation, XcmVersionedMultiLocation, XcmVersionedMultiAssets, u32]>;341      /**342       * Generic tx343       **/344      [key: string]: SubmittableExtrinsicFunction<ApiType>;345    };346    sudo: {347      /**348       * Authenticates the current sudo key and sets the given AccountId (`new`) as the new sudo349       * key.350       * 351       * The dispatch origin for this call must be _Signed_.352       * 353       * # <weight>354       * - O(1).355       * - Limited storage reads.356       * - One DB change.357       * # </weight>358       **/359      setKey: AugmentedSubmittable<(updated: MultiAddress | { Id: any } | { Index: any } | { Raw: any } | { Address32: any } | { Address20: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [MultiAddress]>;360      /**361       * Authenticates the sudo key and dispatches a function call with `Root` origin.362       * 363       * The dispatch origin for this call must be _Signed_.364       * 365       * # <weight>366       * - O(1).367       * - Limited storage reads.368       * - One DB write (event).369       * - Weight of derivative `call` execution + 10,000.370       * # </weight>371       **/372      sudo: AugmentedSubmittable<(call: Call | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [Call]>;373      /**374       * Authenticates the sudo key and dispatches a function call with `Signed` origin from375       * a given account.376       * 377       * The dispatch origin for this call must be _Signed_.378       * 379       * # <weight>380       * - O(1).381       * - Limited storage reads.382       * - One DB write (event).383       * - Weight of derivative `call` execution + 10,000.384       * # </weight>385       **/386      sudoAs: AugmentedSubmittable<(who: MultiAddress | { Id: any } | { Index: any } | { Raw: any } | { Address32: any } | { Address20: any } | string | Uint8Array, call: Call | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [MultiAddress, Call]>;387      /**388       * Authenticates the sudo key and dispatches a function call with `Root` origin.389       * This function does not check the weight of the call, and instead allows the390       * Sudo user to specify the weight of the call.391       * 392       * The dispatch origin for this call must be _Signed_.393       * 394       * # <weight>395       * - O(1).396       * - The weight of this call is defined by the caller.397       * # </weight>398       **/399      sudoUncheckedWeight: AugmentedSubmittable<(call: Call | string | Uint8Array, weight: u64 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [Call, u64]>;400      /**401       * Generic tx402       **/403      [key: string]: SubmittableExtrinsicFunction<ApiType>;404    };405    system: {406      /**407       * A dispatch that will fill the block weight up to the given ratio.408       **/409      fillBlock: AugmentedSubmittable<(ratio: Perbill | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [Perbill]>;410      /**411       * Kill all storage items with a key that starts with the given prefix.412       * 413       * **NOTE:** We rely on the Root origin to provide us the number of subkeys under414       * the prefix we are removing to accurately calculate the weight of this function.415       **/416      killPrefix: AugmentedSubmittable<(prefix: Bytes | string | Uint8Array, subkeys: u32 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [Bytes, u32]>;417      /**418       * Kill some items from storage.419       **/420      killStorage: AugmentedSubmittable<(keys: Vec<Bytes> | (Bytes | string | Uint8Array)[]) => SubmittableExtrinsic<ApiType>, [Vec<Bytes>]>;421      /**422       * Make some on-chain remark.423       * 424       * # <weight>425       * - `O(1)`426       * # </weight>427       **/428      remark: AugmentedSubmittable<(remark: Bytes | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [Bytes]>;429      /**430       * Make some on-chain remark and emit event.431       * 432       * # <weight>433       * - `O(b)` where b is the length of the remark.434       * - 1 event.435       * # </weight>436       **/437      remarkWithEvent: AugmentedSubmittable<(remark: Bytes | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [Bytes]>;438      /**439       * Set the new runtime code.440       * 441       * # <weight>442       * - `O(C + S)` where `C` length of `code` and `S` complexity of `can_set_code`443       * - 1 call to `can_set_code`: `O(S)` (calls `sp_io::misc::runtime_version` which is444       * expensive).445       * - 1 storage write (codec `O(C)`).446       * - 1 digest item.447       * - 1 event.448       * The weight of this function is dependent on the runtime, but generally this is very449       * expensive. We will treat this as a full block.450       * # </weight>451       **/452      setCode: AugmentedSubmittable<(code: Bytes | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [Bytes]>;453      /**454       * Set the new runtime code without doing any checks of the given `code`.455       * 456       * # <weight>457       * - `O(C)` where `C` length of `code`458       * - 1 storage write (codec `O(C)`).459       * - 1 digest item.460       * - 1 event.461       * The weight of this function is dependent on the runtime. We will treat this as a full462       * block. # </weight>463       **/464      setCodeWithoutChecks: AugmentedSubmittable<(code: Bytes | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [Bytes]>;465      /**466       * Set the number of pages in the WebAssembly environment's heap.467       **/468      setHeapPages: AugmentedSubmittable<(pages: u64 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u64]>;469      /**470       * Set some items of storage.471       **/472      setStorage: AugmentedSubmittable<(items: Vec<ITuple<[Bytes, Bytes]>> | ([Bytes | string | Uint8Array, Bytes | string | Uint8Array])[]) => SubmittableExtrinsic<ApiType>, [Vec<ITuple<[Bytes, Bytes]>>]>;473      /**474       * Generic tx475       **/476      [key: string]: SubmittableExtrinsicFunction<ApiType>;477    };478    timestamp: {479      /**480       * Set the current time.481       * 482       * This call should be invoked exactly once per block. It will panic at the finalization483       * phase, if this call hasn't been invoked by that time.484       * 485       * The timestamp should be greater than the previous one by the amount specified by486       * `MinimumPeriod`.487       * 488       * The dispatch origin for this call must be `Inherent`.489       * 490       * # <weight>491       * - `O(1)` (Note that implementations of `OnTimestampSet` must also be `O(1)`)492       * - 1 storage read and 1 storage mutation (codec `O(1)`). (because of `DidUpdate::take` in493       * `on_finalize`)494       * - 1 event handler `on_timestamp_set`. Must be `O(1)`.495       * # </weight>496       **/497      set: AugmentedSubmittable<(now: Compact<u64> | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [Compact<u64>]>;498      /**499       * Generic tx500       **/501      [key: string]: SubmittableExtrinsicFunction<ApiType>;502    };503    treasury: {504      /**505       * Approve a proposal. At a later time, the proposal will be allocated to the beneficiary506       * and the original deposit will be returned.507       * 508       * May only be called from `T::ApproveOrigin`.509       * 510       * # <weight>511       * - Complexity: O(1).512       * - DbReads: `Proposals`, `Approvals`513       * - DbWrite: `Approvals`514       * # </weight>515       **/516      approveProposal: AugmentedSubmittable<(proposalId: Compact<u32> | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [Compact<u32>]>;517      /**518       * Put forward a suggestion for spending. A deposit proportional to the value519       * is reserved and slashed if the proposal is rejected. It is returned once the520       * proposal is awarded.521       * 522       * # <weight>523       * - Complexity: O(1)524       * - DbReads: `ProposalCount`, `origin account`525       * - DbWrites: `ProposalCount`, `Proposals`, `origin account`526       * # </weight>527       **/528      proposeSpend: AugmentedSubmittable<(value: Compact<u128> | AnyNumber | Uint8Array, beneficiary: MultiAddress | { Id: any } | { Index: any } | { Raw: any } | { Address32: any } | { Address20: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [Compact<u128>, MultiAddress]>;529      /**530       * Reject a proposed spend. The original deposit will be slashed.531       * 532       * May only be called from `T::RejectOrigin`.533       * 534       * # <weight>535       * - Complexity: O(1)536       * - DbReads: `Proposals`, `rejected proposer account`537       * - DbWrites: `Proposals`, `rejected proposer account`538       * # </weight>539       **/540      rejectProposal: AugmentedSubmittable<(proposalId: Compact<u32> | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [Compact<u32>]>;541      /**542       * Generic tx543       **/544      [key: string]: SubmittableExtrinsicFunction<ApiType>;545    };546    unique: {547      /**548       * Adds an admin of the Collection.549       * NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.550       * 551       * # Permissions552       * 553       * * Collection Owner.554       * * Collection Admin.555       * 556       * # Arguments557       * 558       * * collection_id: ID of the Collection to add admin for.559       * 560       * * new_admin_id: Address of new admin to add.561       **/562      addCollectionAdmin: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, newAdminId: PalletCommonAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, PalletCommonAccountBasicCrossAccountIdRepr]>;563      /**564       * Add an address to allow list.565       * 566       * # Permissions567       * 568       * * Collection Owner569       * * Collection Admin570       * 571       * # Arguments572       * 573       * * collection_id.574       * 575       * * address.576       **/577      addToAllowList: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, address: PalletCommonAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, PalletCommonAccountBasicCrossAccountIdRepr]>;578      /**579       * Set, change, or remove approved address to transfer the ownership of the NFT.580       * 581       * # Permissions582       * 583       * * Collection Owner584       * * Collection Admin585       * * Current NFT owner586       * 587       * # Arguments588       * 589       * * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).590       * 591       * * collection_id.592       * 593       * * item_id: ID of the item.594       **/595      approve: AugmentedSubmittable<(spender: PalletCommonAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, collectionId: u32 | AnyNumber | Uint8Array, itemId: u32 | AnyNumber | Uint8Array, amount: u128 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [PalletCommonAccountBasicCrossAccountIdRepr, u32, u32, u128]>;596      /**597       * Destroys a concrete instance of NFT on behalf of the owner598       * See also: [`approve`]599       * 600       * # Permissions601       * 602       * * Collection Owner.603       * * Collection Admin.604       * * Current NFT Owner.605       * 606       * # Arguments607       * 608       * * collection_id: ID of the collection.609       * 610       * * item_id: ID of NFT to burn.611       * 612       * * from: owner of item613       **/614      burnFrom: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, from: PalletCommonAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, itemId: u32 | AnyNumber | Uint8Array, value: u128 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, PalletCommonAccountBasicCrossAccountIdRepr, u32, u128]>;615      /**616       * Destroys a concrete instance of NFT.617       * 618       * # Permissions619       * 620       * * Collection Owner.621       * * Collection Admin.622       * * Current NFT Owner.623       * 624       * # Arguments625       * 626       * * collection_id: ID of the collection.627       * 628       * * item_id: ID of NFT to burn.629       **/630      burnItem: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, itemId: u32 | AnyNumber | Uint8Array, value: u128 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, u32, u128]>;631      /**632       * Change the owner of the collection.633       * 634       * # Permissions635       * 636       * * Collection Owner.637       * 638       * # Arguments639       * 640       * * collection_id.641       * 642       * * new_owner.643       **/644      changeCollectionOwner: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, newOwner: AccountId32 | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, AccountId32]>;645      /**646       * # Permissions647       * 648       * * Sponsor.649       * 650       * # Arguments651       * 652       * * collection_id.653       **/654      confirmSponsorship: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32]>;655      /**656       * This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner of the collection is set to the address that signed the transaction and can be changed later.657       * 658       * # Permissions659       * 660       * * Anyone.661       * 662       * # Arguments663       * 664       * * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.665       * 666       * * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.667       * 668       * * token_prefix: UTF-8 string with token prefix.669       * 670       * * mode: [CollectionMode] collection type and type dependent data.671       **/672      createCollection: AugmentedSubmittable<(collectionName: Vec<u16> | (u16 | AnyNumber | Uint8Array)[], collectionDescription: Vec<u16> | (u16 | AnyNumber | Uint8Array)[], tokenPrefix: Bytes | string | Uint8Array, mode: UpDataStructsCollectionMode | { NFT: any } | { Fungible: any } | { ReFungible: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [Vec<u16>, Vec<u16>, Bytes, UpDataStructsCollectionMode]>;673      /**674       * This method creates a concrete instance of NFT Collection created with CreateCollection method.675       * 676       * # Permissions677       * 678       * * Collection Owner.679       * * Collection Admin.680       * * Anyone if681       * * Allow List is enabled, and682       * * Address is added to allow list, and683       * * MintPermission is enabled (see SetMintPermission method)684       * 685       * # Arguments686       * 687       * * collection_id: ID of the collection.688       * 689       * * owner: Address, initial owner of the NFT.690       * 691       * * data: Token data to store on chain.692       **/693      createItem: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, owner: PalletCommonAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, data: UpDataStructsCreateItemData | { NFT: any } | { Fungible: any } | { ReFungible: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, PalletCommonAccountBasicCrossAccountIdRepr, UpDataStructsCreateItemData]>;694      /**695       * This method creates multiple items in a collection created with CreateCollection method.696       * 697       * # Permissions698       * 699       * * Collection Owner.700       * * Collection Admin.701       * * Anyone if702       * * Allow List is enabled, and703       * * Address is added to allow list, and704       * * MintPermission is enabled (see SetMintPermission method)705       * 706       * # Arguments707       * 708       * * collection_id: ID of the collection.709       * 710       * * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].711       * 712       * * owner: Address, initial owner of the NFT.713       **/714      createMultipleItems: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, owner: PalletCommonAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, itemsData: Vec<UpDataStructsCreateItemData> | (UpDataStructsCreateItemData | { NFT: any } | { Fungible: any } | { ReFungible: any } | string | Uint8Array)[]) => SubmittableExtrinsic<ApiType>, [u32, PalletCommonAccountBasicCrossAccountIdRepr, Vec<UpDataStructsCreateItemData>]>;715      /**716       * **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.717       * 718       * # Permissions719       * 720       * * Collection Owner.721       * 722       * # Arguments723       * 724       * * collection_id: collection to destroy.725       **/726      destroyCollection: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32]>;727      /**728       * Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.729       * 730       * # Permissions731       * 732       * * Collection Owner.733       * * Collection Admin.734       * 735       * # Arguments736       * 737       * * collection_id: ID of the Collection to remove admin for.738       * 739       * * account_id: Address of admin to remove.740       **/741      removeCollectionAdmin: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, accountId: PalletCommonAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, PalletCommonAccountBasicCrossAccountIdRepr]>;742      /**743       * Switch back to pay-per-own-transaction model.744       * 745       * # Permissions746       * 747       * * Collection owner.748       * 749       * # Arguments750       * 751       * * collection_id.752       **/753      removeCollectionSponsor: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32]>;754      /**755       * Remove an address from allow list.756       * 757       * # Permissions758       * 759       * * Collection Owner760       * * Collection Admin761       * 762       * # Arguments763       * 764       * * collection_id.765       * 766       * * address.767       **/768      removeFromAllowList: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, address: PalletCommonAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, PalletCommonAccountBasicCrossAccountIdRepr]>;769      setCollectionLimits: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, newLimit: UpDataStructsCollectionLimits | { accountTokenOwnershipLimit?: any; sponsoredDataSize?: any; sponsoredDataRateLimit?: any; tokenLimit?: any; sponsorTransferTimeout?: any; sponsorApproveTimeout?: any; ownerCanTransfer?: any; ownerCanDestroy?: any; transfersEnabled?: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, UpDataStructsCollectionLimits]>;770      /**771       * # Permissions772       * 773       * * Collection Owner774       * 775       * # Arguments776       * 777       * * collection_id.778       * 779       * * new_sponsor.780       **/781      setCollectionSponsor: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, newSponsor: AccountId32 | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, AccountId32]>;782      /**783       * Set const on-chain data schema.784       * 785       * # Permissions786       * 787       * * Collection Owner788       * * Collection Admin789       * 790       * # Arguments791       * 792       * * collection_id.793       * 794       * * schema: String representing the const on-chain data schema.795       **/796      setConstOnChainSchema: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, schema: Bytes | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, Bytes]>;797      /**798       * Set meta_update_permission value for particular collection799       * 800       * # Permissions801       * 802       * * Collection Owner.803       * 804       * # Arguments805       * 806       * * collection_id: ID of the collection.807       * 808       * * value: New flag value.809       **/810      setMetaUpdatePermissionFlag: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, value: UpDataStructsMetaUpdatePermission | 'ItemOwner' | 'Admin' | 'None' | number | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, UpDataStructsMetaUpdatePermission]>;811      /**812       * Allows Anyone to create tokens if:813       * * Allow List is enabled, and814       * * Address is added to allow list, and815       * * This method was called with True parameter816       * 817       * # Permissions818       * * Collection Owner819       * 820       * # Arguments821       * 822       * * collection_id.823       * 824       * * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.825       **/826      setMintPermission: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, mintPermission: bool | boolean | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, bool]>;827      /**828       * Set off-chain data schema.829       * 830       * # Permissions831       * 832       * * Collection Owner833       * * Collection Admin834       * 835       * # Arguments836       * 837       * * collection_id.838       * 839       * * schema: String representing the offchain data schema.840       **/841      setOffchainSchema: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, schema: Bytes | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, Bytes]>;842      /**843       * Toggle between normal and allow list access for the methods with access for `Anyone`.844       * 845       * # Permissions846       * 847       * * Collection Owner.848       * 849       * # Arguments850       * 851       * * collection_id.852       * 853       * * mode: [AccessMode]854       **/855      setPublicAccessMode: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, mode: UpDataStructsAccessMode | 'Normal' | 'AllowList' | number | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, UpDataStructsAccessMode]>;856      /**857       * Set schema standard858       * ImageURL859       * Unique860       * 861       * # Permissions862       * 863       * * Collection Owner864       * * Collection Admin865       * 866       * # Arguments867       * 868       * * collection_id.869       * 870       * * schema: SchemaVersion: enum871       **/872      setSchemaVersion: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, version: UpDataStructsSchemaVersion | 'ImageURL' | 'Unique' | number | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, UpDataStructsSchemaVersion]>;873      /**874       * Set transfers_enabled value for particular collection875       * 876       * # Permissions877       * 878       * * Collection Owner.879       * 880       * # Arguments881       * 882       * * collection_id: ID of the collection.883       * 884       * * value: New flag value.885       **/886      setTransfersEnabledFlag: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, value: bool | boolean | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, bool]>;887      /**888       * Set off-chain data schema.889       * 890       * # Permissions891       * 892       * * Collection Owner893       * * Collection Admin894       * 895       * # Arguments896       * 897       * * collection_id.898       * 899       * * schema: String representing the offchain data schema.900       **/901      setVariableMetaData: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, itemId: u32 | AnyNumber | Uint8Array, data: Bytes | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, u32, Bytes]>;902      /**903       * Set variable on-chain data schema.904       * 905       * # Permissions906       * 907       * * Collection Owner908       * * Collection Admin909       * 910       * # Arguments911       * 912       * * collection_id.913       * 914       * * schema: String representing the variable on-chain data schema.915       **/916      setVariableOnChainSchema: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, schema: Bytes | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, Bytes]>;917      /**918       * Change ownership of the token.919       * 920       * # Permissions921       * 922       * * Collection Owner923       * * Collection Admin924       * * Current NFT owner925       * 926       * # Arguments927       * 928       * * recipient: Address of token recipient.929       * 930       * * collection_id.931       * 932       * * item_id: ID of the item933       * * Non-Fungible Mode: Required.934       * * Fungible Mode: Ignored.935       * * Re-Fungible Mode: Required.936       * 937       * * value: Amount to transfer.938       * * Non-Fungible Mode: Ignored939       * * Fungible Mode: Must specify transferred amount940       * * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)941       **/942      transfer: AugmentedSubmittable<(recipient: PalletCommonAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, collectionId: u32 | AnyNumber | Uint8Array, itemId: u32 | AnyNumber | Uint8Array, value: u128 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [PalletCommonAccountBasicCrossAccountIdRepr, u32, u32, u128]>;943      /**944       * Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.945       * 946       * # Permissions947       * * Collection Owner948       * * Collection Admin949       * * Current NFT owner950       * * Address approved by current NFT owner951       * 952       * # Arguments953       * 954       * * from: Address that owns token.955       * 956       * * recipient: Address of token recipient.957       * 958       * * collection_id.959       * 960       * * item_id: ID of the item.961       * 962       * * value: Amount to transfer.963       **/964      transferFrom: AugmentedSubmittable<(from: PalletCommonAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, recipient: PalletCommonAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, collectionId: u32 | AnyNumber | Uint8Array, itemId: u32 | AnyNumber | Uint8Array, value: u128 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [PalletCommonAccountBasicCrossAccountIdRepr, PalletCommonAccountBasicCrossAccountIdRepr, u32, u32, u128]>;965      /**966       * Generic tx967       **/968      [key: string]: SubmittableExtrinsicFunction<ApiType>;969    };970    vesting: {971      claim: AugmentedSubmittable<() => SubmittableExtrinsic<ApiType>, []>;972      claimFor: AugmentedSubmittable<(dest: MultiAddress | { Id: any } | { Index: any } | { Raw: any } | { Address32: any } | { Address20: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [MultiAddress]>;973      updateVestingSchedules: AugmentedSubmittable<(who: MultiAddress | { Id: any } | { Index: any } | { Raw: any } | { Address32: any } | { Address20: any } | string | Uint8Array, vestingSchedules: Vec<OrmlVestingVestingSchedule> | (OrmlVestingVestingSchedule | { start?: any; period?: any; periodCount?: any; perPeriod?: any } | string | Uint8Array)[]) => SubmittableExtrinsic<ApiType>, [MultiAddress, Vec<OrmlVestingVestingSchedule>]>;974      vestedTransfer: AugmentedSubmittable<(dest: MultiAddress | { Id: any } | { Index: any } | { Raw: any } | { Address32: any } | { Address20: any } | string | Uint8Array, schedule: OrmlVestingVestingSchedule | { start?: any; period?: any; periodCount?: any; perPeriod?: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [MultiAddress, OrmlVestingVestingSchedule]>;975      /**976       * Generic tx977       **/978      [key: string]: SubmittableExtrinsicFunction<ApiType>;979    };980    xcmpQueue: {981      /**982       * Services a single overweight XCM.983       * 984       * - `origin`: Must pass `ExecuteOverweightOrigin`.985       * - `index`: The index of the overweight XCM to service986       * - `weight_limit`: The amount of weight that XCM execution may take.987       * 988       * Errors:989       * - `BadOverweightIndex`: XCM under `index` is not found in the `Overweight` storage map.990       * - `BadXcm`: XCM under `index` cannot be properly decoded into a valid XCM format.991       * - `WeightOverLimit`: XCM execution may use greater `weight_limit`.992       * 993       * Events:994       * - `OverweightServiced`: On success.995       **/996      serviceOverweight: AugmentedSubmittable<(index: u64 | AnyNumber | Uint8Array, weightLimit: u64 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u64, u64]>;997      /**998       * Generic tx999       **/1000      [key: string]: SubmittableExtrinsicFunction<ApiType>;1001    };1002  }10031004  export interface SubmittableExtrinsics<ApiType extends ApiTypes> extends AugmentedSubmittables<ApiType> {1005    (extrinsic: Call | Extrinsic | Uint8Array | string): SubmittableExtrinsic<ApiType>;1006    [key: string]: SubmittableModuleExtrinsics<ApiType>;1007  }1008}
modifiedtests/src/interfaces/augment-types.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-types.ts
+++ b/tests/src/interfaces/augment-types.ts
@@ -3,7 +3,7 @@
 
 import type { EthereumBlock, EthereumLog, EthereumReceipt, EthereumTransactionLegacyTransaction, EvmCoreErrorExitReason, FpRpcTransactionStatus } from './ethereum';
 import type { CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmpQueueInboundStatus, CumulusPalletXcmpQueueOutboundStatus, CumulusPalletXcmpQueueQueueConfigData, CumulusPrimitivesParachainInherentParachainInherentData, PolkadotParachainPrimitivesXcmpMessageFormat, PolkadotPrimitivesV1AbridgedHostConfiguration, PolkadotPrimitivesV1PersistedValidationData } from './polkadot';
-import type { PalletCommonAccountBasicCrossAccountIdRepr, PalletNonfungibleItemData, PalletRefungibleItemData, PalletUnqSchedulerCallSpec, PalletUnqSchedulerReleases, PalletUnqSchedulerScheduledV2, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionId, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionStats, UpDataStructsCreateItemData, UpDataStructsMetaUpdatePermission, UpDataStructsSchemaVersion, UpDataStructsSponsorshipState, UpDataStructsTokenId } from './unique';
+import type { PalletCommonAccountBasicCrossAccountIdRepr, PalletNonfungibleItemData, PalletRefungibleItemData, PalletUnqSchedulerCallSpec, PalletUnqSchedulerReleases, PalletUnqSchedulerScheduledV2, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionId, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionStats, UpDataStructsCreateCollectionData, UpDataStructsCreateItemData, UpDataStructsMetaUpdatePermission, UpDataStructsSchemaVersion, UpDataStructsSponsorshipState, UpDataStructsTokenId } from './unique';
 import type { BitVec, Bool, Bytes, Data, I128, I16, I256, I32, I64, I8, Json, Null, Raw, StorageKey, Text, Type, U128, U16, U256, U32, U64, U8, USize, bool, i128, i16, i256, i32, i64, i8, u128, u16, u256, u32, u64, u8, usize } from '@polkadot/types';
 import type { AssetApproval, AssetApprovalKey, AssetBalance, AssetDestroyWitness, AssetDetails, AssetMetadata, TAssetBalance, TAssetDepositBalance } from '@polkadot/types/interfaces/assets';
 import type { BlockAttestations, IncludedBlocks, MoreAttestations } from '@polkadot/types/interfaces/attestations';
@@ -1021,6 +1021,7 @@
     UpDataStructsCollectionLimits: UpDataStructsCollectionLimits;
     UpDataStructsCollectionMode: UpDataStructsCollectionMode;
     UpDataStructsCollectionStats: UpDataStructsCollectionStats;
+    UpDataStructsCreateCollectionData: UpDataStructsCreateCollectionData;
     UpDataStructsCreateItemData: UpDataStructsCreateItemData;
     UpDataStructsMetaUpdatePermission: UpDataStructsMetaUpdatePermission;
     UpDataStructsSchemaVersion: UpDataStructsSchemaVersion;
modifiedtests/src/interfaces/unique/definitions.tsdiffbeforeafterboth
--- a/tests/src/interfaces/unique/definitions.ts
+++ b/tests/src/interfaces/unique/definitions.ts
@@ -67,6 +67,20 @@
       constOnChainSchema: 'Vec<u8>',
       metaUpdatePermission: 'UpDataStructsMetaUpdatePermission',
     },
+    UpDataStructsCreateCollectionData: {
+      mode: 'UpDataStructsCollectionMode',
+      access: 'Option<UpDataStructsAccessMode>',
+      name: 'Vec<u16>',
+      description: 'Vec<u16>',
+      tokenPrefix: 'Vec<u8>',
+      offchainSchema: 'Vec<u8>',
+      schemaVersion: 'Option<UpDataStructsSchemaVersion>',
+      pendingSponsor: 'Option<AccountId>',
+      limits: 'Option<UpDataStructsCollectionLimits>',
+      variableOnChainSchema: 'Vec<u8>',
+      constOnChainSchema: 'Vec<u8>',
+      metaUpdatePermission: 'Option<UpDataStructsMetaUpdatePermission>',
+    },
     UpDataStructsCollectionStats: {
       created: 'u32',
       destroyed: 'u32',
@@ -76,7 +90,13 @@
     UpDataStructsTokenId: 'u32',
     PalletNonfungibleItemData: mkDummy('NftItemData'),
     PalletRefungibleItemData: mkDummy('RftItemData'),
-    UpDataStructsCollectionMode: mkDummy('CollectionMode'),
+    UpDataStructsCollectionMode: {
+      _enum: {
+        NFT: null,
+        Fungible: 'u32',
+        ReFungible: null,
+      },
+    },
     UpDataStructsCreateItemData: mkDummy('CreateItemData'),
     UpDataStructsCollectionLimits: {
       accountTokenOwnershipLimit: 'Option<u32>',
@@ -101,7 +121,9 @@
     UpDataStructsAccessMode: {
       _enum: ['Normal', 'AllowList'],
     },
-    UpDataStructsSchemaVersion: mkDummy('SchemaVersion'),
+    UpDataStructsSchemaVersion: {
+      _enum: ['ImageURL', 'Unique'],
+    },
 
     PalletUnqSchedulerScheduledV2: mkDummy('ScheduledV2'),
     PalletUnqSchedulerCallSpec: mkDummy('CallSpec'),
modifiedtests/src/interfaces/unique/types.tsdiffbeforeafterboth
--- a/tests/src/interfaces/unique/types.ts
+++ b/tests/src/interfaces/unique/types.ts
@@ -77,8 +77,11 @@
 }
 
 /** @name UpDataStructsCollectionMode */
-export interface UpDataStructsCollectionMode extends Struct {
-  readonly dummyCollectionMode: u32;
+export interface UpDataStructsCollectionMode extends Enum {
+  readonly isNft: boolean;
+  readonly isFungible: boolean;
+  readonly asFungible: u32;
+  readonly isReFungible: boolean;
 }
 
 /** @name UpDataStructsCollectionStats */
@@ -88,6 +91,22 @@
   readonly alive: u32;
 }
 
+/** @name UpDataStructsCreateCollectionData */
+export interface UpDataStructsCreateCollectionData extends Struct {
+  readonly mode: UpDataStructsCollectionMode;
+  readonly access: Option<UpDataStructsAccessMode>;
+  readonly name: Vec<u16>;
+  readonly description: Vec<u16>;
+  readonly tokenPrefix: Bytes;
+  readonly offchainSchema: Bytes;
+  readonly schemaVersion: Option<UpDataStructsSchemaVersion>;
+  readonly pendingSponsor: Option<AccountId>;
+  readonly limits: Option<UpDataStructsCollectionLimits>;
+  readonly variableOnChainSchema: Bytes;
+  readonly constOnChainSchema: Bytes;
+  readonly metaUpdatePermission: Option<UpDataStructsMetaUpdatePermission>;
+}
+
 /** @name UpDataStructsCreateItemData */
 export interface UpDataStructsCreateItemData extends Struct {
   readonly dummyCreateItemData: u32;
@@ -101,8 +120,9 @@
 }
 
 /** @name UpDataStructsSchemaVersion */
-export interface UpDataStructsSchemaVersion extends Struct {
-  readonly dummySchemaVersion: u32;
+export interface UpDataStructsSchemaVersion extends Enum {
+  readonly isImageUrl: boolean;
+  readonly isUnique: boolean;
 }
 
 /** @name UpDataStructsSponsorshipState */
modifiedtests/src/substrate/substrate-api.tsdiffbeforeafterboth
--- a/tests/src/substrate/substrate-api.ts
+++ b/tests/src/substrate/substrate-api.ts
@@ -95,6 +95,32 @@
   return TransactionStatus.Fail;
 }
 
+export function executeTransaction(api: ApiPromise, sender: IKeyringPair, transaction: SubmittableExtrinsic<'promise'>): Promise<EventRecord[]> {
+  return new Promise(async (res, rej) => {
+    try {
+      await transaction.signAndSend(sender, ({events, status}) => {
+        if (!status.isInBlock && !status.isFinalized) return;
+        for (const {event} of events) {
+          if (api.events.system.ExtrinsicSuccess.is(event)) {
+            res(events);
+          } else if (api.events.system.ExtrinsicFailed.is(event)) {
+            const {data: [error]} = event;
+            if (error.isModule) {
+              const decoded = api.registry.findMetaError(error.asModule);
+              const {method, section} = decoded;
+              rej(new Error(`${section}.${method}`));
+            } else {
+              rej(new Error(error.toString()));
+            }
+          }
+        }
+      });
+    } catch (e) {
+      rej(e);
+    }
+  });
+}
+
 export function
 submitTransactionAsync(sender: IKeyringPair, transaction: SubmittableExtrinsic<ApiTypes>): Promise<EventRecord[]> {
   /* eslint no-async-promise-executor: "off" */
modifiedtests/src/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -49,7 +49,7 @@
     };
   } else if ('Substrate' in input) {
     return input;
-  }else if ('substrate' in input) {
+  } else if ('substrate' in input) {
     return {
       Substrate: (input as any).substrate,
     };
@@ -116,15 +116,15 @@
 
 export interface IChainLimits {
   collectionNumbersLimit: number;
-	accountTokenOwnershipLimit: number;
-	collectionsAdminsLimit: number;
-	customDataLimit: number;
-	nftSponsorTransferTimeout: number;
-	fungibleSponsorTransferTimeout: number;
-	refungibleSponsorTransferTimeout: number;
-	offchainSchemaLimit: number;
-	variableOnChainSchemaLimit: number;
-	constOnChainSchemaLimit: number;
+  accountTokenOwnershipLimit: number;
+  collectionsAdminsLimit: number;
+  customDataLimit: number;
+  nftSponsorTransferTimeout: number;
+  fungibleSponsorTransferTimeout: number;
+  refungibleSponsorTransferTimeout: number;
+  offchainSchemaLimit: number;
+  variableOnChainSchemaLimit: number;
+  constOnChainSchemaLimit: number;
 }
 
 export interface IReFungibleTokenDataType {
@@ -283,7 +283,7 @@
       modeprm = {refungible: null};
     }
 
-    const tx = api.tx.unique.createCollection(strToUTF16(name), strToUTF16(description), strToUTF16(tokenPrefix), modeprm as any);
+    const tx = api.tx.unique.createCollectionEx({name: strToUTF16(name), description: strToUTF16(description), tokenPrefix: strToUTF16(tokenPrefix), mode: modeprm as any});
     const events = await submitTransactionAsync(alicePrivateKey, tx);
     const result = getCreateCollectionResult(events);
 
@@ -329,7 +329,7 @@
 
     // Run the CreateCollection transaction
     const alicePrivateKey = privateKey('//Alice');
-    const tx = api.tx.unique.createCollection(strToUTF16(name), strToUTF16(description), strToUTF16(tokenPrefix), modeprm as any);
+    const tx = api.tx.unique.createCollectionEx({name: strToUTF16(name), description: strToUTF16(description), tokenPrefix: strToUTF16(tokenPrefix), mode: modeprm as any});
     const events = await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected;
     const result = getCreateCollectionResult(events);
 
@@ -557,7 +557,7 @@
 
   await usingApi(async (api) => {
 
-    const tx = api.tx.unique.setTransfersEnabledFlag (collectionId, enabled);
+    const tx = api.tx.unique.setTransfersEnabledFlag(collectionId, enabled);
     const events = await submitTransactionAsync(sender, tx);
     const result = getGenericResult(events);
 
@@ -569,7 +569,7 @@
 
   await usingApi(async (api) => {
 
-    const tx = api.tx.unique.setTransfersEnabledFlag (collectionId, enabled);
+    const tx = api.tx.unique.setTransfersEnabledFlag(collectionId, enabled);
     const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;
     const result = getGenericResult(events);
 
@@ -811,8 +811,7 @@
 }
 
 export async function
-getFreeBalance(account: IKeyringPair) : Promise<bigint>
-{
+getFreeBalance(account: IKeyringPair): Promise<bigint> {
   let balance = 0n;
   await usingApi(async (api) => {
     balance = BigInt((await api.query.system.account(account.address)).data.free.toString());
addedtests/src/xcmTransfer.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/xcmTransfer.test.ts
@@ -0,0 +1,178 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+
+import {WsProvider} from '@polkadot/api';
+import {ApiOptions} from '@polkadot/api/types';
+import {IKeyringPair} from '@polkadot/types/types';
+import privateKey from './substrate/privateKey';
+import usingApi, {submitTransactionAsync} from './substrate/substrate-api';
+import {getGenericResult} from './util/helpers';
+import waitNewBlocks from './substrate/wait-new-blocks';
+import getBalance from './substrate/get-balance';
+import {alicesPublicKey} from './accounts';
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+const UNIQUE_CHAIN = 1000;
+const KARURA_CHAIN = 2000;
+const KARURA_PORT = '9946';
+
+describe('Integration test: Exchanging OPL with Karura', () => {
+  let alice: IKeyringPair;
+  
+  before(async () => {
+    await usingApi(async () => {
+      alice = privateKey('//Alice');
+    });
+
+    const karuraApiOptions: ApiOptions = {
+      provider: new WsProvider('ws://127.0.0.1:' + KARURA_PORT),
+    };
+
+    await usingApi(async (api) => {
+      const destination = {
+        V0: {
+          X2: [
+            'Parent',
+            {
+              Parachain: UNIQUE_CHAIN,
+            },
+          ],
+        },
+      };
+
+      const metadata =
+      {
+        name: 'OPL',
+        symbol: 'OPL',
+        decimals: 18,
+        minimalBalance: 1,
+      };
+
+      const tx = api.tx.assetRegistry.registerForeignAsset(destination, metadata);
+      const sudoTx = api.tx.sudo.sudo(tx as any);
+      const events = await submitTransactionAsync(alice, sudoTx);
+      const result = getGenericResult(events);
+      expect(result.success).to.be.true;
+    }, karuraApiOptions);
+  });
+
+  it('Should connect and send OPL to Karura', async () => {
+    let balanceOnKaruraBefore: bigint;
+    
+    await usingApi(async (api) => {
+      const {free} = (await api.query.tokens.accounts(alice.addressRaw, {ForeignAsset: 0})).toJSON() as any;
+      balanceOnKaruraBefore = free;
+    }, {provider: new WsProvider('ws://127.0.0.1:' + KARURA_PORT)});
+
+    await usingApi(async (api) => {
+      const destination = {
+        V0: {
+          X2: [
+            'Parent',
+            {
+              Parachain: KARURA_CHAIN,
+            },
+          ],
+        },
+      };
+
+      const beneficiary = {
+        V0: {
+          X1: {
+            AccountId32: {
+              network: 'Any',
+              id: alice.addressRaw,
+            },
+          },
+        },
+      };
+
+      const assets = {
+        V1: [
+          {
+            id: {
+              Concrete: {
+                parents: 0,
+                interior: 'Here',
+              },
+            },
+            fun: {
+              Fungible: 5000000000,
+            },
+          },
+        ],
+      };
+
+      const feeAssetItem = 0;
+
+      const weightLimit = {
+        Limited: 5000000000,
+      };
+
+      const tx = api.tx.polkadotXcm.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit);
+      const events = await submitTransactionAsync(alice, tx);
+      const result = getGenericResult(events);
+      expect(result.success).to.be.true;
+    });
+
+    await usingApi(async (api) => {
+      // todo do something about instant sealing, where there might not be any new blocks
+      await waitNewBlocks(api, 3);
+      const {free} = (await api.query.tokens.accounts(alice.addressRaw, {ForeignAsset: 0})).toJSON() as any;
+      expect(free > balanceOnKaruraBefore).to.be.true;
+    }, {provider: new WsProvider('ws://127.0.0.1:' + KARURA_PORT)});
+  });
+
+  it('Should connect to Karura and send OPL back', async () => {
+    let balanceBefore: bigint;
+    
+    await usingApi(async (api) => {
+      [balanceBefore] = await getBalance(api, [alicesPublicKey]);
+    });
+
+    await usingApi(async (api) => {
+      const destination = {
+        V0: {
+          X3: [
+            'Parent',
+            {
+              Parachain: UNIQUE_CHAIN,
+            },
+            {
+              AccountId32: {
+                network: 'Any',
+                id: alice.addressRaw,
+              },
+            },
+          ],
+        },
+      };
+
+      const id = {
+        ForeignAsset: 0,
+      };
+
+      const amount = 5000000000;
+      const destWeight = 50000000;
+
+      const tx = api.tx.xTokens.transfer(id, amount, destination, destWeight);
+      const events = await submitTransactionAsync(alice, tx);
+      const result = getGenericResult(events);
+      expect(result.success).to.be.true;
+    }, {provider: new WsProvider('ws://127.0.0.1:' + KARURA_PORT)});
+
+    await usingApi(async (api) => {
+      // todo do something about instant sealing, where there might not be any new blocks
+      await waitNewBlocks(api, 3);
+      const [balanceAfter] = await getBalance(api, [alicesPublicKey]);
+      expect(balanceAfter > balanceBefore).to.be.true;
+    });
+  });
+});
\ No newline at end of file