pallet_nfts/features/buy_sell.rs
1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! This module contains helper functions to perform the buy and sell functionalities of the NFTs
19//! pallet.
20//! The bitflag [`PalletFeature::Trading`] needs to be set in the [`Config::Features`] for NFTs
21//! to have the functionality defined in this module.
22
23use crate::*;
24use frame_support::{
25 pallet_prelude::*,
26 traits::{Currency, ExistenceRequirement, ExistenceRequirement::KeepAlive},
27};
28
29impl<T: Config<I>, I: 'static> Pallet<T, I> {
30 /// Pays the specified tips to the corresponding receivers.
31 ///
32 /// This function is used to pay tips from the `sender` account to multiple receivers. The tips
33 /// are specified as a `BoundedVec` of `ItemTipOf` with a maximum length of `T::MaxTips`. For
34 /// each tip, the function transfers the `amount` to the `receiver` account. The sender is
35 /// responsible for ensuring the validity of the provided tips.
36 ///
37 /// - `sender`: The account that pays the tips.
38 /// - `tips`: A `BoundedVec` containing the tips to be paid, where each tip contains the
39 /// `collection`, `item`, `receiver`, and `amount`.
40 pub(crate) fn do_pay_tips(
41 sender: T::AccountId,
42 tips: BoundedVec<ItemTipOf<T, I>, T::MaxTips>,
43 ) -> DispatchResult {
44 for tip in tips {
45 let ItemTip { collection, item, receiver, amount } = tip;
46 T::Currency::transfer(&sender, &receiver, amount, KeepAlive)?;
47 Self::deposit_event(Event::TipSent {
48 collection,
49 item,
50 sender: sender.clone(),
51 receiver,
52 amount,
53 });
54 }
55 Ok(())
56 }
57
58 /// Sets the price and whitelists a buyer for an item in the specified collection.
59 ///
60 /// This function is used to set the price and whitelist a buyer for an item in the
61 /// specified `collection`. The `sender` account must be the owner of the item. The item's price
62 /// and the whitelisted buyer can be set to allow trading the item. If `price` is `None`, the
63 /// item will be marked as not for sale.
64 ///
65 /// - `collection`: The identifier of the collection containing the item.
66 /// - `item`: The identifier of the item for which the price and whitelist information will be
67 /// set.
68 /// - `sender`: The account that sets the price and whitelist information for the item.
69 /// - `price`: The optional price for the item.
70 /// - `whitelisted_buyer`: The optional account that is whitelisted to buy the item at the set
71 /// price.
72 pub(crate) fn do_set_price(
73 collection: T::CollectionId,
74 item: T::ItemId,
75 sender: T::AccountId,
76 price: Option<ItemPrice<T, I>>,
77 whitelisted_buyer: Option<T::AccountId>,
78 ) -> DispatchResult {
79 ensure!(
80 Self::is_pallet_feature_enabled(PalletFeature::Trading),
81 Error::<T, I>::MethodDisabled
82 );
83
84 let details = Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
85 ensure!(details.owner == sender, Error::<T, I>::NoPermission);
86
87 let collection_config = Self::get_collection_config(&collection)?;
88 ensure!(
89 collection_config.is_setting_enabled(CollectionSetting::TransferableItems),
90 Error::<T, I>::ItemsNonTransferable
91 );
92
93 let item_config = Self::get_item_config(&collection, &item)?;
94 ensure!(
95 item_config.is_setting_enabled(ItemSetting::Transferable),
96 Error::<T, I>::ItemLocked
97 );
98
99 if let Some(ref price) = price {
100 ItemPriceOf::<T, I>::insert(&collection, &item, (price, whitelisted_buyer.clone()));
101 Self::deposit_event(Event::ItemPriceSet {
102 collection,
103 item,
104 price: *price,
105 whitelisted_buyer,
106 });
107 } else {
108 ItemPriceOf::<T, I>::remove(&collection, &item);
109 Self::deposit_event(Event::ItemPriceRemoved { collection, item });
110 }
111
112 Ok(())
113 }
114
115 /// Buys the specified item from the collection.
116 ///
117 /// This function is used to buy an item from the specified `collection`. The `buyer` account
118 /// will attempt to buy the item with the provided `bid_price`. The item's current owner will
119 /// receive the bid price if it is equal to or higher than the item's set price. If
120 /// `whitelisted_buyer` is specified in the item's price information, only that account is
121 /// allowed to buy the item. If the item is not for sale, or the bid price is too low, the
122 /// function will return an error.
123 ///
124 /// - `collection`: The identifier of the collection containing the item to be bought.
125 /// - `item`: The identifier of the item to be bought.
126 /// - `buyer`: The account that attempts to buy the item.
127 /// - `bid_price`: The bid price offered by the buyer for the item.
128 pub(crate) fn do_buy_item(
129 collection: T::CollectionId,
130 item: T::ItemId,
131 buyer: T::AccountId,
132 bid_price: ItemPrice<T, I>,
133 ) -> DispatchResult {
134 ensure!(
135 Self::is_pallet_feature_enabled(PalletFeature::Trading),
136 Error::<T, I>::MethodDisabled
137 );
138
139 let details = Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
140 ensure!(details.owner != buyer, Error::<T, I>::NoPermission);
141
142 let price_info =
143 ItemPriceOf::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::NotForSale)?;
144
145 ensure!(bid_price >= price_info.0, Error::<T, I>::BidTooLow);
146
147 if let Some(only_buyer) = price_info.1 {
148 ensure!(only_buyer == buyer, Error::<T, I>::NoPermission);
149 }
150
151 T::Currency::transfer(
152 &buyer,
153 &details.owner,
154 price_info.0,
155 ExistenceRequirement::KeepAlive,
156 )?;
157
158 let old_owner = details.owner.clone();
159
160 Self::do_transfer(collection, item, buyer.clone(), |_, _| Ok(()))?;
161
162 Self::deposit_event(Event::ItemBought {
163 collection,
164 item,
165 price: price_info.0,
166 seller: old_owner,
167 buyer,
168 });
169
170 Ok(())
171 }
172}