difftreelog
feat add ApproveForAll to Eth and Sub
in: master
39 files changed
client/rpc/src/lib.rsdiffbeforeafterboth--- a/client/rpc/src/lib.rs
+++ b/client/rpc/src/lib.rs
@@ -246,6 +246,16 @@
token_id: TokenId,
at: Option<BlockHash>,
) -> Result<Option<String>>;
+
+ /// Get whether an operator is approved by a given owner.
+ #[method(name = "unique_isApprovedForAll")]
+ fn is_approved_for_all(
+ &self,
+ collection: CollectionId,
+ owner: CrossAccountId,
+ operator: CrossAccountId,
+ at: Option<BlockHash>,
+ ) -> Result<bool>;
}
mod app_promotion_unique_rpc {
@@ -569,6 +579,7 @@
pass_method!(effective_collection_limits(collection_id: CollectionId) -> Option<CollectionLimits>, unique_api);
pass_method!(total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<String> => |o| o.map(|number| number.to_string()) , unique_api);
pass_method!(token_owners(collection: CollectionId, token: TokenId) -> Vec<CrossAccountId>, unique_api);
+ pass_method!(is_approved_for_all(collection: CollectionId, owner: CrossAccountId, operator: CrossAccountId) -> bool, unique_api);
}
impl<C, Block, BlockNumber, CrossAccountId, AccountId>
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -472,6 +472,18 @@
u128,
),
+ /// Amount pieces of token owned by `sender` was approved for `spender`.
+ ApprovedForAll(
+ /// Id of collection to which item is belong.
+ CollectionId,
+ /// Owner of a wallet.
+ T::CrossAccountId,
+ /// Id for which operator status was granted or rewoked.
+ T::CrossAccountId,
+ /// Is operator status was granted or rewoked.
+ bool,
+ ),
+
/// The colletion property has been added or edited.
CollectionPropertySet(
/// Id of collection to which property has been set.
@@ -1521,6 +1533,9 @@
/// The price of retrieving token owner
fn token_owner() -> Weight;
+
+ /// The price of setting approval for all
+ fn set_approval_for_all() -> Weight;
}
/// Weight info extension trait for refungible pallet.
@@ -1828,6 +1843,20 @@
/// Get extension for RFT collection.
fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;
+
+ /// An operator is allowed to transfer all tokens of the sender on their behalf.
+ /// * `owner` - Token owner
+ /// * `operator` - Operator
+ /// * `approve` - Is operator enabled or disabled
+ fn set_approval_for_all(
+ &self,
+ owner: T::CrossAccountId,
+ operator: T::CrossAccountId,
+ approve: bool,
+ ) -> DispatchResultWithPostInfo;
+
+ /// Tells whether an operator is approved by a given owner.
+ fn is_approved_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;
}
/// Extension for RFT collection.
pallets/fungible/src/common.rsdiffbeforeafterboth--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -107,6 +107,10 @@
fn token_owner() -> Weight {
Weight::zero()
}
+
+ fn set_approval_for_all() -> Weight {
+ Weight::zero()
+ }
}
/// Implementation of `CommonCollectionOperations` for `FungibleHandle`. It wraps FungibleHandle Pallete
@@ -424,4 +428,17 @@
}
<TotalSupply<T>>::try_get(self.id).ok()
}
+
+ fn set_approval_for_all(
+ &self,
+ _owner: T::CrossAccountId,
+ _operator: T::CrossAccountId,
+ _approve: bool,
+ ) -> DispatchResultWithPostInfo {
+ fail!(<Error<T>>::SettingApprovalForAllNotAllowed)
+ }
+
+ fn is_approved_for_all(&self, _owner: T::CrossAccountId, _operator: T::CrossAccountId) -> bool {
+ false
+ }
}
pallets/fungible/src/lib.rsdiffbeforeafterboth--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -127,6 +127,8 @@
FungibleDisallowsNesting,
/// Setting item properties is not allowed.
SettingPropertiesNotAllowed,
+ /// Setting approval for all is not allowed.
+ SettingApprovalForAllNotAllowed,
}
#[pallet::config]
pallets/nonfungible/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/benchmarking.rs
+++ b/pallets/nonfungible/src/benchmarking.rs
@@ -222,4 +222,18 @@
let item = create_max_item(&collection, &owner, owner.clone())?;
}: {collection.token_owner(item)}
+
+ set_approval_for_all {
+ bench_init!{
+ owner: sub; collection: collection(owner);
+ operator: cross_from_sub(owner); owner: cross_sub;
+ };
+ }: {<Pallet<T>>::set_approval_for_all(&collection, &owner, &operator, true)}
+
+ is_approved_for_all {
+ bench_init!{
+ owner: sub; collection: collection(owner);
+ operator: cross_from_sub(owner); owner: cross_sub;
+ };
+ }: {<Pallet<T>>::is_approved_for_all(&collection, &owner, &operator)}
}
pallets/nonfungible/src/common.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -122,6 +122,10 @@
fn token_owner() -> Weight {
<SelfWeightOf<T>>::token_owner()
}
+
+ fn set_approval_for_all() -> Weight {
+ <SelfWeightOf<T>>::set_approval_for_all()
+ }
}
fn map_create_data<T: Config>(
@@ -512,4 +516,20 @@
None
}
}
+
+ fn set_approval_for_all(
+ &self,
+ owner: T::CrossAccountId,
+ operator: T::CrossAccountId,
+ approve: bool,
+ ) -> DispatchResultWithPostInfo {
+ with_weight(
+ <Pallet<T>>::set_approval_for_all(self, &owner, &operator, approve),
+ <CommonWeights<T>>::set_approval_for_all(),
+ )
+ }
+
+ fn is_approved_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool {
+ <Pallet<T>>::is_approved_for_all(self, &owner, &operator)
+ }
}
pallets/nonfungible/src/erc.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -469,15 +469,23 @@
Ok(())
}
- /// @dev Not implemented
+ /// @notice Sets or unsets the approval of a given operator.
+ /// An operator is allowed to transfer all tokens of the sender on their behalf.
+ /// @param operator Operator
+ /// @param approved Is operator enabled or disabled
+ #[weight(<SelfWeightOf<T>>::set_approval_for_all())]
fn set_approval_for_all(
&mut self,
- _caller: caller,
- _operator: address,
- _approved: bool,
+ caller: caller,
+ operator: address,
+ approved: bool,
) -> Result<void> {
- // TODO: Not implemetable
- Err("not implemented".into())
+ let caller = T::CrossAccountId::from_eth(caller);
+ let operator = T::CrossAccountId::from_eth(operator);
+
+ <Pallet<T>>::set_approval_for_all(self, &caller, &operator, approved)
+ .map_err(dispatch_to_evm::<T>)?;
+ Ok(())
}
/// @dev Not implemented
@@ -486,10 +494,13 @@
Err("not implemented".into())
}
- /// @dev Not implemented
- fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {
- // TODO: Not implemetable
- Err("not implemented".into())
+ /// @notice Tells whether an operator is approved by a given owner.
+ #[weight(<SelfWeightOf<T>>::is_approved_for_all())]
+ fn is_approved_for_all(&self, owner: address, operator: address) -> Result<bool> {
+ let owner = T::CrossAccountId::from_eth(owner);
+ let operator = T::CrossAccountId::from_eth(operator);
+
+ Ok(<Pallet<T>>::is_approved_for_all(self, &owner, &operator))
}
/// @notice Returns collection helper contract address
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -272,6 +272,18 @@
QueryKind = OptionQuery,
>;
+ /// Operator set by a wallet owner that could perform certain transactions on all tokens in the wallet.
+ #[pallet::storage]
+ pub type WalletOperator<T: Config> = StorageNMap<
+ Key = (
+ Key<Twox64Concat, CollectionId>,
+ Key<Blake2_128Concat, T::CrossAccountId>,
+ Key<Blake2_128Concat, T::CrossAccountId>,
+ ),
+ Value = bool,
+ QueryKind = OptionQuery,
+ >;
+
/// Upgrade from the old schema to properties.
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
@@ -438,6 +450,7 @@
<TokensBurnt<T>>::remove(id);
let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);
let _ = <AccountBalance<T>>::clear_prefix((id,), u32::MAX, None);
+ let _ = <WalletOperator<T>>::clear_prefix((id,), u32::MAX, None);
Ok(())
}
@@ -1193,6 +1206,9 @@
if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {
return Ok(());
}
+ if <WalletOperator<T>>::get((collection.id, from, spender)) == Some(true) {
+ return Ok(());
+ }
ensure!(
collection.ignores_allowance(spender),
<CommonError<T>>::ApprovedValueTooLow
@@ -1326,4 +1342,52 @@
) -> DispatchResult {
Self::create_multiple_items(collection, sender, vec![data], nesting_budget)
}
+
+ /// Sets or unsets the approval of a given operator.
+ ///
+ /// An operator is allowed to transfer all token pieces of the sender on their behalf.
+ /// - `owner`: Token owner
+ /// - `operator`: Operator
+ /// - `approve`: Is operator enabled or disabled
+ pub fn set_approval_for_all(
+ collection: &NonfungibleHandle<T>,
+ owner: &T::CrossAccountId,
+ operator: &T::CrossAccountId,
+ approve: bool,
+ ) -> DispatchResult {
+ if collection.permissions.access() == AccessMode::AllowList {
+ collection.check_allowlist(owner)?;
+ collection.check_allowlist(operator)?;
+ }
+
+ <PalletCommon<T>>::ensure_correct_receiver(operator)?;
+
+ // =========
+
+ <WalletOperator<T>>::insert((collection.id, owner, operator), approve);
+ <PalletEvm<T>>::deposit_log(
+ ERC721Events::ApprovalForAll {
+ owner: *owner.as_eth(),
+ operator: *operator.as_eth(),
+ approved: approve,
+ }
+ .to_log(collection_id_to_address(collection.id)),
+ );
+ <PalletCommon<T>>::deposit_event(CommonEvent::ApprovedForAll(
+ collection.id,
+ owner.clone(),
+ operator.clone(),
+ approve,
+ ));
+ Ok(())
+ }
+
+ /// Tells whether an operator is approved by a given owner.
+ pub fn is_approved_for_all(
+ collection: &NonfungibleHandle<T>,
+ owner: &T::CrossAccountId,
+ operator: &T::CrossAccountId,
+ ) -> bool {
+ <WalletOperator<T>>::get((collection.id, owner, operator)).unwrap_or(false)
+ }
}
pallets/nonfungible/src/stubs/UniqueNFT.rawdiffbeforeafterbothbinary blob — no preview
pallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -1020,7 +1020,10 @@
dummy = 0;
}
- /// @dev Not implemented
+ /// @notice Sets or unsets the approval of a given operator.
+ /// An operator is allowed to transfer all tokens of the sender on their behalf.
+ /// @param operator Operator
+ /// @param approved Is operator enabled or disabled
/// @dev EVM selector for this function is: 0xa22cb465,
/// or in textual repr: setApprovalForAll(address,bool)
function setApprovalForAll(address operator, bool approved) public {
@@ -1040,15 +1043,15 @@
return 0x0000000000000000000000000000000000000000;
}
- /// @dev Not implemented
+ /// @notice Tells whether an operator is approved by a given owner.
/// @dev EVM selector for this function is: 0xe985e9c5,
/// or in textual repr: isApprovedForAll(address,address)
- function isApprovedForAll(address owner, address operator) public view returns (address) {
+ function isApprovedForAll(address owner, address operator) public view returns (bool) {
require(false, stub_error);
owner;
operator;
dummy;
- return 0x0000000000000000000000000000000000000000;
+ return false;
}
/// @notice Returns collection helper contract address
pallets/nonfungible/src/weights.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/weights.rs
+++ b/pallets/nonfungible/src/weights.rs
@@ -26,6 +26,7 @@
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
+#![allow(missing_docs)]
#![allow(clippy::unnecessary_cast)]
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
@@ -47,6 +48,8 @@
fn set_token_properties(b: u32, ) -> Weight;
fn delete_token_properties(b: u32, ) -> Weight;
fn token_owner() -> Weight;
+ fn set_approval_for_all() -> Weight;
+ fn is_approved_for_all() -> Weight;
}
/// Weights for pallet_nonfungible using the Substrate node and recommended hardware.
@@ -195,6 +198,16 @@
Weight::from_ref_time(4_366_000)
.saturating_add(T::DbWeight::get().reads(1 as u64))
}
+ // Storage: Nonfungible WalletOperator (r:0 w:1)
+ fn set_approval_for_all() -> Weight {
+ Weight::from_ref_time(16_231_000 as u64)
+ .saturating_add(T::DbWeight::get().writes(1 as u64))
+ }
+ // Storage: Nonfungible WalletOperator (r:1 w:0)
+ fn is_approved_for_all() -> Weight {
+ Weight::from_ref_time(6_161_000 as u64)
+ .saturating_add(T::DbWeight::get().reads(1 as u64))
+ }
}
// For backwards compatibility and tests
@@ -342,4 +355,14 @@
Weight::from_ref_time(4_366_000)
.saturating_add(RocksDbWeight::get().reads(1 as u64))
}
+ // Storage: Nonfungible WalletOperator (r:0 w:1)
+ fn set_approval_for_all() -> Weight {
+ Weight::from_ref_time(16_231_000 as u64)
+ .saturating_add(RocksDbWeight::get().writes(1 as u64))
+ }
+ // Storage: Nonfungible WalletOperator (r:1 w:0)
+ fn is_approved_for_all() -> Weight {
+ Weight::from_ref_time(6_161_000 as u64)
+ .saturating_add(RocksDbWeight::get().reads(1 as u64))
+ }
}
pallets/refungible/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/refungible/src/benchmarking.rs
+++ b/pallets/refungible/src/benchmarking.rs
@@ -290,4 +290,18 @@
};
let item = create_max_item(&collection, &sender, [(owner.clone(), 100)])?;
}: {<Pallet<T>>::token_owner(collection.id, item)}
+
+ set_approval_for_all {
+ bench_init!{
+ owner: sub; collection: collection(owner);
+ operator: cross_from_sub(owner); owner: cross_sub;
+ };
+ }: {<Pallet<T>>::set_approval_for_all(&collection, &owner, &operator, true)}
+
+ is_approved_for_all {
+ bench_init!{
+ owner: sub; collection: collection(owner);
+ operator: cross_from_sub(owner); owner: cross_sub;
+ };
+ }: {<Pallet<T>>::is_approved_for_all(&collection, &owner, &operator)}
}
pallets/refungible/src/common.rsdiffbeforeafterboth--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -152,6 +152,10 @@
fn token_owner() -> Weight {
<SelfWeightOf<T>>::token_owner()
}
+
+ fn set_approval_for_all() -> Weight {
+ <SelfWeightOf<T>>::set_approval_for_all()
+ }
}
fn map_create_data<T: Config>(
@@ -516,6 +520,22 @@
fn total_pieces(&self, token: TokenId) -> Option<u128> {
<Pallet<T>>::total_pieces(self.id, token)
}
+
+ fn set_approval_for_all(
+ &self,
+ owner: T::CrossAccountId,
+ operator: T::CrossAccountId,
+ approve: bool,
+ ) -> DispatchResultWithPostInfo {
+ with_weight(
+ <Pallet<T>>::set_approval_for_all(self, &owner, &operator, approve),
+ <CommonWeights<T>>::set_approval_for_all(),
+ )
+ }
+
+ fn is_approved_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool {
+ <Pallet<T>>::is_approved_for_all(self, &owner, &operator)
+ }
}
impl<T: Config> RefungibleExtensions<T> for RefungibleHandle<T> {
pallets/refungible/src/erc.rsdiffbeforeafterboth--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -461,15 +461,23 @@
Err("not implemented".into())
}
- /// @dev Not implemented
+ /// @notice Sets or unsets the approval of a given operator.
+ /// An operator is allowed to transfer all tokens of the sender on their behalf.
+ /// @param operator Operator
+ /// @param approved Is operator enabled or disabled
+ #[weight(<SelfWeightOf<T>>::set_approval_for_all())]
fn set_approval_for_all(
&mut self,
- _caller: caller,
- _operator: address,
- _approved: bool,
+ caller: caller,
+ operator: address,
+ approved: bool,
) -> Result<void> {
- // TODO: Not implemetable
- Err("not implemented".into())
+ let caller = T::CrossAccountId::from_eth(caller);
+ let operator = T::CrossAccountId::from_eth(operator);
+
+ <Pallet<T>>::set_approval_for_all(self, &caller, &operator, approved)
+ .map_err(dispatch_to_evm::<T>)?;
+ Ok(())
}
/// @dev Not implemented
@@ -478,10 +486,13 @@
Err("not implemented".into())
}
- /// @dev Not implemented
- fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {
- // TODO: Not implemetable
- Err("not implemented".into())
+ /// @notice Tells whether an operator is approved by a given owner.
+ #[weight(<SelfWeightOf<T>>::is_approved_for_all())]
+ fn is_approved_for_all(&self, owner: address, operator: address) -> Result<bool> {
+ let owner = T::CrossAccountId::from_eth(owner);
+ let operator = T::CrossAccountId::from_eth(operator);
+
+ Ok(<Pallet<T>>::is_approved_for_all(self, &owner, &operator))
}
/// @notice Returns collection helper contract address
pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -273,6 +273,18 @@
QueryKind = ValueQuery,
>;
+ /// Operator set by a wallet owner that could perform certain transactions on all tokens in the wallet.
+ #[pallet::storage]
+ pub type WalletOperator<T: Config> = StorageNMap<
+ Key = (
+ Key<Twox64Concat, CollectionId>,
+ Key<Blake2_128Concat, T::CrossAccountId>,
+ Key<Blake2_128Concat, T::CrossAccountId>,
+ ),
+ Value = bool,
+ QueryKind = OptionQuery,
+ >;
+
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_runtime_upgrade() -> Weight {
@@ -1161,6 +1173,12 @@
}
let allowance =
<Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);
+
+ // Allowance if any would be reduced if spender is also wallet operator
+ if <WalletOperator<T>>::get((collection.id, from, spender)) == Some(true) {
+ return Ok(allowance);
+ }
+
if allowance.is_none() {
ensure!(
collection.ignores_allowance(spender),
@@ -1387,4 +1405,52 @@
Some(res)
}
}
+
+ /// Sets or unsets the approval of a given operator.
+ ///
+ /// An operator is allowed to transfer all tokens of the sender on their behalf.
+ /// - `owner`: Token owner
+ /// - `operator`: Operator
+ /// - `approve`: Is operator enabled or disabled
+ pub fn set_approval_for_all(
+ collection: &RefungibleHandle<T>,
+ owner: &T::CrossAccountId,
+ operator: &T::CrossAccountId,
+ approve: bool,
+ ) -> DispatchResult {
+ if collection.permissions.access() == AccessMode::AllowList {
+ collection.check_allowlist(owner)?;
+ collection.check_allowlist(operator)?;
+ }
+
+ <PalletCommon<T>>::ensure_correct_receiver(operator)?;
+
+ // =========
+
+ <WalletOperator<T>>::insert((collection.id, owner, operator), approve);
+ <PalletEvm<T>>::deposit_log(
+ ERC721Events::ApprovalForAll {
+ owner: *owner.as_eth(),
+ operator: *operator.as_eth(),
+ approved: approve,
+ }
+ .to_log(collection_id_to_address(collection.id)),
+ );
+ <PalletCommon<T>>::deposit_event(CommonEvent::ApprovedForAll(
+ collection.id,
+ owner.clone(),
+ operator.clone(),
+ approve,
+ ));
+ Ok(())
+ }
+
+ /// Tells whether an operator is approved by a given owner.
+ pub fn is_approved_for_all(
+ collection: &RefungibleHandle<T>,
+ owner: &T::CrossAccountId,
+ operator: &T::CrossAccountId,
+ ) -> bool {
+ <WalletOperator<T>>::get((collection.id, owner, operator)).unwrap_or(false)
+ }
}
pallets/refungible/src/stubs/UniqueRefungible.rawdiffbeforeafterbothbinary blob — no preview
pallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth--- a/pallets/refungible/src/stubs/UniqueRefungible.sol
+++ b/pallets/refungible/src/stubs/UniqueRefungible.sol
@@ -1017,7 +1017,10 @@
dummy = 0;
}
- /// @dev Not implemented
+ /// @notice Sets or unsets the approval of a given operator.
+ /// An operator is allowed to transfer all tokens of the sender on their behalf.
+ /// @param operator Operator
+ /// @param approved Is operator enabled or disabled
/// @dev EVM selector for this function is: 0xa22cb465,
/// or in textual repr: setApprovalForAll(address,bool)
function setApprovalForAll(address operator, bool approved) public {
@@ -1037,15 +1040,15 @@
return 0x0000000000000000000000000000000000000000;
}
- /// @dev Not implemented
+ /// @notice Tells whether an operator is approved by a given owner.
/// @dev EVM selector for this function is: 0xe985e9c5,
/// or in textual repr: isApprovedForAll(address,address)
- function isApprovedForAll(address owner, address operator) public view returns (address) {
+ function isApprovedForAll(address owner, address operator) public view returns (bool) {
require(false, stub_error);
owner;
operator;
dummy;
- return 0x0000000000000000000000000000000000000000;
+ return false;
}
/// @notice Returns collection helper contract address
pallets/refungible/src/weights.rsdiffbeforeafterboth--- a/pallets/refungible/src/weights.rs
+++ b/pallets/refungible/src/weights.rs
@@ -3,7 +3,7 @@
//! Autogenerated weights for pallet_refungible
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
-//! DATE: 2022-08-15, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! DATE: 2022-11-25, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024
// Executed Command:
@@ -26,6 +26,7 @@
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
+#![allow(missing_docs)]
#![allow(clippy::unnecessary_cast)]
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
@@ -54,6 +55,8 @@
fn delete_token_properties(b: u32, ) -> Weight;
fn repartition_item() -> Weight;
fn token_owner() -> Weight;
+ fn set_approval_for_all() -> Weight;
+ fn is_approved_for_all() -> Weight;
}
/// Weights for pallet_refungible using the Substrate node and recommended hardware.
@@ -259,6 +262,16 @@
Weight::from_ref_time(9_431_000)
.saturating_add(T::DbWeight::get().reads(2 as u64))
}
+ // Storage: Refungible WalletOperator (r:0 w:1)
+ fn set_approval_for_all() -> Weight {
+ Weight::from_ref_time(16_150_000 as u64)
+ .saturating_add(T::DbWeight::get().writes(1 as u64))
+ }
+ // Storage: Refungible WalletOperator (r:1 w:0)
+ fn is_approved_for_all() -> Weight {
+ Weight::from_ref_time(5_901_000 as u64)
+ .saturating_add(T::DbWeight::get().reads(1 as u64))
+ }
}
// For backwards compatibility and tests
@@ -463,4 +476,14 @@
Weight::from_ref_time(9_431_000)
.saturating_add(RocksDbWeight::get().reads(2 as u64))
}
+ // Storage: Refungible WalletOperator (r:0 w:1)
+ fn set_approval_for_all() -> Weight {
+ Weight::from_ref_time(16_150_000 as u64)
+ .saturating_add(RocksDbWeight::get().writes(1 as u64))
+ }
+ // Storage: Refungible WalletOperator (r:1 w:0)
+ fn is_approved_for_all() -> Weight {
+ Weight::from_ref_time(5_901_000 as u64)
+ .saturating_add(RocksDbWeight::get().reads(1 as u64))
+ }
}
pallets/unique/src/lib.rsdiffbeforeafterboth--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -1126,6 +1126,28 @@
}
})
}
+
+ /// Sets or unsets the approval of a given operator.
+ ///
+ /// An operator is allowed to transfer all tokens of the sender on their behalf.
+ ///
+ /// # Arguments
+ ///
+ /// * `owner`: Token owner
+ /// * `operator`: Operator
+ /// * `approve`: Is operator enabled or disabled
+ #[weight = T::CommonWeightInfo::set_approval_for_all()]
+ pub fn set_approval_for_all(
+ origin,
+ collection_id: CollectionId,
+ operator: T::CrossAccountId,
+ approve: bool,
+ ) -> DispatchResultWithPostInfo {
+ let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
+ dispatch_tx::<T, _>(collection_id, |d| {
+ d.set_approval_for_all(sender, operator, approve)
+ })
+ }
}
}
primitives/rpc/src/lib.rsdiffbeforeafterboth--- a/primitives/rpc/src/lib.rs
+++ b/primitives/rpc/src/lib.rs
@@ -132,5 +132,8 @@
fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Result<Option<u128>>;
fn token_owners(collection: CollectionId, token: TokenId) -> Result<Vec<CrossAccountId>>;
+
+ /// Get whether an operator is approved by a given owner.
+ fn is_approved_for_all(collection: CollectionId, owner: CrossAccountId, operator: CrossAccountId) -> Result<bool>;
}
}
runtime/common/runtime_apis.rsdiffbeforeafterboth--- a/runtime/common/runtime_apis.rs
+++ b/runtime/common/runtime_apis.rs
@@ -187,6 +187,10 @@
fn total_pieces(collection: CollectionId, token_id: TokenId) -> Result<Option<u128>, DispatchError> {
dispatch_unique_runtime!(collection.total_pieces(token_id))
}
+
+ fn is_approved_for_all(collection: CollectionId, owner: CrossAccountId, operator: CrossAccountId) -> Result<bool, DispatchError> {
+ dispatch_unique_runtime!(collection.is_approved_for_all(owner, operator))
+ }
}
impl app_promotion_rpc::AppPromotionApi<Block, BlockNumber, CrossAccountId, AccountId> for Runtime {
runtime/common/weights.rsdiffbeforeafterboth--- a/runtime/common/weights.rs
+++ b/runtime/common/weights.rs
@@ -120,6 +120,10 @@
fn token_owner() -> Weight {
max_weight_of!(token_owner())
}
+
+ fn set_approval_for_all() -> Weight {
+ max_weight_of!(set_approval_for_all())
+ }
}
#[cfg(feature = "refungible")]
tests/src/approve.test.tsdiffbeforeafterboth--- a/tests/src/approve.test.ts
+++ b/tests/src/approve.test.ts
@@ -603,3 +603,40 @@
await expect(approveTx()).to.be.rejected;
});
});
+
+describe('Normal user can approve other users to be wallet operator:', () => {
+ let alice: IKeyringPair;
+ let bob: IKeyringPair;
+
+ before(async () => {
+ await usingPlaygrounds(async (helper, privateKey) => {
+ const donor = await privateKey({filename: __filename});
+ [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor);
+ });
+ });
+
+ itSub('[nft] Enable and disable approval', async ({helper}) => {
+ const {collectionId} = await helper.nft.mintCollection(alice, {name: 'col', description: 'descr', tokenPrefix: 'COL'});
+ await helper.nft.mintToken(alice, {collectionId: collectionId, owner: alice.address});
+ const checkBeforeApprovalTx = () => helper.nft.isApprovedForAll(collectionId, {Substrate: alice.address}, {Substrate: bob.address});
+ expect(await checkBeforeApprovalTx()).to.be.false;
+ await helper.nft.setApprovalForAll(alice, collectionId, {Substrate: bob.address}, true);
+ const checkAfterApprovalTx = () => helper.nft.isApprovedForAll(collectionId, {Substrate: alice.address}, {Substrate: bob.address});
+ expect(await checkAfterApprovalTx()).to.be.true;
+ await helper.nft.setApprovalForAll(alice, collectionId, {Substrate: bob.address}, false);
+ const checkAfterDisapprovalTx = () => helper.nft.isApprovedForAll(collectionId, {Substrate: alice.address}, {Substrate: bob.address});
+ expect(await checkAfterDisapprovalTx()).to.be.false;
+ });
+
+ itSub.ifWithPallets('[rft] Enable and disable approval', [Pallets.ReFungible], async ({helper}) => {
+ const {collectionId} = await helper.rft.mintCollection(alice, {name: 'col', description: 'descr', tokenPrefix: 'COL'});
+ const checkBeforeApprovalTx = () => helper.rft.isApprovedForAll(collectionId, {Substrate: alice.address}, {Substrate: bob.address});
+ expect(await checkBeforeApprovalTx()).to.be.false;
+ await helper.rft.setApprovalForAll(alice, collectionId, {Substrate: bob.address}, true);
+ const checkAfterApprovalTx = () => helper.rft.isApprovedForAll(collectionId, {Substrate: alice.address}, {Substrate: bob.address});
+ expect(await checkAfterApprovalTx()).to.be.true;
+ await helper.rft.setApprovalForAll(alice, collectionId, {Substrate: bob.address}, false);
+ const checkAfterDisapprovalTx = () => helper.rft.isApprovedForAll(collectionId, {Substrate: alice.address}, {Substrate: bob.address});
+ expect(await checkAfterDisapprovalTx()).to.be.false;
+ });
+});
tests/src/eth/abi/nonFungible.jsondiffbeforeafterboth1[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": "approved",15 "type": "address"16 },17 {18 "indexed": true,19 "internalType": "uint256",20 "name": "tokenId",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": "owner",34 "type": "address"35 },36 {37 "indexed": true,38 "internalType": "address",39 "name": "operator",40 "type": "address"41 },42 {43 "indexed": false,44 "internalType": "bool",45 "name": "approved",46 "type": "bool"47 }48 ],49 "name": "ApprovalForAll",50 "type": "event"51 },52 {53 "anonymous": false,54 "inputs": [],55 "name": "MintingFinished",56 "type": "event"57 },58 {59 "anonymous": false,60 "inputs": [61 {62 "indexed": true,63 "internalType": "address",64 "name": "from",65 "type": "address"66 },67 {68 "indexed": true,69 "internalType": "address",70 "name": "to",71 "type": "address"72 },73 {74 "indexed": true,75 "internalType": "uint256",76 "name": "tokenId",77 "type": "uint256"78 }79 ],80 "name": "Transfer",81 "type": "event"82 },83 {84 "inputs": [85 {86 "components": [87 { "internalType": "address", "name": "eth", "type": "address" },88 { "internalType": "uint256", "name": "sub", "type": "uint256" }89 ],90 "internalType": "struct EthCrossAccount",91 "name": "newAdmin",92 "type": "tuple"93 }94 ],95 "name": "addCollectionAdminCross",96 "outputs": [],97 "stateMutability": "nonpayable",98 "type": "function"99 },100 {101 "inputs": [102 {103 "components": [104 { "internalType": "address", "name": "eth", "type": "address" },105 { "internalType": "uint256", "name": "sub", "type": "uint256" }106 ],107 "internalType": "struct EthCrossAccount",108 "name": "user",109 "type": "tuple"110 }111 ],112 "name": "addToCollectionAllowListCross",113 "outputs": [],114 "stateMutability": "nonpayable",115 "type": "function"116 },117 {118 "inputs": [119 {120 "components": [121 { "internalType": "address", "name": "eth", "type": "address" },122 { "internalType": "uint256", "name": "sub", "type": "uint256" }123 ],124 "internalType": "struct EthCrossAccount",125 "name": "user",126 "type": "tuple"127 }128 ],129 "name": "allowlistedCross",130 "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],131 "stateMutability": "view",132 "type": "function"133 },134 {135 "inputs": [136 { "internalType": "address", "name": "approved", "type": "address" },137 { "internalType": "uint256", "name": "tokenId", "type": "uint256" }138 ],139 "name": "approve",140 "outputs": [],141 "stateMutability": "nonpayable",142 "type": "function"143 },144 {145 "inputs": [146 {147 "components": [148 { "internalType": "address", "name": "eth", "type": "address" },149 { "internalType": "uint256", "name": "sub", "type": "uint256" }150 ],151 "internalType": "struct EthCrossAccount",152 "name": "approved",153 "type": "tuple"154 },155 { "internalType": "uint256", "name": "tokenId", "type": "uint256" }156 ],157 "name": "approveCross",158 "outputs": [],159 "stateMutability": "nonpayable",160 "type": "function"161 },162 {163 "inputs": [164 { "internalType": "address", "name": "owner", "type": "address" }165 ],166 "name": "balanceOf",167 "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],168 "stateMutability": "view",169 "type": "function"170 },171 {172 "inputs": [173 { "internalType": "uint256", "name": "tokenId", "type": "uint256" }174 ],175 "name": "burn",176 "outputs": [],177 "stateMutability": "nonpayable",178 "type": "function"179 },180 {181 "inputs": [182 {183 "components": [184 { "internalType": "address", "name": "eth", "type": "address" },185 { "internalType": "uint256", "name": "sub", "type": "uint256" }186 ],187 "internalType": "struct EthCrossAccount",188 "name": "from",189 "type": "tuple"190 },191 { "internalType": "uint256", "name": "tokenId", "type": "uint256" }192 ],193 "name": "burnFromCross",194 "outputs": [],195 "stateMutability": "nonpayable",196 "type": "function"197 },198 {199 "inputs": [200 {201 "components": [202 { "internalType": "address", "name": "eth", "type": "address" },203 { "internalType": "uint256", "name": "sub", "type": "uint256" }204 ],205 "internalType": "struct EthCrossAccount",206 "name": "newOwner",207 "type": "tuple"208 }209 ],210 "name": "changeCollectionOwnerCross",211 "outputs": [],212 "stateMutability": "nonpayable",213 "type": "function"214 },215 {216 "inputs": [],217 "name": "collectionAdmins",218 "outputs": [219 {220 "components": [221 { "internalType": "address", "name": "eth", "type": "address" },222 { "internalType": "uint256", "name": "sub", "type": "uint256" }223 ],224 "internalType": "struct EthCrossAccount[]",225 "name": "",226 "type": "tuple[]"227 }228 ],229 "stateMutability": "view",230 "type": "function"231 },232 {233 "inputs": [],234 "name": "collectionHelperAddress",235 "outputs": [{ "internalType": "address", "name": "", "type": "address" }],236 "stateMutability": "view",237 "type": "function"238 },239 {240 "inputs": [],241 "name": "collectionOwner",242 "outputs": [243 {244 "components": [245 { "internalType": "address", "name": "eth", "type": "address" },246 { "internalType": "uint256", "name": "sub", "type": "uint256" }247 ],248 "internalType": "struct EthCrossAccount",249 "name": "",250 "type": "tuple"251 }252 ],253 "stateMutability": "view",254 "type": "function"255 },256 {257 "inputs": [258 { "internalType": "string[]", "name": "keys", "type": "string[]" }259 ],260 "name": "collectionProperties",261 "outputs": [262 {263 "components": [264 { "internalType": "string", "name": "key", "type": "string" },265 { "internalType": "bytes", "name": "value", "type": "bytes" }266 ],267 "internalType": "struct Property[]",268 "name": "",269 "type": "tuple[]"270 }271 ],272 "stateMutability": "view",273 "type": "function"274 },275 {276 "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],277 "name": "collectionProperty",278 "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],279 "stateMutability": "view",280 "type": "function"281 },282 {283 "inputs": [],284 "name": "collectionSponsor",285 "outputs": [286 {287 "components": [288 { "internalType": "address", "name": "field_0", "type": "address" },289 { "internalType": "uint256", "name": "field_1", "type": "uint256" }290 ],291 "internalType": "struct Tuple30",292 "name": "",293 "type": "tuple"294 }295 ],296 "stateMutability": "view",297 "type": "function"298 },299 {300 "inputs": [],301 "name": "confirmCollectionSponsorship",302 "outputs": [],303 "stateMutability": "nonpayable",304 "type": "function"305 },306 {307 "inputs": [],308 "name": "contractAddress",309 "outputs": [{ "internalType": "address", "name": "", "type": "address" }],310 "stateMutability": "view",311 "type": "function"312 },313 {314 "inputs": [315 { "internalType": "uint256", "name": "tokenId", "type": "uint256" }316 ],317 "name": "crossOwnerOf",318 "outputs": [319 {320 "components": [321 { "internalType": "address", "name": "eth", "type": "address" },322 { "internalType": "uint256", "name": "sub", "type": "uint256" }323 ],324 "internalType": "struct EthCrossAccount",325 "name": "",326 "type": "tuple"327 }328 ],329 "stateMutability": "view",330 "type": "function"331 },332 {333 "inputs": [334 { "internalType": "string[]", "name": "keys", "type": "string[]" }335 ],336 "name": "deleteCollectionProperties",337 "outputs": [],338 "stateMutability": "nonpayable",339 "type": "function"340 },341 {342 "inputs": [343 { "internalType": "uint256", "name": "tokenId", "type": "uint256" },344 { "internalType": "string[]", "name": "keys", "type": "string[]" }345 ],346 "name": "deleteProperties",347 "outputs": [],348 "stateMutability": "nonpayable",349 "type": "function"350 },351 {352 "inputs": [],353 "name": "description",354 "outputs": [{ "internalType": "string", "name": "", "type": "string" }],355 "stateMutability": "view",356 "type": "function"357 },358 {359 "inputs": [],360 "name": "finishMinting",361 "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],362 "stateMutability": "nonpayable",363 "type": "function"364 },365 {366 "inputs": [367 { "internalType": "uint256", "name": "tokenId", "type": "uint256" }368 ],369 "name": "getApproved",370 "outputs": [{ "internalType": "address", "name": "", "type": "address" }],371 "stateMutability": "view",372 "type": "function"373 },374 {375 "inputs": [],376 "name": "hasCollectionPendingSponsor",377 "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],378 "stateMutability": "view",379 "type": "function"380 },381 {382 "inputs": [383 { "internalType": "address", "name": "owner", "type": "address" },384 { "internalType": "address", "name": "operator", "type": "address" }385 ],386 "name": "isApprovedForAll",387 "outputs": [{ "internalType": "address", "name": "", "type": "address" }],388 "stateMutability": "view",389 "type": "function"390 },391 {392 "inputs": [393 {394 "components": [395 { "internalType": "address", "name": "eth", "type": "address" },396 { "internalType": "uint256", "name": "sub", "type": "uint256" }397 ],398 "internalType": "struct EthCrossAccount",399 "name": "user",400 "type": "tuple"401 }402 ],403 "name": "isOwnerOrAdminCross",404 "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],405 "stateMutability": "view",406 "type": "function"407 },408 {409 "inputs": [{ "internalType": "address", "name": "to", "type": "address" }],410 "name": "mint",411 "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],412 "stateMutability": "nonpayable",413 "type": "function"414 },415 {416 "inputs": [417 { "internalType": "address", "name": "to", "type": "address" },418 { "internalType": "string", "name": "tokenUri", "type": "string" }419 ],420 "name": "mintWithTokenURI",421 "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],422 "stateMutability": "nonpayable",423 "type": "function"424 },425 {426 "inputs": [],427 "name": "mintingFinished",428 "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],429 "stateMutability": "view",430 "type": "function"431 },432 {433 "inputs": [],434 "name": "name",435 "outputs": [{ "internalType": "string", "name": "", "type": "string" }],436 "stateMutability": "view",437 "type": "function"438 },439 {440 "inputs": [],441 "name": "nextTokenId",442 "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],443 "stateMutability": "view",444 "type": "function"445 },446 {447 "inputs": [448 { "internalType": "uint256", "name": "tokenId", "type": "uint256" }449 ],450 "name": "ownerOf",451 "outputs": [{ "internalType": "address", "name": "", "type": "address" }],452 "stateMutability": "view",453 "type": "function"454 },455 {456 "inputs": [457 { "internalType": "uint256", "name": "tokenId", "type": "uint256" },458 { "internalType": "string[]", "name": "keys", "type": "string[]" }459 ],460 "name": "properties",461 "outputs": [462 {463 "components": [464 { "internalType": "string", "name": "key", "type": "string" },465 { "internalType": "bytes", "name": "value", "type": "bytes" }466 ],467 "internalType": "struct Property[]",468 "name": "",469 "type": "tuple[]"470 }471 ],472 "stateMutability": "view",473 "type": "function"474 },475 {476 "inputs": [477 { "internalType": "uint256", "name": "tokenId", "type": "uint256" },478 { "internalType": "string", "name": "key", "type": "string" }479 ],480 "name": "property",481 "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],482 "stateMutability": "view",483 "type": "function"484 },485 {486 "inputs": [487 {488 "components": [489 { "internalType": "address", "name": "eth", "type": "address" },490 { "internalType": "uint256", "name": "sub", "type": "uint256" }491 ],492 "internalType": "struct EthCrossAccount",493 "name": "admin",494 "type": "tuple"495 }496 ],497 "name": "removeCollectionAdminCross",498 "outputs": [],499 "stateMutability": "nonpayable",500 "type": "function"501 },502 {503 "inputs": [],504 "name": "removeCollectionSponsor",505 "outputs": [],506 "stateMutability": "nonpayable",507 "type": "function"508 },509 {510 "inputs": [511 {512 "components": [513 { "internalType": "address", "name": "eth", "type": "address" },514 { "internalType": "uint256", "name": "sub", "type": "uint256" }515 ],516 "internalType": "struct EthCrossAccount",517 "name": "user",518 "type": "tuple"519 }520 ],521 "name": "removeFromCollectionAllowListCross",522 "outputs": [],523 "stateMutability": "nonpayable",524 "type": "function"525 },526 {527 "inputs": [528 { "internalType": "address", "name": "from", "type": "address" },529 { "internalType": "address", "name": "to", "type": "address" },530 { "internalType": "uint256", "name": "tokenId", "type": "uint256" }531 ],532 "name": "safeTransferFrom",533 "outputs": [],534 "stateMutability": "nonpayable",535 "type": "function"536 },537 {538 "inputs": [539 { "internalType": "address", "name": "from", "type": "address" },540 { "internalType": "address", "name": "to", "type": "address" },541 { "internalType": "uint256", "name": "tokenId", "type": "uint256" },542 { "internalType": "bytes", "name": "data", "type": "bytes" }543 ],544 "name": "safeTransferFrom",545 "outputs": [],546 "stateMutability": "nonpayable",547 "type": "function"548 },549 {550 "inputs": [551 { "internalType": "address", "name": "operator", "type": "address" },552 { "internalType": "bool", "name": "approved", "type": "bool" }553 ],554 "name": "setApprovalForAll",555 "outputs": [],556 "stateMutability": "nonpayable",557 "type": "function"558 },559 {560 "inputs": [{ "internalType": "uint8", "name": "mode", "type": "uint8" }],561 "name": "setCollectionAccess",562 "outputs": [],563 "stateMutability": "nonpayable",564 "type": "function"565 },566 {567 "inputs": [568 { "internalType": "string", "name": "limit", "type": "string" },569 { "internalType": "uint256", "name": "value", "type": "uint256" }570 ],571 "name": "setCollectionLimit",572 "outputs": [],573 "stateMutability": "nonpayable",574 "type": "function"575 },576 {577 "inputs": [{ "internalType": "bool", "name": "mode", "type": "bool" }],578 "name": "setCollectionMintMode",579 "outputs": [],580 "stateMutability": "nonpayable",581 "type": "function"582 },583 {584 "inputs": [{ "internalType": "bool", "name": "enable", "type": "bool" }],585 "name": "setCollectionNesting",586 "outputs": [],587 "stateMutability": "nonpayable",588 "type": "function"589 },590 {591 "inputs": [592 { "internalType": "bool", "name": "enable", "type": "bool" },593 {594 "internalType": "address[]",595 "name": "collections",596 "type": "address[]"597 }598 ],599 "name": "setCollectionNesting",600 "outputs": [],601 "stateMutability": "nonpayable",602 "type": "function"603 },604 {605 "inputs": [606 {607 "components": [608 { "internalType": "string", "name": "key", "type": "string" },609 { "internalType": "bytes", "name": "value", "type": "bytes" }610 ],611 "internalType": "struct Property[]",612 "name": "properties",613 "type": "tuple[]"614 }615 ],616 "name": "setCollectionProperties",617 "outputs": [],618 "stateMutability": "nonpayable",619 "type": "function"620 },621 {622 "inputs": [623 {624 "components": [625 { "internalType": "address", "name": "eth", "type": "address" },626 { "internalType": "uint256", "name": "sub", "type": "uint256" }627 ],628 "internalType": "struct EthCrossAccount",629 "name": "sponsor",630 "type": "tuple"631 }632 ],633 "name": "setCollectionSponsorCross",634 "outputs": [],635 "stateMutability": "nonpayable",636 "type": "function"637 },638 {639 "inputs": [640 { "internalType": "uint256", "name": "tokenId", "type": "uint256" },641 {642 "components": [643 { "internalType": "string", "name": "key", "type": "string" },644 { "internalType": "bytes", "name": "value", "type": "bytes" }645 ],646 "internalType": "struct Property[]",647 "name": "properties",648 "type": "tuple[]"649 }650 ],651 "name": "setProperties",652 "outputs": [],653 "stateMutability": "nonpayable",654 "type": "function"655 },656 {657 "inputs": [658 { "internalType": "string", "name": "key", "type": "string" },659 { "internalType": "bool", "name": "isMutable", "type": "bool" },660 { "internalType": "bool", "name": "collectionAdmin", "type": "bool" },661 { "internalType": "bool", "name": "tokenOwner", "type": "bool" }662 ],663 "name": "setTokenPropertyPermission",664 "outputs": [],665 "stateMutability": "nonpayable",666 "type": "function"667 },668 {669 "inputs": [670 { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }671 ],672 "name": "supportsInterface",673 "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],674 "stateMutability": "view",675 "type": "function"676 },677 {678 "inputs": [],679 "name": "symbol",680 "outputs": [{ "internalType": "string", "name": "", "type": "string" }],681 "stateMutability": "view",682 "type": "function"683 },684 {685 "inputs": [686 { "internalType": "uint256", "name": "index", "type": "uint256" }687 ],688 "name": "tokenByIndex",689 "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],690 "stateMutability": "view",691 "type": "function"692 },693 {694 "inputs": [695 { "internalType": "address", "name": "owner", "type": "address" },696 { "internalType": "uint256", "name": "index", "type": "uint256" }697 ],698 "name": "tokenOfOwnerByIndex",699 "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],700 "stateMutability": "view",701 "type": "function"702 },703 {704 "inputs": [705 { "internalType": "uint256", "name": "tokenId", "type": "uint256" }706 ],707 "name": "tokenURI",708 "outputs": [{ "internalType": "string", "name": "", "type": "string" }],709 "stateMutability": "view",710 "type": "function"711 },712 {713 "inputs": [],714 "name": "totalSupply",715 "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],716 "stateMutability": "view",717 "type": "function"718 },719 {720 "inputs": [721 { "internalType": "address", "name": "to", "type": "address" },722 { "internalType": "uint256", "name": "tokenId", "type": "uint256" }723 ],724 "name": "transfer",725 "outputs": [],726 "stateMutability": "nonpayable",727 "type": "function"728 },729 {730 "inputs": [731 {732 "components": [733 { "internalType": "address", "name": "eth", "type": "address" },734 { "internalType": "uint256", "name": "sub", "type": "uint256" }735 ],736 "internalType": "struct EthCrossAccount",737 "name": "to",738 "type": "tuple"739 },740 { "internalType": "uint256", "name": "tokenId", "type": "uint256" }741 ],742 "name": "transferCross",743 "outputs": [],744 "stateMutability": "nonpayable",745 "type": "function"746 },747 {748 "inputs": [749 { "internalType": "address", "name": "from", "type": "address" },750 { "internalType": "address", "name": "to", "type": "address" },751 { "internalType": "uint256", "name": "tokenId", "type": "uint256" }752 ],753 "name": "transferFrom",754 "outputs": [],755 "stateMutability": "nonpayable",756 "type": "function"757 },758 {759 "inputs": [760 {761 "components": [762 { "internalType": "address", "name": "eth", "type": "address" },763 { "internalType": "uint256", "name": "sub", "type": "uint256" }764 ],765 "internalType": "struct EthCrossAccount",766 "name": "from",767 "type": "tuple"768 },769 {770 "components": [771 { "internalType": "address", "name": "eth", "type": "address" },772 { "internalType": "uint256", "name": "sub", "type": "uint256" }773 ],774 "internalType": "struct EthCrossAccount",775 "name": "to",776 "type": "tuple"777 },778 { "internalType": "uint256", "name": "tokenId", "type": "uint256" }779 ],780 "name": "transferFromCross",781 "outputs": [],782 "stateMutability": "nonpayable",783 "type": "function"784 },785 {786 "inputs": [],787 "name": "uniqueCollectionType",788 "outputs": [{ "internalType": "string", "name": "", "type": "string" }],789 "stateMutability": "view",790 "type": "function"791 }792]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": "approved",15 "type": "address"16 },17 {18 "indexed": true,19 "internalType": "uint256",20 "name": "tokenId",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": "owner",34 "type": "address"35 },36 {37 "indexed": true,38 "internalType": "address",39 "name": "operator",40 "type": "address"41 },42 {43 "indexed": false,44 "internalType": "bool",45 "name": "approved",46 "type": "bool"47 }48 ],49 "name": "ApprovalForAll",50 "type": "event"51 },52 {53 "anonymous": false,54 "inputs": [],55 "name": "MintingFinished",56 "type": "event"57 },58 {59 "anonymous": false,60 "inputs": [61 {62 "indexed": true,63 "internalType": "address",64 "name": "from",65 "type": "address"66 },67 {68 "indexed": true,69 "internalType": "address",70 "name": "to",71 "type": "address"72 },73 {74 "indexed": true,75 "internalType": "uint256",76 "name": "tokenId",77 "type": "uint256"78 }79 ],80 "name": "Transfer",81 "type": "event"82 },83 {84 "inputs": [85 {86 "components": [87 { "internalType": "address", "name": "eth", "type": "address" },88 { "internalType": "uint256", "name": "sub", "type": "uint256" }89 ],90 "internalType": "struct EthCrossAccount",91 "name": "newAdmin",92 "type": "tuple"93 }94 ],95 "name": "addCollectionAdminCross",96 "outputs": [],97 "stateMutability": "nonpayable",98 "type": "function"99 },100 {101 "inputs": [102 {103 "components": [104 { "internalType": "address", "name": "eth", "type": "address" },105 { "internalType": "uint256", "name": "sub", "type": "uint256" }106 ],107 "internalType": "struct EthCrossAccount",108 "name": "user",109 "type": "tuple"110 }111 ],112 "name": "addToCollectionAllowListCross",113 "outputs": [],114 "stateMutability": "nonpayable",115 "type": "function"116 },117 {118 "inputs": [119 {120 "components": [121 { "internalType": "address", "name": "eth", "type": "address" },122 { "internalType": "uint256", "name": "sub", "type": "uint256" }123 ],124 "internalType": "struct EthCrossAccount",125 "name": "user",126 "type": "tuple"127 }128 ],129 "name": "allowlistedCross",130 "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],131 "stateMutability": "view",132 "type": "function"133 },134 {135 "inputs": [136 { "internalType": "address", "name": "approved", "type": "address" },137 { "internalType": "uint256", "name": "tokenId", "type": "uint256" }138 ],139 "name": "approve",140 "outputs": [],141 "stateMutability": "nonpayable",142 "type": "function"143 },144 {145 "inputs": [146 {147 "components": [148 { "internalType": "address", "name": "eth", "type": "address" },149 { "internalType": "uint256", "name": "sub", "type": "uint256" }150 ],151 "internalType": "struct EthCrossAccount",152 "name": "approved",153 "type": "tuple"154 },155 { "internalType": "uint256", "name": "tokenId", "type": "uint256" }156 ],157 "name": "approveCross",158 "outputs": [],159 "stateMutability": "nonpayable",160 "type": "function"161 },162 {163 "inputs": [164 { "internalType": "address", "name": "owner", "type": "address" }165 ],166 "name": "balanceOf",167 "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],168 "stateMutability": "view",169 "type": "function"170 },171 {172 "inputs": [173 { "internalType": "uint256", "name": "tokenId", "type": "uint256" }174 ],175 "name": "burn",176 "outputs": [],177 "stateMutability": "nonpayable",178 "type": "function"179 },180 {181 "inputs": [182 {183 "components": [184 { "internalType": "address", "name": "eth", "type": "address" },185 { "internalType": "uint256", "name": "sub", "type": "uint256" }186 ],187 "internalType": "struct EthCrossAccount",188 "name": "from",189 "type": "tuple"190 },191 { "internalType": "uint256", "name": "tokenId", "type": "uint256" }192 ],193 "name": "burnFromCross",194 "outputs": [],195 "stateMutability": "nonpayable",196 "type": "function"197 },198 {199 "inputs": [200 {201 "components": [202 { "internalType": "address", "name": "eth", "type": "address" },203 { "internalType": "uint256", "name": "sub", "type": "uint256" }204 ],205 "internalType": "struct EthCrossAccount",206 "name": "newOwner",207 "type": "tuple"208 }209 ],210 "name": "changeCollectionOwnerCross",211 "outputs": [],212 "stateMutability": "nonpayable",213 "type": "function"214 },215 {216 "inputs": [],217 "name": "collectionAdmins",218 "outputs": [219 {220 "components": [221 { "internalType": "address", "name": "eth", "type": "address" },222 { "internalType": "uint256", "name": "sub", "type": "uint256" }223 ],224 "internalType": "struct EthCrossAccount[]",225 "name": "",226 "type": "tuple[]"227 }228 ],229 "stateMutability": "view",230 "type": "function"231 },232 {233 "inputs": [],234 "name": "collectionHelperAddress",235 "outputs": [{ "internalType": "address", "name": "", "type": "address" }],236 "stateMutability": "view",237 "type": "function"238 },239 {240 "inputs": [],241 "name": "collectionOwner",242 "outputs": [243 {244 "components": [245 { "internalType": "address", "name": "eth", "type": "address" },246 { "internalType": "uint256", "name": "sub", "type": "uint256" }247 ],248 "internalType": "struct EthCrossAccount",249 "name": "",250 "type": "tuple"251 }252 ],253 "stateMutability": "view",254 "type": "function"255 },256 {257 "inputs": [258 { "internalType": "string[]", "name": "keys", "type": "string[]" }259 ],260 "name": "collectionProperties",261 "outputs": [262 {263 "components": [264 { "internalType": "string", "name": "key", "type": "string" },265 { "internalType": "bytes", "name": "value", "type": "bytes" }266 ],267 "internalType": "struct Property[]",268 "name": "",269 "type": "tuple[]"270 }271 ],272 "stateMutability": "view",273 "type": "function"274 },275 {276 "inputs": [{ "internalType": "string", "name": "key", "type": "string" }],277 "name": "collectionProperty",278 "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],279 "stateMutability": "view",280 "type": "function"281 },282 {283 "inputs": [],284 "name": "collectionSponsor",285 "outputs": [286 {287 "components": [288 { "internalType": "address", "name": "field_0", "type": "address" },289 { "internalType": "uint256", "name": "field_1", "type": "uint256" }290 ],291 "internalType": "struct Tuple30",292 "name": "",293 "type": "tuple"294 }295 ],296 "stateMutability": "view",297 "type": "function"298 },299 {300 "inputs": [],301 "name": "confirmCollectionSponsorship",302 "outputs": [],303 "stateMutability": "nonpayable",304 "type": "function"305 },306 {307 "inputs": [],308 "name": "contractAddress",309 "outputs": [{ "internalType": "address", "name": "", "type": "address" }],310 "stateMutability": "view",311 "type": "function"312 },313 {314 "inputs": [315 { "internalType": "uint256", "name": "tokenId", "type": "uint256" }316 ],317 "name": "crossOwnerOf",318 "outputs": [319 {320 "components": [321 { "internalType": "address", "name": "eth", "type": "address" },322 { "internalType": "uint256", "name": "sub", "type": "uint256" }323 ],324 "internalType": "struct EthCrossAccount",325 "name": "",326 "type": "tuple"327 }328 ],329 "stateMutability": "view",330 "type": "function"331 },332 {333 "inputs": [334 { "internalType": "string[]", "name": "keys", "type": "string[]" }335 ],336 "name": "deleteCollectionProperties",337 "outputs": [],338 "stateMutability": "nonpayable",339 "type": "function"340 },341 {342 "inputs": [343 { "internalType": "uint256", "name": "tokenId", "type": "uint256" },344 { "internalType": "string[]", "name": "keys", "type": "string[]" }345 ],346 "name": "deleteProperties",347 "outputs": [],348 "stateMutability": "nonpayable",349 "type": "function"350 },351 {352 "inputs": [],353 "name": "description",354 "outputs": [{ "internalType": "string", "name": "", "type": "string" }],355 "stateMutability": "view",356 "type": "function"357 },358 {359 "inputs": [],360 "name": "finishMinting",361 "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],362 "stateMutability": "nonpayable",363 "type": "function"364 },365 {366 "inputs": [367 { "internalType": "uint256", "name": "tokenId", "type": "uint256" }368 ],369 "name": "getApproved",370 "outputs": [{ "internalType": "address", "name": "", "type": "address" }],371 "stateMutability": "view",372 "type": "function"373 },374 {375 "inputs": [],376 "name": "hasCollectionPendingSponsor",377 "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],378 "stateMutability": "view",379 "type": "function"380 },381 {382 "inputs": [383 { "internalType": "address", "name": "owner", "type": "address" },384 { "internalType": "address", "name": "operator", "type": "address" }385 ],386 "name": "isApprovedForAll",387 "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],388 "stateMutability": "view",389 "type": "function"390 },391 {392 "inputs": [393 {394 "components": [395 { "internalType": "address", "name": "eth", "type": "address" },396 { "internalType": "uint256", "name": "sub", "type": "uint256" }397 ],398 "internalType": "struct EthCrossAccount",399 "name": "user",400 "type": "tuple"401 }402 ],403 "name": "isOwnerOrAdminCross",404 "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],405 "stateMutability": "view",406 "type": "function"407 },408 {409 "inputs": [{ "internalType": "address", "name": "to", "type": "address" }],410 "name": "mint",411 "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],412 "stateMutability": "nonpayable",413 "type": "function"414 },415 {416 "inputs": [417 { "internalType": "address", "name": "to", "type": "address" },418 { "internalType": "string", "name": "tokenUri", "type": "string" }419 ],420 "name": "mintWithTokenURI",421 "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],422 "stateMutability": "nonpayable",423 "type": "function"424 },425 {426 "inputs": [],427 "name": "mintingFinished",428 "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],429 "stateMutability": "view",430 "type": "function"431 },432 {433 "inputs": [],434 "name": "name",435 "outputs": [{ "internalType": "string", "name": "", "type": "string" }],436 "stateMutability": "view",437 "type": "function"438 },439 {440 "inputs": [],441 "name": "nextTokenId",442 "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],443 "stateMutability": "view",444 "type": "function"445 },446 {447 "inputs": [448 { "internalType": "uint256", "name": "tokenId", "type": "uint256" }449 ],450 "name": "ownerOf",451 "outputs": [{ "internalType": "address", "name": "", "type": "address" }],452 "stateMutability": "view",453 "type": "function"454 },455 {456 "inputs": [457 { "internalType": "uint256", "name": "tokenId", "type": "uint256" },458 { "internalType": "string[]", "name": "keys", "type": "string[]" }459 ],460 "name": "properties",461 "outputs": [462 {463 "components": [464 { "internalType": "string", "name": "key", "type": "string" },465 { "internalType": "bytes", "name": "value", "type": "bytes" }466 ],467 "internalType": "struct Property[]",468 "name": "",469 "type": "tuple[]"470 }471 ],472 "stateMutability": "view",473 "type": "function"474 },475 {476 "inputs": [477 { "internalType": "uint256", "name": "tokenId", "type": "uint256" },478 { "internalType": "string", "name": "key", "type": "string" }479 ],480 "name": "property",481 "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }],482 "stateMutability": "view",483 "type": "function"484 },485 {486 "inputs": [487 {488 "components": [489 { "internalType": "address", "name": "eth", "type": "address" },490 { "internalType": "uint256", "name": "sub", "type": "uint256" }491 ],492 "internalType": "struct EthCrossAccount",493 "name": "admin",494 "type": "tuple"495 }496 ],497 "name": "removeCollectionAdminCross",498 "outputs": [],499 "stateMutability": "nonpayable",500 "type": "function"501 },502 {503 "inputs": [],504 "name": "removeCollectionSponsor",505 "outputs": [],506 "stateMutability": "nonpayable",507 "type": "function"508 },509 {510 "inputs": [511 {512 "components": [513 { "internalType": "address", "name": "eth", "type": "address" },514 { "internalType": "uint256", "name": "sub", "type": "uint256" }515 ],516 "internalType": "struct EthCrossAccount",517 "name": "user",518 "type": "tuple"519 }520 ],521 "name": "removeFromCollectionAllowListCross",522 "outputs": [],523 "stateMutability": "nonpayable",524 "type": "function"525 },526 {527 "inputs": [528 { "internalType": "address", "name": "from", "type": "address" },529 { "internalType": "address", "name": "to", "type": "address" },530 { "internalType": "uint256", "name": "tokenId", "type": "uint256" }531 ],532 "name": "safeTransferFrom",533 "outputs": [],534 "stateMutability": "nonpayable",535 "type": "function"536 },537 {538 "inputs": [539 { "internalType": "address", "name": "from", "type": "address" },540 { "internalType": "address", "name": "to", "type": "address" },541 { "internalType": "uint256", "name": "tokenId", "type": "uint256" },542 { "internalType": "bytes", "name": "data", "type": "bytes" }543 ],544 "name": "safeTransferFrom",545 "outputs": [],546 "stateMutability": "nonpayable",547 "type": "function"548 },549 {550 "inputs": [551 { "internalType": "address", "name": "operator", "type": "address" },552 { "internalType": "bool", "name": "approved", "type": "bool" }553 ],554 "name": "setApprovalForAll",555 "outputs": [],556 "stateMutability": "nonpayable",557 "type": "function"558 },559 {560 "inputs": [{ "internalType": "uint8", "name": "mode", "type": "uint8" }],561 "name": "setCollectionAccess",562 "outputs": [],563 "stateMutability": "nonpayable",564 "type": "function"565 },566 {567 "inputs": [568 { "internalType": "string", "name": "limit", "type": "string" },569 { "internalType": "uint256", "name": "value", "type": "uint256" }570 ],571 "name": "setCollectionLimit",572 "outputs": [],573 "stateMutability": "nonpayable",574 "type": "function"575 },576 {577 "inputs": [{ "internalType": "bool", "name": "mode", "type": "bool" }],578 "name": "setCollectionMintMode",579 "outputs": [],580 "stateMutability": "nonpayable",581 "type": "function"582 },583 {584 "inputs": [{ "internalType": "bool", "name": "enable", "type": "bool" }],585 "name": "setCollectionNesting",586 "outputs": [],587 "stateMutability": "nonpayable",588 "type": "function"589 },590 {591 "inputs": [592 { "internalType": "bool", "name": "enable", "type": "bool" },593 {594 "internalType": "address[]",595 "name": "collections",596 "type": "address[]"597 }598 ],599 "name": "setCollectionNesting",600 "outputs": [],601 "stateMutability": "nonpayable",602 "type": "function"603 },604 {605 "inputs": [606 {607 "components": [608 { "internalType": "string", "name": "key", "type": "string" },609 { "internalType": "bytes", "name": "value", "type": "bytes" }610 ],611 "internalType": "struct Property[]",612 "name": "properties",613 "type": "tuple[]"614 }615 ],616 "name": "setCollectionProperties",617 "outputs": [],618 "stateMutability": "nonpayable",619 "type": "function"620 },621 {622 "inputs": [623 {624 "components": [625 { "internalType": "address", "name": "eth", "type": "address" },626 { "internalType": "uint256", "name": "sub", "type": "uint256" }627 ],628 "internalType": "struct EthCrossAccount",629 "name": "sponsor",630 "type": "tuple"631 }632 ],633 "name": "setCollectionSponsorCross",634 "outputs": [],635 "stateMutability": "nonpayable",636 "type": "function"637 },638 {639 "inputs": [640 { "internalType": "uint256", "name": "tokenId", "type": "uint256" },641 {642 "components": [643 { "internalType": "string", "name": "key", "type": "string" },644 { "internalType": "bytes", "name": "value", "type": "bytes" }645 ],646 "internalType": "struct Property[]",647 "name": "properties",648 "type": "tuple[]"649 }650 ],651 "name": "setProperties",652 "outputs": [],653 "stateMutability": "nonpayable",654 "type": "function"655 },656 {657 "inputs": [658 { "internalType": "string", "name": "key", "type": "string" },659 { "internalType": "bool", "name": "isMutable", "type": "bool" },660 { "internalType": "bool", "name": "collectionAdmin", "type": "bool" },661 { "internalType": "bool", "name": "tokenOwner", "type": "bool" }662 ],663 "name": "setTokenPropertyPermission",664 "outputs": [],665 "stateMutability": "nonpayable",666 "type": "function"667 },668 {669 "inputs": [670 { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }671 ],672 "name": "supportsInterface",673 "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],674 "stateMutability": "view",675 "type": "function"676 },677 {678 "inputs": [],679 "name": "symbol",680 "outputs": [{ "internalType": "string", "name": "", "type": "string" }],681 "stateMutability": "view",682 "type": "function"683 },684 {685 "inputs": [686 { "internalType": "uint256", "name": "index", "type": "uint256" }687 ],688 "name": "tokenByIndex",689 "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],690 "stateMutability": "view",691 "type": "function"692 },693 {694 "inputs": [695 { "internalType": "address", "name": "owner", "type": "address" },696 { "internalType": "uint256", "name": "index", "type": "uint256" }697 ],698 "name": "tokenOfOwnerByIndex",699 "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],700 "stateMutability": "view",701 "type": "function"702 },703 {704 "inputs": [705 { "internalType": "uint256", "name": "tokenId", "type": "uint256" }706 ],707 "name": "tokenURI",708 "outputs": [{ "internalType": "string", "name": "", "type": "string" }],709 "stateMutability": "view",710 "type": "function"711 },712 {713 "inputs": [],714 "name": "totalSupply",715 "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],716 "stateMutability": "view",717 "type": "function"718 },719 {720 "inputs": [721 { "internalType": "address", "name": "to", "type": "address" },722 { "internalType": "uint256", "name": "tokenId", "type": "uint256" }723 ],724 "name": "transfer",725 "outputs": [],726 "stateMutability": "nonpayable",727 "type": "function"728 },729 {730 "inputs": [731 {732 "components": [733 { "internalType": "address", "name": "eth", "type": "address" },734 { "internalType": "uint256", "name": "sub", "type": "uint256" }735 ],736 "internalType": "struct EthCrossAccount",737 "name": "to",738 "type": "tuple"739 },740 { "internalType": "uint256", "name": "tokenId", "type": "uint256" }741 ],742 "name": "transferCross",743 "outputs": [],744 "stateMutability": "nonpayable",745 "type": "function"746 },747 {748 "inputs": [749 { "internalType": "address", "name": "from", "type": "address" },750 { "internalType": "address", "name": "to", "type": "address" },751 { "internalType": "uint256", "name": "tokenId", "type": "uint256" }752 ],753 "name": "transferFrom",754 "outputs": [],755 "stateMutability": "nonpayable",756 "type": "function"757 },758 {759 "inputs": [760 {761 "components": [762 { "internalType": "address", "name": "eth", "type": "address" },763 { "internalType": "uint256", "name": "sub", "type": "uint256" }764 ],765 "internalType": "struct EthCrossAccount",766 "name": "from",767 "type": "tuple"768 },769 {770 "components": [771 { "internalType": "address", "name": "eth", "type": "address" },772 { "internalType": "uint256", "name": "sub", "type": "uint256" }773 ],774 "internalType": "struct EthCrossAccount",775 "name": "to",776 "type": "tuple"777 },778 { "internalType": "uint256", "name": "tokenId", "type": "uint256" }779 ],780 "name": "transferFromCross",781 "outputs": [],782 "stateMutability": "nonpayable",783 "type": "function"784 },785 {786 "inputs": [],787 "name": "uniqueCollectionType",788 "outputs": [{ "internalType": "string", "name": "", "type": "string" }],789 "stateMutability": "view",790 "type": "function"791 }792]tests/src/eth/abi/reFungible.jsondiffbeforeafterboth--- a/tests/src/eth/abi/reFungible.json
+++ b/tests/src/eth/abi/reFungible.json
@@ -366,7 +366,7 @@
{ "internalType": "address", "name": "operator", "type": "address" }
],
"name": "isApprovedForAll",
- "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "view",
"type": "function"
},
tests/src/eth/api/UniqueNFT.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -671,7 +671,10 @@
/// or in textual repr: approve(address,uint256)
function approve(address approved, uint256 tokenId) external;
- /// @dev Not implemented
+ /// @notice Sets or unsets the approval of a given operator.
+ /// An operator is allowed to transfer all tokens of the sender on their behalf.
+ /// @param operator Operator
+ /// @param approved Is operator enabled or disabled
/// @dev EVM selector for this function is: 0xa22cb465,
/// or in textual repr: setApprovalForAll(address,bool)
function setApprovalForAll(address operator, bool approved) external;
@@ -681,10 +684,10 @@
/// or in textual repr: getApproved(uint256)
function getApproved(uint256 tokenId) external view returns (address);
- /// @dev Not implemented
+ /// @notice Tells whether an operator is approved by a given owner.
/// @dev EVM selector for this function is: 0xe985e9c5,
/// or in textual repr: isApprovedForAll(address,address)
- function isApprovedForAll(address owner, address operator) external view returns (address);
+ function isApprovedForAll(address owner, address operator) external view returns (bool);
/// @notice Returns collection helper contract address
/// @dev EVM selector for this function is: 0x1896cce6,
tests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueRefungible.sol
+++ b/tests/src/eth/api/UniqueRefungible.sol
@@ -668,7 +668,10 @@
/// or in textual repr: approve(address,uint256)
function approve(address approved, uint256 tokenId) external;
- /// @dev Not implemented
+ /// @notice Sets or unsets the approval of a given operator.
+ /// An operator is allowed to transfer all tokens of the sender on their behalf.
+ /// @param operator Operator
+ /// @param approved Is operator enabled or disabled
/// @dev EVM selector for this function is: 0xa22cb465,
/// or in textual repr: setApprovalForAll(address,bool)
function setApprovalForAll(address operator, bool approved) external;
@@ -678,10 +681,10 @@
/// or in textual repr: getApproved(uint256)
function getApproved(uint256 tokenId) external view returns (address);
- /// @dev Not implemented
+ /// @notice Tells whether an operator is approved by a given owner.
/// @dev EVM selector for this function is: 0xe985e9c5,
/// or in textual repr: isApprovedForAll(address,address)
- function isApprovedForAll(address owner, address operator) external view returns (address);
+ function isApprovedForAll(address owner, address operator) external view returns (bool);
/// @notice Returns collection helper contract address
/// @dev EVM selector for this function is: 0x1896cce6,
tests/src/eth/nonFungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/nonFungible.test.ts
+++ b/tests/src/eth/nonFungible.test.ts
@@ -249,6 +249,114 @@
}
});
+ itEth('Can perform setApprovalForAll()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const operator = helper.eth.createAccount();
+
+ const collection = await helper.nft.mintCollection(minter, {});
+
+ const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
+ const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
+
+ const approvedBefore = await contract.methods.isApprovedForAll(owner, operator).call();
+ expect(approvedBefore).to.be.equal(false);
+
+ {
+ const result = await contract.methods.setApprovalForAll(operator, true).send({from: owner});
+
+ expect(result.events.ApprovalForAll).to.be.like({
+ address: collectionAddress,
+ event: 'ApprovalForAll',
+ returnValues: {
+ owner,
+ operator,
+ approved: true,
+ },
+ });
+
+ const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();
+ expect(approvedAfter).to.be.equal(true);
+ }
+
+ {
+ const result = await contract.methods.setApprovalForAll(operator, false).send({from: owner});
+
+ expect(result.events.ApprovalForAll).to.be.like({
+ address: collectionAddress,
+ event: 'ApprovalForAll',
+ returnValues: {
+ owner,
+ operator,
+ approved: false,
+ },
+ });
+
+ const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();
+ expect(approvedAfter).to.be.equal(false);
+ }
+ });
+
+ itEth('Can perform burn with ApprovalForAll', async ({helper}) => {
+ const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const operator = await helper.eth.createAccountWithBalance(donor, 100n);
+
+ const token = await collection.mintToken(minter, {Ethereum: owner});
+
+ const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+ const contract = helper.ethNativeContract.collection(address, 'nft');
+
+ {
+ await contract.methods.setApprovalForAll(operator, true).send({from: owner});
+ const ownerCross = helper.ethCrossAccount.fromAddress(owner);
+ const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: operator});
+ const events = result.events.Transfer;
+
+ expect(events).to.be.like({
+ address,
+ event: 'Transfer',
+ returnValues: {
+ from: owner,
+ to: '0x0000000000000000000000000000000000000000',
+ tokenId: token.tokenId.toString(),
+ },
+ });
+ }
+ });
+
+ itEth('Can perform transfer with ApprovalForAll', async ({helper}) => {
+ const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const operator = await helper.eth.createAccountWithBalance(donor);
+ const receiver = charlie;
+
+ const token = await collection.mintToken(minter, {Ethereum: owner});
+
+ const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+ const contract = helper.ethNativeContract.collection(address, 'nft');
+
+ {
+ await contract.methods.setApprovalForAll(operator, true).send({from: owner});
+ const ownerCross = helper.ethCrossAccount.fromAddress(owner);
+ const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);
+ const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: operator});
+ const event = result.events.Transfer;
+ expect(event).to.be.like({
+ address: helper.ethAddress.fromCollectionId(collection.collectionId),
+ event: 'Transfer',
+ returnValues: {
+ from: owner,
+ to: helper.address.substrateToEth(receiver.address),
+ tokenId: token.tokenId.toString(),
+ },
+ });
+ }
+
+ expect(await token.getOwner()).to.be.like({Substrate: receiver.address});
+ });
+
itEth('Can perform burnFromCross()', async ({helper}) => {
const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
const ownerSub = bob;
@@ -822,3 +930,53 @@
expect(symbol).to.equal('CHANGE');
});
});
+
+describe('Negative tests', () => {
+ let donor: IKeyringPair;
+ let minter: IKeyringPair;
+ let alice: IKeyringPair;
+ let bob: IKeyringPair;
+
+ before(async function() {
+ await usingEthPlaygrounds(async (helper, privateKey) => {
+ donor = await privateKey({filename: __filename});
+ [minter, alice, bob] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
+ });
+ });
+
+ itEth('[negative] Cant perform burn without approval', async ({helper}) => {
+ const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+ const owner = bob;
+ const spender = await helper.eth.createAccountWithBalance(donor, 100n);
+
+ const token = await collection.mintToken(minter, {Substrate: owner.address});
+
+ const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+ const contract = helper.ethNativeContract.collection(address, 'nft');
+
+ {
+ const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);
+ await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;
+ }
+ });
+
+ itEth('[negative] Cant perform transfer without approval', async ({helper}) => {
+ const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
+ const owner = bob;
+ const receiver = alice;
+
+ const spender = await helper.eth.createAccountWithBalance(donor, 100n);
+
+ const token = await collection.mintToken(minter, {Substrate: owner.address});
+
+ const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+ const contract = helper.ethNativeContract.collection(address, 'nft');
+
+ {
+ const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);
+ const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);
+ await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;
+ }
+ });
+});
tests/src/eth/reFungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/reFungible.test.ts
+++ b/tests/src/eth/reFungible.test.ts
@@ -169,6 +169,136 @@
}
});
+ itEth('Can perform setApprovalForAll()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const operator = helper.eth.createAccount();
+
+ const collection = await helper.rft.mintCollection(minter, {});
+
+ const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
+ const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
+
+ const approvedBefore = await contract.methods.isApprovedForAll(owner, operator).call();
+ expect(approvedBefore).to.be.equal(false);
+
+ {
+ const result = await contract.methods.setApprovalForAll(operator, true).send({from: owner});
+
+ expect(result.events.ApprovalForAll).to.be.like({
+ address: collectionAddress,
+ event: 'ApprovalForAll',
+ returnValues: {
+ owner,
+ operator,
+ approved: true,
+ },
+ });
+
+ const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();
+ expect(approvedAfter).to.be.equal(true);
+ }
+
+ {
+ const result = await contract.methods.setApprovalForAll(operator, false).send({from: owner});
+
+ expect(result.events.ApprovalForAll).to.be.like({
+ address: collectionAddress,
+ event: 'ApprovalForAll',
+ returnValues: {
+ owner,
+ operator,
+ approved: false,
+ },
+ });
+
+ const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();
+ expect(approvedAfter).to.be.equal(false);
+ }
+ });
+
+ itEth('Can perform burn with ApprovalForAll', async ({helper}) => {
+ const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const operator = await helper.eth.createAccountWithBalance(donor, 100n);
+
+ const token = await collection.mintToken(minter, 100n, {Ethereum: owner});
+
+ const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+ const contract = helper.ethNativeContract.collection(address, 'rft');
+
+ {
+ await contract.methods.setApprovalForAll(operator, true).send({from: owner});
+ const ownerCross = helper.ethCrossAccount.fromAddress(owner);
+ const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: operator});
+ const events = result.events.Transfer;
+
+ expect(events).to.be.like({
+ address,
+ event: 'Transfer',
+ returnValues: {
+ from: owner,
+ to: '0x0000000000000000000000000000000000000000',
+ tokenId: token.tokenId.toString(),
+ },
+ });
+ }
+ });
+
+ itEth('Can perform burn with approve and approvalForAll', async ({helper}) => {
+ const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const operator = await helper.eth.createAccountWithBalance(donor, 100n);
+
+ const token = await collection.mintToken(minter, 100n, {Ethereum: owner});
+
+ const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+ const contract = helper.ethNativeContract.collection(address, 'rft');
+
+ const rftToken = helper.ethNativeContract.rftTokenById(token.collectionId, token.tokenId, owner);
+
+ {
+ await rftToken.methods.approve(operator, 15n).send({from: owner});
+ await contract.methods.setApprovalForAll(operator, true).send({from: owner});
+ await rftToken.methods.burnFrom(owner, 10n).send({from: operator});
+ const allowance = await rftToken.methods.allowance(owner, operator).call();
+ expect(allowance).to.be.equal('5');
+ }
+ });
+
+ itEth('Can perform transfer with ApprovalForAll', async ({helper}) => {
+ const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const operator = await helper.eth.createAccountWithBalance(donor);
+ const receiver = charlie;
+
+ const token = await collection.mintToken(minter, 100n, {Ethereum: owner});
+
+ const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+ const contract = helper.ethNativeContract.collection(address, 'rft');
+
+ {
+ await contract.methods.setApprovalForAll(operator, true).send({from: owner});
+ const ownerCross = helper.ethCrossAccount.fromAddress(owner);
+ const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);
+ const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: operator});
+ const event = result.events.Transfer;
+ expect(event).to.be.like({
+ address: helper.ethAddress.fromCollectionId(collection.collectionId),
+ event: 'Transfer',
+ returnValues: {
+ from: owner,
+ to: helper.address.substrateToEth(receiver.address),
+ tokenId: token.tokenId.toString(),
+ },
+ });
+ }
+
+ expect(await token.getTop10Owners()).to.be.like([{Substrate: receiver.address}]);
+ });
+
itEth('Can perform burn()', async ({helper}) => {
const caller = await helper.eth.createAccountWithBalance(donor);
const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Burny', '6', '6');
@@ -594,3 +724,52 @@
expect(symbol).to.equal('12');
});
});
+
+describe('Negative tests', () => {
+ let donor: IKeyringPair;
+ let minter: IKeyringPair;
+ let alice: IKeyringPair;
+
+ before(async function() {
+ await usingEthPlaygrounds(async (helper, privateKey) => {
+ donor = await privateKey({filename: __filename});
+ [minter, alice] = await helper.arrange.createAccounts([100n, 100n], donor);
+ });
+ });
+
+ itEth('[negative] Cant perform burn without approval', async ({helper}) => {
+ const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+ const owner = await helper.eth.createAccountWithBalance(donor, 100n);
+ const spender = await helper.eth.createAccountWithBalance(donor, 100n);
+
+ const token = await collection.mintToken(minter, 100n, {Ethereum: owner});
+
+ const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+ const contract = helper.ethNativeContract.collection(address, 'rft');
+
+ {
+ const ownerCross = helper.ethCrossAccount.fromAddress(owner);
+ await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;
+ }
+ });
+
+ itEth('[negative] Cant perform transfer without approval', async ({helper}) => {
+ const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});
+ const owner = await helper.eth.createAccountWithBalance(donor, 100n);
+ const receiver = alice;
+
+ const spender = await helper.eth.createAccountWithBalance(donor, 100n);
+
+ const token = await collection.mintToken(minter, 100n, {Ethereum: owner});
+
+ const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+ const contract = helper.ethNativeContract.collection(address, 'rft');
+
+ {
+ const ownerCross = helper.ethCrossAccount.fromAddress(owner);
+ const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);
+ await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;
+ }
+ });
+});
tests/src/interfaces/augment-api-errors.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-errors.ts
+++ b/tests/src/interfaces/augment-api-errors.ts
@@ -386,6 +386,10 @@
**/
NotFungibleDataUsedToMintFungibleCollectionToken: AugmentedError<ApiType>;
/**
+ * Setting approval for all is not allowed.
+ **/
+ SettingApprovalForAllNotAllowed: AugmentedError<ApiType>;
+ /**
* Setting item properties is not allowed.
**/
SettingPropertiesNotAllowed: AugmentedError<ApiType>;
tests/src/interfaces/augment-api-events.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-events.ts
+++ b/tests/src/interfaces/augment-api-events.ts
@@ -107,6 +107,10 @@
**/
Approved: AugmentedEvent<ApiType, [u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr, u128]>;
/**
+ * Amount pieces of token owned by `sender` was approved for `spender`.
+ **/
+ ApprovedForAll: AugmentedEvent<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr, bool]>;
+ /**
* New collection was created
**/
CollectionCreated: AugmentedEvent<ApiType, [u32, u8, AccountId32]>;
tests/src/interfaces/augment-api-query.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-query.ts
+++ b/tests/src/interfaces/augment-api-query.ts
@@ -442,6 +442,10 @@
**/
tokensMinted: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<u32>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
/**
+ * Operator set by a wallet owner that could perform certain transactions on all tokens in the wallet.
+ **/
+ walletOperator: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, arg3: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array) => Observable<Option<bool>>, [u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr]> & QueryableStorageEntry<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr]>;
+ /**
* Generic query
**/
[key: string]: QueryableStorageEntry<ApiType>;
@@ -645,6 +649,10 @@
**/
totalSupply: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array) => Observable<u128>, [u32, u32]> & QueryableStorageEntry<ApiType, [u32, u32]>;
/**
+ * Operator set by a wallet owner that could perform certain transactions on all tokens in the wallet.
+ **/
+ walletOperator: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, arg3: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array) => Observable<Option<bool>>, [u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr]> & QueryableStorageEntry<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr]>;
+ /**
* Generic query
**/
[key: string]: QueryableStorageEntry<ApiType>;
tests/src/interfaces/augment-api-rpc.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-rpc.ts
+++ b/tests/src/interfaces/augment-api-rpc.ts
@@ -720,6 +720,10 @@
**/
effectiveCollectionLimits: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<Option<UpDataStructsCollectionLimits>>>;
/**
+ * Tells whether an operator is approved by a given owner.
+ **/
+ isApprovedForAll: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, owner: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, operator: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, at?: Hash | string | Uint8Array) => Observable<Option<bool>>>;
+ /**
* Get the last token ID created in a collection
**/
lastTokenId: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<u32>>;
tests/src/interfaces/augment-api-tx.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-tx.ts
+++ b/tests/src/interfaces/augment-api-tx.ts
@@ -1545,6 +1545,18 @@
**/
repartition: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, tokenId: u32 | AnyNumber | Uint8Array, amount: u128 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, u32, u128]>;
/**
+ * Sets or unsets the approval of a given operator.
+ *
+ * An operator is allowed to transfer all tokens of the sender on their behalf.
+ *
+ * # Arguments
+ *
+ * * `owner`: Token owner
+ * * `operator`: Operator
+ * * `approve`: Is operator enabled or disabled
+ **/
+ setApprovalForAll: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, operator: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, approve: bool | boolean | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, PalletEvmAccountBasicCrossAccountIdRepr, bool]>;
+ /**
* Set specific limits of a collection. Empty, or None fields mean chain default.
*
* # Permissions
tests/src/interfaces/default/types.tsdiffbeforeafterboth--- a/tests/src/interfaces/default/types.ts
+++ b/tests/src/interfaces/default/types.ts
@@ -1286,6 +1286,8 @@
readonly asTransfer: ITuple<[u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr, u128]>;
readonly isApproved: boolean;
readonly asApproved: ITuple<[u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr, u128]>;
+ readonly isApprovedForAll: boolean;
+ readonly asApprovedForAll: ITuple<[u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr, bool]>;
readonly isCollectionPropertySet: boolean;
readonly asCollectionPropertySet: ITuple<[u32, Bytes]>;
readonly isCollectionPropertyDeleted: boolean;
@@ -1296,7 +1298,7 @@
readonly asTokenPropertyDeleted: ITuple<[u32, u32, Bytes]>;
readonly isPropertyPermissionSet: boolean;
readonly asPropertyPermissionSet: ITuple<[u32, Bytes]>;
- readonly type: 'CollectionCreated' | 'CollectionDestroyed' | 'ItemCreated' | 'ItemDestroyed' | 'Transfer' | 'Approved' | 'CollectionPropertySet' | 'CollectionPropertyDeleted' | 'TokenPropertySet' | 'TokenPropertyDeleted' | 'PropertyPermissionSet';
+ readonly type: 'CollectionCreated' | 'CollectionDestroyed' | 'ItemCreated' | 'ItemDestroyed' | 'Transfer' | 'Approved' | 'ApprovedForAll' | 'CollectionPropertySet' | 'CollectionPropertyDeleted' | 'TokenPropertySet' | 'TokenPropertyDeleted' | 'PropertyPermissionSet';
}
/** @name PalletConfigurationCall */
@@ -1603,7 +1605,8 @@
readonly isFungibleItemsDontHaveData: boolean;
readonly isFungibleDisallowsNesting: boolean;
readonly isSettingPropertiesNotAllowed: boolean;
- readonly type: 'NotFungibleDataUsedToMintFungibleCollectionToken' | 'FungibleItemsHaveNoId' | 'FungibleItemsDontHaveData' | 'FungibleDisallowsNesting' | 'SettingPropertiesNotAllowed';
+ readonly isSettingApprovalForAllNotAllowed: boolean;
+ readonly type: 'NotFungibleDataUsedToMintFungibleCollectionToken' | 'FungibleItemsHaveNoId' | 'FungibleItemsDontHaveData' | 'FungibleDisallowsNesting' | 'SettingPropertiesNotAllowed' | 'SettingApprovalForAllNotAllowed';
}
/** @name PalletInflationCall */
@@ -2309,7 +2312,13 @@
readonly tokenId: u32;
readonly amount: u128;
} & Struct;
- readonly type: 'CreateCollection' | 'CreateCollectionEx' | 'DestroyCollection' | 'AddToAllowList' | 'RemoveFromAllowList' | 'ChangeCollectionOwner' | 'AddCollectionAdmin' | 'RemoveCollectionAdmin' | 'SetCollectionSponsor' | 'ConfirmSponsorship' | 'RemoveCollectionSponsor' | 'CreateItem' | 'CreateMultipleItems' | 'SetCollectionProperties' | 'DeleteCollectionProperties' | 'SetTokenProperties' | 'DeleteTokenProperties' | 'SetTokenPropertyPermissions' | 'CreateMultipleItemsEx' | 'SetTransfersEnabledFlag' | 'BurnItem' | 'BurnFrom' | 'Transfer' | 'Approve' | 'TransferFrom' | 'SetCollectionLimits' | 'SetCollectionPermissions' | 'Repartition';
+ readonly isSetApprovalForAll: boolean;
+ readonly asSetApprovalForAll: {
+ readonly collectionId: u32;
+ readonly operator: PalletEvmAccountBasicCrossAccountIdRepr;
+ readonly approve: bool;
+ } & Struct;
+ readonly type: 'CreateCollection' | 'CreateCollectionEx' | 'DestroyCollection' | 'AddToAllowList' | 'RemoveFromAllowList' | 'ChangeCollectionOwner' | 'AddCollectionAdmin' | 'RemoveCollectionAdmin' | 'SetCollectionSponsor' | 'ConfirmSponsorship' | 'RemoveCollectionSponsor' | 'CreateItem' | 'CreateMultipleItems' | 'SetCollectionProperties' | 'DeleteCollectionProperties' | 'SetTokenProperties' | 'DeleteTokenProperties' | 'SetTokenPropertyPermissions' | 'CreateMultipleItemsEx' | 'SetTransfersEnabledFlag' | 'BurnItem' | 'BurnFrom' | 'Transfer' | 'Approve' | 'TransferFrom' | 'SetCollectionLimits' | 'SetCollectionPermissions' | 'Repartition' | 'SetApprovalForAll';
}
/** @name PalletUniqueError */
tests/src/interfaces/lookup.tsdiffbeforeafterboth--- a/tests/src/interfaces/lookup.ts
+++ b/tests/src/interfaces/lookup.ts
@@ -1046,6 +1046,7 @@
ItemDestroyed: '(u32,u32,PalletEvmAccountBasicCrossAccountIdRepr,u128)',
Transfer: '(u32,u32,PalletEvmAccountBasicCrossAccountIdRepr,PalletEvmAccountBasicCrossAccountIdRepr,u128)',
Approved: '(u32,u32,PalletEvmAccountBasicCrossAccountIdRepr,PalletEvmAccountBasicCrossAccountIdRepr,u128)',
+ ApprovedForAll: '(u32,PalletEvmAccountBasicCrossAccountIdRepr,PalletEvmAccountBasicCrossAccountIdRepr,bool)',
CollectionPropertySet: '(u32,Bytes)',
CollectionPropertyDeleted: '(u32,Bytes)',
TokenPropertySet: '(u32,u32,Bytes)',
@@ -1054,7 +1055,7 @@
}
},
/**
- * Lookup99: pallet_structure::pallet::Event<T>
+ * Lookup100: pallet_structure::pallet::Event<T>
**/
PalletStructureEvent: {
_enum: {
@@ -1062,7 +1063,7 @@
}
},
/**
- * Lookup100: pallet_rmrk_core::pallet::Event<T>
+ * Lookup101: pallet_rmrk_core::pallet::Event<T>
**/
PalletRmrkCoreEvent: {
_enum: {
@@ -1139,7 +1140,7 @@
}
},
/**
- * Lookup101: rmrk_traits::nft::AccountIdOrCollectionNftTuple<sp_core::crypto::AccountId32>
+ * Lookup102: rmrk_traits::nft::AccountIdOrCollectionNftTuple<sp_core::crypto::AccountId32>
**/
RmrkTraitsNftAccountIdOrCollectionNftTuple: {
_enum: {
@@ -2302,7 +2303,12 @@
repartition: {
collectionId: 'u32',
tokenId: 'u32',
- amount: 'u128'
+ amount: 'u128',
+ },
+ set_approval_for_all: {
+ collectionId: 'u32',
+ operator: 'PalletEvmAccountBasicCrossAccountIdRepr',
+ approve: 'bool'
}
}
},
@@ -3445,7 +3451,7 @@
* Lookup430: pallet_fungible::pallet::Error<T>
**/
PalletFungibleError: {
- _enum: ['NotFungibleDataUsedToMintFungibleCollectionToken', 'FungibleItemsHaveNoId', 'FungibleItemsDontHaveData', 'FungibleDisallowsNesting', 'SettingPropertiesNotAllowed']
+ _enum: ['NotFungibleDataUsedToMintFungibleCollectionToken', 'FungibleItemsHaveNoId', 'FungibleItemsDontHaveData', 'FungibleDisallowsNesting', 'SettingPropertiesNotAllowed', 'SettingApprovalForAllNotAllowed']
},
/**
* Lookup431: pallet_refungible::ItemData
tests/src/interfaces/types-lookup.tsdiffbeforeafterboth--- a/tests/src/interfaces/types-lookup.ts
+++ b/tests/src/interfaces/types-lookup.ts
@@ -1182,6 +1182,8 @@
readonly asTransfer: ITuple<[u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr, u128]>;
readonly isApproved: boolean;
readonly asApproved: ITuple<[u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr, u128]>;
+ readonly isApprovedForAll: boolean;
+ readonly asApprovedForAll: ITuple<[u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr, bool]>;
readonly isCollectionPropertySet: boolean;
readonly asCollectionPropertySet: ITuple<[u32, Bytes]>;
readonly isCollectionPropertyDeleted: boolean;
@@ -1192,17 +1194,17 @@
readonly asTokenPropertyDeleted: ITuple<[u32, u32, Bytes]>;
readonly isPropertyPermissionSet: boolean;
readonly asPropertyPermissionSet: ITuple<[u32, Bytes]>;
- readonly type: 'CollectionCreated' | 'CollectionDestroyed' | 'ItemCreated' | 'ItemDestroyed' | 'Transfer' | 'Approved' | 'CollectionPropertySet' | 'CollectionPropertyDeleted' | 'TokenPropertySet' | 'TokenPropertyDeleted' | 'PropertyPermissionSet';
+ readonly type: 'CollectionCreated' | 'CollectionDestroyed' | 'ItemCreated' | 'ItemDestroyed' | 'Transfer' | 'Approved' | 'ApprovedForAll' | 'CollectionPropertySet' | 'CollectionPropertyDeleted' | 'TokenPropertySet' | 'TokenPropertyDeleted' | 'PropertyPermissionSet';
}
- /** @name PalletStructureEvent (99) */
+ /** @name PalletStructureEvent (100) */
interface PalletStructureEvent extends Enum {
readonly isExecuted: boolean;
readonly asExecuted: Result<Null, SpRuntimeDispatchError>;
readonly type: 'Executed';
}
- /** @name PalletRmrkCoreEvent (100) */
+ /** @name PalletRmrkCoreEvent (101) */
interface PalletRmrkCoreEvent extends Enum {
readonly isCollectionCreated: boolean;
readonly asCollectionCreated: {
@@ -1292,7 +1294,7 @@
readonly type: 'CollectionCreated' | 'CollectionDestroyed' | 'IssuerChanged' | 'CollectionLocked' | 'NftMinted' | 'NftBurned' | 'NftSent' | 'NftAccepted' | 'NftRejected' | 'PropertySet' | 'ResourceAdded' | 'ResourceRemoval' | 'ResourceAccepted' | 'ResourceRemovalAccepted' | 'PrioritySet';
}
- /** @name RmrkTraitsNftAccountIdOrCollectionNftTuple (101) */
+ /** @name RmrkTraitsNftAccountIdOrCollectionNftTuple (102) */
interface RmrkTraitsNftAccountIdOrCollectionNftTuple extends Enum {
readonly isAccountId: boolean;
readonly asAccountId: AccountId32;
@@ -2539,7 +2541,13 @@
readonly tokenId: u32;
readonly amount: u128;
} & Struct;
- readonly type: 'CreateCollection' | 'CreateCollectionEx' | 'DestroyCollection' | 'AddToAllowList' | 'RemoveFromAllowList' | 'ChangeCollectionOwner' | 'AddCollectionAdmin' | 'RemoveCollectionAdmin' | 'SetCollectionSponsor' | 'ConfirmSponsorship' | 'RemoveCollectionSponsor' | 'CreateItem' | 'CreateMultipleItems' | 'SetCollectionProperties' | 'DeleteCollectionProperties' | 'SetTokenProperties' | 'DeleteTokenProperties' | 'SetTokenPropertyPermissions' | 'CreateMultipleItemsEx' | 'SetTransfersEnabledFlag' | 'BurnItem' | 'BurnFrom' | 'Transfer' | 'Approve' | 'TransferFrom' | 'SetCollectionLimits' | 'SetCollectionPermissions' | 'Repartition';
+ readonly isSetApprovalForAll: boolean;
+ readonly asSetApprovalForAll: {
+ readonly collectionId: u32;
+ readonly operator: PalletEvmAccountBasicCrossAccountIdRepr;
+ readonly approve: bool;
+ } & Struct;
+ readonly type: 'CreateCollection' | 'CreateCollectionEx' | 'DestroyCollection' | 'AddToAllowList' | 'RemoveFromAllowList' | 'ChangeCollectionOwner' | 'AddCollectionAdmin' | 'RemoveCollectionAdmin' | 'SetCollectionSponsor' | 'ConfirmSponsorship' | 'RemoveCollectionSponsor' | 'CreateItem' | 'CreateMultipleItems' | 'SetCollectionProperties' | 'DeleteCollectionProperties' | 'SetTokenProperties' | 'DeleteTokenProperties' | 'SetTokenPropertyPermissions' | 'CreateMultipleItemsEx' | 'SetTransfersEnabledFlag' | 'BurnItem' | 'BurnFrom' | 'Transfer' | 'Approve' | 'TransferFrom' | 'SetCollectionLimits' | 'SetCollectionPermissions' | 'Repartition' | 'SetApprovalForAll';
}
/** @name UpDataStructsCollectionMode (240) */
@@ -3655,7 +3663,8 @@
readonly isFungibleItemsDontHaveData: boolean;
readonly isFungibleDisallowsNesting: boolean;
readonly isSettingPropertiesNotAllowed: boolean;
- readonly type: 'NotFungibleDataUsedToMintFungibleCollectionToken' | 'FungibleItemsHaveNoId' | 'FungibleItemsDontHaveData' | 'FungibleDisallowsNesting' | 'SettingPropertiesNotAllowed';
+ readonly isSettingApprovalForAllNotAllowed: boolean;
+ readonly type: 'NotFungibleDataUsedToMintFungibleCollectionToken' | 'FungibleItemsHaveNoId' | 'FungibleItemsDontHaveData' | 'FungibleDisallowsNesting' | 'SettingPropertiesNotAllowed' | 'SettingApprovalForAllNotAllowed';
}
/** @name PalletRefungibleItemData (431) */
tests/src/interfaces/unique/definitions.tsdiffbeforeafterboth--- a/tests/src/interfaces/unique/definitions.ts
+++ b/tests/src/interfaces/unique/definitions.ts
@@ -175,5 +175,10 @@
[collectionParam, tokenParam],
'Option<u128>',
),
+ isApprovedForAll: fun(
+ 'Tells whether an operator is approved by a given owner.',
+ [collectionParam, crossAccountParam('owner'), crossAccountParam('operator')],
+ 'Option<bool>',
+ ),
},
};
tests/src/util/playgrounds/unique.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/unique.ts
+++ b/tests/src/util/playgrounds/unique.ts
@@ -1413,6 +1413,32 @@
getTokenObject(_collectionId: number, _tokenId: number): any {
return null;
}
+
+ /**
+ * Tells whether an operator is approved by a given owner.
+ * @param collectionId ID of collection
+ * @param owner owner address
+ * @param operator operator addrees
+ * @returns true if operator is enabled
+ */
+ async isApprovedForAll(collectionId: number, owner: ICrossAccountId, operator: ICrossAccountId): Promise<boolean> {
+ return (await this.helper.callRpc('api.rpc.unique.isApprovedForAll', [collectionId, owner, operator])).toJSON();
+ }
+
+ /** Sets or unsets the approval of a given operator.
+ * An operator is allowed to transfer all tokens of the sender on their behalf.
+ * @param operator Operator
+ * @param approved Is operator enabled or disabled
+ * @returns ```true``` if extrinsic success, otherwise ```false```
+ */
+ async setApprovalForAll(signer: TSigner, collectionId: number, operator: ICrossAccountId, approved: boolean): Promise<boolean> {
+ const result = await this.helper.executeExtrinsic(
+ signer,
+ 'api.tx.unique.setApprovalForAll', [collectionId, operator, approved],
+ true,
+ );
+ return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'ApprovedForAll');
+ }
}