Skip to main content

komple_framework_mint_module/
contract.rs

1#[cfg(not(feature = "library"))]
2use cosmwasm_std::entry_point;
3use cosmwasm_std::{
4    coins, from_binary, to_binary, Addr, Attribute, BankMsg, Binary, Coin, CosmosMsg, Deps,
5    DepsMut, Env, MessageInfo, Order, Reply, ReplyOn, Response, StdError, StdResult, SubMsg,
6    Timestamp, Uint128, WasmMsg,
7};
8use cw2::{get_contract_version, set_contract_version};
9use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg};
10use cw_storage_plus::Bound;
11use cw_utils::parse_reply_instantiate_data;
12
13use komple_framework_permission_module::msg::ExecuteMsg as PermissionExecuteMsg;
14use komple_framework_token_module::{
15    helper::KompleTokenModule,
16    msg::{InstantiateMsg as TokenInstantiateMsg, MetadataInfo, TokenInfo},
17    state::CollectionConfig,
18};
19use komple_framework_types::modules::fee::{FundInfo, MintFees};
20use komple_framework_types::modules::whitelist::WHITELIST_NAMESPACE;
21use komple_framework_types::modules::Modules;
22use komple_framework_types::shared::query::ResponseWrapper;
23use komple_framework_types::shared::RegisterMsg;
24use komple_framework_utils::{
25    check_admin_privileges,
26    funds::{check_cw20_fund_info, FundsError},
27    response::ResponseHelper,
28    shared::{execute_lock_execute, execute_update_operators},
29    storage::StorageHelper,
30};
31use komple_framework_utils::{funds::check_single_coin, response::EventHelper};
32use komple_framework_whitelist_module::helper::KompleWhitelistHelper;
33use semver::Version;
34
35use crate::{error::ContractError, msg::ReceiveMsg, state::EXECUTE_LOCK};
36use crate::{
37    msg::CollectionFundInfo,
38    state::{
39        CollectionInfo, Config, BLACKLIST_COLLECTION_ADDRS, COLLECTION_ADDRS, COLLECTION_FUND_INFO,
40        COLLECTION_ID, COLLECTION_INFO, CONFIG, HUB_ADDR, LINKED_COLLECTIONS, MINT_LOCKS,
41        OPERATORS,
42    },
43};
44use crate::{
45    msg::{CollectionsResponse, ExecuteMsg, MigrateMsg, MintMsg, QueryMsg},
46    state::CREATORS,
47};
48
49// version info for migration info
50const CONTRACT_NAME: &str = "crates.io:komple-framework-mint-module";
51const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
52
53const TOKEN_INSTANTIATE_REPLY_ID: u64 = 1;
54
55#[cfg_attr(not(feature = "library"), entry_point)]
56pub fn instantiate(
57    deps: DepsMut,
58    _env: Env,
59    info: MessageInfo,
60    msg: RegisterMsg,
61) -> Result<Response, ContractError> {
62    set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
63
64    let admin = deps.api.addr_validate(&msg.admin)?;
65
66    let config = Config {
67        admin,
68        public_collection_creation: false,
69        mint_lock: false,
70    };
71    CONFIG.save(deps.storage, &config)?;
72
73    COLLECTION_ID.save(deps.storage, &0)?;
74
75    HUB_ADDR.save(deps.storage, &info.sender)?;
76
77    EXECUTE_LOCK.save(deps.storage, &false)?;
78
79    Ok(ResponseHelper::new_module("mint", "instantiate").add_event(
80        EventHelper::new("mint_instantiate")
81            .add_attribute("admin", config.admin)
82            .add_attribute(
83                "public_collection_creation",
84                config.public_collection_creation.to_string(),
85            )
86            .add_attribute("mint_lock", config.mint_lock.to_string())
87            .get(),
88    ))
89}
90
91#[cfg_attr(not(feature = "library"), entry_point)]
92pub fn execute(
93    deps: DepsMut,
94    env: Env,
95    info: MessageInfo,
96    msg: ExecuteMsg,
97) -> Result<Response, ContractError> {
98    let execute_lock = EXECUTE_LOCK.load(deps.storage)?;
99    if execute_lock {
100        return Err(ContractError::ExecuteLocked {});
101    };
102
103    match msg {
104        ExecuteMsg::CreateCollection {
105            code_id,
106            collection_config,
107            collection_info,
108            metadata_info,
109            token_info,
110            fund_info,
111            linked_collections,
112        } => execute_create_collection(
113            deps,
114            env,
115            info,
116            code_id,
117            collection_config,
118            collection_info,
119            metadata_info,
120            token_info,
121            fund_info,
122            linked_collections,
123        ),
124        ExecuteMsg::UpdatePublicCollectionCreation {
125            public_collection_creation,
126        } => execute_update_public_collection_creation(deps, env, info, public_collection_creation),
127        ExecuteMsg::UpdateCollectionMintLock {
128            collection_id,
129            lock,
130        } => execute_update_collection_mint_lock(deps, env, info, collection_id, lock),
131        ExecuteMsg::Mint {
132            collection_id,
133            metadata_id,
134        } => execute_mint(deps, env, info, collection_id, metadata_id),
135        ExecuteMsg::AdminMint {
136            collection_id,
137            recipient,
138            metadata_id,
139        } => execute_admin_mint(deps, env, info, collection_id, recipient, metadata_id),
140        ExecuteMsg::PermissionMint {
141            permission_msg,
142            mint_msg,
143        } => execute_permission_mint(deps, env, info, permission_msg, mint_msg),
144        ExecuteMsg::UpdateLinkedCollections {
145            collection_id,
146            linked_collections,
147        } => execute_update_linked_collections(deps, env, info, collection_id, linked_collections),
148        ExecuteMsg::UpdateCreators { addrs } => execute_update_creators(deps, env, info, addrs),
149        ExecuteMsg::UpdateCollectionStatus {
150            collection_id,
151            is_blacklist,
152        } => execute_update_collection_status(deps, env, info, collection_id, is_blacklist),
153        ExecuteMsg::UpdateOperators { addrs } => {
154            let config = CONFIG.load(deps.storage)?;
155            let res = execute_update_operators(
156                deps,
157                info,
158                "mint",
159                &env.contract.address,
160                &config.admin,
161                OPERATORS,
162                addrs,
163            );
164            match res {
165                Ok(res) => Ok(res),
166                Err(err) => Err(err.into()),
167            }
168        }
169        ExecuteMsg::LockExecute {} => {
170            let res = execute_lock_execute(deps, info, "mint", &env.contract.address, EXECUTE_LOCK);
171            match res {
172                Ok(res) => Ok(res),
173                Err(err) => Err(err.into()),
174            }
175        }
176        ExecuteMsg::Receive(msg) => execute_receive(deps, env, info, msg),
177    }
178}
179
180pub fn execute_create_collection(
181    deps: DepsMut,
182    env: Env,
183    info: MessageInfo,
184    code_id: u64,
185    collection_config: CollectionConfig,
186    collection_info: CollectionInfo,
187    metadata_info: MetadataInfo,
188    mut token_info: TokenInfo,
189    fund_info: CollectionFundInfo,
190    linked_collections: Option<Vec<u32>>,
191) -> Result<Response, ContractError> {
192    let config = CONFIG.load(deps.storage)?;
193
194    // If public collection creation is enabled skip privilege checks
195    if !config.public_collection_creation {
196        let hub_addr = HUB_ADDR.may_load(deps.storage)?;
197        let operators = OPERATORS.may_load(deps.storage)?;
198
199        // Check for creators privilege
200        let creators = CREATORS.may_load(deps.storage)?;
201        match creators {
202            Some(creators) => {
203                // If the sender is not a creator
204                if !creators.contains(&info.sender) {
205                    check_admin_privileges(
206                        &info.sender,
207                        &env.contract.address,
208                        &config.admin,
209                        hub_addr,
210                        operators,
211                    )?;
212                }
213            }
214            // If there are no creators
215            None => {
216                check_admin_privileges(
217                    &info.sender,
218                    &env.contract.address,
219                    &config.admin,
220                    hub_addr,
221                    operators,
222                )?;
223            }
224        }
225    };
226
227    token_info.minter = env.contract.address.to_string();
228    let token_instantiate_msg = TokenInstantiateMsg {
229        creator: info.sender.to_string(),
230        collection_config: collection_config.clone(),
231        collection_type: collection_info.clone().collection_type,
232        collection_name: collection_info.clone().name,
233        metadata_info,
234        token_info,
235    };
236    let register_msg = RegisterMsg {
237        admin: config.admin.to_string(),
238        data: Some(to_binary(&token_instantiate_msg)?),
239    };
240
241    let contract_info = deps
242        .querier
243        .query_wasm_contract_info(env.contract.address)?;
244    let sub_msg: SubMsg = SubMsg {
245        msg: WasmMsg::Instantiate {
246            code_id,
247            msg: to_binary(&register_msg)?,
248            funds: info.funds,
249            admin: contract_info.admin,
250            label: String::from("Komple Framework Token Module"),
251        }
252        .into(),
253        id: TOKEN_INSTANTIATE_REPLY_ID,
254        gas_limit: None,
255        reply_on: ReplyOn::Success,
256    };
257
258    let collection_id = (COLLECTION_ID.load(deps.storage)?) + 1;
259
260    if linked_collections.is_some() {
261        check_collection_ids_exists(&deps, &linked_collections.clone().unwrap())?;
262        LINKED_COLLECTIONS.save(deps.storage, collection_id, &linked_collections.unwrap())?;
263    }
264
265    COLLECTION_ID.save(deps.storage, &collection_id)?;
266
267    COLLECTION_INFO.save(deps.storage, collection_id, &collection_info)?;
268
269    MINT_LOCKS.save(deps.storage, collection_id, &false)?;
270
271    let cw20_address = match fund_info.cw20_address {
272        Some(addr) => Some(deps.api.addr_validate(&addr)?),
273        None => None,
274    };
275    let fund_info = FundInfo {
276        is_native: fund_info.is_native,
277        denom: fund_info.denom,
278        cw20_address,
279    };
280
281    if !fund_info.is_native {
282        check_cw20_fund_info(&deps, &fund_info)?;
283    };
284    COLLECTION_FUND_INFO.save(deps.storage, collection_id, &fund_info)?;
285
286    Ok(ResponseHelper::new_module("mint", "create_collection")
287        .add_submessage(sub_msg)
288        .add_event(
289            EventHelper::new("mint_create_collection")
290                .add_attribute("creator", token_instantiate_msg.creator)
291                .add_attribute("minter", token_instantiate_msg.token_info.minter)
292                .add_attribute("symbol", token_instantiate_msg.token_info.symbol)
293                .add_attribute(
294                    "collection_type",
295                    token_instantiate_msg.collection_type.to_string(),
296                )
297                .add_attribute("collection_name", token_instantiate_msg.collection_name)
298                .add_attribute("description", collection_info.description)
299                .add_attribute("image", collection_info.image)
300                .check_add_attribute(
301                    &collection_info.external_link,
302                    "external_link",
303                    collection_info
304                        .external_link
305                        .as_ref()
306                        .unwrap_or(&String::from("")),
307                )
308                .add_attribute("is_native", fund_info.is_native.to_string())
309                .add_attribute("denom", fund_info.denom.to_string())
310                .check_add_attribute(
311                    &fund_info.cw20_address,
312                    "cw20_address",
313                    fund_info
314                        .cw20_address
315                        .as_ref()
316                        .unwrap_or(&Addr::unchecked("")),
317                )
318                .check_add_attribute(
319                    &collection_config.start_time,
320                    "start_time",
321                    collection_config
322                        .start_time
323                        .unwrap_or(Timestamp::from_nanos(0))
324                        .to_string(),
325                )
326                .check_add_attribute(
327                    &collection_config.max_token_limit,
328                    "max_token_limit",
329                    collection_config.max_token_limit.unwrap_or(0).to_string(),
330                )
331                .check_add_attribute(
332                    &collection_config.per_address_limit,
333                    "per_address_limit",
334                    collection_config.per_address_limit.unwrap_or(0).to_string(),
335                )
336                .check_add_attribute(
337                    &collection_config.ipfs_link,
338                    "ipfs_link",
339                    collection_config
340                        .ipfs_link
341                        .as_ref()
342                        .unwrap_or(&String::from("")),
343                )
344                .get(),
345        ))
346}
347
348pub fn execute_update_public_collection_creation(
349    deps: DepsMut,
350    env: Env,
351    info: MessageInfo,
352    public_collection_creation: bool,
353) -> Result<Response, ContractError> {
354    let hub_addr = HUB_ADDR.may_load(deps.storage)?;
355    let operators = OPERATORS.may_load(deps.storage)?;
356    let mut config = CONFIG.load(deps.storage)?;
357
358    check_admin_privileges(
359        &info.sender,
360        &env.contract.address,
361        &config.admin,
362        hub_addr,
363        operators,
364    )?;
365
366    config.public_collection_creation = public_collection_creation;
367    CONFIG.save(deps.storage, &config)?;
368
369    Ok(
370        ResponseHelper::new_module("mint", "update_public_collection_creation").add_event(
371            EventHelper::new("mint_update_public_collection_creation")
372                .add_attribute(
373                    "public_collection_creation",
374                    public_collection_creation.to_string(),
375                )
376                .get(),
377        ),
378    )
379}
380
381pub fn execute_update_collection_mint_lock(
382    deps: DepsMut,
383    env: Env,
384    info: MessageInfo,
385    collection_id: u32,
386    lock: bool,
387) -> Result<Response, ContractError> {
388    let hub_addr = HUB_ADDR.may_load(deps.storage)?;
389    let operators = OPERATORS.may_load(deps.storage)?;
390    let config = CONFIG.load(deps.storage)?;
391
392    check_admin_privileges(
393        &info.sender,
394        &env.contract.address,
395        &config.admin,
396        hub_addr,
397        operators,
398    )?;
399
400    MINT_LOCKS.save(deps.storage, collection_id, &lock)?;
401
402    Ok(
403        ResponseHelper::new_module("mint", "update_mint_lock").add_event(
404            EventHelper::new("mint_update_mint_lock")
405                .add_attribute("collection_id", collection_id.to_string())
406                .add_attribute("mint_lock", lock.to_string())
407                .get(),
408        ),
409    )
410}
411
412fn execute_mint(
413    deps: DepsMut,
414    _env: Env,
415    info: MessageInfo,
416    collection_id: u32,
417    metadata_id: Option<u32>,
418) -> Result<Response, ContractError> {
419    let mint_lock = MINT_LOCKS.load(deps.storage, collection_id)?;
420    if mint_lock {
421        return Err(ContractError::LockedMint {});
422    }
423
424    let mint_msg = MintMsg {
425        collection_id,
426        recipient: info.sender.to_string(),
427        metadata_id,
428    };
429    let msgs: Vec<CosmosMsg> =
430        process_minting_prices(&deps, &info, &info.sender.to_string(), collection_id, None)?;
431
432    _execute_mint(deps, "mint", msgs, mint_msg)
433}
434
435fn execute_admin_mint(
436    deps: DepsMut,
437    env: Env,
438    info: MessageInfo,
439    collection_id: u32,
440    recipient: String,
441    metadata_id: Option<u32>,
442) -> Result<Response, ContractError> {
443    let hub_addr = HUB_ADDR.may_load(deps.storage)?;
444    let operators = OPERATORS.may_load(deps.storage)?;
445    let config = CONFIG.load(deps.storage)?;
446
447    check_admin_privileges(
448        &info.sender,
449        &env.contract.address,
450        &config.admin,
451        hub_addr,
452        operators,
453    )?;
454
455    let recipient = deps.api.addr_validate(&recipient)?;
456
457    let msgs: Vec<CosmosMsg> = vec![];
458    let mint_msg = MintMsg {
459        collection_id,
460        recipient: recipient.to_string(),
461        metadata_id,
462    };
463
464    _execute_mint(deps, "admin_mint", msgs, mint_msg)
465}
466
467fn execute_permission_mint(
468    deps: DepsMut,
469    env: Env,
470    info: MessageInfo,
471    permission_msg: Binary,
472    mint_msg: MintMsg,
473) -> Result<Response, ContractError> {
474    let hub_addr = HUB_ADDR.may_load(deps.storage)?;
475    let operators = OPERATORS.may_load(deps.storage)?;
476    let config = CONFIG.load(deps.storage)?;
477    check_admin_privileges(
478        &info.sender,
479        &env.contract.address,
480        &config.admin,
481        hub_addr.clone(),
482        operators,
483    )?;
484
485    let permission_module_addr = StorageHelper::query_module_address(
486        &deps.querier,
487        &hub_addr.unwrap(),
488        Modules::Permission.to_string(),
489    )?;
490
491    let mut msgs: Vec<WasmMsg> = vec![];
492
493    let permission_msg = PermissionExecuteMsg::Check {
494        module: Modules::Mint.to_string(),
495        msg: permission_msg,
496    };
497    msgs.push(WasmMsg::Execute {
498        contract_addr: permission_module_addr.to_string(),
499        msg: to_binary(&permission_msg)?,
500        funds: info.funds.clone(),
501    });
502
503    msgs.push(WasmMsg::Execute {
504        contract_addr: env.contract.address.to_string(),
505        msg: to_binary(&ExecuteMsg::AdminMint {
506            collection_id: mint_msg.collection_id,
507            recipient: mint_msg.recipient.clone(),
508            metadata_id: mint_msg.metadata_id,
509        })?,
510        funds: info.funds,
511    });
512
513    Ok(ResponseHelper::new_module("mint", "permission_mint")
514        .add_messages(msgs)
515        .add_event(
516            EventHelper::new("mint_permission_mint")
517                .add_attribute("collection_id", mint_msg.collection_id.to_string())
518                .add_attribute("recipient", mint_msg.recipient)
519                .check_add_attribute(
520                    &mint_msg.metadata_id,
521                    "metadata_id",
522                    mint_msg.metadata_id.as_ref().unwrap_or(&0).to_string(),
523                )
524                .get(),
525        ))
526}
527
528fn _execute_mint(
529    deps: DepsMut,
530    action: &str,
531    mut msgs: Vec<CosmosMsg>,
532    mint_msg: MintMsg,
533) -> Result<Response, ContractError> {
534    let collection_addr = COLLECTION_ADDRS.load(deps.storage, mint_msg.collection_id)?;
535
536    let msg = KompleTokenModule(collection_addr)
537        .mint_msg(mint_msg.recipient.clone(), mint_msg.metadata_id)?;
538    msgs.push(msg.into());
539
540    Ok(ResponseHelper::new_module("mint", action)
541        .add_messages(msgs)
542        .add_event(
543            EventHelper::new(format!("mint_{}", action))
544                .add_attribute("recipient", mint_msg.recipient)
545                .add_attribute("collection_id", mint_msg.collection_id.to_string())
546                .check_add_attribute(
547                    &mint_msg.metadata_id,
548                    "metadata_id",
549                    mint_msg.metadata_id.as_ref().unwrap_or(&0).to_string(),
550                )
551                .get(),
552        ))
553}
554
555fn execute_update_linked_collections(
556    deps: DepsMut,
557    env: Env,
558    info: MessageInfo,
559    collection_id: u32,
560    linked_collections: Vec<u32>,
561) -> Result<Response, ContractError> {
562    let hub_addr = HUB_ADDR.may_load(deps.storage)?;
563    let operators = OPERATORS.may_load(deps.storage)?;
564    let config = CONFIG.load(deps.storage)?;
565
566    check_admin_privileges(
567        &info.sender,
568        &env.contract.address,
569        &config.admin,
570        hub_addr,
571        operators,
572    )?;
573
574    if linked_collections.contains(&collection_id) {
575        return Err(ContractError::SelfLinkedCollection {});
576    };
577
578    let mut ids_to_check = vec![collection_id];
579    ids_to_check.extend(&linked_collections);
580    check_collection_ids_exists(&deps, &ids_to_check)?;
581
582    LINKED_COLLECTIONS.save(deps.storage, collection_id, &linked_collections)?;
583
584    Ok(Response::new()
585        .add_attribute("name", "komple_framework")
586        .add_attribute("module", "mint")
587        .add_attribute("action", "update_linked_collections")
588        .add_event(EventHelper::new("mint_update_linked_collections").get()))
589}
590
591fn execute_update_collection_status(
592    deps: DepsMut,
593    env: Env,
594    info: MessageInfo,
595    collection_id: u32,
596    is_blacklist: bool,
597) -> Result<Response, ContractError> {
598    let hub_addr = HUB_ADDR.may_load(deps.storage)?;
599    let operators = OPERATORS.may_load(deps.storage)?;
600    let config = CONFIG.load(deps.storage)?;
601
602    check_admin_privileges(
603        &info.sender,
604        &env.contract.address,
605        &config.admin,
606        hub_addr,
607        operators,
608    )?;
609
610    match is_blacklist {
611        // Blacklist case
612        true => {
613            if BLACKLIST_COLLECTION_ADDRS.has(deps.storage, collection_id) {
614                return Err(ContractError::AlreadyBlacklisted {});
615            };
616
617            let collection_addr = COLLECTION_ADDRS.may_load(deps.storage, collection_id)?;
618            if collection_addr.is_none() {
619                return Err(ContractError::CollectionIdNotFound {});
620            };
621
622            COLLECTION_ADDRS.remove(deps.storage, collection_id);
623            BLACKLIST_COLLECTION_ADDRS.save(
624                deps.storage,
625                collection_id,
626                &collection_addr.unwrap(),
627            )?;
628        }
629        // Whitelist case
630        false => {
631            if COLLECTION_ADDRS.has(deps.storage, collection_id) {
632                return Err(ContractError::AlreadyWhitelistlisted {});
633            };
634
635            let collection_addr =
636                BLACKLIST_COLLECTION_ADDRS.may_load(deps.storage, collection_id)?;
637            if collection_addr.is_none() {
638                return Err(ContractError::CollectionIdNotFound {});
639            };
640
641            BLACKLIST_COLLECTION_ADDRS.remove(deps.storage, collection_id);
642            COLLECTION_ADDRS.save(deps.storage, collection_id, &collection_addr.unwrap())?;
643        }
644    }
645
646    Ok(Response::new()
647        .add_attribute("name", "komple_framework")
648        .add_attribute("module", "mint")
649        .add_attribute("action", "update_collection_status")
650        .add_event(
651            EventHelper::new("mint_update_collection_status")
652                .add_attribute("collection_id", collection_id.to_string())
653                .add_attribute("is_blacklist", is_blacklist.to_string())
654                .get(),
655        ))
656}
657
658fn execute_update_creators(
659    deps: DepsMut,
660    env: Env,
661    info: MessageInfo,
662    mut addrs: Vec<String>,
663) -> Result<Response, ContractError> {
664    let hub_addr = HUB_ADDR.may_load(deps.storage)?;
665    let operators = OPERATORS.may_load(deps.storage)?;
666    let config = CONFIG.load(deps.storage)?;
667
668    check_admin_privileges(
669        &info.sender,
670        &env.contract.address,
671        &config.admin,
672        hub_addr,
673        operators,
674    )?;
675
676    addrs.sort_unstable();
677    addrs.dedup();
678
679    let mut event_attributes: Vec<Attribute> = vec![];
680
681    let addrs = addrs
682        .iter()
683        .map(|addr| -> StdResult<Addr> {
684            let addr = deps.api.addr_validate(addr)?;
685            event_attributes.push(Attribute {
686                key: "addrs".to_string(),
687                value: addr.to_string(),
688            });
689            Ok(addr)
690        })
691        .collect::<StdResult<Vec<Addr>>>()?;
692
693    CREATORS.save(deps.storage, &addrs)?;
694
695    Ok(
696        ResponseHelper::new_module("mint", "update_creators").add_event(
697            EventHelper::new("mint_update_creators")
698                .add_attributes(event_attributes)
699                .get(),
700        ),
701    )
702}
703
704fn execute_receive(
705    deps: DepsMut,
706    _env: Env,
707    info: MessageInfo,
708    cw20_receive_msg: Cw20ReceiveMsg,
709) -> Result<Response, ContractError> {
710    let msg: ReceiveMsg = from_binary(&cw20_receive_msg.msg)?;
711    let sender = cw20_receive_msg.sender;
712    let amount = cw20_receive_msg.amount;
713    match msg {
714        ReceiveMsg::Mint {
715            collection_id,
716            metadata_id,
717        } => {
718            let mint_lock = MINT_LOCKS.load(deps.storage, collection_id)?;
719            if mint_lock {
720                return Err(ContractError::LockedMint {});
721            }
722
723            let mint_msg = MintMsg {
724                collection_id,
725                recipient: info.sender.to_string(),
726                metadata_id,
727            };
728            let msgs: Vec<CosmosMsg> =
729                process_minting_prices(&deps, &info, &sender, collection_id, Some(amount))?;
730
731            _execute_mint(deps, "mint", msgs, mint_msg)
732        }
733    }
734}
735
736fn process_minting_prices(
737    deps: &DepsMut,
738    info: &MessageInfo,
739    recipient: &str,
740    collection_id: u32,
741    cw20_token_amount: Option<Uint128>,
742) -> Result<Vec<CosmosMsg>, ContractError> {
743    let hub_addr = HUB_ADDR.load(deps.storage)?;
744    let config = CONFIG.load(deps.storage)?;
745
746    let mut msgs: Vec<CosmosMsg> = vec![];
747    let mut total_price = Uint128::zero();
748    let mut is_whitelist = false;
749
750    // Check for fee module address
751    let fee_module_addr_res =
752        StorageHelper::query_module_address(&deps.querier, &hub_addr, Modules::Fee.to_string());
753    let fee_module_addr = match fee_module_addr_res {
754        Ok(addr) => Some(addr),
755        Err(_) => None,
756    };
757
758    // Get collection fund info
759    let collection_fund_info = COLLECTION_FUND_INFO.load(deps.storage, collection_id)?;
760
761    // Get sub modules from collection
762    let collection_addr = COLLECTION_ADDRS.load(deps.storage, collection_id)?;
763    let sub_modules = StorageHelper::query_token_sub_modules(&deps.querier, &collection_addr)?;
764
765    // Check for whitelist status
766    if let Some(whitelist_addr) = sub_modules.whitelist {
767        let res = KompleWhitelistHelper::new(whitelist_addr.clone()).query_is_active(&deps.querier);
768
769        // Continue if whitelist is active
770        if let Ok(is_active) = res {
771            if is_active {
772                // Query whitelist storage with owner address
773                let query_key = StorageHelper::get_map_storage_key(
774                    WHITELIST_NAMESPACE,
775                    &[recipient.as_bytes()],
776                )?;
777                let res = StorageHelper::query_storage::<bool>(
778                    &deps.querier,
779                    &whitelist_addr,
780                    &query_key,
781                )?;
782                if res.is_none() {
783                    return Err(ContractError::AddressNotWhitelisted {});
784                }
785
786                // If fee module is registered, check for whitelist minting price
787                if fee_module_addr.is_some() {
788                    // Whitelist is active and user is member
789                    let res = StorageHelper::query_fixed_fee(
790                        &deps.querier,
791                        &fee_module_addr.as_ref().unwrap(),
792                        Modules::Mint.to_string(),
793                        MintFees::new_whitelist_price(collection_id),
794                    );
795
796                    // If whitelist price exists
797                    if let Ok(fixed_fee_response) = res {
798                        // Create send message if not zero
799                        if !fixed_fee_response.value.is_zero() {
800                            let msg = match collection_fund_info.is_native {
801                                true => CosmosMsg::Bank(BankMsg::Send {
802                                    to_address: config.admin.to_string(),
803                                    amount: coins(
804                                        fixed_fee_response.value.u128(),
805                                        collection_fund_info.denom.to_string(),
806                                    ),
807                                }),
808                                false => CosmosMsg::Wasm(WasmMsg::Execute {
809                                    contract_addr: collection_fund_info
810                                        .cw20_address
811                                        .as_ref()
812                                        .unwrap()
813                                        .to_string(),
814                                    msg: to_binary(&Cw20ExecuteMsg::Transfer {
815                                        recipient: config.admin.to_string(),
816                                        amount: fixed_fee_response.value,
817                                    })?,
818                                    funds: vec![],
819                                }),
820                            };
821                            msgs.push(msg.into());
822                            total_price += fixed_fee_response.value;
823                        }
824                    }
825                };
826
827                is_whitelist = true;
828            }
829        }
830    }
831
832    // Standard collection mint flow
833    if !is_whitelist {
834        // If fee module is registered, check for standard minting price
835        if fee_module_addr.is_some() {
836            // Token mint price
837            let res = StorageHelper::query_fixed_fee(
838                &deps.querier,
839                &fee_module_addr.unwrap(),
840                Modules::Mint.to_string(),
841                MintFees::new_price(collection_id),
842            );
843            if let Ok(fixed_fee_response) = res {
844                let msg = match collection_fund_info.is_native {
845                    true => CosmosMsg::Bank(BankMsg::Send {
846                        to_address: config.admin.to_string(),
847                        amount: coins(
848                            fixed_fee_response.value.u128(),
849                            collection_fund_info.denom.to_string(),
850                        ),
851                    }),
852                    false => CosmosMsg::Wasm(WasmMsg::Execute {
853                        contract_addr: collection_fund_info
854                            .cw20_address
855                            .as_ref()
856                            .unwrap()
857                            .to_string(),
858                        msg: to_binary(&Cw20ExecuteMsg::Transfer {
859                            recipient: config.admin.to_string(),
860                            amount: fixed_fee_response.value,
861                        })?,
862                        funds: vec![],
863                    }),
864                };
865                msgs.push(msg.into());
866                total_price += fixed_fee_response.value;
867            }
868        }
869    }
870
871    if !total_price.is_zero() {
872        if collection_fund_info.is_native {
873            check_single_coin(
874                &info,
875                Coin {
876                    denom: collection_fund_info.denom,
877                    amount: total_price,
878                },
879            )?;
880        } else {
881            if cw20_token_amount.is_none() && cw20_token_amount.unwrap() != total_price {
882                return Err(FundsError::InvalidCw20Token {}.into());
883            };
884        }
885    };
886
887    Ok(msgs)
888}
889
890fn check_collection_ids_exists(
891    deps: &DepsMut,
892    collection_ids: &Vec<u32>,
893) -> Result<(), ContractError> {
894    for collection_id in collection_ids {
895        if !COLLECTION_ADDRS.has(deps.storage, *collection_id) {
896            return Err(ContractError::CollectionIdNotFound {});
897        }
898    }
899    Ok(())
900}
901
902#[cfg_attr(not(feature = "library"), entry_point)]
903pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
904    match msg {
905        QueryMsg::Config {} => to_binary(&query_config(deps)?),
906        QueryMsg::CollectionAddress { collection_id } => {
907            to_binary(&query_collection_address(deps, collection_id)?)
908        }
909        QueryMsg::CollectionInfo { collection_id } => {
910            to_binary(&query_collection_info(deps, collection_id)?)
911        }
912        QueryMsg::Operators {} => to_binary(&query_operators(deps)?),
913        QueryMsg::LinkedCollections { collection_id } => {
914            to_binary(&query_linked_collections(deps, collection_id)?)
915        }
916        QueryMsg::Collections {
917            blacklist,
918            start_after,
919            limit,
920        } => to_binary(&query_collections(deps, blacklist, start_after, limit)?),
921        QueryMsg::Creators {} => to_binary(&query_creators(deps)?),
922        QueryMsg::MintLock { collection_id } => to_binary(&query_mint_lock(deps, collection_id)?),
923    }
924}
925
926fn query_config(deps: Deps) -> StdResult<ResponseWrapper<Config>> {
927    let config = CONFIG.load(deps.storage)?;
928    Ok(ResponseWrapper::new("config", config))
929}
930
931fn query_collection_address(deps: Deps, collection_id: u32) -> StdResult<ResponseWrapper<String>> {
932    let addr = COLLECTION_ADDRS.load(deps.storage, collection_id)?;
933    Ok(ResponseWrapper::new("collection_address", addr.to_string()))
934}
935
936fn query_collection_info(
937    deps: Deps,
938    collection_id: u32,
939) -> StdResult<ResponseWrapper<CollectionInfo>> {
940    let collection_info = COLLECTION_INFO.load(deps.storage, collection_id)?;
941    Ok(ResponseWrapper::new("collection_info", collection_info))
942}
943
944fn query_operators(deps: Deps) -> StdResult<ResponseWrapper<Vec<String>>> {
945    let addrs = OPERATORS.may_load(deps.storage)?;
946    let addrs = match addrs {
947        Some(addrs) => addrs.iter().map(|a| a.to_string()).collect(),
948        None => vec![],
949    };
950    Ok(ResponseWrapper::new("operators", addrs))
951}
952
953fn query_linked_collections(
954    deps: Deps,
955    collection_id: u32,
956) -> StdResult<ResponseWrapper<Vec<u32>>> {
957    let linked_collection_ids = LINKED_COLLECTIONS.may_load(deps.storage, collection_id)?;
958    let linked_collection_ids = match linked_collection_ids {
959        Some(linked_collection_ids) => linked_collection_ids,
960        None => vec![],
961    };
962    Ok(ResponseWrapper::new(
963        "linked_collections",
964        linked_collection_ids,
965    ))
966}
967
968fn query_collections(
969    deps: Deps,
970    blacklist: bool,
971    start_after: Option<u32>,
972    limit: Option<u8>,
973) -> StdResult<ResponseWrapper<Vec<CollectionsResponse>>> {
974    let limit = limit.unwrap_or(30) as usize;
975    let start = start_after.map(Bound::exclusive);
976
977    let collections_state = match blacklist {
978        true => BLACKLIST_COLLECTION_ADDRS,
979        false => COLLECTION_ADDRS,
980    };
981
982    let collections = collections_state
983        .range(deps.storage, start, None, Order::Ascending)
984        .take(limit)
985        .map(|item| {
986            let (collection_id, address) = item.unwrap();
987            CollectionsResponse {
988                collection_id,
989                address: address.to_string(),
990            }
991        })
992        .collect::<Vec<CollectionsResponse>>();
993
994    Ok(ResponseWrapper::new("collections", collections))
995}
996
997fn query_creators(deps: Deps) -> StdResult<ResponseWrapper<Vec<String>>> {
998    let addrs = CREATORS.may_load(deps.storage)?;
999    let addrs = match addrs {
1000        Some(addrs) => addrs.iter().map(|a| a.to_string()).collect(),
1001        None => vec![],
1002    };
1003    Ok(ResponseWrapper::new("creators", addrs))
1004}
1005
1006fn query_mint_lock(deps: Deps, collection_id: u32) -> StdResult<ResponseWrapper<bool>> {
1007    let mint_lock = MINT_LOCKS.load(deps.storage, collection_id)?;
1008    Ok(ResponseWrapper::new("mint_lock", mint_lock))
1009}
1010
1011#[cfg_attr(not(feature = "library"), entry_point)]
1012pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result<Response, ContractError> {
1013    if msg.id != TOKEN_INSTANTIATE_REPLY_ID {
1014        return Err(ContractError::InvalidReplyID {});
1015    }
1016
1017    let reply = parse_reply_instantiate_data(msg);
1018    match reply {
1019        Ok(res) => {
1020            let collection_id = COLLECTION_ID.load(deps.storage)?;
1021            COLLECTION_ADDRS.save(
1022                deps.storage,
1023                collection_id,
1024                &Addr::unchecked(res.contract_address),
1025            )?;
1026            Ok(Response::default().add_attribute("action", "instantiate_token_reply"))
1027        }
1028        Err(_) => Err(ContractError::TokenInstantiateError {}),
1029    }
1030}
1031
1032#[cfg_attr(not(feature = "library"), entry_point)]
1033pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
1034    let version: Version = CONTRACT_VERSION.parse()?;
1035    let contract_version: cw2::ContractVersion = get_contract_version(deps.storage)?;
1036    let storage_version: Version = contract_version.version.parse()?;
1037
1038    if contract_version.contract != CONTRACT_NAME {
1039        return Err(
1040            StdError::generic_err("New version name should match the current version").into(),
1041        );
1042    }
1043    if storage_version >= version {
1044        return Err(
1045            StdError::generic_err("New version cannot be smaller than current version").into(),
1046        );
1047    }
1048
1049    set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
1050
1051    Ok(Response::default())
1052}