git.delta.rocks / unique-network / refs/commits / 149c99dda705

difftreelog

Merge pull request #719 from UniqueNetwork/feature/transferCross

Yaroslav Bolyukin2022-11-21parents: #5f0218a #171847d.patch.diff
in: master

26 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
--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -93,6 +93,7 @@
 		<Pallet<T>>::transfer(self, &caller, &to, amount, &budget).map_err(|_| "transfer error")?;
 		Ok(true)
 	}
+
 	#[weight(<SelfWeightOf<T>>::transfer_from())]
 	fn transfer_from(
 		&mut self,
@@ -238,6 +239,24 @@
 		Ok(true)
 	}
 
+	#[weight(<SelfWeightOf<T>>::transfer())]
+	fn transfer_cross(
+		&mut self,
+		caller: caller,
+		to: EthCrossAccount,
+		amount: uint256,
+	) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let to = to.into_sub_cross_account::<T>()?;
+		let amount = amount.try_into().map_err(|_| "amount overflow")?;
+		let budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+		<Pallet<T>>::transfer(self, &caller, &to, amount, &budget).map_err(|_| "transfer error")?;
+		Ok(true)
+	}
+
 	#[weight(<SelfWeightOf<T>>::transfer_from())]
 	fn transfer_from_cross(
 		&mut self,
modifiedpallets/fungible/src/stubs/UniqueFungible.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/fungible/src/stubs/UniqueFungible.soldiffbeforeafterboth
--- a/pallets/fungible/src/stubs/UniqueFungible.sol
+++ b/pallets/fungible/src/stubs/UniqueFungible.sol
@@ -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
@@ -739,6 +739,29 @@
 		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.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth
--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -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,34 @@
 	/// @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.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth
--- a/pallets/refungible/src/stubs/UniqueRefungible.sol
+++ b/pallets/refungible/src/stubs/UniqueRefungible.sol
@@ -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/base.test.tsdiffbeforeafterboth
--- a/tests/src/eth/base.test.ts
+++ b/tests/src/eth/base.test.ts
@@ -117,7 +117,7 @@
   });
 
   itEth('ERC721UniqueExtensions support', async ({helper}) => {
-    await checkInterface(helper, '0x244543ee', true, true);
+    await checkInterface(helper, '0x0e9fc611', true, true);
   });
 
   itEth('ERC721Burnable - 0x42966c68 - support', async ({helper}) => {
modifiedtests/src/eth/fungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/fungible.test.ts
+++ b/tests/src/eth/fungible.test.ts
@@ -230,6 +230,59 @@
     }
   });
 
+  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 toSubstrate = helper.ethCrossAccount.fromKeyringPair(donor);
+    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);
+    }
+    
+    {
+      const result = await contract.methods.transferCross(toSubstrate, 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(helper.address.substrateToEth(donor.address));
+      expect(event.returnValues.value).to.be.equal('50');
+    }
+
+    {
+      const balance = await collection.getBalance({Ethereum: owner});
+      expect(balance).to.equal(100n);
+    }
+
+    {
+      const balance = await collection.getBalance({Substrate: donor.address});
+      expect(balance).to.equal(50n);
+    }
+    
+  });
+  
   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
before · tests/src/eth/fungibleAbi.json
1[2  {3    "anonymous": false,4    "inputs": [5      {6        "indexed": true,7        "internalType": "address",8        "name": "owner",9        "type": "address"10      },11      {12        "indexed": true,13        "internalType": "address",14        "name": "spender",15        "type": "address"16      },17      {18        "indexed": false,19        "internalType": "uint256",20        "name": "value",21        "type": "uint256"22      }23    ],24    "name": "Approval",25    "type": "event"26  },27  {28    "anonymous": false,29    "inputs": [30      {31        "indexed": true,32        "internalType": "address",33        "name": "from",34        "type": "address"35      },36      {37        "indexed": true,38        "internalType": "address",39        "name": "to",40        "type": "address"41      },42      {43        "indexed": false,44        "internalType": "uint256",45        "name": "value",46        "type": "uint256"47      }48    ],49    "name": "Transfer",50    "type": "event"51  },52  {53    "inputs": [54      { "internalType": "address", "name": "newAdmin", "type": "address" }55    ],56    "name": "addCollectionAdmin",57    "outputs": [],58    "stateMutability": "nonpayable",59    "type": "function"60  },61  {62    "inputs": [63      {64        "components": [65          { "internalType": "address", "name": "eth", "type": "address" },66          { "internalType": "uint256", "name": "sub", "type": "uint256" }67        ],68        "internalType": "struct EthCrossAccount",69        "name": "newAdmin",70        "type": "tuple"71      }72    ],73    "name": "addCollectionAdminCross",74    "outputs": [],75    "stateMutability": "nonpayable",76    "type": "function"77  },78  {79    "inputs": [80      { "internalType": "address", "name": "user", "type": "address" }81    ],82    "name": "addToCollectionAllowList",83    "outputs": [],84    "stateMutability": "nonpayable",85    "type": "function"86  },87  {88    "inputs": [89      {90        "components": [91          { "internalType": "address", "name": "eth", "type": "address" },92          { "internalType": "uint256", "name": "sub", "type": "uint256" }93        ],94        "internalType": "struct EthCrossAccount",95        "name": "user",96        "type": "tuple"97      }98    ],99    "name": "addToCollectionAllowListCross",100    "outputs": [],101    "stateMutability": "nonpayable",102    "type": "function"103  },104  {105    "inputs": [106      { "internalType": "address", "name": "owner", "type": "address" },107      { "internalType": "address", "name": "spender", "type": "address" }108    ],109    "name": "allowance",110    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],111    "stateMutability": "view",112    "type": "function"113  },114  {115    "inputs": [116      { "internalType": "address", "name": "user", "type": "address" }117    ],118    "name": "allowed",119    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],120    "stateMutability": "view",121    "type": "function"122  },123  {124    "inputs": [125      { "internalType": "address", "name": "spender", "type": "address" },126      { "internalType": "uint256", "name": "amount", "type": "uint256" }127    ],128    "name": "approve",129    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],130    "stateMutability": "nonpayable",131    "type": "function"132  },133  {134    "inputs": [135      {136        "components": [137          { "internalType": "address", "name": "eth", "type": "address" },138          { "internalType": "uint256", "name": "sub", "type": "uint256" }139        ],140        "internalType": "struct EthCrossAccount",141        "name": "spender",142        "type": "tuple"143      },144      { "internalType": "uint256", "name": "amount", "type": "uint256" }145    ],146    "name": "approveCross",147    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],148    "stateMutability": "nonpayable",149    "type": "function"150  },151  {152    "inputs": [153      { "internalType": "address", "name": "owner", "type": "address" }154    ],155    "name": "balanceOf",156    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],157    "stateMutability": "view",158    "type": "function"159  },160  {161    "inputs": [162      { "internalType": "address", "name": "from", "type": "address" },163      { "internalType": "uint256", "name": "amount", "type": "uint256" }164    ],165    "name": "burnFrom",166    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],167    "stateMutability": "nonpayable",168    "type": "function"169  },170  {171    "inputs": [172      {173        "components": [174          { "internalType": "address", "name": "eth", "type": "address" },175          { "internalType": "uint256", "name": "sub", "type": "uint256" }176        ],177        "internalType": "struct EthCrossAccount",178        "name": "from",179        "type": "tuple"180      },181      { "internalType": "uint256", "name": "amount", "type": "uint256" }182    ],183    "name": "burnFromCross",184    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],185    "stateMutability": "nonpayable",186    "type": "function"187  },188  {189    "inputs": [190      { "internalType": "address", "name": "newOwner", "type": "address" }191    ],192    "name": "changeCollectionOwner",193    "outputs": [],194    "stateMutability": "nonpayable",195    "type": "function"196  },197  {198    "inputs": [],199    "name": "collectionAdmins",200    "outputs": [201      {202        "components": [203          { "internalType": "address", "name": "eth", "type": "address" },204          { "internalType": "uint256", "name": "sub", "type": "uint256" }205        ],206        "internalType": "struct EthCrossAccount[]",207        "name": "",208        "type": "tuple[]"209      }210    ],211    "stateMutability": "view",212    "type": "function"213  },214  {215    "inputs": [],216    "name": "collectionOwner",217    "outputs": [218      {219        "components": [220          { "internalType": "address", "name": "eth", "type": "address" },221          { "internalType": "uint256", "name": "sub", "type": "uint256" }222        ],223        "internalType": "struct EthCrossAccount",224        "name": "",225        "type": "tuple"226      }227    ],228    "stateMutability": "view",229    "type": "function"230  },231  {232    "inputs": [233      { "internalType": "string[]", "name": "keys", "type": "string[]" }234    ],235    "name": "collectionProperties",236    "outputs": [237      {238        "components": [239          { "internalType": "string", "name": "field_0", "type": "string" },240          { "internalType": "bytes", "name": "field_1", "type": "bytes" }241        ],242        "internalType": "struct Tuple14[]",243        "name": "",244        "type": "tuple[]"245      }246    ],247    "stateMutability": "view",248    "type": "function"249  },250  {251    "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],252    "name": "collectionProperty",253    "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],254    "stateMutability": "view",255    "type": "function"256  },257  {258    "inputs": [],259    "name": "collectionSponsor",260    "outputs": [261      {262        "components": [263          { "internalType": "address", "name": "field_0", "type": "address" },264          { "internalType": "uint256", "name": "field_1", "type": "uint256" }265        ],266        "internalType": "struct Tuple8",267        "name": "",268        "type": "tuple"269      }270    ],271    "stateMutability": "view",272    "type": "function"273  },274  {275    "inputs": [],276    "name": "confirmCollectionSponsorship",277    "outputs": [],278    "stateMutability": "nonpayable",279    "type": "function"280  },281  {282    "inputs": [],283    "name": "contractAddress",284    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],285    "stateMutability": "view",286    "type": "function"287  },288  {289    "inputs": [],290    "name": "decimals",291    "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }],292    "stateMutability": "view",293    "type": "function"294  },295  {296    "inputs": [297      { "internalType": "string[]", "name": "keys", "type": "string[]" }298    ],299    "name": "deleteCollectionProperties",300    "outputs": [],301    "stateMutability": "nonpayable",302    "type": "function"303  },304  {305    "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],306    "name": "deleteCollectionProperty",307    "outputs": [],308    "stateMutability": "nonpayable",309    "type": "function"310  },311  {312    "inputs": [],313    "name": "hasCollectionPendingSponsor",314    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],315    "stateMutability": "view",316    "type": "function"317  },318  {319    "inputs": [320      { "internalType": "address", "name": "user", "type": "address" }321    ],322    "name": "isOwnerOrAdmin",323    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],324    "stateMutability": "view",325    "type": "function"326  },327  {328    "inputs": [329      {330        "components": [331          { "internalType": "address", "name": "eth", "type": "address" },332          { "internalType": "uint256", "name": "sub", "type": "uint256" }333        ],334        "internalType": "struct EthCrossAccount",335        "name": "user",336        "type": "tuple"337      }338    ],339    "name": "isOwnerOrAdminCross",340    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],341    "stateMutability": "view",342    "type": "function"343  },344  {345    "inputs": [346      { "internalType": "address", "name": "to", "type": "address" },347      { "internalType": "uint256", "name": "amount", "type": "uint256" }348    ],349    "name": "mint",350    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],351    "stateMutability": "nonpayable",352    "type": "function"353  },354  {355    "inputs": [356      {357        "components": [358          { "internalType": "address", "name": "field_0", "type": "address" },359          { "internalType": "uint256", "name": "field_1", "type": "uint256" }360        ],361        "internalType": "struct Tuple8[]",362        "name": "amounts",363        "type": "tuple[]"364      }365    ],366    "name": "mintBulk",367    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],368    "stateMutability": "nonpayable",369    "type": "function"370  },371  {372    "inputs": [],373    "name": "name",374    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],375    "stateMutability": "view",376    "type": "function"377  },378  {379    "inputs": [380      { "internalType": "address", "name": "admin", "type": "address" }381    ],382    "name": "removeCollectionAdmin",383    "outputs": [],384    "stateMutability": "nonpayable",385    "type": "function"386  },387  {388    "inputs": [389      {390        "components": [391          { "internalType": "address", "name": "eth", "type": "address" },392          { "internalType": "uint256", "name": "sub", "type": "uint256" }393        ],394        "internalType": "struct EthCrossAccount",395        "name": "admin",396        "type": "tuple"397      }398    ],399    "name": "removeCollectionAdminCross",400    "outputs": [],401    "stateMutability": "nonpayable",402    "type": "function"403  },404  {405    "inputs": [],406    "name": "removeCollectionSponsor",407    "outputs": [],408    "stateMutability": "nonpayable",409    "type": "function"410  },411  {412    "inputs": [413      { "internalType": "address", "name": "user", "type": "address" }414    ],415    "name": "removeFromCollectionAllowList",416    "outputs": [],417    "stateMutability": "nonpayable",418    "type": "function"419  },420  {421    "inputs": [422      {423        "components": [424          { "internalType": "address", "name": "eth", "type": "address" },425          { "internalType": "uint256", "name": "sub", "type": "uint256" }426        ],427        "internalType": "struct EthCrossAccount",428        "name": "user",429        "type": "tuple"430      }431    ],432    "name": "removeFromCollectionAllowListCross",433    "outputs": [],434    "stateMutability": "nonpayable",435    "type": "function"436  },437  {438    "inputs": [{ "internalType": "uint8", "name": "mode", "type": "uint8" }],439    "name": "setCollectionAccess",440    "outputs": [],441    "stateMutability": "nonpayable",442    "type": "function"443  },444  {445    "inputs": [446      { "internalType": "string", "name": "limit", "type": "string" },447      { "internalType": "uint32", "name": "value", "type": "uint32" }448    ],449    "name": "setCollectionLimit",450    "outputs": [],451    "stateMutability": "nonpayable",452    "type": "function"453  },454  {455    "inputs": [456      { "internalType": "string", "name": "limit", "type": "string" },457      { "internalType": "bool", "name": "value", "type": "bool" }458    ],459    "name": "setCollectionLimit",460    "outputs": [],461    "stateMutability": "nonpayable",462    "type": "function"463  },464  {465    "inputs": [{ "internalType": "bool", "name": "mode", "type": "bool" }],466    "name": "setCollectionMintMode",467    "outputs": [],468    "stateMutability": "nonpayable",469    "type": "function"470  },471  {472    "inputs": [{ "internalType": "bool", "name": "enable", "type": "bool" }],473    "name": "setCollectionNesting",474    "outputs": [],475    "stateMutability": "nonpayable",476    "type": "function"477  },478  {479    "inputs": [480      { "internalType": "bool", "name": "enable", "type": "bool" },481      {482        "internalType": "address[]",483        "name": "collections",484        "type": "address[]"485      }486    ],487    "name": "setCollectionNesting",488    "outputs": [],489    "stateMutability": "nonpayable",490    "type": "function"491  },492  {493    "inputs": [494      {495        "components": [496          { "internalType": "string", "name": "field_0", "type": "string" },497          { "internalType": "bytes", "name": "field_1", "type": "bytes" }498        ],499        "internalType": "struct Tuple14[]",500        "name": "properties",501        "type": "tuple[]"502      }503    ],504    "name": "setCollectionProperties",505    "outputs": [],506    "stateMutability": "nonpayable",507    "type": "function"508  },509  {510    "inputs": [511      { "internalType": "string", "name": "key", "type": "string" },512      { "internalType": "bytes", "name": "value", "type": "bytes" }513    ],514    "name": "setCollectionProperty",515    "outputs": [],516    "stateMutability": "nonpayable",517    "type": "function"518  },519  {520    "inputs": [521      { "internalType": "address", "name": "sponsor", "type": "address" }522    ],523    "name": "setCollectionSponsor",524    "outputs": [],525    "stateMutability": "nonpayable",526    "type": "function"527  },528  {529    "inputs": [530      {531        "components": [532          { "internalType": "address", "name": "eth", "type": "address" },533          { "internalType": "uint256", "name": "sub", "type": "uint256" }534        ],535        "internalType": "struct EthCrossAccount",536        "name": "sponsor",537        "type": "tuple"538      }539    ],540    "name": "setCollectionSponsorCross",541    "outputs": [],542    "stateMutability": "nonpayable",543    "type": "function"544  },545  {546    "inputs": [547      {548        "components": [549          { "internalType": "address", "name": "eth", "type": "address" },550          { "internalType": "uint256", "name": "sub", "type": "uint256" }551        ],552        "internalType": "struct EthCrossAccount",553        "name": "newOwner",554        "type": "tuple"555      }556    ],557    "name": "setOwnerCross",558    "outputs": [],559    "stateMutability": "nonpayable",560    "type": "function"561  },562  {563    "inputs": [564      { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }565    ],566    "name": "supportsInterface",567    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],568    "stateMutability": "view",569    "type": "function"570  },571  {572    "inputs": [],573    "name": "symbol",574    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],575    "stateMutability": "view",576    "type": "function"577  },578  {579    "inputs": [],580    "name": "totalSupply",581    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],582    "stateMutability": "view",583    "type": "function"584  },585  {586    "inputs": [587      { "internalType": "address", "name": "to", "type": "address" },588      { "internalType": "uint256", "name": "amount", "type": "uint256" }589    ],590    "name": "transfer",591    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],592    "stateMutability": "nonpayable",593    "type": "function"594  },595  {596    "inputs": [597      { "internalType": "address", "name": "from", "type": "address" },598      { "internalType": "address", "name": "to", "type": "address" },599      { "internalType": "uint256", "name": "amount", "type": "uint256" }600    ],601    "name": "transferFrom",602    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],603    "stateMutability": "nonpayable",604    "type": "function"605  },606  {607    "inputs": [608      {609        "components": [610          { "internalType": "address", "name": "eth", "type": "address" },611          { "internalType": "uint256", "name": "sub", "type": "uint256" }612        ],613        "internalType": "struct EthCrossAccount",614        "name": "from",615        "type": "tuple"616      },617      {618        "components": [619          { "internalType": "address", "name": "eth", "type": "address" },620          { "internalType": "uint256", "name": "sub", "type": "uint256" }621        ],622        "internalType": "struct EthCrossAccount",623        "name": "to",624        "type": "tuple"625      },626      { "internalType": "uint256", "name": "amount", "type": "uint256" }627    ],628    "name": "transferFromCross",629    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],630    "stateMutability": "nonpayable",631    "type": "function"632  },633  {634    "inputs": [],635    "name": "uniqueCollectionType",636    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],637    "stateMutability": "view",638    "type": "function"639  }640]
after · tests/src/eth/fungibleAbi.json
1[2  {3    "anonymous": false,4    "inputs": [5      {6        "indexed": true,7        "internalType": "address",8        "name": "owner",9        "type": "address"10      },11      {12        "indexed": true,13        "internalType": "address",14        "name": "spender",15        "type": "address"16      },17      {18        "indexed": false,19        "internalType": "uint256",20        "name": "value",21        "type": "uint256"22      }23    ],24    "name": "Approval",25    "type": "event"26  },27  {28    "anonymous": false,29    "inputs": [30      {31        "indexed": true,32        "internalType": "address",33        "name": "from",34        "type": "address"35      },36      {37        "indexed": true,38        "internalType": "address",39        "name": "to",40        "type": "address"41      },42      {43        "indexed": false,44        "internalType": "uint256",45        "name": "value",46        "type": "uint256"47      }48    ],49    "name": "Transfer",50    "type": "event"51  },52  {53    "inputs": [54      { "internalType": "address", "name": "newAdmin", "type": "address" }55    ],56    "name": "addCollectionAdmin",57    "outputs": [],58    "stateMutability": "nonpayable",59    "type": "function"60  },61  {62    "inputs": [63      {64        "components": [65          { "internalType": "address", "name": "eth", "type": "address" },66          { "internalType": "uint256", "name": "sub", "type": "uint256" }67        ],68        "internalType": "struct EthCrossAccount",69        "name": "newAdmin",70        "type": "tuple"71      }72    ],73    "name": "addCollectionAdminCross",74    "outputs": [],75    "stateMutability": "nonpayable",76    "type": "function"77  },78  {79    "inputs": [80      { "internalType": "address", "name": "user", "type": "address" }81    ],82    "name": "addToCollectionAllowList",83    "outputs": [],84    "stateMutability": "nonpayable",85    "type": "function"86  },87  {88    "inputs": [89      {90        "components": [91          { "internalType": "address", "name": "eth", "type": "address" },92          { "internalType": "uint256", "name": "sub", "type": "uint256" }93        ],94        "internalType": "struct EthCrossAccount",95        "name": "user",96        "type": "tuple"97      }98    ],99    "name": "addToCollectionAllowListCross",100    "outputs": [],101    "stateMutability": "nonpayable",102    "type": "function"103  },104  {105    "inputs": [106      { "internalType": "address", "name": "owner", "type": "address" },107      { "internalType": "address", "name": "spender", "type": "address" }108    ],109    "name": "allowance",110    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],111    "stateMutability": "view",112    "type": "function"113  },114  {115    "inputs": [116      { "internalType": "address", "name": "user", "type": "address" }117    ],118    "name": "allowed",119    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],120    "stateMutability": "view",121    "type": "function"122  },123  {124    "inputs": [125      { "internalType": "address", "name": "spender", "type": "address" },126      { "internalType": "uint256", "name": "amount", "type": "uint256" }127    ],128    "name": "approve",129    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],130    "stateMutability": "nonpayable",131    "type": "function"132  },133  {134    "inputs": [135      {136        "components": [137          { "internalType": "address", "name": "eth", "type": "address" },138          { "internalType": "uint256", "name": "sub", "type": "uint256" }139        ],140        "internalType": "struct EthCrossAccount",141        "name": "spender",142        "type": "tuple"143      },144      { "internalType": "uint256", "name": "amount", "type": "uint256" }145    ],146    "name": "approveCross",147    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],148    "stateMutability": "nonpayable",149    "type": "function"150  },151  {152    "inputs": [153      { "internalType": "address", "name": "owner", "type": "address" }154    ],155    "name": "balanceOf",156    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],157    "stateMutability": "view",158    "type": "function"159  },160  {161    "inputs": [162      { "internalType": "address", "name": "from", "type": "address" },163      { "internalType": "uint256", "name": "amount", "type": "uint256" }164    ],165    "name": "burnFrom",166    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],167    "stateMutability": "nonpayable",168    "type": "function"169  },170  {171    "inputs": [172      {173        "components": [174          { "internalType": "address", "name": "eth", "type": "address" },175          { "internalType": "uint256", "name": "sub", "type": "uint256" }176        ],177        "internalType": "struct EthCrossAccount",178        "name": "from",179        "type": "tuple"180      },181      { "internalType": "uint256", "name": "amount", "type": "uint256" }182    ],183    "name": "burnFromCross",184    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],185    "stateMutability": "nonpayable",186    "type": "function"187  },188  {189    "inputs": [190      { "internalType": "address", "name": "newOwner", "type": "address" }191    ],192    "name": "changeCollectionOwner",193    "outputs": [],194    "stateMutability": "nonpayable",195    "type": "function"196  },197  {198    "inputs": [],199    "name": "collectionAdmins",200    "outputs": [201      {202        "components": [203          { "internalType": "address", "name": "eth", "type": "address" },204          { "internalType": "uint256", "name": "sub", "type": "uint256" }205        ],206        "internalType": "struct EthCrossAccount[]",207        "name": "",208        "type": "tuple[]"209      }210    ],211    "stateMutability": "view",212    "type": "function"213  },214  {215    "inputs": [],216    "name": "collectionOwner",217    "outputs": [218      {219        "components": [220          { "internalType": "address", "name": "eth", "type": "address" },221          { "internalType": "uint256", "name": "sub", "type": "uint256" }222        ],223        "internalType": "struct EthCrossAccount",224        "name": "",225        "type": "tuple"226      }227    ],228    "stateMutability": "view",229    "type": "function"230  },231  {232    "inputs": [233      { "internalType": "string[]", "name": "keys", "type": "string[]" }234    ],235    "name": "collectionProperties",236    "outputs": [237      {238        "components": [239          { "internalType": "string", "name": "field_0", "type": "string" },240          { "internalType": "bytes", "name": "field_1", "type": "bytes" }241        ],242        "internalType": "struct Tuple15[]",243        "name": "",244        "type": "tuple[]"245      }246    ],247    "stateMutability": "view",248    "type": "function"249  },250  {251    "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],252    "name": "collectionProperty",253    "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],254    "stateMutability": "view",255    "type": "function"256  },257  {258    "inputs": [],259    "name": "collectionSponsor",260    "outputs": [261      {262        "components": [263          { "internalType": "address", "name": "field_0", "type": "address" },264          { "internalType": "uint256", "name": "field_1", "type": "uint256" }265        ],266        "internalType": "struct Tuple8",267        "name": "",268        "type": "tuple"269      }270    ],271    "stateMutability": "view",272    "type": "function"273  },274  {275    "inputs": [],276    "name": "confirmCollectionSponsorship",277    "outputs": [],278    "stateMutability": "nonpayable",279    "type": "function"280  },281  {282    "inputs": [],283    "name": "contractAddress",284    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],285    "stateMutability": "view",286    "type": "function"287  },288  {289    "inputs": [],290    "name": "decimals",291    "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }],292    "stateMutability": "view",293    "type": "function"294  },295  {296    "inputs": [297      { "internalType": "string[]", "name": "keys", "type": "string[]" }298    ],299    "name": "deleteCollectionProperties",300    "outputs": [],301    "stateMutability": "nonpayable",302    "type": "function"303  },304  {305    "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],306    "name": "deleteCollectionProperty",307    "outputs": [],308    "stateMutability": "nonpayable",309    "type": "function"310  },311  {312    "inputs": [],313    "name": "hasCollectionPendingSponsor",314    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],315    "stateMutability": "view",316    "type": "function"317  },318  {319    "inputs": [320      { "internalType": "address", "name": "user", "type": "address" }321    ],322    "name": "isOwnerOrAdmin",323    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],324    "stateMutability": "view",325    "type": "function"326  },327  {328    "inputs": [329      {330        "components": [331          { "internalType": "address", "name": "eth", "type": "address" },332          { "internalType": "uint256", "name": "sub", "type": "uint256" }333        ],334        "internalType": "struct EthCrossAccount",335        "name": "user",336        "type": "tuple"337      }338    ],339    "name": "isOwnerOrAdminCross",340    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],341    "stateMutability": "view",342    "type": "function"343  },344  {345    "inputs": [346      { "internalType": "address", "name": "to", "type": "address" },347      { "internalType": "uint256", "name": "amount", "type": "uint256" }348    ],349    "name": "mint",350    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],351    "stateMutability": "nonpayable",352    "type": "function"353  },354  {355    "inputs": [356      {357        "components": [358          { "internalType": "address", "name": "field_0", "type": "address" },359          { "internalType": "uint256", "name": "field_1", "type": "uint256" }360        ],361        "internalType": "struct Tuple8[]",362        "name": "amounts",363        "type": "tuple[]"364      }365    ],366    "name": "mintBulk",367    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],368    "stateMutability": "nonpayable",369    "type": "function"370  },371  {372    "inputs": [],373    "name": "name",374    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],375    "stateMutability": "view",376    "type": "function"377  },378  {379    "inputs": [380      { "internalType": "address", "name": "admin", "type": "address" }381    ],382    "name": "removeCollectionAdmin",383    "outputs": [],384    "stateMutability": "nonpayable",385    "type": "function"386  },387  {388    "inputs": [389      {390        "components": [391          { "internalType": "address", "name": "eth", "type": "address" },392          { "internalType": "uint256", "name": "sub", "type": "uint256" }393        ],394        "internalType": "struct EthCrossAccount",395        "name": "admin",396        "type": "tuple"397      }398    ],399    "name": "removeCollectionAdminCross",400    "outputs": [],401    "stateMutability": "nonpayable",402    "type": "function"403  },404  {405    "inputs": [],406    "name": "removeCollectionSponsor",407    "outputs": [],408    "stateMutability": "nonpayable",409    "type": "function"410  },411  {412    "inputs": [413      { "internalType": "address", "name": "user", "type": "address" }414    ],415    "name": "removeFromCollectionAllowList",416    "outputs": [],417    "stateMutability": "nonpayable",418    "type": "function"419  },420  {421    "inputs": [422      {423        "components": [424          { "internalType": "address", "name": "eth", "type": "address" },425          { "internalType": "uint256", "name": "sub", "type": "uint256" }426        ],427        "internalType": "struct EthCrossAccount",428        "name": "user",429        "type": "tuple"430      }431    ],432    "name": "removeFromCollectionAllowListCross",433    "outputs": [],434    "stateMutability": "nonpayable",435    "type": "function"436  },437  {438    "inputs": [{ "internalType": "uint8", "name": "mode", "type": "uint8" }],439    "name": "setCollectionAccess",440    "outputs": [],441    "stateMutability": "nonpayable",442    "type": "function"443  },444  {445    "inputs": [446      { "internalType": "string", "name": "limit", "type": "string" },447      { "internalType": "uint32", "name": "value", "type": "uint32" }448    ],449    "name": "setCollectionLimit",450    "outputs": [],451    "stateMutability": "nonpayable",452    "type": "function"453  },454  {455    "inputs": [456      { "internalType": "string", "name": "limit", "type": "string" },457      { "internalType": "bool", "name": "value", "type": "bool" }458    ],459    "name": "setCollectionLimit",460    "outputs": [],461    "stateMutability": "nonpayable",462    "type": "function"463  },464  {465    "inputs": [{ "internalType": "bool", "name": "mode", "type": "bool" }],466    "name": "setCollectionMintMode",467    "outputs": [],468    "stateMutability": "nonpayable",469    "type": "function"470  },471  {472    "inputs": [{ "internalType": "bool", "name": "enable", "type": "bool" }],473    "name": "setCollectionNesting",474    "outputs": [],475    "stateMutability": "nonpayable",476    "type": "function"477  },478  {479    "inputs": [480      { "internalType": "bool", "name": "enable", "type": "bool" },481      {482        "internalType": "address[]",483        "name": "collections",484        "type": "address[]"485      }486    ],487    "name": "setCollectionNesting",488    "outputs": [],489    "stateMutability": "nonpayable",490    "type": "function"491  },492  {493    "inputs": [494      {495        "components": [496          { "internalType": "string", "name": "field_0", "type": "string" },497          { "internalType": "bytes", "name": "field_1", "type": "bytes" }498        ],499        "internalType": "struct Tuple15[]",500        "name": "properties",501        "type": "tuple[]"502      }503    ],504    "name": "setCollectionProperties",505    "outputs": [],506    "stateMutability": "nonpayable",507    "type": "function"508  },509  {510    "inputs": [511      { "internalType": "string", "name": "key", "type": "string" },512      { "internalType": "bytes", "name": "value", "type": "bytes" }513    ],514    "name": "setCollectionProperty",515    "outputs": [],516    "stateMutability": "nonpayable",517    "type": "function"518  },519  {520    "inputs": [521      { "internalType": "address", "name": "sponsor", "type": "address" }522    ],523    "name": "setCollectionSponsor",524    "outputs": [],525    "stateMutability": "nonpayable",526    "type": "function"527  },528  {529    "inputs": [530      {531        "components": [532          { "internalType": "address", "name": "eth", "type": "address" },533          { "internalType": "uint256", "name": "sub", "type": "uint256" }534        ],535        "internalType": "struct EthCrossAccount",536        "name": "sponsor",537        "type": "tuple"538      }539    ],540    "name": "setCollectionSponsorCross",541    "outputs": [],542    "stateMutability": "nonpayable",543    "type": "function"544  },545  {546    "inputs": [547      {548        "components": [549          { "internalType": "address", "name": "eth", "type": "address" },550          { "internalType": "uint256", "name": "sub", "type": "uint256" }551        ],552        "internalType": "struct EthCrossAccount",553        "name": "newOwner",554        "type": "tuple"555      }556    ],557    "name": "setOwnerCross",558    "outputs": [],559    "stateMutability": "nonpayable",560    "type": "function"561  },562  {563    "inputs": [564      { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }565    ],566    "name": "supportsInterface",567    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],568    "stateMutability": "view",569    "type": "function"570  },571  {572    "inputs": [],573    "name": "symbol",574    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],575    "stateMutability": "view",576    "type": "function"577  },578  {579    "inputs": [],580    "name": "totalSupply",581    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],582    "stateMutability": "view",583    "type": "function"584  },585  {586    "inputs": [587      { "internalType": "address", "name": "to", "type": "address" },588      { "internalType": "uint256", "name": "amount", "type": "uint256" }589    ],590    "name": "transfer",591    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],592    "stateMutability": "nonpayable",593    "type": "function"594  },595  {596    "inputs": [597      {598        "components": [599          { "internalType": "address", "name": "eth", "type": "address" },600          { "internalType": "uint256", "name": "sub", "type": "uint256" }601        ],602        "internalType": "struct EthCrossAccount",603        "name": "to",604        "type": "tuple"605      },606      { "internalType": "uint256", "name": "amount", "type": "uint256" }607    ],608    "name": "transferCross",609    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],610    "stateMutability": "nonpayable",611    "type": "function"612  },613  {614    "inputs": [615      { "internalType": "address", "name": "from", "type": "address" },616      { "internalType": "address", "name": "to", "type": "address" },617      { "internalType": "uint256", "name": "amount", "type": "uint256" }618    ],619    "name": "transferFrom",620    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],621    "stateMutability": "nonpayable",622    "type": "function"623  },624  {625    "inputs": [626      {627        "components": [628          { "internalType": "address", "name": "eth", "type": "address" },629          { "internalType": "uint256", "name": "sub", "type": "uint256" }630        ],631        "internalType": "struct EthCrossAccount",632        "name": "from",633        "type": "tuple"634      },635      {636        "components": [637          { "internalType": "address", "name": "eth", "type": "address" },638          { "internalType": "uint256", "name": "sub", "type": "uint256" }639        ],640        "internalType": "struct EthCrossAccount",641        "name": "to",642        "type": "tuple"643      },644      { "internalType": "uint256", "name": "amount", "type": "uint256" }645    ],646    "name": "transferFromCross",647    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],648    "stateMutability": "nonpayable",649    "type": "function"650  },651  {652    "inputs": [],653    "name": "uniqueCollectionType",654    "outputs": [{ "internalType": "string", "name": "", "type": "string" }],655    "stateMutability": "view",656    "type": "function"657  }658]
modifiedtests/src/eth/nonFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/nonFungible.test.ts
+++ b/tests/src/eth/nonFungible.test.ts
@@ -402,6 +402,60 @@
       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 = await helper.eth.createAccountWithBalance(donor);
+    const to = helper.ethCrossAccount.fromAddress(receiver);
+    const toSubstrate = helper.ethCrossAccount.fromKeyringPair(minter);
+    
+    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);
+    }
+    
+    {
+      const substrateResult = await contract.methods.transferCross(toSubstrate, tokenId).send({from: receiver});
+      
+
+      const event = substrateResult.events.Transfer;
+      expect(event.address).to.be.equal(collectionAddress);
+      expect(event.returnValues.from).to.be.equal(receiver);
+      expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(minter.address));
+      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);
+    }
+
+    {
+      const balance = await contract.methods.balanceOf(receiver).call();
+      expect(+balance).to.equal(0);
+    }
+
+    {
+      const balance = await helper.nft.getTokensByAddress(collection.collectionId, {Substrate: minter.address});
+      expect(balance).to.be.contain(tokenId);
+    }
+  });
 });
 
 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,59 @@
       expect(+balance).to.equal(1);
     }
   });
+  
+  itEth('Can perform transferCross()', async ({helper}) => {
+    const caller = await helper.eth.createAccountWithBalance(donor);
+    const receiver = await helper.eth.createAccountWithBalance(donor);
+    const to = helper.ethCrossAccount.fromAddress(receiver);
+    const toSubstrate = helper.ethCrossAccount.fromKeyringPair(minter);
+    const collection = await helper.rft.mintCollection(minter, {});
+    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
+
+    const {tokenId} = await collection.mintToken(minter, 1n, {Ethereum: caller});
+
+    {
+      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);
+    }
+    
+    {
+      const substrateResult = await contract.methods.transferCross(toSubstrate, tokenId).send({from: receiver});
+      
+
+      const event = substrateResult.events.Transfer;
+      expect(event.address).to.be.equal(collectionAddress);
+      expect(event.returnValues.from).to.be.equal(receiver);
+      expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(minter.address));
+      expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);
+    }
+
+    {
+      const balance = await contract.methods.balanceOf(receiver).call();
+      expect(+balance).to.equal(0);
+    }
+
+    {
+      const balance = await helper.nft.getTokensByAddress(collection.collectionId, {Substrate: minter.address});
+      expect(balance).to.be.contain(tokenId);
+    }
+  });
 
   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" }