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 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 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}