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
--- 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.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
before · tests/src/eth/fungible.test.ts
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/>.1617import {expect, itEth, usingEthPlaygrounds} from './util';18import {IKeyringPair} from '@polkadot/types/types';1920describe('Fungible: Information getting', () => {21  let donor: IKeyringPair;22  let alice: IKeyringPair;2324  before(async function() {25    await usingEthPlaygrounds(async (helper, privateKey) => {26      donor = await privateKey({filename: __filename});27      [alice] = await helper.arrange.createAccounts([20n], donor);28    });29  });3031  itEth('totalSupply', async ({helper}) => {32    const caller = await helper.eth.createAccountWithBalance(donor);33    const collection = await helper.ft.mintCollection(alice);34    await collection.mint(alice, 200n);3536    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'ft', caller);37    const totalSupply = await contract.methods.totalSupply().call();38    expect(totalSupply).to.equal('200');39  });4041  itEth('balanceOf', async ({helper}) => {42    const caller = await helper.eth.createAccountWithBalance(donor);43    const collection = await helper.ft.mintCollection(alice);44    await collection.mint(alice, 200n, {Ethereum: caller});4546    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'ft', caller);47    const balance = await contract.methods.balanceOf(caller).call();48    expect(balance).to.equal('200');49  });50});5152describe('Fungible: Plain calls', () => {53  let donor: IKeyringPair;54  let alice: IKeyringPair;55  let owner: IKeyringPair;5657  before(async function() {58    await usingEthPlaygrounds(async (helper, privateKey) => {59      donor = await privateKey({filename: __filename});60      [alice, owner] = await helper.arrange.createAccounts([20n, 20n], donor);61    });62  });6364  itEth('Can perform mint()', async ({helper}) => {65    const owner = await helper.eth.createAccountWithBalance(donor);66    const receiver = helper.eth.createAccount();67    const collection = await helper.ft.mintCollection(alice);68    await collection.addAdmin(alice, {Ethereum: owner});6970    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);71    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);7273    const result = await contract.methods.mint(receiver, 100).send();74    75    const event = result.events.Transfer;76    expect(event.address).to.equal(collectionAddress);77    expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');78    expect(event.returnValues.to).to.equal(receiver);79    expect(event.returnValues.value).to.equal('100');80  });8182  itEth('Can perform mintBulk()', async ({helper}) => {83    const owner = await helper.eth.createAccountWithBalance(donor);84    const bulkSize = 3;85    const receivers = [...new Array(bulkSize)].map(() => helper.eth.createAccount());86    const collection = await helper.ft.mintCollection(alice);87    await collection.addAdmin(alice, {Ethereum: owner});8889    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);90    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);9192    const result = await contract.methods.mintBulk(Array.from({length: bulkSize}, (_, i) => (93      [receivers[i], (i + 1) * 10]94    ))).send();95    const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.value - b.returnValues.value);96    for (let i = 0; i < bulkSize; i++) {97      const event = events[i];98      expect(event.address).to.equal(collectionAddress);99      expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');100      expect(event.returnValues.to).to.equal(receivers[i]);101      expect(event.returnValues.value).to.equal(String(10 * (i + 1)));102    }103  });104105  itEth('Can perform burn()', async ({helper}) => {106    const owner = await helper.eth.createAccountWithBalance(donor);107    const receiver = await helper.eth.createAccountWithBalance(donor);108    const collection = await helper.ft.mintCollection(alice);109    await collection.addAdmin(alice, {Ethereum: owner});110111    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);112    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);113    await contract.methods.mint(receiver, 100).send();114115    const result = await contract.methods.burnFrom(receiver, 49).send({from: receiver});116    117    const event = result.events.Transfer;118    expect(event.address).to.equal(collectionAddress);119    expect(event.returnValues.from).to.equal(receiver);120    expect(event.returnValues.to).to.equal('0x0000000000000000000000000000000000000000');121    expect(event.returnValues.value).to.equal('49');122123    const balance = await contract.methods.balanceOf(receiver).call();124    expect(balance).to.equal('51');125  });126127  itEth('Can perform approve()', async ({helper}) => {128    const owner = await helper.eth.createAccountWithBalance(donor);129    const spender = helper.eth.createAccount();130    const collection = await helper.ft.mintCollection(alice);131    await collection.mint(alice, 200n, {Ethereum: owner});132133    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);134    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);135136    {137      const result = await contract.methods.approve(spender, 100).send({from: owner});138139      const event = result.events.Approval;140      expect(event.address).to.be.equal(collectionAddress);141      expect(event.returnValues.owner).to.be.equal(owner);142      expect(event.returnValues.spender).to.be.equal(spender);143      expect(event.returnValues.value).to.be.equal('100');144    }145146    {147      const allowance = await contract.methods.allowance(owner, spender).call();148      expect(+allowance).to.equal(100);149    }150  });151152  itEth('Can perform burnFromCross()', async ({helper}) => {153    const sender = await helper.eth.createAccountWithBalance(donor, 100n);154155    const collection = await helper.ft.mintCollection(owner, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);156157    await collection.mint(owner, 200n, {Substrate: owner.address});158    await collection.approveTokens(owner, {Ethereum: sender}, 100n);159160    const address = helper.ethAddress.fromCollectionId(collection.collectionId);161    const contract = helper.ethNativeContract.collection(address, 'ft');162163    const fromBalanceBefore = await collection.getBalance({Substrate: owner.address});164    165    const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);166    const result = await contract.methods.burnFromCross(ownerCross, 49).send({from: sender});167    const events = result.events;168169    expect(events).to.be.like({170      Transfer: {171        address: helper.ethAddress.fromCollectionId(collection.collectionId),172        event: 'Transfer',173        returnValues: {174          from: helper.address.substrateToEth(owner.address),175          to: '0x0000000000000000000000000000000000000000',176          value: '49',177        },178      },179      Approval: {180        address: helper.ethAddress.fromCollectionId(collection.collectionId),181        returnValues: {182          owner: helper.address.substrateToEth(owner.address),183          spender: sender,184          value: '51',185        },186        event: 'Approval',187      },188    });189190    const fromBalanceAfter = await collection.getBalance({Substrate: owner.address});191    expect(fromBalanceBefore - fromBalanceAfter).to.be.eq(49n);192  });193194  itEth('Can perform transferFrom()', async ({helper}) => {195    const owner = await helper.eth.createAccountWithBalance(donor);196    const spender = await helper.eth.createAccountWithBalance(donor);197    const receiver = helper.eth.createAccount();198    const collection = await helper.ft.mintCollection(alice);199    await collection.mint(alice, 200n, {Ethereum: owner});200201    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);202    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);203204    await contract.methods.approve(spender, 100).send();205206    {207      const result = await contract.methods.transferFrom(owner, receiver, 49).send({from: spender});208      209      let event = result.events.Transfer;210      expect(event.address).to.be.equal(collectionAddress);211      expect(event.returnValues.from).to.be.equal(owner);212      expect(event.returnValues.to).to.be.equal(receiver);213      expect(event.returnValues.value).to.be.equal('49');214215      event = result.events.Approval;216      expect(event.address).to.be.equal(collectionAddress);217      expect(event.returnValues.owner).to.be.equal(owner);218      expect(event.returnValues.spender).to.be.equal(spender);219      expect(event.returnValues.value).to.be.equal('51');220    }221222    {223      const balance = await contract.methods.balanceOf(receiver).call();224      expect(+balance).to.equal(49);225    }226227    {228      const balance = await contract.methods.balanceOf(owner).call();229      expect(+balance).to.equal(151);230    }231  });232233  itEth('Can perform transfer()', async ({helper}) => {234    const owner = await helper.eth.createAccountWithBalance(donor);235    const receiver = await helper.eth.createAccountWithBalance(donor);236    const collection = await helper.ft.mintCollection(alice);237    await collection.mint(alice, 200n, {Ethereum: owner});238239    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);240    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);241242    {243      const result = await contract.methods.transfer(receiver, 50).send({from: owner});244      245      const event = result.events.Transfer;246      expect(event.address).to.be.equal(collectionAddress);247      expect(event.returnValues.from).to.be.equal(owner);248      expect(event.returnValues.to).to.be.equal(receiver);249      expect(event.returnValues.value).to.be.equal('50');250    }251252    {253      const balance = await contract.methods.balanceOf(owner).call();254      expect(+balance).to.equal(150);255    }256257    {258      const balance = await contract.methods.balanceOf(receiver).call();259      expect(+balance).to.equal(50);260    }261  });262263  itEth('Can perform transferFromCross()', async ({helper, privateKey}) => {264    const sender = await helper.eth.createAccountWithBalance(donor, 100n);265266    const collection = await helper.ft.mintCollection(owner, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);267268    const receiver = helper.eth.createAccount();269270    await collection.mint(owner, 200n, {Substrate: owner.address});271    await collection.approveTokens(owner, {Ethereum: sender}, 100n);272273    const address = helper.ethAddress.fromCollectionId(collection.collectionId);274    const contract = helper.ethNativeContract.collection(address, 'ft');275276    const from = helper.ethCrossAccount.fromKeyringPair(owner);277    const to = helper.ethCrossAccount.fromAddress(receiver);278279    const fromBalanceBefore = await collection.getBalance({Substrate: owner.address});280    const toBalanceBefore = await collection.getBalance({Ethereum: receiver});281    282    const result = await contract.methods.transferFromCross(from, to, 51).send({from: sender});283284    expect(result.events).to.be.like({285      Transfer: {286        address,287        event: 'Transfer',288        returnValues: {289          from: helper.address.substrateToEth(owner.address),290          to: receiver,291          value: '51',292        },293      },294      Approval: {295        address,296        event: 'Approval',297        returnValues: {298          owner: helper.address.substrateToEth(owner.address),299          spender: sender,300          value: '49',301        },302      }});303304    const fromBalanceAfter = await collection.getBalance({Substrate: owner.address});305    expect(fromBalanceBefore - fromBalanceAfter).to.be.eq(51n);306    const toBalanceAfter = await collection.getBalance({Ethereum: receiver});307    expect(toBalanceAfter - toBalanceBefore).to.be.eq(51n);308  });309});310311describe('Fungible: Fees', () => {312  let donor: IKeyringPair;313  let alice: IKeyringPair;314315  before(async function() {316    await usingEthPlaygrounds(async (helper, privateKey) => {317      donor = await privateKey({filename: __filename});318      [alice] = await helper.arrange.createAccounts([20n], donor);319    });320  });321  322  itEth('approve() call fee is less than 0.2UNQ', async ({helper}) => {323    const owner = await helper.eth.createAccountWithBalance(donor);324    const spender = helper.eth.createAccount();325    const collection = await helper.ft.mintCollection(alice);326    await collection.mint(alice, 200n, {Ethereum: owner});327328    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);329    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);330331    const cost = await helper.eth.recordCallFee(owner, () => contract.methods.approve(spender, 100).send({from: owner}));332    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));333  });334335  itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {336    const owner = await helper.eth.createAccountWithBalance(donor);337    const spender = await helper.eth.createAccountWithBalance(donor);338    const collection = await helper.ft.mintCollection(alice);339    await collection.mint(alice, 200n, {Ethereum: owner});340341    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);342    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);343344    await contract.methods.approve(spender, 100).send({from: owner});345346    const cost = await helper.eth.recordCallFee(spender, () => contract.methods.transferFrom(owner, spender, 100).send({from: spender}));347    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));348  });349350  itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {351    const owner = await helper.eth.createAccountWithBalance(donor);352    const receiver = helper.eth.createAccount();353    const collection = await helper.ft.mintCollection(alice);354    await collection.mint(alice, 200n, {Ethereum: owner});355356    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);357    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);358359    const cost = await helper.eth.recordCallFee(owner, () => contract.methods.transfer(receiver, 100).send({from: owner}));360    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));361  });362});363364describe('Fungible: Substrate calls', () => {365  let donor: IKeyringPair;366  let alice: IKeyringPair;367  let owner: IKeyringPair;368369  before(async function() {370    await usingEthPlaygrounds(async (helper, privateKey) => {371      donor = await privateKey({filename: __filename});372      [alice, owner] = await helper.arrange.createAccounts([20n, 20n], donor);373    });374  });375376  itEth('Events emitted for approve()', async ({helper}) => {377    const receiver = helper.eth.createAccount();378    const collection = await helper.ft.mintCollection(alice);379    await collection.mint(alice, 200n);380381    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);382    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft');383384    const events: any = [];385    contract.events.allEvents((_: any, event: any) => {386      events.push(event);387    });388    389    await collection.approveTokens(alice, {Ethereum: receiver}, 100n);390    if (events.length == 0) await helper.wait.newBlocks(1);391    const event = events[0];392393    expect(event.event).to.be.equal('Approval');394    expect(event.address).to.be.equal(collectionAddress);395    expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));396    expect(event.returnValues.spender).to.be.equal(receiver);397    expect(event.returnValues.value).to.be.equal('100');398  });399400  itEth('Events emitted for transferFrom()', async ({helper}) => {401    const [bob] = await helper.arrange.createAccounts([10n], donor);402    const receiver = helper.eth.createAccount();403    const collection = await helper.ft.mintCollection(alice);404    await collection.mint(alice, 200n);405    await collection.approveTokens(alice, {Substrate: bob.address}, 100n);406407    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);408    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft');409410    const events: any = [];411    contract.events.allEvents((_: any, event: any) => {412      events.push(event);413    });414415    await collection.transferFrom(bob, {Substrate: alice.address}, {Ethereum: receiver}, 51n);416    if (events.length == 0) await helper.wait.newBlocks(1);417    let event = events[0];418419    expect(event.event).to.be.equal('Transfer');420    expect(event.address).to.be.equal(collectionAddress);421    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));422    expect(event.returnValues.to).to.be.equal(receiver);423    expect(event.returnValues.value).to.be.equal('51');424425    event = events[1];426    expect(event.event).to.be.equal('Approval');427    expect(event.address).to.be.equal(collectionAddress);428    expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));429    expect(event.returnValues.spender).to.be.equal(helper.address.substrateToEth(bob.address));430    expect(event.returnValues.value).to.be.equal('49');431  });432433  itEth('Events emitted for transfer()', async ({helper}) => {434    const receiver = helper.eth.createAccount();435    const collection = await helper.ft.mintCollection(alice);436    await collection.mint(alice, 200n);437438    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);439    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft');440441    const events: any = [];442    contract.events.allEvents((_: any, event: any) => {443      events.push(event);444    });445    446    await collection.transfer(alice, {Ethereum:receiver}, 51n);447    if (events.length == 0) await helper.wait.newBlocks(1);448    const event = events[0];449450    expect(event.event).to.be.equal('Transfer');451    expect(event.address).to.be.equal(collectionAddress);452    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));453    expect(event.returnValues.to).to.be.equal(receiver);454    expect(event.returnValues.value).to.be.equal('51');455  });456457  itEth('Events emitted for transferFromCross()', async ({helper, privateKey}) => {458    const sender = await helper.eth.createAccountWithBalance(donor, 100n);459460    const collection = await helper.ft.mintCollection(owner, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);461462    const receiver = helper.eth.createAccount();463464    await collection.mint(owner, 200n, {Substrate: owner.address});465    await collection.approveTokens(owner, {Ethereum: sender}, 100n);466467    const address = helper.ethAddress.fromCollectionId(collection.collectionId);468    const contract = helper.ethNativeContract.collection(address, 'ft');469470    const from = helper.ethCrossAccount.fromKeyringPair(owner);471    const to = helper.ethCrossAccount.fromAddress(receiver);472    473    const result = await contract.methods.transferFromCross(from, to, 51).send({from: sender});474475    expect(result.events).to.be.like({476      Transfer: {477        address,478        event: 'Transfer',479        returnValues: {480          from: helper.address.substrateToEth(owner.address),481          to: receiver,482          value: '51',483        },484      },485      Approval: {486        address,487        event: 'Approval',488        returnValues: {489          owner: helper.address.substrateToEth(owner.address),490          spender: sender,491          value: '49',492        },493      }});494  });495});
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" }