referrerpolicy=no-referrer-when-downgrade

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}