diff --git a/libraries/chain/include/graphene/chain/nft_object.hpp b/libraries/chain/include/graphene/chain/nft_object.hpp index 4300baa9..458a3ce1 100644 --- a/libraries/chain/include/graphene/chain/nft_object.hpp +++ b/libraries/chain/include/graphene/chain/nft_object.hpp @@ -18,6 +18,8 @@ namespace graphene { namespace chain { std::string base_uri; optional revenue_partner; optional revenue_split; + bool isTransferable = false; + bool isSellable = true; }; class nft_object : public abstract_object @@ -91,7 +93,9 @@ FC_REFLECT_DERIVED( graphene::chain::nft_metadata_object, (graphene::db::object) (symbol) (base_uri) (revenue_partner) - (revenue_split) ) + (revenue_split) + (isTransferable) + (isSellable) ) FC_REFLECT_DERIVED( graphene::chain::nft_object, (graphene::db::object), (nft_metadata_id) diff --git a/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp b/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp index 388ce14b..06869d44 100644 --- a/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp +++ b/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp @@ -15,6 +15,8 @@ namespace graphene { namespace chain { std::string base_uri; optional revenue_partner; optional revenue_split; + bool isTransferable = false; + bool isSellable = true; account_id_type fee_payer()const { return owner; } void validate() const; @@ -32,6 +34,8 @@ namespace graphene { namespace chain { optional base_uri; optional revenue_partner; optional revenue_split; + optional isTransferable; + optional isSellable; account_id_type fee_payer()const { return owner; } void validate() const; @@ -104,8 +108,8 @@ FC_REFLECT( graphene::chain::nft_safe_transfer_from_operation::fee_parameters_ty FC_REFLECT( graphene::chain::nft_approve_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::nft_set_approval_for_all_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::chain::nft_metadata_create_operation, (fee) (owner) (name) (symbol) (base_uri) (revenue_partner) (revenue_split) ) -FC_REFLECT( graphene::chain::nft_metadata_update_operation, (fee) (owner) (nft_metadata_id) (name) (symbol) (base_uri) (revenue_partner) (revenue_split) ) +FC_REFLECT( graphene::chain::nft_metadata_create_operation, (fee) (owner) (name) (symbol) (base_uri) (revenue_partner) (revenue_split) (isTransferable) (isSellable) ) +FC_REFLECT( graphene::chain::nft_metadata_update_operation, (fee) (owner) (nft_metadata_id) (name) (symbol) (base_uri) (revenue_partner) (revenue_split) (isTransferable) (isSellable) ) FC_REFLECT( graphene::chain::nft_mint_operation, (fee) (payer) (nft_metadata_id) (owner) (approved) (approved_operators) (token_uri) ) FC_REFLECT( graphene::chain::nft_safe_transfer_from_operation, (fee) (operator_) (from) (to) (token_id) (data) ) FC_REFLECT( graphene::chain::nft_approve_operation, (fee) (operator_) (approved) (token_id) ) diff --git a/libraries/chain/nft_evaluator.cpp b/libraries/chain/nft_evaluator.cpp index be77474b..ab859a23 100644 --- a/libraries/chain/nft_evaluator.cpp +++ b/libraries/chain/nft_evaluator.cpp @@ -27,6 +27,8 @@ object_id_type nft_metadata_create_evaluator::do_apply( const nft_metadata_creat obj.base_uri = op.base_uri; obj.revenue_partner = op.revenue_partner; obj.revenue_split = op.revenue_split; + obj.isTransferable = op.isTransferable; + obj.isSellable = op.isSellable; }); return new_nft_metadata_object.id; } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -60,6 +62,10 @@ void_result nft_metadata_update_evaluator::do_apply( const nft_metadata_update_o obj.revenue_partner = op.revenue_partner; if( op.revenue_split.valid() ) obj.revenue_split = op.revenue_split; + if( op.isTransferable.valid() ) + obj.isTransferable = *op.isTransferable; + if( op.isSellable.valid() ) + obj.isSellable = *op.isSellable; }); return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -120,6 +126,9 @@ void_result nft_safe_transfer_from_evaluator::do_evaluate( const nft_safe_transf auto itr_approved_op = std::find(itr_nft->approved_operators.begin(), itr_nft->approved_operators.end(), op.operator_); FC_ASSERT( (itr_nft->owner == op.operator_) || (itr_nft->approved == itr_operator->id) || (itr_approved_op != itr_nft->approved_operators.end()), "Operator is not NFT owner or approved operator" ); + const auto& nft_meta_obj = itr_nft->nft_metadata_id(db()); + FC_ASSERT( nft_meta_obj.isTransferable == true, "NFT is not transferable"); + return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } diff --git a/libraries/chain/offer_evaluator.cpp b/libraries/chain/offer_evaluator.cpp index 79f7243b..ac7f00f5 100644 --- a/libraries/chain/offer_evaluator.cpp +++ b/libraries/chain/offer_evaluator.cpp @@ -34,6 +34,8 @@ namespace graphene { FC_ASSERT(is_owner, "Issuer has no authority to sell the item"); } + const auto& nft_meta_obj = nft_obj.nft_metadata_id(d); + FC_ASSERT( nft_meta_obj.isSellable == true, "NFT is not sellable"); } FC_ASSERT(op.offer_expiration_date > d.head_block_time(), "Expiration should be in future"); FC_ASSERT(op.fee.amount >= 0, "Invalid fee"); @@ -229,7 +231,6 @@ namespace graphene { database &d = db(); offer_object offer = op.offer_id(d); - vector xfer_ops; // Calculate the fees for all revenue partners of the items auto calc_fee = [&](int64_t &tot_fees) { map fee_map; @@ -282,25 +283,22 @@ namespace graphene // Sell Offer, send the seller his amount d.adjust_balance(offer.issuer, seller_amount); } - // Safely tranfer the NFTs with the ops - for (const auto &item : offer.item_ids) + // Tranfer the NFTs + for (auto item : offer.item_ids) { - const auto &nft_obj = item(d); - nft_safe_transfer_from_operation xfer_op; - xfer_op.from = nft_obj.owner; - xfer_op.token_id = item; - xfer_op.fee = asset(0, asset_id_type()); - if (offer.buying_item) - { - xfer_op.to = offer.issuer; - xfer_op.operator_ = *offer.bidder; - } - else - { - xfer_op.to = *offer.bidder; - xfer_op.operator_ = offer.issuer; - } - xfer_ops.push_back(std::move(xfer_op)); + auto &nft_obj = item(d); + d.modify(nft_obj, [&offer](nft_object &obj) { + if (offer.buying_item) + { + obj.owner = offer.issuer; + } + else + { + obj.owner = *offer.bidder; + } + obj.approved = {}; + obj.approved_operators.clear(); + }); } } else @@ -323,16 +321,6 @@ namespace graphene }); // This should unlock the item d.remove(op.offer_id(d)); - // Send safe transfer from ops - if (xfer_ops.size() > 0) - { - transaction_evaluation_state xfer_context(&d); - xfer_context.skip_fee_schedule_check = true; - for (const auto &xfer_op : xfer_ops) - { - d.apply_operation(xfer_context, xfer_op); - } - } return void_result(); } FC_CAPTURE_AND_RETHROW((op)) diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index 2d76159e..8929a36f 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -1937,6 +1937,8 @@ class wallet_api * @param base_uri Base URI for token URI * @param revenue_partner revenue partner for this type of Token * @param revenue_split revenue split for the sale + * @param isTransferable can transfer the NFT or not + * @param isSellable can sell NFT or not * @param broadcast true to broadcast transaction to the network * @return Signed transaction transfering the funds */ @@ -1946,6 +1948,8 @@ class wallet_api string base_uri, optional revenue_partner, optional revenue_split, + bool isTransferable, + bool isSellable, bool broadcast); /** @@ -1956,6 +1960,8 @@ class wallet_api * @param base_uri Base URI for token URI * @param revenue_partner revenue partner for this type of Token * @param revenue_split revenue split for the sale + * @param isTransferable can transfer the NFT or not + * @param isSellable can sell NFT or not * @param broadcast true to broadcast transaction to the network * @return Signed transaction transfering the funds */ @@ -1965,6 +1971,8 @@ class wallet_api string base_uri, optional revenue_partner, optional revenue_split, + optional isTransferable, + optional isSellable, bool broadcast); /** diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 77a00369..517a3e70 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -6373,6 +6373,8 @@ signed_transaction wallet_api::nft_metadata_create(string owner_account_id_or_na string base_uri, optional revenue_partner, optional revenue_split, + bool isTransferable, + bool isSellable, bool broadcast) { account_object owner_account = my->get_account(owner_account_id_or_name); @@ -6393,6 +6395,8 @@ signed_transaction wallet_api::nft_metadata_create(string owner_account_id_or_na } op.revenue_split = rev_split; } + op.isTransferable = isTransferable; + op.isSellable = isSellable; signed_transaction trx; trx.operations.push_back(op); @@ -6408,6 +6412,8 @@ signed_transaction wallet_api::nft_metadata_update(string owner_account_id_or_na string base_uri, optional revenue_partner, optional revenue_split, + optional isTransferable, + optional isSellable, bool broadcast) { account_object owner_account = my->get_account(owner_account_id_or_name); @@ -6428,6 +6434,8 @@ signed_transaction wallet_api::nft_metadata_update(string owner_account_id_or_na } op.revenue_split = rev_split; } + op.isTransferable = isTransferable; + op.isSellable = isSellable; signed_transaction trx; trx.operations.push_back(op); diff --git a/tests/tests/nft_tests.cpp b/tests/tests/nft_tests.cpp index 89ef0cc3..8e8eef41 100644 --- a/tests/tests/nft_tests.cpp +++ b/tests/tests/nft_tests.cpp @@ -30,6 +30,7 @@ BOOST_AUTO_TEST_CASE( nft_metadata_create_test ) { op.symbol = "NFT"; op.base_uri = "http://nft.example.com"; op.name = "123"; + op.isTransferable = true; BOOST_CHECK_THROW(op.validate(), fc::exception); op.name = ""; BOOST_CHECK_THROW(op.validate(), fc::exception);