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

difftreelog

feature: Added `transfer_cross` in eth functions.

PraetorP2022-11-14parent: #20c695f.patch.diff
in: master

22 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6121,7 +6121,7 @@
 
 [[package]]
 name = "pallet-fungible"
-version = "0.1.6"
+version = "0.1.7"
 dependencies = [
  "ethereum",
  "evm-coder",
@@ -6376,7 +6376,7 @@
 
 [[package]]
 name = "pallet-nonfungible"
-version = "0.1.8"
+version = "0.1.9"
 dependencies = [
  "ethereum",
  "evm-coder",
@@ -6498,7 +6498,7 @@
 
 [[package]]
 name = "pallet-refungible"
-version = "0.2.7"
+version = "0.2.8"
 dependencies = [
  "derivative",
  "ethereum",
modifiedpallets/fungible/CHANGELOG.mddiffbeforeafterboth
--- a/pallets/fungible/CHANGELOG.md
+++ b/pallets/fungible/CHANGELOG.md
@@ -2,22 +2,32 @@
 
 All notable changes to this project will be documented in this file.
 
+<!-- bureaucrate goes here -->
+
+## [0.1.7] - 2022-11-14
+
+### Changed
+
+- Added `transfer_cross` in eth functions.
+
 ## [0.1.6] - 2022-11-02
+
 ### Changed
- - Use named structure `EthCrossAccount` in eth functions.
 
+- Use named structure `EthCrossAccount` in eth functions.
+
 ## [0.1.5] - 2022-08-29
 
 ### Added
 
- - Implementation of `mint` and `mint_bulk` methods for ERC20 API.
+- Implementation of `mint` and `mint_bulk` methods for ERC20 API.
 
 ## [v0.1.4] - 2022-08-24
 
 ### Change
- - Add bound `AsRef<[u8; 32]>` to `T::CrossAccountId`.
 
-<!-- bureaucrate goes here -->
+- Add bound `AsRef<[u8; 32]>` to `T::CrossAccountId`.
+
 ## [v0.1.3] 2022-08-16
 
 ### Other changes
@@ -41,11 +51,11 @@
 
 ### Fixed
 
- - Issue with ItemCreated event containing total supply of tokens instead minted amount
+- Issue with ItemCreated event containing total supply of tokens instead minted amount
 
 ## [0.1.1] - 2022-07-14
 
 ### Added
 
- - Implementation of RPC method `token_owners` returning 10 owners in no particular order.
-    This was an internal request to improve the web interface and support fractionalization event.
+- Implementation of RPC method `token_owners` returning 10 owners in no particular order.
+  This was an internal request to improve the web interface and support fractionalization event.
modifiedpallets/fungible/Cargo.tomldiffbeforeafterboth
--- a/pallets/fungible/Cargo.toml
+++ b/pallets/fungible/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "pallet-fungible"
-version = "0.1.6"
+version = "0.1.7"
 license = "GPLv3"
 edition = "2021"
 
modifiedpallets/fungible/src/erc.rsdiffbeforeafterboth
before · pallets/fungible/src/erc.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! ERC-20 standart support implementation.1819extern crate alloc;20use core::char::{REPLACEMENT_CHARACTER, decode_utf16};21use core::convert::TryInto;22use evm_coder::{23	abi::AbiType, ToLog, execution::*, generate_stubgen, solidity_interface, types::*, weight,24};25use up_data_structs::CollectionMode;26use pallet_common::erc::{CommonEvmHandler, PrecompileResult};27use sp_std::vec::Vec;28use pallet_evm::{account::CrossAccountId, PrecompileHandle};29use pallet_evm_coder_substrate::{call, dispatch_to_evm};30use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};31use pallet_common::{CollectionHandle, erc::CollectionCall};3233use crate::{34	Allowance, Balance, Config, FungibleHandle, Pallet, SelfWeightOf, TotalSupply,35	weights::WeightInfo,36};3738#[derive(ToLog)]39pub enum ERC20Events {40	Transfer {41		#[indexed]42		from: address,43		#[indexed]44		to: address,45		value: uint256,46	},47	Approval {48		#[indexed]49		owner: address,50		#[indexed]51		spender: address,52		value: uint256,53	},54}5556#[solidity_interface(name = ERC20, events(ERC20Events))]57impl<T: Config> FungibleHandle<T> {58	fn name(&self) -> Result<string> {59		Ok(decode_utf16(self.name.iter().copied())60			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))61			.collect::<string>())62	}63	fn symbol(&self) -> Result<string> {64		Ok(string::from_utf8_lossy(&self.token_prefix).into())65	}66	fn total_supply(&self) -> Result<uint256> {67		self.consume_store_reads(1)?;68		Ok(<TotalSupply<T>>::get(self.id).into())69	}7071	fn decimals(&self) -> Result<uint8> {72		Ok(if let CollectionMode::Fungible(decimals) = &self.mode {73			*decimals74		} else {75			unreachable!()76		})77	}78	fn balance_of(&self, owner: address) -> Result<uint256> {79		self.consume_store_reads(1)?;80		let owner = T::CrossAccountId::from_eth(owner);81		let balance = <Balance<T>>::get((self.id, owner));82		Ok(balance.into())83	}84	#[weight(<SelfWeightOf<T>>::transfer())]85	fn transfer(&mut self, caller: caller, to: address, amount: uint256) -> Result<bool> {86		let caller = T::CrossAccountId::from_eth(caller);87		let to = T::CrossAccountId::from_eth(to);88		let amount = amount.try_into().map_err(|_| "amount overflow")?;89		let budget = self90			.recorder91			.weight_calls_budget(<StructureWeight<T>>::find_parent());9293		<Pallet<T>>::transfer(self, &caller, &to, amount, &budget).map_err(|_| "transfer error")?;94		Ok(true)95	}96	#[weight(<SelfWeightOf<T>>::transfer_from())]97	fn transfer_from(98		&mut self,99		caller: caller,100		from: address,101		to: address,102		amount: uint256,103	) -> Result<bool> {104		let caller = T::CrossAccountId::from_eth(caller);105		let from = T::CrossAccountId::from_eth(from);106		let to = T::CrossAccountId::from_eth(to);107		let amount = amount.try_into().map_err(|_| "amount overflow")?;108		let budget = self109			.recorder110			.weight_calls_budget(<StructureWeight<T>>::find_parent());111112		<Pallet<T>>::transfer_from(self, &caller, &from, &to, amount, &budget)113			.map_err(dispatch_to_evm::<T>)?;114		Ok(true)115	}116	#[weight(<SelfWeightOf<T>>::approve())]117	fn approve(&mut self, caller: caller, spender: address, amount: uint256) -> Result<bool> {118		let caller = T::CrossAccountId::from_eth(caller);119		let spender = T::CrossAccountId::from_eth(spender);120		let amount = amount.try_into().map_err(|_| "amount overflow")?;121122		<Pallet<T>>::set_allowance(self, &caller, &spender, amount)123			.map_err(dispatch_to_evm::<T>)?;124		Ok(true)125	}126	fn allowance(&self, owner: address, spender: address) -> Result<uint256> {127		self.consume_store_reads(1)?;128		let owner = T::CrossAccountId::from_eth(owner);129		let spender = T::CrossAccountId::from_eth(spender);130131		Ok(<Allowance<T>>::get((self.id, owner, spender)).into())132	}133}134135#[solidity_interface(name = ERC20Mintable)]136impl<T: Config> FungibleHandle<T> {137	/// Mint tokens for `to` account.138	/// @param to account that will receive minted tokens139	/// @param amount amount of tokens to mint140	#[weight(<SelfWeightOf<T>>::create_item())]141	fn mint(&mut self, caller: caller, to: address, amount: uint256) -> Result<bool> {142		let caller = T::CrossAccountId::from_eth(caller);143		let to = T::CrossAccountId::from_eth(to);144		let amount = amount.try_into().map_err(|_| "amount overflow")?;145		let budget = self146			.recorder147			.weight_calls_budget(<StructureWeight<T>>::find_parent());148		<Pallet<T>>::create_item(&self, &caller, (to, amount), &budget)149			.map_err(dispatch_to_evm::<T>)?;150		Ok(true)151	}152}153154#[solidity_interface(name = ERC20UniqueExtensions)]155impl<T: Config> FungibleHandle<T>156where157	T::AccountId: From<[u8; 32]>,158{159	#[weight(<SelfWeightOf<T>>::approve())]160	fn approve_cross(161		&mut self,162		caller: caller,163		spender: EthCrossAccount,164		amount: uint256,165	) -> Result<bool> {166		let caller = T::CrossAccountId::from_eth(caller);167		let spender = spender.into_sub_cross_account::<T>()?;168		let amount = amount.try_into().map_err(|_| "amount overflow")?;169170		<Pallet<T>>::set_allowance(self, &caller, &spender, amount)171			.map_err(dispatch_to_evm::<T>)?;172		Ok(true)173	}174175	/// Burn tokens from account176	/// @dev Function that burns an `amount` of the tokens of a given account,177	/// deducting from the sender's allowance for said account.178	/// @param from The account whose tokens will be burnt.179	/// @param amount The amount that will be burnt.180	#[weight(<SelfWeightOf<T>>::burn_from())]181	fn burn_from(&mut self, caller: caller, from: address, amount: uint256) -> Result<bool> {182		let caller = T::CrossAccountId::from_eth(caller);183		let from = T::CrossAccountId::from_eth(from);184		let amount = amount.try_into().map_err(|_| "amount overflow")?;185		let budget = self186			.recorder187			.weight_calls_budget(<StructureWeight<T>>::find_parent());188189		<Pallet<T>>::burn_from(self, &caller, &from, amount, &budget)190			.map_err(dispatch_to_evm::<T>)?;191		Ok(true)192	}193194	/// Burn tokens from account195	/// @dev Function that burns an `amount` of the tokens of a given account,196	/// deducting from the sender's allowance for said account.197	/// @param from The account whose tokens will be burnt.198	/// @param amount The amount that will be burnt.199	#[weight(<SelfWeightOf<T>>::burn_from())]200	fn burn_from_cross(201		&mut self,202		caller: caller,203		from: EthCrossAccount,204		amount: uint256,205	) -> Result<bool> {206		let caller = T::CrossAccountId::from_eth(caller);207		let from = from.into_sub_cross_account::<T>()?;208		let amount = amount.try_into().map_err(|_| "amount overflow")?;209		let budget = self210			.recorder211			.weight_calls_budget(<StructureWeight<T>>::find_parent());212213		<Pallet<T>>::burn_from(self, &caller, &from, amount, &budget)214			.map_err(dispatch_to_evm::<T>)?;215		Ok(true)216	}217218	/// Mint tokens for multiple accounts.219	/// @param amounts array of pairs of account address and amount220	#[weight(<SelfWeightOf<T>>::create_multiple_items_ex(amounts.len() as u32))]221	fn mint_bulk(&mut self, caller: caller, amounts: Vec<(address, uint256)>) -> Result<bool> {222		let caller = T::CrossAccountId::from_eth(caller);223		let budget = self224			.recorder225			.weight_calls_budget(<StructureWeight<T>>::find_parent());226		let amounts = amounts227			.into_iter()228			.map(|(to, amount)| {229				Ok((230					T::CrossAccountId::from_eth(to),231					amount.try_into().map_err(|_| "amount overflow")?,232				))233			})234			.collect::<Result<_>>()?;235236		<Pallet<T>>::create_multiple_items(&self, &caller, amounts, &budget)237			.map_err(dispatch_to_evm::<T>)?;238		Ok(true)239	}240241	#[weight(<SelfWeightOf<T>>::transfer_from())]242	fn transfer_from_cross(243		&mut self,244		caller: caller,245		from: EthCrossAccount,246		to: EthCrossAccount,247		amount: uint256,248	) -> Result<bool> {249		let caller = T::CrossAccountId::from_eth(caller);250		let from = from.into_sub_cross_account::<T>()?;251		let to = to.into_sub_cross_account::<T>()?;252		let amount = amount.try_into().map_err(|_| "amount overflow")?;253		let budget = self254			.recorder255			.weight_calls_budget(<StructureWeight<T>>::find_parent());256257		<Pallet<T>>::transfer_from(self, &caller, &from, &to, amount, &budget)258			.map_err(dispatch_to_evm::<T>)?;259		Ok(true)260	}261}262263#[solidity_interface(264	name = UniqueFungible,265	is(266		ERC20,267		ERC20Mintable,268		ERC20UniqueExtensions,269		Collection(via(common_mut returns CollectionHandle<T>)),270	)271)]272impl<T: Config> FungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}273274generate_stubgen!(gen_impl, UniqueFungibleCall<()>, true);275generate_stubgen!(gen_iface, UniqueFungibleCall<()>, false);276277impl<T: Config> CommonEvmHandler for FungibleHandle<T>278where279	T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,280{281	const CODE: &'static [u8] = include_bytes!("./stubs/UniqueFungible.raw");282283	fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {284		call::<T, UniqueFungibleCall<T>, _, _>(handle, self)285	}286}
after · pallets/fungible/src/erc.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! ERC-20 standart support implementation.1819extern crate alloc;20use core::char::{REPLACEMENT_CHARACTER, decode_utf16};21use core::convert::TryInto;22use evm_coder::{23	abi::AbiType, ToLog, execution::*, generate_stubgen, solidity_interface, types::*, weight,24};25use up_data_structs::CollectionMode;26use pallet_common::erc::{CommonEvmHandler, PrecompileResult};27use sp_std::vec::Vec;28use pallet_evm::{account::CrossAccountId, PrecompileHandle};29use pallet_evm_coder_substrate::{call, dispatch_to_evm};30use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};31use pallet_common::{CollectionHandle, erc::CollectionCall};3233use crate::{34	Allowance, Balance, Config, FungibleHandle, Pallet, SelfWeightOf, TotalSupply,35	weights::WeightInfo,36};3738#[derive(ToLog)]39pub enum ERC20Events {40	Transfer {41		#[indexed]42		from: address,43		#[indexed]44		to: address,45		value: uint256,46	},47	Approval {48		#[indexed]49		owner: address,50		#[indexed]51		spender: address,52		value: uint256,53	},54}5556#[solidity_interface(name = ERC20, events(ERC20Events))]57impl<T: Config> FungibleHandle<T> {58	fn name(&self) -> Result<string> {59		Ok(decode_utf16(self.name.iter().copied())60			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))61			.collect::<string>())62	}63	fn symbol(&self) -> Result<string> {64		Ok(string::from_utf8_lossy(&self.token_prefix).into())65	}66	fn total_supply(&self) -> Result<uint256> {67		self.consume_store_reads(1)?;68		Ok(<TotalSupply<T>>::get(self.id).into())69	}7071	fn decimals(&self) -> Result<uint8> {72		Ok(if let CollectionMode::Fungible(decimals) = &self.mode {73			*decimals74		} else {75			unreachable!()76		})77	}78	fn balance_of(&self, owner: address) -> Result<uint256> {79		self.consume_store_reads(1)?;80		let owner = T::CrossAccountId::from_eth(owner);81		let balance = <Balance<T>>::get((self.id, owner));82		Ok(balance.into())83	}84	#[weight(<SelfWeightOf<T>>::transfer())]85	fn transfer(&mut self, caller: caller, to: address, amount: uint256) -> Result<bool> {86		let caller = T::CrossAccountId::from_eth(caller);87		let to = T::CrossAccountId::from_eth(to);88		let amount = amount.try_into().map_err(|_| "amount overflow")?;89		let budget = self90			.recorder91			.weight_calls_budget(<StructureWeight<T>>::find_parent());9293		<Pallet<T>>::transfer(self, &caller, &to, amount, &budget).map_err(|_| "transfer error")?;94		Ok(true)95	}9697	#[weight(<SelfWeightOf<T>>::transfer_from())]98	fn transfer_from(99		&mut self,100		caller: caller,101		from: address,102		to: address,103		amount: uint256,104	) -> Result<bool> {105		let caller = T::CrossAccountId::from_eth(caller);106		let from = T::CrossAccountId::from_eth(from);107		let to = T::CrossAccountId::from_eth(to);108		let amount = amount.try_into().map_err(|_| "amount overflow")?;109		let budget = self110			.recorder111			.weight_calls_budget(<StructureWeight<T>>::find_parent());112113		<Pallet<T>>::transfer_from(self, &caller, &from, &to, amount, &budget)114			.map_err(dispatch_to_evm::<T>)?;115		Ok(true)116	}117	#[weight(<SelfWeightOf<T>>::approve())]118	fn approve(&mut self, caller: caller, spender: address, amount: uint256) -> Result<bool> {119		let caller = T::CrossAccountId::from_eth(caller);120		let spender = T::CrossAccountId::from_eth(spender);121		let amount = amount.try_into().map_err(|_| "amount overflow")?;122123		<Pallet<T>>::set_allowance(self, &caller, &spender, amount)124			.map_err(dispatch_to_evm::<T>)?;125		Ok(true)126	}127	fn allowance(&self, owner: address, spender: address) -> Result<uint256> {128		self.consume_store_reads(1)?;129		let owner = T::CrossAccountId::from_eth(owner);130		let spender = T::CrossAccountId::from_eth(spender);131132		Ok(<Allowance<T>>::get((self.id, owner, spender)).into())133	}134}135136#[solidity_interface(name = ERC20Mintable)]137impl<T: Config> FungibleHandle<T> {138	/// Mint tokens for `to` account.139	/// @param to account that will receive minted tokens140	/// @param amount amount of tokens to mint141	#[weight(<SelfWeightOf<T>>::create_item())]142	fn mint(&mut self, caller: caller, to: address, amount: uint256) -> Result<bool> {143		let caller = T::CrossAccountId::from_eth(caller);144		let to = T::CrossAccountId::from_eth(to);145		let amount = amount.try_into().map_err(|_| "amount overflow")?;146		let budget = self147			.recorder148			.weight_calls_budget(<StructureWeight<T>>::find_parent());149		<Pallet<T>>::create_item(&self, &caller, (to, amount), &budget)150			.map_err(dispatch_to_evm::<T>)?;151		Ok(true)152	}153}154155#[solidity_interface(name = ERC20UniqueExtensions)]156impl<T: Config> FungibleHandle<T>157where158	T::AccountId: From<[u8; 32]>,159{160	#[weight(<SelfWeightOf<T>>::approve())]161	fn approve_cross(162		&mut self,163		caller: caller,164		spender: EthCrossAccount,165		amount: uint256,166	) -> Result<bool> {167		let caller = T::CrossAccountId::from_eth(caller);168		let spender = spender.into_sub_cross_account::<T>()?;169		let amount = amount.try_into().map_err(|_| "amount overflow")?;170171		<Pallet<T>>::set_allowance(self, &caller, &spender, amount)172			.map_err(dispatch_to_evm::<T>)?;173		Ok(true)174	}175176	/// Burn tokens from account177	/// @dev Function that burns an `amount` of the tokens of a given account,178	/// deducting from the sender's allowance for said account.179	/// @param from The account whose tokens will be burnt.180	/// @param amount The amount that will be burnt.181	#[weight(<SelfWeightOf<T>>::burn_from())]182	fn burn_from(&mut self, caller: caller, from: address, amount: uint256) -> Result<bool> {183		let caller = T::CrossAccountId::from_eth(caller);184		let from = T::CrossAccountId::from_eth(from);185		let amount = amount.try_into().map_err(|_| "amount overflow")?;186		let budget = self187			.recorder188			.weight_calls_budget(<StructureWeight<T>>::find_parent());189190		<Pallet<T>>::burn_from(self, &caller, &from, amount, &budget)191			.map_err(dispatch_to_evm::<T>)?;192		Ok(true)193	}194195	/// Burn tokens from account196	/// @dev Function that burns an `amount` of the tokens of a given account,197	/// deducting from the sender's allowance for said account.198	/// @param from The account whose tokens will be burnt.199	/// @param amount The amount that will be burnt.200	#[weight(<SelfWeightOf<T>>::burn_from())]201	fn burn_from_cross(202		&mut self,203		caller: caller,204		from: EthCrossAccount,205		amount: uint256,206	) -> Result<bool> {207		let caller = T::CrossAccountId::from_eth(caller);208		let from = from.into_sub_cross_account::<T>()?;209		let amount = amount.try_into().map_err(|_| "amount overflow")?;210		let budget = self211			.recorder212			.weight_calls_budget(<StructureWeight<T>>::find_parent());213214		<Pallet<T>>::burn_from(self, &caller, &from, amount, &budget)215			.map_err(dispatch_to_evm::<T>)?;216		Ok(true)217	}218219	/// Mint tokens for multiple accounts.220	/// @param amounts array of pairs of account address and amount221	#[weight(<SelfWeightOf<T>>::create_multiple_items_ex(amounts.len() as u32))]222	fn mint_bulk(&mut self, caller: caller, amounts: Vec<(address, uint256)>) -> Result<bool> {223		let caller = T::CrossAccountId::from_eth(caller);224		let budget = self225			.recorder226			.weight_calls_budget(<StructureWeight<T>>::find_parent());227		let amounts = amounts228			.into_iter()229			.map(|(to, amount)| {230				Ok((231					T::CrossAccountId::from_eth(to),232					amount.try_into().map_err(|_| "amount overflow")?,233				))234			})235			.collect::<Result<_>>()?;236237		<Pallet<T>>::create_multiple_items(&self, &caller, amounts, &budget)238			.map_err(dispatch_to_evm::<T>)?;239		Ok(true)240	}241242	#[weight(<SelfWeightOf<T>>::transfer())]243	fn transfer_cross(244		&mut self,245		caller: caller,246		to: EthCrossAccount,247		amount: uint256,248	) -> Result<bool> {249		let caller = T::CrossAccountId::from_eth(caller);250		let to = to.into_sub_cross_account::<T>()?;251		let amount = amount.try_into().map_err(|_| "amount overflow")?;252		let budget = self253			.recorder254			.weight_calls_budget(<StructureWeight<T>>::find_parent());255256		<Pallet<T>>::transfer(self, &caller, &to, amount, &budget).map_err(|_| "transfer error")?;257		Ok(true)258	}259260	#[weight(<SelfWeightOf<T>>::transfer_from())]261	fn transfer_from_cross(262		&mut self,263		caller: caller,264		from: EthCrossAccount,265		to: EthCrossAccount,266		amount: uint256,267	) -> Result<bool> {268		let caller = T::CrossAccountId::from_eth(caller);269		let from = from.into_sub_cross_account::<T>()?;270		let to = to.into_sub_cross_account::<T>()?;271		let amount = amount.try_into().map_err(|_| "amount overflow")?;272		let budget = self273			.recorder274			.weight_calls_budget(<StructureWeight<T>>::find_parent());275276		<Pallet<T>>::transfer_from(self, &caller, &from, &to, amount, &budget)277			.map_err(dispatch_to_evm::<T>)?;278		Ok(true)279	}280}281282#[solidity_interface(283	name = UniqueFungible,284	is(285		ERC20,286		ERC20Mintable,287		ERC20UniqueExtensions,288		Collection(via(common_mut returns CollectionHandle<T>)),289	)290)]291impl<T: Config> FungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}292293generate_stubgen!(gen_impl, UniqueFungibleCall<()>, true);294generate_stubgen!(gen_iface, UniqueFungibleCall<()>, false);295296impl<T: Config> CommonEvmHandler for FungibleHandle<T>297where298	T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,299{300	const CODE: &'static [u8] = include_bytes!("./stubs/UniqueFungible.raw");301302	fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {303		call::<T, UniqueFungibleCall<T>, _, _>(handle, self)304	}305}
modifiedpallets/fungible/src/stubs/UniqueFungible.soldiffbeforeafterboth
--- a/pallets/fungible/src/stubs/UniqueFungible.sol
+++ b/pallets/fungible/src/stubs/UniqueFungible.sol
@@ -38,7 +38,7 @@
 	/// @param properties Vector of properties key/value pair.
 	/// @dev EVM selector for this function is: 0x50b26b2a,
 	///  or in textual repr: setCollectionProperties((string,bytes)[])
-	function setCollectionProperties(Tuple14[] memory properties) public {
+	function setCollectionProperties(Tuple15[] memory properties) public {
 		require(false, stub_error);
 		properties;
 		dummy = 0;
@@ -87,11 +87,11 @@
 	/// @return Vector of properties key/value pairs.
 	/// @dev EVM selector for this function is: 0x285fb8e6,
 	///  or in textual repr: collectionProperties(string[])
-	function collectionProperties(string[] memory keys) public view returns (Tuple14[] memory) {
+	function collectionProperties(string[] memory keys) public view returns (Tuple15[] memory) {
 		require(false, stub_error);
 		keys;
 		dummy;
-		return new Tuple14[](0);
+		return new Tuple15[](0);
 	}
 
 	/// Set the sponsor of the collection.
@@ -439,12 +439,12 @@
 }
 
 /// @dev anonymous struct
-struct Tuple14 {
+struct Tuple15 {
 	string field_0;
 	bytes field_1;
 }
 
-/// @dev the ERC-165 identifier for this interface is 0x032e5926
+/// @dev the ERC-165 identifier for this interface is 0x29f4dcd9
 contract ERC20UniqueExtensions is Dummy, ERC165 {
 	/// @dev EVM selector for this function is: 0x0ecd0ab0,
 	///  or in textual repr: approveCross((address,uint256),uint256)
@@ -497,6 +497,16 @@
 		return false;
 	}
 
+	/// @dev EVM selector for this function is: 0x2ada85ff,
+	///  or in textual repr: transferCross((address,uint256),uint256)
+	function transferCross(EthCrossAccount memory to, uint256 amount) public returns (bool) {
+		require(false, stub_error);
+		to;
+		amount;
+		dummy = 0;
+		return false;
+	}
+
 	/// @dev EVM selector for this function is: 0xd5cf430b,
 	///  or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256)
 	function transferFromCross(
modifiedpallets/nonfungible/CHANGELOG.mddiffbeforeafterboth
--- a/pallets/nonfungible/CHANGELOG.md
+++ b/pallets/nonfungible/CHANGELOG.md
@@ -4,6 +4,12 @@
 
 <!-- bureaucrate goes here -->
 
+## [0.1.9] - 2022-11-14
+
+### Changed
+
+- Added `transfer_cross` in eth functions.
+
 ## [v0.1.8] - 2022-11-11
 
 ### Changed
modifiedpallets/nonfungible/Cargo.tomldiffbeforeafterboth
--- a/pallets/nonfungible/Cargo.toml
+++ b/pallets/nonfungible/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "pallet-nonfungible"
-version = "0.1.8"
+version = "0.1.9"
 license = "GPLv3"
 edition = "2021"
 
modifiedpallets/nonfungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -738,7 +738,25 @@
 		<Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;
 		Ok(())
 	}
+	
+	/// @notice Transfer ownership of an NFT
+	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
+	///  is the zero address. Throws if `tokenId` is not a valid NFT.
+	/// @param to The new owner
+	/// @param tokenId The NFT to transfer
+	#[weight(<SelfWeightOf<T>>::transfer())]
+	fn transfer_cross(&mut self, caller: caller, to: EthCrossAccount, token_id: uint256) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let to = to.into_sub_cross_account::<T>()?;
+		let token = token_id.try_into()?;
+		let budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
+		<Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;
+		Ok(())
+	}
+	
 	/// @notice Transfer ownership of an NFT from cross account address to cross account address
 	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
 	///  is the zero address. Throws if `tokenId` is not a valid NFT.
modifiedpallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth
--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -67,7 +67,7 @@
 	/// @param properties settable properties
 	/// @dev EVM selector for this function is: 0x14ed3a6e,
 	///  or in textual repr: setProperties(uint256,(string,bytes)[])
-	function setProperties(uint256 tokenId, Tuple21[] memory properties) public {
+	function setProperties(uint256 tokenId, Tuple22[] memory properties) public {
 		require(false, stub_error);
 		tokenId;
 		properties;
@@ -137,7 +137,7 @@
 	/// @param properties Vector of properties key/value pair.
 	/// @dev EVM selector for this function is: 0x50b26b2a,
 	///  or in textual repr: setCollectionProperties((string,bytes)[])
-	function setCollectionProperties(Tuple21[] memory properties) public {
+	function setCollectionProperties(Tuple22[] memory properties) public {
 		require(false, stub_error);
 		properties;
 		dummy = 0;
@@ -186,11 +186,11 @@
 	/// @return Vector of properties key/value pairs.
 	/// @dev EVM selector for this function is: 0x285fb8e6,
 	///  or in textual repr: collectionProperties(string[])
-	function collectionProperties(string[] memory keys) public view returns (Tuple21[] memory) {
+	function collectionProperties(string[] memory keys) public view returns (Tuple22[] memory) {
 		require(false, stub_error);
 		keys;
 		dummy;
-		return new Tuple21[](0);
+		return new Tuple22[](0);
 	}
 
 	/// Set the sponsor of the collection.
@@ -251,10 +251,10 @@
 	/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
 	/// @dev EVM selector for this function is: 0x6ec0a9f1,
 	///  or in textual repr: collectionSponsor()
-	function collectionSponsor() public view returns (Tuple24 memory) {
+	function collectionSponsor() public view returns (Tuple25 memory) {
 		require(false, stub_error);
 		dummy;
-		return Tuple24(0x0000000000000000000000000000000000000000, 0);
+		return Tuple25(0x0000000000000000000000000000000000000000, 0);
 	}
 
 	/// Set limits for the collection.
@@ -538,13 +538,13 @@
 }
 
 /// @dev anonymous struct
-struct Tuple24 {
+struct Tuple25 {
 	address field_0;
 	uint256 field_1;
 }
 
 /// @dev anonymous struct
-struct Tuple21 {
+struct Tuple22 {
 	string field_0;
 	bytes field_1;
 }
@@ -693,7 +693,7 @@
 }
 
 /// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0x244543ee
+/// @dev the ERC-165 identifier for this interface is 0x0e9fc611
 contract ERC721UniqueExtensions is Dummy, ERC165 {
 	/// @notice A descriptive name for a collection of NFTs in this contract
 	/// @dev EVM selector for this function is: 0x06fdde03,
@@ -742,6 +742,20 @@
 		dummy = 0;
 	}
 
+	/// @notice Transfer ownership of an NFT
+	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
+	///  is the zero address. Throws if `tokenId` is not a valid NFT.
+	/// @param to The new owner
+	/// @param tokenId The NFT to transfer
+	/// @dev EVM selector for this function is: 0x2ada85ff,
+	///  or in textual repr: transferCross((address,uint256),uint256)
+	function transferCross(EthCrossAccount memory to, uint256 tokenId) public {
+		require(false, stub_error);
+		to;
+		tokenId;
+		dummy = 0;
+	}
+
 	/// @notice Transfer ownership of an NFT from cross account address to cross account address
 	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
 	///  is the zero address. Throws if `tokenId` is not a valid NFT.
@@ -822,7 +836,7 @@
 	// /// @param tokens array of pairs of token ID and token URI for minted tokens
 	// /// @dev EVM selector for this function is: 0x36543006,
 	// ///  or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[])
-	// function mintBulkWithTokenURI(address to, Tuple10[] memory tokens) public returns (bool) {
+	// function mintBulkWithTokenURI(address to, Tuple11[] memory tokens) public returns (bool) {
 	// 	require(false, stub_error);
 	// 	to;
 	// 	tokens;
@@ -833,7 +847,7 @@
 }
 
 /// @dev anonymous struct
-struct Tuple10 {
+struct Tuple11 {
 	uint256 field_0;
 	string field_1;
 }
modifiedpallets/refungible/CHANGELOG.mddiffbeforeafterboth
--- a/pallets/refungible/CHANGELOG.md
+++ b/pallets/refungible/CHANGELOG.md
@@ -4,6 +4,12 @@
 
 <!-- bureaucrate goes here -->
 
+## [0.2.8] - 2022-11-14
+
+### Changed
+
+- Added `transfer_cross` in eth functions.
+
 ## [v0.2.7] - 2022-11-11
 
 ### Changed
modifiedpallets/refungible/Cargo.tomldiffbeforeafterboth
--- a/pallets/refungible/Cargo.toml
+++ b/pallets/refungible/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "pallet-refungible"
-version = "0.2.7"
+version = "0.2.8"
 license = "GPLv3"
 edition = "2021"
 
modifiedpallets/refungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -756,6 +756,29 @@
 	/// @param to The new owner
 	/// @param tokenId The RFT to transfer
 	#[weight(<SelfWeightOf<T>>::transfer_creating_removing())]
+	fn transfer_cross(&mut self, caller: caller, to: EthCrossAccount, token_id: uint256) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let to = to.into_sub_cross_account::<T>()?;
+		let token = token_id.try_into()?;
+		let budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+		let balance = balance(self, token, &caller)?;
+		ensure_single_owner(self, token, balance)?;
+
+		<Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(())
+	}
+
+	/// @notice Transfer ownership of an RFT
+	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
+	///  is the zero address. Throws if `tokenId` is not a valid RFT.
+	///  Throws if RFT pieces have multiple owners.
+	/// @param to The new owner
+	/// @param tokenId The RFT to transfer
+	#[weight(<SelfWeightOf<T>>::transfer_creating_removing())]
 	fn transfer_from_cross(
 		&mut self,
 		caller: caller,
modifiedpallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth
--- a/pallets/refungible/src/stubs/UniqueRefungible.sol
+++ b/pallets/refungible/src/stubs/UniqueRefungible.sol
@@ -67,7 +67,7 @@
 	/// @param properties settable properties
 	/// @dev EVM selector for this function is: 0x14ed3a6e,
 	///  or in textual repr: setProperties(uint256,(string,bytes)[])
-	function setProperties(uint256 tokenId, Tuple20[] memory properties) public {
+	function setProperties(uint256 tokenId, Tuple21[] memory properties) public {
 		require(false, stub_error);
 		tokenId;
 		properties;
@@ -137,7 +137,7 @@
 	/// @param properties Vector of properties key/value pair.
 	/// @dev EVM selector for this function is: 0x50b26b2a,
 	///  or in textual repr: setCollectionProperties((string,bytes)[])
-	function setCollectionProperties(Tuple20[] memory properties) public {
+	function setCollectionProperties(Tuple21[] memory properties) public {
 		require(false, stub_error);
 		properties;
 		dummy = 0;
@@ -186,11 +186,11 @@
 	/// @return Vector of properties key/value pairs.
 	/// @dev EVM selector for this function is: 0x285fb8e6,
 	///  or in textual repr: collectionProperties(string[])
-	function collectionProperties(string[] memory keys) public view returns (Tuple20[] memory) {
+	function collectionProperties(string[] memory keys) public view returns (Tuple21[] memory) {
 		require(false, stub_error);
 		keys;
 		dummy;
-		return new Tuple20[](0);
+		return new Tuple21[](0);
 	}
 
 	/// Set the sponsor of the collection.
@@ -251,10 +251,10 @@
 	/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
 	/// @dev EVM selector for this function is: 0x6ec0a9f1,
 	///  or in textual repr: collectionSponsor()
-	function collectionSponsor() public view returns (Tuple23 memory) {
+	function collectionSponsor() public view returns (Tuple24 memory) {
 		require(false, stub_error);
 		dummy;
-		return Tuple23(0x0000000000000000000000000000000000000000, 0);
+		return Tuple24(0x0000000000000000000000000000000000000000, 0);
 	}
 
 	/// Set limits for the collection.
@@ -538,13 +538,13 @@
 }
 
 /// @dev anonymous struct
-struct Tuple23 {
+struct Tuple24 {
 	address field_0;
 	uint256 field_1;
 }
 
 /// @dev anonymous struct
-struct Tuple20 {
+struct Tuple21 {
 	string field_0;
 	bytes field_1;
 }
@@ -691,7 +691,7 @@
 }
 
 /// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0x81feb398
+/// @dev the ERC-165 identifier for this interface is 0xab243667
 contract ERC721UniqueExtensions is Dummy, ERC165 {
 	/// @notice A descriptive name for a collection of NFTs in this contract
 	/// @dev EVM selector for this function is: 0x06fdde03,
@@ -732,6 +732,21 @@
 	///  Throws if RFT pieces have multiple owners.
 	/// @param to The new owner
 	/// @param tokenId The RFT to transfer
+	/// @dev EVM selector for this function is: 0x2ada85ff,
+	///  or in textual repr: transferCross((address,uint256),uint256)
+	function transferCross(EthCrossAccount memory to, uint256 tokenId) public {
+		require(false, stub_error);
+		to;
+		tokenId;
+		dummy = 0;
+	}
+
+	/// @notice Transfer ownership of an RFT
+	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
+	///  is the zero address. Throws if `tokenId` is not a valid RFT.
+	///  Throws if RFT pieces have multiple owners.
+	/// @param to The new owner
+	/// @param tokenId The RFT to transfer
 	/// @dev EVM selector for this function is: 0xd5cf430b,
 	///  or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256)
 	function transferFromCross(
@@ -809,7 +824,7 @@
 	// /// @param tokens array of pairs of token ID and token URI for minted tokens
 	// /// @dev EVM selector for this function is: 0x36543006,
 	// ///  or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[])
-	// function mintBulkWithTokenURI(address to, Tuple9[] memory tokens) public returns (bool) {
+	// function mintBulkWithTokenURI(address to, Tuple10[] memory tokens) public returns (bool) {
 	// 	require(false, stub_error);
 	// 	to;
 	// 	tokens;
@@ -831,7 +846,7 @@
 }
 
 /// @dev anonymous struct
-struct Tuple9 {
+struct Tuple10 {
 	uint256 field_0;
 	string field_1;
 }
modifiedtests/src/eth/api/UniqueFungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueFungible.sol
+++ b/tests/src/eth/api/UniqueFungible.sol
@@ -28,7 +28,7 @@
 	/// @param properties Vector of properties key/value pair.
 	/// @dev EVM selector for this function is: 0x50b26b2a,
 	///  or in textual repr: setCollectionProperties((string,bytes)[])
-	function setCollectionProperties(Tuple14[] memory properties) external;
+	function setCollectionProperties(Tuple15[] memory properties) external;
 
 	/// Delete collection property.
 	///
@@ -60,7 +60,7 @@
 	/// @return Vector of properties key/value pairs.
 	/// @dev EVM selector for this function is: 0x285fb8e6,
 	///  or in textual repr: collectionProperties(string[])
-	function collectionProperties(string[] memory keys) external view returns (Tuple14[] memory);
+	function collectionProperties(string[] memory keys) external view returns (Tuple15[] memory);
 
 	/// Set the sponsor of the collection.
 	///
@@ -287,12 +287,12 @@
 }
 
 /// @dev anonymous struct
-struct Tuple14 {
+struct Tuple15 {
 	string field_0;
 	bytes field_1;
 }
 
-/// @dev the ERC-165 identifier for this interface is 0x032e5926
+/// @dev the ERC-165 identifier for this interface is 0x29f4dcd9
 interface ERC20UniqueExtensions is Dummy, ERC165 {
 	/// @dev EVM selector for this function is: 0x0ecd0ab0,
 	///  or in textual repr: approveCross((address,uint256),uint256)
@@ -322,6 +322,10 @@
 	///  or in textual repr: mintBulk((address,uint256)[])
 	function mintBulk(Tuple8[] memory amounts) external returns (bool);
 
+	/// @dev EVM selector for this function is: 0x2ada85ff,
+	///  or in textual repr: transferCross((address,uint256),uint256)
+	function transferCross(EthCrossAccount memory to, uint256 amount) external returns (bool);
+
 	/// @dev EVM selector for this function is: 0xd5cf430b,
 	///  or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256)
 	function transferFromCross(
modifiedtests/src/eth/api/UniqueNFT.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -49,7 +49,7 @@
 	/// @param properties settable properties
 	/// @dev EVM selector for this function is: 0x14ed3a6e,
 	///  or in textual repr: setProperties(uint256,(string,bytes)[])
-	function setProperties(uint256 tokenId, Tuple21[] memory properties) external;
+	function setProperties(uint256 tokenId, Tuple22[] memory properties) external;
 
 	// /// @notice Delete token property value.
 	// /// @dev Throws error if `msg.sender` has no permission to edit the property.
@@ -93,7 +93,7 @@
 	/// @param properties Vector of properties key/value pair.
 	/// @dev EVM selector for this function is: 0x50b26b2a,
 	///  or in textual repr: setCollectionProperties((string,bytes)[])
-	function setCollectionProperties(Tuple21[] memory properties) external;
+	function setCollectionProperties(Tuple22[] memory properties) external;
 
 	/// Delete collection property.
 	///
@@ -125,7 +125,7 @@
 	/// @return Vector of properties key/value pairs.
 	/// @dev EVM selector for this function is: 0x285fb8e6,
 	///  or in textual repr: collectionProperties(string[])
-	function collectionProperties(string[] memory keys) external view returns (Tuple21[] memory);
+	function collectionProperties(string[] memory keys) external view returns (Tuple22[] memory);
 
 	/// Set the sponsor of the collection.
 	///
@@ -167,7 +167,7 @@
 	/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
 	/// @dev EVM selector for this function is: 0x6ec0a9f1,
 	///  or in textual repr: collectionSponsor()
-	function collectionSponsor() external view returns (Tuple24 memory);
+	function collectionSponsor() external view returns (Tuple25 memory);
 
 	/// Set limits for the collection.
 	/// @dev Throws error if limit not found.
@@ -352,13 +352,13 @@
 }
 
 /// @dev anonymous struct
-struct Tuple24 {
+struct Tuple25 {
 	address field_0;
 	uint256 field_1;
 }
 
 /// @dev anonymous struct
-struct Tuple21 {
+struct Tuple22 {
 	string field_0;
 	bytes field_1;
 }
@@ -458,7 +458,7 @@
 }
 
 /// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0x244543ee
+/// @dev the ERC-165 identifier for this interface is 0x0e9fc611
 interface ERC721UniqueExtensions is Dummy, ERC165 {
 	/// @notice A descriptive name for a collection of NFTs in this contract
 	/// @dev EVM selector for this function is: 0x06fdde03,
@@ -489,6 +489,15 @@
 	///  or in textual repr: transfer(address,uint256)
 	function transfer(address to, uint256 tokenId) external;
 
+	/// @notice Transfer ownership of an NFT
+	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
+	///  is the zero address. Throws if `tokenId` is not a valid NFT.
+	/// @param to The new owner
+	/// @param tokenId The NFT to transfer
+	/// @dev EVM selector for this function is: 0x2ada85ff,
+	///  or in textual repr: transferCross((address,uint256),uint256)
+	function transferCross(EthCrossAccount memory to, uint256 tokenId) external;
+
 	/// @notice Transfer ownership of an NFT from cross account address to cross account address
 	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
 	///  is the zero address. Throws if `tokenId` is not a valid NFT.
@@ -543,12 +552,12 @@
 	// /// @param tokens array of pairs of token ID and token URI for minted tokens
 	// /// @dev EVM selector for this function is: 0x36543006,
 	// ///  or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[])
-	// function mintBulkWithTokenURI(address to, Tuple10[] memory tokens) external returns (bool);
+	// function mintBulkWithTokenURI(address to, Tuple11[] memory tokens) external returns (bool);
 
 }
 
 /// @dev anonymous struct
-struct Tuple10 {
+struct Tuple11 {
 	uint256 field_0;
 	string field_1;
 }
modifiedtests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueRefungible.sol
+++ b/tests/src/eth/api/UniqueRefungible.sol
@@ -49,7 +49,7 @@
 	/// @param properties settable properties
 	/// @dev EVM selector for this function is: 0x14ed3a6e,
 	///  or in textual repr: setProperties(uint256,(string,bytes)[])
-	function setProperties(uint256 tokenId, Tuple20[] memory properties) external;
+	function setProperties(uint256 tokenId, Tuple21[] memory properties) external;
 
 	// /// @notice Delete token property value.
 	// /// @dev Throws error if `msg.sender` has no permission to edit the property.
@@ -93,7 +93,7 @@
 	/// @param properties Vector of properties key/value pair.
 	/// @dev EVM selector for this function is: 0x50b26b2a,
 	///  or in textual repr: setCollectionProperties((string,bytes)[])
-	function setCollectionProperties(Tuple20[] memory properties) external;
+	function setCollectionProperties(Tuple21[] memory properties) external;
 
 	/// Delete collection property.
 	///
@@ -125,7 +125,7 @@
 	/// @return Vector of properties key/value pairs.
 	/// @dev EVM selector for this function is: 0x285fb8e6,
 	///  or in textual repr: collectionProperties(string[])
-	function collectionProperties(string[] memory keys) external view returns (Tuple20[] memory);
+	function collectionProperties(string[] memory keys) external view returns (Tuple21[] memory);
 
 	/// Set the sponsor of the collection.
 	///
@@ -167,7 +167,7 @@
 	/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
 	/// @dev EVM selector for this function is: 0x6ec0a9f1,
 	///  or in textual repr: collectionSponsor()
-	function collectionSponsor() external view returns (Tuple23 memory);
+	function collectionSponsor() external view returns (Tuple24 memory);
 
 	/// Set limits for the collection.
 	/// @dev Throws error if limit not found.
@@ -352,13 +352,13 @@
 }
 
 /// @dev anonymous struct
-struct Tuple23 {
+struct Tuple24 {
 	address field_0;
 	uint256 field_1;
 }
 
 /// @dev anonymous struct
-struct Tuple20 {
+struct Tuple21 {
 	string field_0;
 	bytes field_1;
 }
@@ -456,7 +456,7 @@
 }
 
 /// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0x81feb398
+/// @dev the ERC-165 identifier for this interface is 0xab243667
 interface ERC721UniqueExtensions is Dummy, ERC165 {
 	/// @notice A descriptive name for a collection of NFTs in this contract
 	/// @dev EVM selector for this function is: 0x06fdde03,
@@ -484,6 +484,16 @@
 	///  Throws if RFT pieces have multiple owners.
 	/// @param to The new owner
 	/// @param tokenId The RFT to transfer
+	/// @dev EVM selector for this function is: 0x2ada85ff,
+	///  or in textual repr: transferCross((address,uint256),uint256)
+	function transferCross(EthCrossAccount memory to, uint256 tokenId) external;
+
+	/// @notice Transfer ownership of an RFT
+	/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
+	///  is the zero address. Throws if `tokenId` is not a valid RFT.
+	///  Throws if RFT pieces have multiple owners.
+	/// @param to The new owner
+	/// @param tokenId The RFT to transfer
 	/// @dev EVM selector for this function is: 0xd5cf430b,
 	///  or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256)
 	function transferFromCross(
@@ -535,7 +545,7 @@
 	// /// @param tokens array of pairs of token ID and token URI for minted tokens
 	// /// @dev EVM selector for this function is: 0x36543006,
 	// ///  or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[])
-	// function mintBulkWithTokenURI(address to, Tuple9[] memory tokens) external returns (bool);
+	// function mintBulkWithTokenURI(address to, Tuple10[] memory tokens) external returns (bool);
 
 	/// Returns EVM address for refungible token
 	///
@@ -546,7 +556,7 @@
 }
 
 /// @dev anonymous struct
-struct Tuple9 {
+struct Tuple10 {
 	uint256 field_0;
 	string field_1;
 }
modifiedtests/src/eth/fungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/fungible.test.ts
+++ b/tests/src/eth/fungible.test.ts
@@ -230,6 +230,37 @@
     }
   });
 
+  itEth('Can perform transferCross()', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+    const receiver = await helper.eth.createAccountWithBalance(donor);
+    const to = helper.ethCrossAccount.fromAddress(receiver);
+    const collection = await helper.ft.mintCollection(alice);
+    await collection.mint(alice, 200n, {Ethereum: owner});
+
+    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+    {
+      const result = await contract.methods.transferCross(to, 50).send({from: owner});
+      
+      const event = result.events.Transfer;
+      expect(event.address).to.be.equal(collectionAddress);
+      expect(event.returnValues.from).to.be.equal(owner);
+      expect(event.returnValues.to).to.be.equal(receiver);
+      expect(event.returnValues.value).to.be.equal('50');
+    }
+
+    {
+      const balance = await contract.methods.balanceOf(owner).call();
+      expect(+balance).to.equal(150);
+    }
+
+    {
+      const balance = await contract.methods.balanceOf(receiver).call();
+      expect(+balance).to.equal(50);
+    }
+  });
+  
   itEth('Can perform transfer()', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
     const receiver = await helper.eth.createAccountWithBalance(donor);
modifiedtests/src/eth/fungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/fungibleAbi.json
+++ b/tests/src/eth/fungibleAbi.json
@@ -239,7 +239,7 @@
           { "internalType": "string", "name": "field_0", "type": "string" },
           { "internalType": "bytes", "name": "field_1", "type": "bytes" }
         ],
-        "internalType": "struct Tuple14[]",
+        "internalType": "struct Tuple15[]",
         "name": "",
         "type": "tuple[]"
       }
@@ -496,7 +496,7 @@
           { "internalType": "string", "name": "field_0", "type": "string" },
           { "internalType": "bytes", "name": "field_1", "type": "bytes" }
         ],
-        "internalType": "struct Tuple14[]",
+        "internalType": "struct Tuple15[]",
         "name": "properties",
         "type": "tuple[]"
       }
@@ -594,6 +594,24 @@
   },
   {
     "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "eth", "type": "address" },
+          { "internalType": "uint256", "name": "sub", "type": "uint256" }
+        ],
+        "internalType": "struct EthCrossAccount",
+        "name": "to",
+        "type": "tuple"
+      },
+      { "internalType": "uint256", "name": "amount", "type": "uint256" }
+    ],
+    "name": "transferCross",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "from", "type": "address" },
       { "internalType": "address", "name": "to", "type": "address" },
       { "internalType": "uint256", "name": "amount", "type": "uint256" }
modifiedtests/src/eth/nonFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/nonFungible.test.ts
+++ b/tests/src/eth/nonFungible.test.ts
@@ -402,6 +402,37 @@
       expect(+balance).to.equal(1);
     }
   });
+  
+  itEth('Can perform transferCross()', async ({helper}) => {
+    const collection = await helper.nft.mintCollection(minter, {});
+    const owner = await helper.eth.createAccountWithBalance(donor);
+    const receiver = helper.eth.createAccount();
+    const to = helper.ethCrossAccount.fromAddress(receiver);
+    const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});
+
+    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
+
+    {
+      const result = await contract.methods.transferCross(to, tokenId).send({from: owner});
+
+      const event = result.events.Transfer;
+      expect(event.address).to.be.equal(collectionAddress);
+      expect(event.returnValues.from).to.be.equal(owner);
+      expect(event.returnValues.to).to.be.equal(receiver);
+      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);
+    }
+
+    {
+      const balance = await contract.methods.balanceOf(owner).call();
+      expect(+balance).to.equal(0);
+    }
+
+    {
+      const balance = await contract.methods.balanceOf(receiver).call();
+      expect(+balance).to.equal(1);
+    }
+  });
 });
 
 describe('NFT: Fees', () => {
modifiedtests/src/eth/nonFungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/nonFungibleAbi.json
+++ b/tests/src/eth/nonFungibleAbi.json
@@ -269,7 +269,7 @@
           { "internalType": "string", "name": "field_0", "type": "string" },
           { "internalType": "bytes", "name": "field_1", "type": "bytes" }
         ],
-        "internalType": "struct Tuple21[]",
+        "internalType": "struct Tuple22[]",
         "name": "",
         "type": "tuple[]"
       }
@@ -293,7 +293,7 @@
           { "internalType": "address", "name": "field_0", "type": "address" },
           { "internalType": "uint256", "name": "field_1", "type": "uint256" }
         ],
-        "internalType": "struct Tuple24",
+        "internalType": "struct Tuple25",
         "name": "",
         "type": "tuple"
       }
@@ -611,7 +611,7 @@
           { "internalType": "string", "name": "field_0", "type": "string" },
           { "internalType": "bytes", "name": "field_1", "type": "bytes" }
         ],
-        "internalType": "struct Tuple21[]",
+        "internalType": "struct Tuple22[]",
         "name": "properties",
         "type": "tuple[]"
       }
@@ -682,7 +682,7 @@
           { "internalType": "string", "name": "field_0", "type": "string" },
           { "internalType": "bytes", "name": "field_1", "type": "bytes" }
         ],
-        "internalType": "struct Tuple21[]",
+        "internalType": "struct Tuple22[]",
         "name": "properties",
         "type": "tuple[]"
       }
@@ -778,6 +778,24 @@
   },
   {
     "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "eth", "type": "address" },
+          { "internalType": "uint256", "name": "sub", "type": "uint256" }
+        ],
+        "internalType": "struct EthCrossAccount",
+        "name": "to",
+        "type": "tuple"
+      },
+      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
+    ],
+    "name": "transferCross",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "from", "type": "address" },
       { "internalType": "address", "name": "to", "type": "address" },
       { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
modifiedtests/src/eth/reFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/reFungible.test.ts
+++ b/tests/src/eth/reFungible.test.ts
@@ -359,6 +359,37 @@
       expect(+balance).to.equal(1);
     }
   });
+  
+  itEth('Can perform transferCross()', async ({helper}) => {
+    const caller = await helper.eth.createAccountWithBalance(donor);
+    const receiver = helper.eth.createAccount();
+    const to = helper.ethCrossAccount.fromAddress(receiver);
+    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry', '6', '6');
+    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
+
+    const result = await contract.methods.mint(caller).send();
+    const tokenId = result.events.Transfer.returnValues.tokenId;
+
+    {
+      const result = await contract.methods.transferCross(to, tokenId).send({from: caller});
+
+      const event = result.events.Transfer;
+      expect(event.address).to.equal(collectionAddress);
+      expect(event.returnValues.from).to.equal(caller);
+      expect(event.returnValues.to).to.equal(receiver);
+      expect(event.returnValues.tokenId).to.equal(tokenId.toString());
+    }
+
+    {
+      const balance = await contract.methods.balanceOf(caller).call();
+      expect(+balance).to.equal(0);
+    }
+
+    {
+      const balance = await contract.methods.balanceOf(receiver).call();
+      expect(+balance).to.equal(1);
+    }
+  });
 
   itEth('transfer event on transfer from partial ownership to full ownership', async ({helper}) => {
     const caller = await helper.eth.createAccountWithBalance(donor);
modifiedtests/src/eth/reFungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/reFungibleAbi.json
+++ b/tests/src/eth/reFungibleAbi.json
@@ -251,7 +251,7 @@
           { "internalType": "string", "name": "field_0", "type": "string" },
           { "internalType": "bytes", "name": "field_1", "type": "bytes" }
         ],
-        "internalType": "struct Tuple20[]",
+        "internalType": "struct Tuple21[]",
         "name": "",
         "type": "tuple[]"
       }
@@ -275,7 +275,7 @@
           { "internalType": "address", "name": "field_0", "type": "address" },
           { "internalType": "uint256", "name": "field_1", "type": "uint256" }
         ],
-        "internalType": "struct Tuple23",
+        "internalType": "struct Tuple24",
         "name": "",
         "type": "tuple"
       }
@@ -593,7 +593,7 @@
           { "internalType": "string", "name": "field_0", "type": "string" },
           { "internalType": "bytes", "name": "field_1", "type": "bytes" }
         ],
-        "internalType": "struct Tuple20[]",
+        "internalType": "struct Tuple21[]",
         "name": "properties",
         "type": "tuple[]"
       }
@@ -664,7 +664,7 @@
           { "internalType": "string", "name": "field_0", "type": "string" },
           { "internalType": "bytes", "name": "field_1", "type": "bytes" }
         ],
-        "internalType": "struct Tuple20[]",
+        "internalType": "struct Tuple21[]",
         "name": "properties",
         "type": "tuple[]"
       }
@@ -769,6 +769,24 @@
   },
   {
     "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "eth", "type": "address" },
+          { "internalType": "uint256", "name": "sub", "type": "uint256" }
+        ],
+        "internalType": "struct EthCrossAccount",
+        "name": "to",
+        "type": "tuple"
+      },
+      { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
+    ],
+    "name": "transferCross",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "from", "type": "address" },
       { "internalType": "address", "name": "to", "type": "address" },
       { "internalType": "uint256", "name": "tokenId", "type": "uint256" }