komple_framework_token_module/
contract.rs

1#[cfg(not(feature = "library"))]
2use cosmwasm_std::entry_point;
3use cosmwasm_std::{
4    from_binary, to_binary, Addr, Binary, CosmosMsg, Deps, DepsMut, Empty, Env, MessageInfo, Reply,
5    ReplyOn, Response, StdError, StdResult, SubMsg, Timestamp, WasmMsg,
6};
7use cw2::{get_contract_version, set_contract_version, ContractVersion};
8use cw_utils::parse_reply_instantiate_data;
9use komple_framework_types::modules::metadata::Metadata as MetadataType;
10use komple_framework_types::modules::mint::Collections;
11use komple_framework_types::modules::token::{Locks, SubModules};
12use komple_framework_types::shared::query::ResponseWrapper;
13use komple_framework_types::shared::RegisterMsg;
14use komple_framework_utils::check_admin_privileges;
15use komple_framework_utils::response::{EventHelper, ResponseHelper};
16use komple_framework_utils::shared::execute_update_operators;
17use komple_framework_whitelist_module::helper::KompleWhitelistHelper;
18use semver::Version;
19
20use crate::error::ContractError;
21use crate::msg::{
22    ExecuteMsg as TokenExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg as TokenQueryMsg,
23};
24use crate::state::{
25    CollectionConfig, Config, COLLECTION_TYPE, CONFIG, LOCKS, MINTED_TOKENS_PER_ADDR, OPERATORS,
26    PARENT_ADDR, SUB_MODULES, TOKEN_IDS, TOKEN_LOCKS,
27};
28
29use cw721::ContractInfoResponse;
30use cw721_base::{msg::ExecuteMsg as Cw721ExecuteMsg, MintMsg};
31
32use komple_framework_metadata_module::{
33    helper::KompleMetadataModule, state::MetaInfo as MetadataMetaInfo,
34};
35use komple_framework_whitelist_module::msg::InstantiateMsg as WhitelistInstantiateMsg;
36
37pub type Cw721Contract<'a> =
38    cw721_base::Cw721Contract<'a, Empty, Empty, TokenExecuteMsg, TokenQueryMsg>;
39pub type ExecuteMsg = cw721_base::ExecuteMsg<Empty, TokenExecuteMsg>;
40pub type QueryMsg = cw721_base::QueryMsg<TokenQueryMsg>;
41
42// version info for migration info
43const CONTRACT_NAME: &str = "crates.io:komple-framework-token-module";
44const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
45
46const METADATA_MODULE_INSTANTIATE_REPLY_ID: u64 = 1;
47const WHITELIST_MODULE_INSTANTIATE_REPLY_ID: u64 = 2;
48
49#[cfg_attr(not(feature = "library"), entry_point)]
50pub fn instantiate(
51    deps: DepsMut,
52    env: Env,
53    info: MessageInfo,
54    msg: RegisterMsg,
55) -> Result<Response, ContractError> {
56    set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
57
58    if msg.data.is_none() {
59        return Err(ContractError::InvalidInstantiateMsg {});
60    };
61    let data: InstantiateMsg = from_binary(&msg.data.unwrap())?;
62
63    if data.collection_config.start_time.is_some()
64        && env.block.time >= data.collection_config.start_time.unwrap()
65    {
66        return Err(ContractError::InvalidStartTime {});
67    };
68    if data.collection_config.max_token_limit.is_some()
69        && data.collection_config.max_token_limit.unwrap() <= 0
70    {
71        return Err(ContractError::InvalidMaxTokenLimit {});
72    };
73    if data.collection_config.per_address_limit.is_some()
74        && data.collection_config.per_address_limit.unwrap() <= 0
75    {
76        return Err(ContractError::InvalidPerAddressLimit {});
77    };
78    if data.collection_type == Collections::Standard && data.collection_config.ipfs_link.is_none() {
79        return Err(ContractError::IpfsNotFound {});
80    };
81
82    if (data.collection_type == Collections::Standard
83        && data.metadata_info.instantiate_msg.metadata_type != MetadataType::Standard)
84        || (data.collection_type != Collections::Standard
85            && data.metadata_info.instantiate_msg.metadata_type == MetadataType::Standard)
86    {
87        return Err(ContractError::InvalidCollectionMetadataType {});
88    }
89
90    COLLECTION_TYPE.save(deps.storage, &data.collection_type)?;
91
92    let admin = deps.api.addr_validate(&msg.admin)?;
93    let creator = deps.api.addr_validate(&data.creator)?;
94    let config = Config {
95        admin,
96        creator,
97        start_time: data.collection_config.start_time,
98        max_token_limit: data.collection_config.max_token_limit,
99        per_address_limit: data.collection_config.per_address_limit,
100        ipfs_link: data.collection_config.ipfs_link,
101    };
102    CONFIG.save(deps.storage, &config)?;
103
104    let locks = Locks {
105        burn_lock: false,
106        mint_lock: false,
107        transfer_lock: false,
108        send_lock: false,
109    };
110    LOCKS.save(deps.storage, &locks)?;
111
112    TOKEN_IDS.save(deps.storage, &0)?;
113
114    PARENT_ADDR.save(deps.storage, &info.sender)?;
115
116    let sub_modules = SubModules {
117        whitelist: None,
118        metadata: None,
119    };
120    SUB_MODULES.save(deps.storage, &sub_modules)?;
121
122    let contract_info = ContractInfoResponse {
123        name: data.collection_name.clone(),
124        symbol: data.token_info.symbol.clone(),
125    };
126    Cw721Contract::default()
127        .contract_info
128        .save(deps.storage, &contract_info)?;
129
130    let minter = deps.api.addr_validate(&data.token_info.minter)?;
131    Cw721Contract::default()
132        .minter
133        .save(deps.storage, &minter)?;
134
135    let contract_info = deps.querier.query_wasm_contract_info(info.sender.clone())?;
136    let metadata_register_msg = RegisterMsg {
137        admin: config.admin.to_string(),
138        data: Some(to_binary(&data.metadata_info.instantiate_msg)?),
139    };
140    let sub_msg: SubMsg = SubMsg {
141        msg: WasmMsg::Instantiate {
142            code_id: data.metadata_info.code_id,
143            msg: to_binary(&metadata_register_msg)?,
144            funds: vec![],
145            admin: contract_info.admin,
146            label: String::from("Komple Framework Metadata Module"),
147        }
148        .into(),
149        id: METADATA_MODULE_INSTANTIATE_REPLY_ID,
150        gas_limit: None,
151        reply_on: ReplyOn::Success,
152    };
153
154    Ok(ResponseHelper::new_module("token", "instantiate")
155        .add_submessage(sub_msg)
156        .add_event(
157            EventHelper::new("token_instantiate")
158                .add_attribute("mint_module_addr", info.sender)
159                .add_attribute("creator", config.creator)
160                .add_attribute("minter", minter)
161                .get(),
162        ))
163}
164
165#[cfg_attr(not(feature = "library"), entry_point)]
166pub fn execute(
167    deps: DepsMut,
168    env: Env,
169    info: MessageInfo,
170    msg: ExecuteMsg,
171) -> Result<Response, ContractError> {
172    match msg {
173        ExecuteMsg::Extension { msg } => match msg {
174            TokenExecuteMsg::UpdateLocks { locks } => execute_update_locks(deps, env, info, locks),
175            TokenExecuteMsg::UpdateTokenLocks { token_id, locks } => {
176                execute_update_token_locks(deps, env, info, token_id, locks)
177            }
178            TokenExecuteMsg::Mint { owner, metadata_id } => {
179                execute_mint(deps, env, info, owner, metadata_id)
180            }
181            TokenExecuteMsg::Burn { token_id } => execute_burn(deps, env, info, token_id),
182            TokenExecuteMsg::TransferNft {
183                token_id,
184                recipient,
185            } => execute_transfer(deps, env, info, token_id, recipient),
186            TokenExecuteMsg::SendNft {
187                token_id,
188                contract,
189                msg,
190            } => execute_send(deps, env, info, token_id, contract, msg),
191            TokenExecuteMsg::UpdateCollectionConfig { collection_config } => {
192                execute_update_collection_config(deps, env, info, collection_config)
193            }
194            TokenExecuteMsg::AdminTransferNft {
195                recipient,
196                token_id,
197            } => execute_admin_transfer(deps, env, info, token_id, recipient),
198            TokenExecuteMsg::InitWhitelistContract {
199                code_id,
200                instantiate_msg,
201            } => execute_init_whitelist_module(deps, env, info, code_id, instantiate_msg),
202            TokenExecuteMsg::UpdateModuleOperators { addrs } => {
203                let config = CONFIG.load(deps.storage)?;
204                let res = execute_update_operators(
205                    deps,
206                    info,
207                    "token",
208                    &env.contract.address,
209                    &config.admin,
210                    OPERATORS,
211                    addrs,
212                );
213                match res {
214                    Ok(res) => Ok(res),
215                    Err(e) => Err(e.into()),
216                }
217            }
218        },
219        _ => {
220            match msg {
221                // We are not allowing for normal mint endpoint
222                Cw721ExecuteMsg::Mint(_mint_msg) => Err(ContractError::Unauthorized {}),
223                Cw721ExecuteMsg::Burn { token_id } => execute_burn(deps, env, info, token_id),
224                Cw721ExecuteMsg::SendNft {
225                    token_id,
226                    contract,
227                    msg,
228                } => execute_send(deps, env, info, token_id, contract, msg),
229                Cw721ExecuteMsg::TransferNft {
230                    token_id,
231                    recipient,
232                } => execute_transfer(deps, env, info, token_id, recipient),
233                _ => {
234                    let res = Cw721Contract::default().execute(deps, env, info, msg);
235                    match res {
236                        Ok(res) => Ok(res),
237                        Err(e) => Err(e.into()),
238                    }
239                }
240            }
241        }
242    }
243}
244
245pub fn execute_update_locks(
246    deps: DepsMut,
247    env: Env,
248    info: MessageInfo,
249    locks: Locks,
250) -> Result<Response, ContractError> {
251    let mint_module_addr = PARENT_ADDR.may_load(deps.storage)?;
252    let operators = OPERATORS.may_load(deps.storage)?;
253    let config = CONFIG.load(deps.storage)?;
254
255    check_admin_privileges(
256        &info.sender,
257        &env.contract.address,
258        &config.admin,
259        mint_module_addr,
260        operators,
261    )?;
262
263    LOCKS.save(deps.storage, &locks)?;
264
265    Ok(
266        ResponseHelper::new_module("token", "update_locks").add_event(
267            EventHelper::new("token_update_locks")
268                .add_attribute("action", "update_locks")
269                .add_attribute("mint_lock", locks.mint_lock.to_string())
270                .add_attribute("burn_lock", locks.burn_lock.to_string())
271                .add_attribute("transfer_lock", locks.transfer_lock.to_string())
272                .add_attribute("send_lock", locks.send_lock.to_string())
273                .get(),
274        ),
275    )
276}
277
278pub fn execute_update_token_locks(
279    deps: DepsMut,
280    env: Env,
281    info: MessageInfo,
282    token_id: String,
283    locks: Locks,
284) -> Result<Response, ContractError> {
285    let mint_module_addr = PARENT_ADDR.may_load(deps.storage)?;
286    let operators = OPERATORS.may_load(deps.storage)?;
287    let config = CONFIG.load(deps.storage)?;
288
289    check_admin_privileges(
290        &info.sender,
291        &env.contract.address,
292        &config.admin,
293        mint_module_addr,
294        operators,
295    )?;
296
297    if !Cw721Contract::default().tokens.has(deps.storage, &token_id) {
298        return Err(ContractError::TokenNotFound {});
299    }
300
301    TOKEN_LOCKS.save(deps.storage, &token_id, &locks)?;
302
303    Ok(
304        ResponseHelper::new_module("token", "update_token_locks").add_event(
305            EventHelper::new("token_update_token_locks")
306                .add_attribute("action", "update_token_locks")
307                .add_attribute("token_id", token_id)
308                .add_attribute("mint_lock", locks.mint_lock.to_string())
309                .add_attribute("burn_lock", locks.burn_lock.to_string())
310                .add_attribute("transfer_lock", locks.transfer_lock.to_string())
311                .add_attribute("send_lock", locks.send_lock.to_string())
312                .get(),
313        ),
314    )
315}
316
317pub fn execute_mint(
318    deps: DepsMut,
319    env: Env,
320    info: MessageInfo,
321    owner: String,
322    metadata_id: Option<u32>,
323) -> Result<Response, ContractError> {
324    let config = CONFIG.load(deps.storage)?;
325    let collection_type = COLLECTION_TYPE.load(deps.storage)?;
326
327    let total_minted = MINTED_TOKENS_PER_ADDR
328        .may_load(deps.storage, &owner)?
329        .unwrap_or(0);
330    let token_id = (TOKEN_IDS.load(deps.storage)?) + 1;
331
332    let locks = LOCKS.load(deps.storage)?;
333    if locks.mint_lock {
334        return Err(ContractError::MintLocked {});
335    }
336
337    let token_lock = TOKEN_LOCKS.may_load(deps.storage, &token_id.to_string())?;
338    if token_lock.is_some() && token_lock.unwrap().mint_lock {
339        return Err(ContractError::MintLocked {});
340    }
341
342    if config.max_token_limit.is_some() && token_id > config.max_token_limit.unwrap() {
343        return Err(ContractError::TokenLimitReached {});
344    }
345
346    if config.per_address_limit.is_some() && total_minted + 1 > config.per_address_limit.unwrap() {
347        return Err(ContractError::TokenLimitReached {});
348    }
349
350    if config.start_time.is_some() && env.block.time < config.start_time.unwrap() {
351        return Err(ContractError::MintingNotStarted {});
352    }
353
354    // Whitelist checks
355    let sub_modules = SUB_MODULES.load(deps.storage)?;
356    if let Some(whitelist_addr) = sub_modules.whitelist {
357        let whitelist_config =
358            KompleWhitelistHelper::new(whitelist_addr).query_config(&deps.querier)?;
359
360        if total_minted + 1 > (whitelist_config.per_address_limit as u32) {
361            return Err(ContractError::TokenLimitReached {});
362        }
363    };
364
365    let mint_msg = MintMsg {
366        token_id: token_id.to_string(),
367        owner: owner.clone(),
368        token_uri: None,
369        extension: Empty {},
370    };
371
372    MINTED_TOKENS_PER_ADDR.save(deps.storage, &owner, &(total_minted + 1))?;
373    TOKEN_IDS.save(deps.storage, &token_id)?;
374
375    let sub_modules = SUB_MODULES.load(deps.storage)?;
376    if sub_modules.metadata.is_none() {
377        return Err(ContractError::MetadataContractNotFound {});
378    };
379
380    let res = Cw721Contract::default().mint(deps, env, info, mint_msg);
381
382    let mut msgs: Vec<CosmosMsg> = vec![];
383
384    // If the collection is standard
385    // Execute add_metadata message to save the ifps link to metadata module
386    if collection_type == Collections::Standard {
387        if config.ipfs_link.is_none() {
388            return Err(ContractError::IpfsNotFound {});
389        };
390
391        let ifps_link = format!("{}/{}", config.ipfs_link.unwrap(), token_id);
392
393        let msg = KompleMetadataModule(sub_modules.metadata.clone().unwrap()).add_metadata_msg(
394            MetadataMetaInfo {
395                image: Some(ifps_link),
396                external_url: None,
397                description: None,
398                youtube_url: None,
399                animation_url: None,
400            },
401            vec![],
402        )?;
403        msgs.push(msg.into())
404    }
405    // Link the metadata
406    let msg = KompleMetadataModule(sub_modules.metadata.unwrap())
407        .link_metadata_msg(token_id, metadata_id)?;
408    msgs.push(msg.into());
409
410    match res {
411        Ok(res) => Ok(res
412            .add_messages(msgs)
413            .add_attribute("name", "komple_framework")
414            .add_attribute("module", "token")
415            .add_attribute("action", "mint")
416            .add_event(
417                EventHelper::new("token_mint")
418                    .add_attribute("token_id", token_id.to_string())
419                    .add_attribute("owner", owner)
420                    .check_add_attribute(
421                        &metadata_id,
422                        "metadata_id",
423                        metadata_id.unwrap_or(0).to_string(),
424                    )
425                    .get(),
426            )),
427        Err(e) => Err(e.into()),
428    }
429}
430
431pub fn execute_burn(
432    deps: DepsMut,
433    env: Env,
434    info: MessageInfo,
435    token_id: String,
436) -> Result<Response, ContractError> {
437    let locks = LOCKS.load(deps.storage)?;
438    if locks.burn_lock {
439        return Err(ContractError::BurnLocked {});
440    }
441
442    let token_lock = TOKEN_LOCKS.may_load(deps.storage, &token_id)?;
443    if token_lock.is_some() && token_lock.unwrap().burn_lock {
444        return Err(ContractError::BurnLocked {});
445    }
446
447    let sub_modules = SUB_MODULES.load(deps.storage)?;
448    if sub_modules.metadata.is_none() {
449        return Err(ContractError::MetadataContractNotFound {});
450    };
451
452    let unlink_metadata_msg = KompleMetadataModule(sub_modules.metadata.unwrap())
453        .unlink_metadata_msg(token_id.parse::<u32>().unwrap())?;
454
455    let res = Cw721Contract::default().execute(
456        deps,
457        env,
458        info,
459        ExecuteMsg::Burn {
460            token_id: token_id.clone(),
461        },
462    );
463    match res {
464        Ok(res) => Ok(res
465            .add_message(unlink_metadata_msg)
466            .add_attribute("name", "komple_framework")
467            .add_attribute("module", "token")
468            .add_attribute("action", "burn")
469            .add_event(
470                EventHelper::new("token_burn")
471                    .add_attribute("token_id", token_id)
472                    .get(),
473            )),
474        Err(e) => Err(e.into()),
475    }
476}
477
478pub fn execute_transfer(
479    deps: DepsMut,
480    env: Env,
481    info: MessageInfo,
482    token_id: String,
483    recipient: String,
484) -> Result<Response, ContractError> {
485    let locks = LOCKS.load(deps.storage)?;
486    if locks.transfer_lock {
487        return Err(ContractError::TransferLocked {});
488    }
489
490    let token_lock = TOKEN_LOCKS.may_load(deps.storage, &token_id)?;
491    if token_lock.is_some() && token_lock.unwrap().transfer_lock {
492        return Err(ContractError::TransferLocked {});
493    }
494
495    let res = Cw721Contract::default().execute(
496        deps,
497        env,
498        info,
499        ExecuteMsg::TransferNft {
500            recipient: recipient.clone(),
501            token_id: token_id.clone(),
502        },
503    );
504    match res {
505        Ok(res) => Ok(res
506            .add_attribute("name", "komple_framework")
507            .add_attribute("module", "token")
508            .add_attribute("action", "transfer")
509            .add_event(
510                EventHelper::new("token_transfer")
511                    .add_attribute("token_id", token_id)
512                    .add_attribute("recipient", recipient)
513                    .get(),
514            )),
515        Err(e) => Err(e.into()),
516    }
517}
518
519pub fn execute_admin_transfer(
520    deps: DepsMut,
521    env: Env,
522    info: MessageInfo,
523    token_id: String,
524    recipient: String,
525) -> Result<Response, ContractError> {
526    let config = CONFIG.load(deps.storage)?;
527    let mint_module_addr = PARENT_ADDR.may_load(deps.storage)?;
528    let operators = OPERATORS.may_load(deps.storage)?;
529
530    check_admin_privileges(
531        &info.sender,
532        &env.contract.address,
533        &config.admin,
534        mint_module_addr,
535        operators,
536    )?;
537
538    let res = Cw721Contract::default().execute(
539        deps,
540        env,
541        info,
542        ExecuteMsg::TransferNft {
543            recipient: recipient.clone(),
544            token_id: token_id.clone(),
545        },
546    );
547    match res {
548        Ok(res) => Ok(res
549            .add_attribute("name", "komple_framework")
550            .add_attribute("module", "token")
551            .add_attribute("action", "admin_transfer")
552            .add_event(
553                EventHelper::new("token_admin_transfer")
554                    .add_attribute("token_id", token_id)
555                    .add_attribute("recipient", recipient)
556                    .get(),
557            )),
558        Err(e) => Err(e.into()),
559    }
560}
561
562pub fn execute_send(
563    deps: DepsMut,
564    env: Env,
565    info: MessageInfo,
566    token_id: String,
567    contract: String,
568    msg: Binary,
569) -> Result<Response, ContractError> {
570    let locks = LOCKS.load(deps.storage)?;
571    if locks.send_lock {
572        return Err(ContractError::SendLocked {});
573    }
574
575    let token_lock = TOKEN_LOCKS.may_load(deps.storage, &token_id)?;
576    if token_lock.is_some() && token_lock.unwrap().send_lock {
577        return Err(ContractError::SendLocked {});
578    }
579
580    let res = Cw721Contract::default().execute(
581        deps,
582        env,
583        info,
584        ExecuteMsg::SendNft {
585            contract: contract.clone(),
586            token_id: contract.clone(),
587            msg,
588        },
589    );
590    match res {
591        Ok(res) => Ok(res
592            .add_attribute("name", "komple_framework")
593            .add_attribute("module", "token")
594            .add_attribute("action", "send")
595            .add_event(
596                EventHelper::new("token_send")
597                    .add_attribute("token_id", token_id)
598                    .add_attribute("contract", contract)
599                    .get(),
600            )),
601        Err(e) => Err(e.into()),
602    }
603}
604
605fn execute_update_collection_config(
606    deps: DepsMut,
607    env: Env,
608    info: MessageInfo,
609    collection_config: CollectionConfig,
610) -> Result<Response, ContractError> {
611    let mint_module_addr = PARENT_ADDR.may_load(deps.storage)?;
612    let operators = OPERATORS.may_load(deps.storage)?;
613    let mut config = CONFIG.load(deps.storage)?;
614
615    check_admin_privileges(
616        &info.sender,
617        &env.contract.address,
618        &config.admin,
619        mint_module_addr,
620        operators,
621    )?;
622
623    // Start time
624    if config.start_time.is_some() && env.block.time >= config.start_time.unwrap() {
625        return Err(ContractError::AlreadyStarted {});
626    };
627    match collection_config.start_time {
628        Some(time) => {
629            if env.block.time >= time {
630                return Err(ContractError::InvalidStartTime {});
631            }
632            config.start_time = collection_config.start_time;
633        }
634        None => config.start_time = None,
635    };
636
637    // Per address limit
638    if collection_config.per_address_limit.is_some()
639        && collection_config.per_address_limit.unwrap() <= 0
640    {
641        return Err(ContractError::InvalidPerAddressLimit {});
642    };
643    config.per_address_limit = collection_config.per_address_limit;
644
645    // Max token limit
646    if collection_config.max_token_limit.is_some()
647        && collection_config.max_token_limit.unwrap() <= 0
648    {
649        return Err(ContractError::InvalidMaxTokenLimit {});
650    };
651    config.max_token_limit = collection_config.max_token_limit;
652
653    // IPFS link
654    if collection_config.ipfs_link.is_some() {
655        config.ipfs_link = collection_config.ipfs_link;
656    };
657
658    CONFIG.save(deps.storage, &config)?;
659
660    Ok(Response::new()
661        .add_attribute("name", "komple_framework")
662        .add_attribute("module", "token")
663        .add_attribute("action", "update_collection_config")
664        .add_event(
665            EventHelper::new("token_update_collection_config")
666                .check_add_attribute(
667                    &config.start_time,
668                    "start_time",
669                    config
670                        .start_time
671                        .unwrap_or(Timestamp::from_nanos(0))
672                        .to_string(),
673                )
674                .check_add_attribute(
675                    &config.max_token_limit,
676                    "max_token_limit",
677                    config.max_token_limit.unwrap_or(0).to_string(),
678                )
679                .check_add_attribute(
680                    &config.per_address_limit,
681                    "per_address_limit",
682                    config.per_address_limit.unwrap_or(0).to_string(),
683                )
684                .check_add_attribute(
685                    &config.ipfs_link,
686                    "ipfs_link",
687                    config.ipfs_link.as_ref().unwrap_or(&String::from("")),
688                )
689                .get(),
690        ))
691}
692
693fn execute_init_whitelist_module(
694    deps: DepsMut,
695    env: Env,
696    info: MessageInfo,
697    code_id: u64,
698    instantiate_msg: WhitelistInstantiateMsg,
699) -> Result<Response, ContractError> {
700    let mint_module_addr = PARENT_ADDR.load(deps.storage)?;
701    let operators = OPERATORS.may_load(deps.storage)?;
702    let config = CONFIG.load(deps.storage)?;
703
704    check_admin_privileges(
705        &info.sender,
706        &env.contract.address,
707        &config.admin,
708        Some(mint_module_addr.clone()),
709        operators,
710    )?;
711
712    let contract_info = deps
713        .querier
714        .query_wasm_contract_info(env.contract.address)?;
715    let register_msg = RegisterMsg {
716        admin: config.admin.to_string(),
717        data: Some(to_binary(&instantiate_msg)?),
718    };
719    let sub_msg: SubMsg = SubMsg {
720        msg: WasmMsg::Instantiate {
721            code_id,
722            msg: to_binary(&register_msg)?,
723            funds: vec![],
724            admin: contract_info.admin,
725            label: String::from("Komple Framework Whitelist Module"),
726        }
727        .into(),
728        id: WHITELIST_MODULE_INSTANTIATE_REPLY_ID,
729        gas_limit: None,
730        reply_on: ReplyOn::Success,
731    };
732
733    Ok(ResponseHelper::new_module("token", "init_whitelist_module")
734        .add_submessage(sub_msg)
735        .add_event(EventHelper::new("token_init_whitelist_module").get()))
736}
737
738#[cfg_attr(not(feature = "library"), entry_point)]
739pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
740    match msg {
741        QueryMsg::Extension { msg } => match msg {
742            TokenQueryMsg::Config {} => to_binary(&query_config(deps)?),
743            TokenQueryMsg::Locks {} => to_binary(&query_locks(deps)?),
744            TokenQueryMsg::TokenLocks { token_id } => {
745                to_binary(&query_token_locks(deps, token_id)?)
746            }
747            TokenQueryMsg::MintedTokensPerAddress { address } => {
748                to_binary(&query_minted_tokens_per_address(deps, address)?)
749            }
750            TokenQueryMsg::SubModules {} => to_binary(&query_sub_modules(deps)?),
751            TokenQueryMsg::ModuleOperators {} => to_binary(&query_module_operators(deps)?),
752        },
753        _ => Cw721Contract::default().query(deps, env, msg),
754    }
755}
756
757fn query_config(deps: Deps) -> StdResult<ResponseWrapper<Config>> {
758    let config = CONFIG.load(deps.storage)?;
759    Ok(ResponseWrapper::new("locks", config))
760}
761
762fn query_locks(deps: Deps) -> StdResult<ResponseWrapper<Locks>> {
763    let locks = LOCKS.load(deps.storage)?;
764    Ok(ResponseWrapper::new("locks", locks))
765}
766
767fn query_token_locks(deps: Deps, token_id: String) -> StdResult<ResponseWrapper<Locks>> {
768    let locks = TOKEN_LOCKS.load(deps.storage, &token_id)?;
769    Ok(ResponseWrapper::new("locks", locks))
770}
771
772fn query_minted_tokens_per_address(deps: Deps, address: String) -> StdResult<ResponseWrapper<u32>> {
773    let amount = MINTED_TOKENS_PER_ADDR
774        .may_load(deps.storage, &address)?
775        .unwrap_or(0);
776    Ok(ResponseWrapper::new("minted_tokens_per_address", amount))
777}
778
779fn query_sub_modules(deps: Deps) -> StdResult<ResponseWrapper<SubModules>> {
780    let sub_modules = SUB_MODULES.load(deps.storage)?;
781    Ok(ResponseWrapper::new("sub_modules", sub_modules))
782}
783
784fn query_module_operators(deps: Deps) -> StdResult<ResponseWrapper<Vec<String>>> {
785    let operators = OPERATORS.load(deps.storage).unwrap_or_default();
786    Ok(ResponseWrapper::new(
787        "module_operators",
788        operators.iter().map(|o| o.to_string()).collect(),
789    ))
790}
791
792#[cfg_attr(not(feature = "library"), entry_point)]
793pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result<Response, ContractError> {
794    if msg.id != METADATA_MODULE_INSTANTIATE_REPLY_ID
795        && msg.id != WHITELIST_MODULE_INSTANTIATE_REPLY_ID
796    {
797        return Err(ContractError::InvalidReplyID {});
798    }
799
800    let reply = parse_reply_instantiate_data(msg.clone());
801    match reply {
802        Ok(res) => {
803            let mut sub_modules = SUB_MODULES.load(deps.storage)?;
804            let sub_module: &str;
805            match msg.id {
806                METADATA_MODULE_INSTANTIATE_REPLY_ID => {
807                    sub_modules.metadata = Some(Addr::unchecked(res.contract_address));
808                    sub_module = "metadata";
809                }
810                WHITELIST_MODULE_INSTANTIATE_REPLY_ID => {
811                    sub_modules.whitelist = Some(Addr::unchecked(res.contract_address));
812                    sub_module = "whitelist";
813                }
814                _ => unreachable!(),
815            }
816            SUB_MODULES.save(deps.storage, &sub_modules)?;
817            Ok(Response::default()
818                .add_attribute("action", format!("instantiate_{}_reply", sub_module)))
819        }
820        Err(_) => Err(ContractError::ContractsInstantiateError {}),
821    }
822}
823
824#[cfg_attr(not(feature = "library"), entry_point)]
825pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
826    let version: Version = CONTRACT_VERSION.parse()?;
827    let contract_version: ContractVersion = get_contract_version(deps.storage)?;
828    let storage_version: Version = contract_version.version.parse()?;
829
830    if contract_version.contract != CONTRACT_NAME {
831        return Err(
832            StdError::generic_err("New version name should match the current version").into(),
833        );
834    }
835    if storage_version >= version {
836        return Err(
837            StdError::generic_err("New version cannot be smaller than current version").into(),
838        );
839    }
840
841    set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
842
843    Ok(Response::default())
844}