multiversx_sc_modules/token_merge/
mod.rs

1multiversx_sc::imports!();
2multiversx_sc::derive_imports!();
3
4pub mod custom_merged_token_attributes;
5pub mod merged_token_instances;
6pub mod merged_token_setup;
7
8use merged_token_instances::{MergedTokenInstances, MAX_MERGED_TOKENS};
9
10use self::custom_merged_token_attributes::MergedTokenAttributesCreator;
11
12static SC_DOES_NOT_OWN_NFT_PARTS_ERR_MSG: &[u8] = b"NFT parts belong to another merging SC";
13
14const MIN_MERGE_PAYMENTS: usize = 2;
15
16#[multiversx_sc::module]
17pub trait TokenMergeModule:
18    merged_token_setup::MergedTokenSetupModule
19    + crate::default_issue_callbacks::DefaultIssueCallbacksModule
20    + crate::pause::PauseModule
21{
22    fn merge_tokens<AttributesCreator: MergedTokenAttributesCreator<ScType = Self>>(
23        &self,
24        payments: &ManagedVec<EsdtTokenPayment>,
25        attr_creator: &AttributesCreator,
26    ) -> EsdtTokenPayment {
27        self.require_not_paused();
28        require!(
29            payments.len() >= MIN_MERGE_PAYMENTS,
30            "Must send at least 2 tokens"
31        );
32
33        let merged_token_id = self.merged_token().get_token_id();
34        let sc_address = self.blockchain().get_sc_address();
35        let token_whitelist = self.mergeable_tokens_whitelist();
36
37        let mut already_merged_tokens = ArrayVec::<_, MAX_MERGED_TOKENS>::new();
38        let mut single_tokens = ArrayVec::<_, MAX_MERGED_TOKENS>::new();
39        for token in payments {
40            if token.token_identifier == merged_token_id {
41                let token_data = self.blockchain().get_esdt_token_data(
42                    &sc_address,
43                    &token.token_identifier,
44                    token.token_nonce,
45                );
46
47                require!(
48                    token_data.creator == sc_address,
49                    SC_DOES_NOT_OWN_NFT_PARTS_ERR_MSG
50                );
51
52                let merged_instances =
53                    MergedTokenInstances::decode_from_first_uri(&token_data.uris);
54                already_merged_tokens.push(merged_instances);
55            } else {
56                require!(
57                    token_whitelist.contains(&token.token_identifier),
58                    "Token {} cannot be merged",
59                    (token.token_identifier)
60                );
61
62                single_tokens.push(token);
63            }
64        }
65
66        let mut all_merged_instances = MergedTokenInstances::new();
67        for already_merged in already_merged_tokens {
68            all_merged_instances.merge_with_other(already_merged);
69        }
70
71        for single_token_instance in single_tokens {
72            all_merged_instances.add_or_update_instance(single_token_instance.clone());
73        }
74
75        let merged_token_payment =
76            self.create_merged_token(merged_token_id, &all_merged_instances, attr_creator);
77
78        self.tx()
79            .to(ToCaller)
80            .payment(&merged_token_payment)
81            .transfer_if_not_empty();
82
83        merged_token_payment
84    }
85
86    fn split_tokens(
87        &self,
88        payments: &ManagedVec<EsdtTokenPayment>,
89    ) -> ManagedVec<EsdtTokenPayment> {
90        self.require_not_paused();
91        require!(!payments.is_empty(), "No payments");
92
93        let merged_token_id = self.merged_token().get_token_id();
94        let sc_address = self.blockchain().get_sc_address();
95
96        let mut output_payments = ManagedVec::new();
97        for token in payments {
98            require!(
99                token.token_identifier == merged_token_id,
100                "Invalid token to split"
101            );
102
103            let token_data = self.blockchain().get_esdt_token_data(
104                &sc_address,
105                &token.token_identifier,
106                token.token_nonce,
107            );
108            require!(
109                token_data.creator == sc_address,
110                SC_DOES_NOT_OWN_NFT_PARTS_ERR_MSG
111            );
112
113            let previously_merged_instance_attributes =
114                MergedTokenInstances::decode_from_first_uri(&token_data.uris);
115            for inst in previously_merged_instance_attributes.into_instances() {
116                output_payments.push(inst);
117            }
118
119            self.send()
120                .esdt_local_burn(&token.token_identifier, token.token_nonce, &token.amount);
121        }
122
123        self.tx().to(ToCaller).payment(&output_payments).transfer();
124
125        output_payments
126    }
127
128    fn split_token_partial<AttributesCreator: MergedTokenAttributesCreator<ScType = Self>>(
129        &self,
130        merged_token: EsdtTokenPayment,
131        mut tokens_to_remove: ManagedVec<EsdtTokenPayment>,
132        attr_creator: &AttributesCreator,
133    ) -> ManagedVec<EsdtTokenPayment> {
134        self.require_not_paused();
135        self.merged_token()
136            .require_same_token(&merged_token.token_identifier);
137
138        let sc_address = self.blockchain().get_sc_address();
139        let merged_token_data = self.blockchain().get_esdt_token_data(
140            &sc_address,
141            &merged_token.token_identifier,
142            merged_token.token_nonce,
143        );
144        require!(
145            merged_token_data.creator == sc_address,
146            SC_DOES_NOT_OWN_NFT_PARTS_ERR_MSG
147        );
148
149        let mut merged_attributes =
150            MergedTokenInstances::decode_from_first_uri(&merged_token_data.uris);
151        for token in &tokens_to_remove {
152            merged_attributes.deduct_balance_for_instance(&token);
153        }
154
155        self.send().esdt_local_burn(
156            &merged_token.token_identifier,
157            merged_token.token_nonce,
158            &merged_token.amount,
159        );
160
161        // all removed tokens get sent to user, so we can re-use this as output payments
162        let new_merged_token = self.create_merged_token(
163            merged_token.token_identifier,
164            &merged_attributes,
165            attr_creator,
166        );
167        tokens_to_remove.push(new_merged_token);
168
169        self.tx().to(ToCaller).payment(&tokens_to_remove).transfer();
170
171        tokens_to_remove
172    }
173}