frame_support/traits/tokens/nonfungibles_v2.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//! Traits for dealing with multiple collections of non-fungible items.
19//!
20//! This assumes a dual-level namespace identified by `Inspect::ItemId`, and could
21//! reasonably be implemented by pallets which want to expose multiple independent collections of
22//! NFT-like objects.
23//!
24//! For an NFT API which has single-level namespacing, the traits in `nonfungible` are better to
25//! use.
26//!
27//! Implementations of these traits may be converted to implementations of corresponding
28//! `nonfungible` traits by using the `nonfungible::ItemOf` type adapter.
29
30use crate::dispatch::{DispatchResult, Parameter};
31use alloc::vec::Vec;
32use codec::{Decode, Encode};
33use sp_runtime::{DispatchError, TokenError};
34
35/// Trait for providing an interface to many read-only NFT-like sets of items.
36pub trait Inspect<AccountId> {
37 /// Type for identifying an item.
38 type ItemId: Parameter;
39
40 /// Type for identifying a collection (an identifier for an independent collection of
41 /// items).
42 type CollectionId: Parameter;
43
44 /// Returns the owner of `item` of `collection`, or `None` if the item doesn't exist
45 /// (or somehow has no owner).
46 fn owner(collection: &Self::CollectionId, item: &Self::ItemId) -> Option<AccountId>;
47
48 /// Returns the owner of the `collection`, if there is one. For many NFTs this may not
49 /// make any sense, so users of this API should not be surprised to find a collection
50 /// results in `None` here.
51 fn collection_owner(_collection: &Self::CollectionId) -> Option<AccountId> {
52 None
53 }
54
55 /// Returns the attribute value of `item` of `collection` corresponding to `key`.
56 ///
57 /// By default this is `None`; no attributes are defined.
58 fn attribute(
59 _collection: &Self::CollectionId,
60 _item: &Self::ItemId,
61 _key: &[u8],
62 ) -> Option<Vec<u8>> {
63 None
64 }
65
66 /// Returns the custom attribute value of `item` of `collection` corresponding to `key`.
67 ///
68 /// By default this is `None`; no attributes are defined.
69 fn custom_attribute(
70 _account: &AccountId,
71 _collection: &Self::CollectionId,
72 _item: &Self::ItemId,
73 _key: &[u8],
74 ) -> Option<Vec<u8>> {
75 None
76 }
77
78 /// Returns the system attribute value of `item` of `collection` corresponding to `key` if
79 /// `item` is `Some`. Otherwise, returns the system attribute value of `collection`
80 /// corresponding to `key`.
81 ///
82 /// By default this is `None`; no attributes are defined.
83 fn system_attribute(
84 _collection: &Self::CollectionId,
85 _item: Option<&Self::ItemId>,
86 _key: &[u8],
87 ) -> Option<Vec<u8>> {
88 None
89 }
90
91 /// Returns the strongly-typed attribute value of `item` of `collection` corresponding to
92 /// `key`.
93 ///
94 /// By default this just attempts to use `attribute`.
95 fn typed_attribute<K: Encode, V: Decode>(
96 collection: &Self::CollectionId,
97 item: &Self::ItemId,
98 key: &K,
99 ) -> Option<V> {
100 key.using_encoded(|d| Self::attribute(collection, item, d))
101 .and_then(|v| V::decode(&mut &v[..]).ok())
102 }
103
104 /// Returns the strongly-typed custom attribute value of `item` of `collection` corresponding to
105 /// `key`.
106 ///
107 /// By default this just attempts to use `custom_attribute`.
108 fn typed_custom_attribute<K: Encode, V: Decode>(
109 account: &AccountId,
110 collection: &Self::CollectionId,
111 item: &Self::ItemId,
112 key: &K,
113 ) -> Option<V> {
114 key.using_encoded(|d| Self::custom_attribute(account, collection, item, d))
115 .and_then(|v| V::decode(&mut &v[..]).ok())
116 }
117
118 /// Returns the strongly-typed system attribute value of `item` corresponding to `key` if
119 /// `item` is `Some`. Otherwise, returns the strongly-typed system attribute value of
120 /// `collection` corresponding to `key`.
121 ///
122 /// By default this just attempts to use `system_attribute`.
123 fn typed_system_attribute<K: Encode, V: Decode>(
124 collection: &Self::CollectionId,
125 item: Option<&Self::ItemId>,
126 key: &K,
127 ) -> Option<V> {
128 key.using_encoded(|d| Self::system_attribute(collection, item, d))
129 .and_then(|v| V::decode(&mut &v[..]).ok())
130 }
131
132 /// Returns the attribute value of `collection` corresponding to `key`.
133 ///
134 /// By default this is `None`; no attributes are defined.
135 fn collection_attribute(_collection: &Self::CollectionId, _key: &[u8]) -> Option<Vec<u8>> {
136 None
137 }
138
139 /// Returns the strongly-typed attribute value of `collection` corresponding to `key`.
140 ///
141 /// By default this just attempts to use `collection_attribute`.
142 fn typed_collection_attribute<K: Encode, V: Decode>(
143 collection: &Self::CollectionId,
144 key: &K,
145 ) -> Option<V> {
146 key.using_encoded(|d| Self::collection_attribute(collection, d))
147 .and_then(|v| V::decode(&mut &v[..]).ok())
148 }
149
150 /// Returns `true` if the `item` of `collection` may be transferred.
151 ///
152 /// Default implementation is that all items are transferable.
153 fn can_transfer(_collection: &Self::CollectionId, _item: &Self::ItemId) -> bool {
154 true
155 }
156}
157
158/// Interface for enumerating items in existence or owned by a given account over many collections
159/// of NFTs.
160pub trait InspectEnumerable<AccountId>: Inspect<AccountId> {
161 /// The iterator type for [`Self::collections`].
162 type CollectionsIterator: Iterator<Item = Self::CollectionId>;
163 /// The iterator type for [`Self::items`].
164 type ItemsIterator: Iterator<Item = Self::ItemId>;
165 /// The iterator type for [`Self::owned`].
166 type OwnedIterator: Iterator<Item = (Self::CollectionId, Self::ItemId)>;
167 /// The iterator type for [`Self::owned_in_collection`].
168 type OwnedInCollectionIterator: Iterator<Item = Self::ItemId>;
169
170 /// Returns an iterator of the collections in existence.
171 fn collections() -> Self::CollectionsIterator;
172
173 /// Returns an iterator of the items of a `collection` in existence.
174 fn items(collection: &Self::CollectionId) -> Self::ItemsIterator;
175
176 /// Returns an iterator of the items of all collections owned by `who`.
177 fn owned(who: &AccountId) -> Self::OwnedIterator;
178
179 /// Returns an iterator of the items of `collection` owned by `who`.
180 fn owned_in_collection(
181 collection: &Self::CollectionId,
182 who: &AccountId,
183 ) -> Self::OwnedInCollectionIterator;
184}
185
186/// Trait for providing an interface to check the account's role within the collection.
187pub trait InspectRole<AccountId>: Inspect<AccountId> {
188 /// Returns `true` if `who` is the issuer of the `collection`.
189 fn is_issuer(collection: &Self::CollectionId, who: &AccountId) -> bool;
190 /// Returns `true` if `who` is the admin of the `collection`.
191 fn is_admin(collection: &Self::CollectionId, who: &AccountId) -> bool;
192 /// Returns `true` if `who` is the freezer of the `collection`.
193 fn is_freezer(collection: &Self::CollectionId, who: &AccountId) -> bool;
194}
195
196/// Trait for providing the ability to create collections of nonfungible items.
197pub trait Create<AccountId, CollectionConfig>: Inspect<AccountId> {
198 /// Create a `collection` of nonfungible items to be owned by `who` and managed by `admin`.
199 fn create_collection(
200 who: &AccountId,
201 admin: &AccountId,
202 config: &CollectionConfig,
203 ) -> Result<Self::CollectionId, DispatchError>;
204
205 fn create_collection_with_id(
206 collection: Self::CollectionId,
207 who: &AccountId,
208 admin: &AccountId,
209 config: &CollectionConfig,
210 ) -> Result<(), DispatchError>;
211}
212
213/// Trait for providing the ability to destroy collections of nonfungible items.
214pub trait Destroy<AccountId>: Inspect<AccountId> {
215 /// The witness data needed to destroy an item.
216 type DestroyWitness: Parameter;
217
218 /// Provide the appropriate witness data needed to destroy an item.
219 fn get_destroy_witness(collection: &Self::CollectionId) -> Option<Self::DestroyWitness>;
220
221 /// Destroy an existing fungible item.
222 /// * `collection`: The `CollectionId` to be destroyed.
223 /// * `witness`: Any witness data that needs to be provided to complete the operation
224 /// successfully.
225 /// * `maybe_check_owner`: An optional `AccountId` that can be used to authorize the destroy
226 /// command. If not provided, we will not do any authorization checks before destroying the
227 /// item.
228 ///
229 /// If successful, this function will return the actual witness data from the destroyed item.
230 /// This may be different than the witness data provided, and can be used to refund weight.
231 fn destroy(
232 collection: Self::CollectionId,
233 witness: Self::DestroyWitness,
234 maybe_check_owner: Option<AccountId>,
235 ) -> Result<Self::DestroyWitness, DispatchError>;
236}
237
238/// Trait for providing an interface for multiple collections of NFT-like items which may be
239/// minted, burned and/or have attributes and metadata set on them.
240pub trait Mutate<AccountId, ItemConfig>: Inspect<AccountId> {
241 /// Mint some `item` of `collection` to be owned by `who`.
242 ///
243 /// By default, this is not a supported operation.
244 fn mint_into(
245 _collection: &Self::CollectionId,
246 _item: &Self::ItemId,
247 _who: &AccountId,
248 _config: &ItemConfig,
249 _deposit_collection_owner: bool,
250 ) -> DispatchResult {
251 Err(TokenError::Unsupported.into())
252 }
253
254 /// Burn some `item` of `collection`.
255 ///
256 /// By default, this is not a supported operation.
257 fn burn(
258 _collection: &Self::CollectionId,
259 _item: &Self::ItemId,
260 _maybe_check_owner: Option<&AccountId>,
261 ) -> DispatchResult {
262 Err(TokenError::Unsupported.into())
263 }
264
265 /// Set attribute `value` of `item` of `collection`'s `key`.
266 ///
267 /// By default, this is not a supported operation.
268 fn set_attribute(
269 _collection: &Self::CollectionId,
270 _item: &Self::ItemId,
271 _key: &[u8],
272 _value: &[u8],
273 ) -> DispatchResult {
274 Err(TokenError::Unsupported.into())
275 }
276
277 /// Attempt to set the strongly-typed attribute `value` of `item` of `collection`'s `key`.
278 ///
279 /// By default this just attempts to use `set_attribute`.
280 fn set_typed_attribute<K: Encode, V: Encode>(
281 collection: &Self::CollectionId,
282 item: &Self::ItemId,
283 key: &K,
284 value: &V,
285 ) -> DispatchResult {
286 key.using_encoded(|k| value.using_encoded(|v| Self::set_attribute(collection, item, k, v)))
287 }
288
289 /// Set attribute `value` of `collection`'s `key`.
290 ///
291 /// By default, this is not a supported operation.
292 fn set_collection_attribute(
293 _collection: &Self::CollectionId,
294 _key: &[u8],
295 _value: &[u8],
296 ) -> DispatchResult {
297 Err(TokenError::Unsupported.into())
298 }
299
300 /// Attempt to set the strongly-typed attribute `value` of `collection`'s `key`.
301 ///
302 /// By default this just attempts to use `set_attribute`.
303 fn set_typed_collection_attribute<K: Encode, V: Encode>(
304 collection: &Self::CollectionId,
305 key: &K,
306 value: &V,
307 ) -> DispatchResult {
308 key.using_encoded(|k| {
309 value.using_encoded(|v| Self::set_collection_attribute(collection, k, v))
310 })
311 }
312
313 /// Set the metadata `data` of an `item` of `collection`.
314 ///
315 /// By default, this is not a supported operation.
316 fn set_item_metadata(
317 _who: Option<&AccountId>,
318 _collection: &Self::CollectionId,
319 _item: &Self::ItemId,
320 _data: &[u8],
321 ) -> DispatchResult {
322 Err(TokenError::Unsupported.into())
323 }
324
325 /// Set the metadata `data` of a `collection`.
326 ///
327 /// By default, this is not a supported operation.
328 fn set_collection_metadata(
329 _who: Option<&AccountId>,
330 _collection: &Self::CollectionId,
331 _data: &[u8],
332 ) -> DispatchResult {
333 Err(TokenError::Unsupported.into())
334 }
335
336 /// Clear attribute of `item` of `collection`'s `key`.
337 ///
338 /// By default, this is not a supported operation.
339 fn clear_attribute(
340 _collection: &Self::CollectionId,
341 _item: &Self::ItemId,
342 _key: &[u8],
343 ) -> DispatchResult {
344 Err(TokenError::Unsupported.into())
345 }
346
347 /// Attempt to clear the strongly-typed attribute of `item` of `collection`'s `key`.
348 ///
349 /// By default this just attempts to use `clear_attribute`.
350 fn clear_typed_attribute<K: Encode>(
351 collection: &Self::CollectionId,
352 item: &Self::ItemId,
353 key: &K,
354 ) -> DispatchResult {
355 key.using_encoded(|k| Self::clear_attribute(collection, item, k))
356 }
357
358 /// Clear attribute of `collection`'s `key`.
359 ///
360 /// By default, this is not a supported operation.
361 fn clear_collection_attribute(_collection: &Self::CollectionId, _key: &[u8]) -> DispatchResult {
362 Err(TokenError::Unsupported.into())
363 }
364
365 /// Attempt to clear the strongly-typed attribute of `collection`'s `key`.
366 ///
367 /// By default this just attempts to use `clear_attribute`.
368 fn clear_typed_collection_attribute<K: Encode>(
369 collection: &Self::CollectionId,
370 key: &K,
371 ) -> DispatchResult {
372 key.using_encoded(|k| Self::clear_collection_attribute(collection, k))
373 }
374
375 /// Clear the metadata of an `item` of `collection`.
376 ///
377 /// By default, this is not a supported operation.
378 fn clear_item_metadata(
379 _who: Option<&AccountId>,
380 _collection: &Self::CollectionId,
381 _item: &Self::ItemId,
382 ) -> DispatchResult {
383 Err(TokenError::Unsupported.into())
384 }
385
386 /// Clear the metadata of a `collection`.
387 ///
388 /// By default, this is not a supported operation.
389 fn clear_collection_metadata(
390 _who: Option<&AccountId>,
391 _collection: &Self::CollectionId,
392 ) -> DispatchResult {
393 Err(TokenError::Unsupported.into())
394 }
395}
396
397/// Trait for transferring non-fungible sets of items.
398pub trait Transfer<AccountId>: Inspect<AccountId> {
399 /// Transfer `item` of `collection` into `destination` account.
400 fn transfer(
401 collection: &Self::CollectionId,
402 item: &Self::ItemId,
403 destination: &AccountId,
404 ) -> DispatchResult;
405
406 /// Disable the `item` of `collection` transfer.
407 ///
408 /// By default, this is not a supported operation.
409 fn disable_transfer(_collection: &Self::CollectionId, _item: &Self::ItemId) -> DispatchResult {
410 Err(TokenError::Unsupported.into())
411 }
412
413 /// Re-enable the `item` of `collection` transfer.
414 ///
415 /// By default, this is not a supported operation.
416 fn enable_transfer(_collection: &Self::CollectionId, _item: &Self::ItemId) -> DispatchResult {
417 Err(TokenError::Unsupported.into())
418 }
419}
420
421/// Trait for trading non-fungible items.
422pub trait Trading<AccountId, ItemPrice>: Inspect<AccountId> {
423 /// Allows `buyer` to buy an `item` of `collection` if it's up for sale with a `bid_price` to
424 /// pay.
425 fn buy_item(
426 collection: &Self::CollectionId,
427 item: &Self::ItemId,
428 buyer: &AccountId,
429 bid_price: &ItemPrice,
430 ) -> DispatchResult;
431
432 /// Sets the item price for `item` to make it available for sale.
433 fn set_price(
434 collection: &Self::CollectionId,
435 item: &Self::ItemId,
436 sender: &AccountId,
437 price: Option<ItemPrice>,
438 whitelisted_buyer: Option<AccountId>,
439 ) -> DispatchResult;
440
441 /// Returns the item price of `item` or `None` if the item is not for sale.
442 fn item_price(collection: &Self::CollectionId, item: &Self::ItemId) -> Option<ItemPrice>;
443}