multiversx_sc/storage/mappers/token/
non_fungible_token_mapper.rs

1use multiversx_chain_core::types::EsdtLocalRole;
2
3use crate::{
4    abi::TypeAbiFrom,
5    codec::{EncodeErrorHandler, TopDecode, TopEncode, TopEncodeMulti, TopEncodeMultiOutput},
6    storage::mappers::{
7        source::{CurrentStorage, StorageAddress},
8        StorageMapperFromAddress,
9    },
10    storage_clear, storage_get, storage_get_len, storage_set,
11    types::{
12        system_proxy::ESDTSystemSCProxy, ESDTSystemSCAddress, EgldPayment, FunctionCall,
13        ManagedVec, OriginalResultMarker, Tx, TxScEnv,
14    },
15};
16
17use super::{
18    super::StorageMapper,
19    error::{
20        INVALID_PAYMENT_TOKEN_ERR_MSG, INVALID_TOKEN_ID_ERR_MSG, MUST_SET_TOKEN_ID_ERR_MSG,
21        PENDING_ERR_MSG, TOKEN_ID_ALREADY_SET_ERR_MSG,
22    },
23    fungible_token_mapper::DEFAULT_ISSUE_CALLBACK_NAME,
24    TokenMapperState,
25};
26use crate::{
27    abi::{TypeAbi, TypeName},
28    api::{CallTypeApi, ErrorApiImpl, StorageMapperApi},
29    contract_base::{BlockchainWrapper, SendWrapper},
30    storage::StorageKey,
31    types::{
32        system_proxy::{
33            MetaTokenProperties, NonFungibleTokenProperties, SemiFungibleTokenProperties,
34        },
35        BigUint, CallbackClosure, EsdtTokenData, EsdtTokenPayment, EsdtTokenType, ManagedAddress,
36        ManagedBuffer, ManagedType, TokenIdentifier,
37    },
38};
39
40const INVALID_TOKEN_TYPE_ERR_MSG: &[u8] = b"Invalid token type for NonFungible issue";
41
42pub type IssueCallTo<Api> = Tx<
43    TxScEnv<Api>,
44    (),
45    ESDTSystemSCAddress,
46    EgldPayment<Api>,
47    (),
48    FunctionCall<Api>,
49    OriginalResultMarker<TokenIdentifier<Api>>,
50>;
51
52pub struct NonFungibleTokenMapper<SA, A = CurrentStorage>
53where
54    SA: StorageMapperApi + CallTypeApi,
55    A: StorageAddress<SA>,
56{
57    key: StorageKey<SA>,
58    token_state: TokenMapperState<SA>,
59    address: A,
60}
61
62impl<SA> StorageMapper<SA> for NonFungibleTokenMapper<SA, CurrentStorage>
63where
64    SA: StorageMapperApi + CallTypeApi,
65{
66    fn new(base_key: StorageKey<SA>) -> Self {
67        Self {
68            token_state: storage_get(base_key.as_ref()),
69            key: base_key,
70            address: CurrentStorage,
71        }
72    }
73}
74
75impl<SA> StorageMapperFromAddress<SA> for NonFungibleTokenMapper<SA, ManagedAddress<SA>>
76where
77    SA: StorageMapperApi + CallTypeApi,
78{
79    fn new_from_address(address: ManagedAddress<SA>, base_key: StorageKey<SA>) -> Self {
80        Self {
81            token_state: storage_get(base_key.as_ref()),
82            key: base_key,
83            address,
84        }
85    }
86}
87
88impl<SA> NonFungibleTokenMapper<SA, CurrentStorage>
89where
90    SA: StorageMapperApi + CallTypeApi,
91{
92    /// Important: If you use custom callback, remember to save the token ID in the callback and clear the mapper in case of error! Clear is unusable outside this specific case.
93    ///
94    /// #[callback]
95    /// fn my_custom_callback(
96    ///     &self,
97    ///     #[call_result] result: ManagedAsyncCallResult<()>,
98    /// ) {
99    ///      match result {
100    ///     ManagedAsyncCallResult::Ok(token_id) => {
101    ///         self.fungible_token_mapper().set_token_id(token_id);
102    ///     },
103    ///     ManagedAsyncCallResult::Err(_) => {
104    ///         self.fungible_token_mapper().clear();
105    ///     },
106    /// }
107    ///
108    /// If you want to use default callbacks, import the default_issue_callbacks::DefaultIssueCallbacksModule from multiversx-sc-modules
109    /// and pass None for the opt_callback argument
110    pub fn issue(
111        &self,
112        token_type: EsdtTokenType,
113        issue_cost: BigUint<SA>,
114        token_display_name: ManagedBuffer<SA>,
115        token_ticker: ManagedBuffer<SA>,
116        num_decimals: usize,
117        opt_callback: Option<CallbackClosure<SA>>,
118    ) -> ! {
119        self.check_not_set();
120
121        let callback = match opt_callback {
122            Some(cb) => cb,
123            None => self.default_callback_closure_obj(),
124        };
125        let contract_call = match token_type {
126            EsdtTokenType::NonFungible => {
127                Self::nft_issue(issue_cost, token_display_name, token_ticker)
128            }
129            EsdtTokenType::SemiFungible => {
130                Self::sft_issue(issue_cost, token_display_name, token_ticker)
131            }
132            EsdtTokenType::MetaFungible => {
133                Self::meta_issue(issue_cost, token_display_name, token_ticker, num_decimals)
134            }
135            _ => SA::error_api_impl().signal_error(INVALID_TOKEN_TYPE_ERR_MSG),
136        };
137
138        storage_set(self.get_storage_key(), &TokenMapperState::<SA>::Pending);
139        contract_call.with_callback(callback).async_call_and_exit();
140    }
141
142    /// Important: If you use custom callback, remember to save the token ID in the callback and clear the mapper in case of error! Clear is unusable outside this specific case.
143    ///
144    /// #[callback]
145    /// fn my_custom_callback(
146    ///     &self,
147    ///     #[call_result] result: ManagedAsyncCallResult<()>,
148    /// ) {
149    ///      match result {
150    ///     ManagedAsyncCallResult::Ok(token_id) => {
151    ///         self.fungible_token_mapper().set_token_id(token_id);
152    ///     },
153    ///     ManagedAsyncCallResult::Err(_) => {
154    ///         self.fungible_token_mapper().clear();
155    ///     },
156    /// }
157    ///
158    /// If you want to use default callbacks, import the default_issue_callbacks::DefaultIssueCallbacksModule from multiversx-sc-modules
159    /// and pass None for the opt_callback argument
160    pub fn issue_and_set_all_roles(
161        &self,
162        token_type: EsdtTokenType,
163        issue_cost: BigUint<SA>,
164        token_display_name: ManagedBuffer<SA>,
165        token_ticker: ManagedBuffer<SA>,
166        num_decimals: usize,
167        opt_callback: Option<CallbackClosure<SA>>,
168    ) -> ! {
169        self.check_not_set();
170
171        if token_type == EsdtTokenType::Fungible || token_type == EsdtTokenType::Invalid {
172            SA::error_api_impl().signal_error(INVALID_TOKEN_TYPE_ERR_MSG);
173        }
174
175        let callback = match opt_callback {
176            Some(cb) => cb,
177            None => self.default_callback_closure_obj(),
178        };
179
180        storage_set(self.get_storage_key(), &TokenMapperState::<SA>::Pending);
181        Tx::new_tx_from_sc()
182            .to(ESDTSystemSCAddress)
183            .typed(ESDTSystemSCProxy)
184            .issue_and_set_all_roles(
185                issue_cost,
186                token_display_name,
187                token_ticker,
188                token_type,
189                num_decimals,
190            )
191            .callback(callback)
192            .async_call_and_exit()
193    }
194
195    pub fn clear(&mut self) {
196        let state: TokenMapperState<SA> = storage_get(self.key.as_ref());
197        if state.is_pending() {
198            storage_clear(self.key.as_ref());
199        }
200    }
201
202    pub fn nft_issue(
203        issue_cost: BigUint<SA>,
204        token_display_name: ManagedBuffer<SA>,
205        token_ticker: ManagedBuffer<SA>,
206    ) -> IssueCallTo<SA> {
207        Tx::new_tx_from_sc()
208            .to(ESDTSystemSCAddress)
209            .typed(ESDTSystemSCProxy)
210            .issue_non_fungible(
211                issue_cost,
212                &token_display_name,
213                &token_ticker,
214                NonFungibleTokenProperties::default(),
215            )
216    }
217
218    pub fn sft_issue(
219        issue_cost: BigUint<SA>,
220        token_display_name: ManagedBuffer<SA>,
221        token_ticker: ManagedBuffer<SA>,
222    ) -> IssueCallTo<SA> {
223        Tx::new_tx_from_sc()
224            .to(ESDTSystemSCAddress)
225            .typed(ESDTSystemSCProxy)
226            .issue_semi_fungible(
227                issue_cost,
228                &token_display_name,
229                &token_ticker,
230                SemiFungibleTokenProperties::default(),
231            )
232    }
233
234    pub fn meta_issue(
235        issue_cost: BigUint<SA>,
236        token_display_name: ManagedBuffer<SA>,
237        token_ticker: ManagedBuffer<SA>,
238        num_decimals: usize,
239    ) -> IssueCallTo<SA> {
240        let properties = MetaTokenProperties {
241            num_decimals,
242            ..Default::default()
243        };
244
245        Tx::new_tx_from_sc()
246            .to(ESDTSystemSCAddress)
247            .typed(ESDTSystemSCProxy)
248            .register_meta_esdt(issue_cost, &token_display_name, &token_ticker, properties)
249    }
250
251    pub fn nft_create<T: TopEncode>(
252        &self,
253        amount: BigUint<SA>,
254        attributes: &T,
255    ) -> EsdtTokenPayment<SA> {
256        let send_wrapper = SendWrapper::<SA>::new();
257        let token_id = self.get_token_id();
258
259        let token_nonce = send_wrapper.esdt_nft_create_compact(&token_id, &amount, attributes);
260
261        EsdtTokenPayment::new(token_id, token_nonce, amount)
262    }
263
264    pub fn nft_create_named<T: TopEncode>(
265        &self,
266        amount: BigUint<SA>,
267        name: &ManagedBuffer<SA>,
268        attributes: &T,
269    ) -> EsdtTokenPayment<SA> {
270        let send_wrapper = SendWrapper::<SA>::new();
271        let token_id = self.get_token_id();
272
273        let token_nonce =
274            send_wrapper.esdt_nft_create_compact_named(&token_id, &amount, name, attributes);
275
276        EsdtTokenPayment::new(token_id, token_nonce, amount)
277    }
278
279    pub fn nft_create_and_send<T: TopEncode>(
280        &self,
281        to: &ManagedAddress<SA>,
282        amount: BigUint<SA>,
283        attributes: &T,
284    ) -> EsdtTokenPayment<SA> {
285        let payment = self.nft_create(amount, attributes);
286        self.send_payment(to, &payment);
287
288        payment
289    }
290
291    pub fn nft_create_and_send_named<T: TopEncode>(
292        &self,
293        to: &ManagedAddress<SA>,
294        amount: BigUint<SA>,
295        name: &ManagedBuffer<SA>,
296        attributes: &T,
297    ) -> EsdtTokenPayment<SA> {
298        let payment = self.nft_create_named(amount, name, attributes);
299        self.send_payment(to, &payment);
300
301        payment
302    }
303
304    pub fn nft_add_quantity(&self, token_nonce: u64, amount: BigUint<SA>) -> EsdtTokenPayment<SA> {
305        let send_wrapper = SendWrapper::<SA>::new();
306        let token_id = self.get_token_id();
307
308        send_wrapper.esdt_local_mint(&token_id, token_nonce, &amount);
309
310        EsdtTokenPayment::new(token_id, token_nonce, amount)
311    }
312
313    pub fn nft_add_quantity_and_send(
314        &self,
315        to: &ManagedAddress<SA>,
316        token_nonce: u64,
317        amount: BigUint<SA>,
318    ) -> EsdtTokenPayment<SA> {
319        let payment = self.nft_add_quantity(token_nonce, amount);
320        self.send_payment(to, &payment);
321
322        payment
323    }
324
325    pub fn nft_update_attributes<T: TopEncode>(&self, token_nonce: u64, new_attributes: &T) {
326        let send_wrapper = SendWrapper::<SA>::new();
327        let token_id = self.get_token_id_ref();
328        send_wrapper.nft_update_attributes(token_id, token_nonce, new_attributes);
329    }
330
331    pub fn nft_burn(&self, token_nonce: u64, amount: &BigUint<SA>) {
332        let send_wrapper = SendWrapper::<SA>::new();
333        let token_id = self.get_token_id_ref();
334
335        send_wrapper.esdt_local_burn(token_id, token_nonce, amount);
336    }
337
338    pub fn send_payment(&self, to: &ManagedAddress<SA>, payment: &EsdtTokenPayment<SA>) {
339        Tx::new_tx_from_sc()
340            .to(to)
341            .single_esdt(
342                &payment.token_identifier,
343                payment.token_nonce,
344                &payment.amount,
345            )
346            .transfer();
347    }
348
349    pub fn set_token_id(&mut self, token_id: TokenIdentifier<SA>) {
350        self.store_token_id(&token_id);
351        self.token_state = TokenMapperState::Token(token_id);
352    }
353
354    pub fn set_if_empty(&mut self, token_id: TokenIdentifier<SA>) {
355        if self.is_empty() {
356            self.set_token_id(token_id);
357        }
358    }
359
360    pub fn set_local_roles(
361        &self,
362        roles: &[EsdtLocalRole],
363        opt_callback: Option<CallbackClosure<SA>>,
364    ) -> ! {
365        let own_sc_address = Self::get_sc_address();
366        self.set_local_roles_for_address(&own_sc_address, roles, opt_callback);
367    }
368
369    pub fn set_local_roles_for_address(
370        &self,
371        address: &ManagedAddress<SA>,
372        roles: &[EsdtLocalRole],
373        opt_callback: Option<CallbackClosure<SA>>,
374    ) -> ! {
375        self.require_issued_or_set();
376
377        let token_id = self.get_token_id_ref();
378        Tx::new_tx_from_sc()
379            .to(ESDTSystemSCAddress)
380            .typed(ESDTSystemSCProxy)
381            .set_special_roles(address, token_id, roles[..].iter().cloned())
382            .callback(opt_callback)
383            .async_call_and_exit()
384    }
385
386    pub(crate) fn store_token_id(&self, token_id: &TokenIdentifier<SA>) {
387        if self.get_token_state().is_set() {
388            SA::error_api_impl().signal_error(TOKEN_ID_ALREADY_SET_ERR_MSG);
389        }
390        if !token_id.is_valid_esdt_identifier() {
391            SA::error_api_impl().signal_error(INVALID_TOKEN_ID_ERR_MSG);
392        }
393        storage_set(
394            self.get_storage_key(),
395            &TokenMapperState::Token(token_id.clone()),
396        );
397    }
398
399    pub fn get_balance(&self, token_nonce: u64) -> BigUint<SA> {
400        let b_wrapper = BlockchainWrapper::new();
401        let own_sc_address = Self::get_sc_address();
402        let token_id = self.get_token_id_ref();
403
404        b_wrapper.get_esdt_balance(&own_sc_address, token_id, token_nonce)
405    }
406
407    pub fn get_sc_address() -> ManagedAddress<SA> {
408        let b_wrapper = BlockchainWrapper::new();
409        b_wrapper.get_sc_address()
410    }
411
412    pub fn get_all_token_data(&self, token_nonce: u64) -> EsdtTokenData<SA> {
413        let b_wrapper = BlockchainWrapper::new();
414        let own_sc_address = Self::get_sc_address();
415        let token_id = self.get_token_id_ref();
416
417        b_wrapper.get_esdt_token_data(&own_sc_address, token_id, token_nonce)
418    }
419
420    pub fn get_token_attributes<T: TopDecode>(&self, token_nonce: u64) -> T {
421        let token_data = self.get_all_token_data(token_nonce);
422        token_data.decode_attributes()
423    }
424}
425
426impl<SA, A> NonFungibleTokenMapper<SA, A>
427where
428    SA: StorageMapperApi + CallTypeApi,
429    A: StorageAddress<SA>,
430{
431    pub(crate) fn check_not_set(&self) {
432        let storage_value: TokenMapperState<SA> = storage_get(self.get_storage_key());
433        match storage_value {
434            TokenMapperState::NotSet => {}
435            TokenMapperState::Pending => {
436                SA::error_api_impl().signal_error(PENDING_ERR_MSG);
437            }
438            TokenMapperState::Token(_) => {
439                SA::error_api_impl().signal_error(TOKEN_ID_ALREADY_SET_ERR_MSG);
440            }
441        }
442    }
443
444    pub fn is_empty(&self) -> bool {
445        storage_get_len(self.get_storage_key()) == 0
446    }
447
448    pub fn require_issued_or_set(&self) {
449        if self.is_empty() {
450            SA::error_api_impl().signal_error(MUST_SET_TOKEN_ID_ERR_MSG);
451        }
452    }
453
454    pub fn require_same_token(&self, expected_token_id: &TokenIdentifier<SA>) {
455        let actual_token_id = self.get_token_id_ref();
456        if actual_token_id != expected_token_id {
457            SA::error_api_impl().signal_error(INVALID_PAYMENT_TOKEN_ERR_MSG);
458        }
459    }
460
461    pub fn require_all_same_token(&self, payments: &ManagedVec<SA, EsdtTokenPayment<SA>>) {
462        let actual_token_id = self.get_token_id_ref();
463        for p in payments {
464            if actual_token_id != &p.token_identifier {
465                SA::error_api_impl().signal_error(INVALID_PAYMENT_TOKEN_ERR_MSG);
466            }
467        }
468    }
469
470    pub fn get_storage_key(&self) -> crate::types::ManagedRef<'_, SA, StorageKey<SA>> {
471        self.key.as_ref()
472    }
473
474    pub fn get_token_state(&self) -> TokenMapperState<SA> {
475        self.token_state.clone()
476    }
477
478    pub fn get_token_id(&self) -> TokenIdentifier<SA> {
479        if let TokenMapperState::Token(token) = &self.token_state {
480            token.clone()
481        } else {
482            SA::error_api_impl().signal_error(INVALID_TOKEN_ID_ERR_MSG);
483        }
484    }
485
486    pub fn get_token_id_ref(&self) -> &TokenIdentifier<SA> {
487        if let TokenMapperState::Token(token) = &self.token_state {
488            token
489        } else {
490            SA::error_api_impl().signal_error(INVALID_TOKEN_ID_ERR_MSG);
491        }
492    }
493
494    pub fn default_callback_closure_obj(&self) -> CallbackClosure<SA> {
495        let initial_caller = BlockchainWrapper::<SA>::new().get_caller();
496        let cb_name = DEFAULT_ISSUE_CALLBACK_NAME;
497
498        let mut cb_closure = CallbackClosure::new(cb_name);
499        cb_closure.push_endpoint_arg(&initial_caller);
500        cb_closure.push_endpoint_arg(&self.key.buffer);
501
502        cb_closure
503    }
504}
505
506impl<SA> TopEncodeMulti for NonFungibleTokenMapper<SA>
507where
508    SA: StorageMapperApi + CallTypeApi,
509{
510    fn multi_encode_or_handle_err<O, H>(&self, output: &mut O, h: H) -> Result<(), H::HandledErr>
511    where
512        O: TopEncodeMultiOutput,
513        H: EncodeErrorHandler,
514    {
515        if self.is_empty() {
516            output.push_single_value(&ManagedBuffer::<SA>::new(), h)
517        } else {
518            output.push_single_value(&self.get_token_id(), h)
519        }
520    }
521}
522
523impl<SA> TypeAbiFrom<NonFungibleTokenMapper<SA>> for TokenIdentifier<SA> where
524    SA: StorageMapperApi + CallTypeApi
525{
526}
527
528impl<SA> TypeAbiFrom<Self> for NonFungibleTokenMapper<SA> where SA: StorageMapperApi + CallTypeApi {}
529
530impl<SA> TypeAbi for NonFungibleTokenMapper<SA>
531where
532    SA: StorageMapperApi + CallTypeApi,
533{
534    type Unmanaged = Self;
535
536    fn type_name() -> TypeName {
537        TokenIdentifier::<SA>::type_name()
538    }
539
540    fn type_name_rust() -> TypeName {
541        TokenIdentifier::<SA>::type_name_rust()
542    }
543
544    fn provide_type_descriptions<TDC: crate::abi::TypeDescriptionContainer>(accumulator: &mut TDC) {
545        TokenIdentifier::<SA>::provide_type_descriptions(accumulator);
546    }
547
548    fn is_variadic() -> bool {
549        false
550    }
551}