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