1use crate::error::ContractError;
2use crate::helpers::mint_nft_msg;
3use crate::msg::{
4 ConfigResponse, EndTimeResponse, ExecuteMsg, MintCountResponse, MintPriceResponse,
5 MintableNumTokensResponse, QueryMsg, StartTimeResponse, TotalMintCountResponse,
6};
7use crate::state::{
8 increment_token_index, Config, ConfigExtension, CONFIG, MINTABLE_NUM_TOKENS, MINTER_ADDRS,
9 SG721_ADDRESS, STATUS, TOTAL_MINT_COUNT,
10};
11#[cfg(not(feature = "library"))]
12use cosmwasm_std::entry_point;
13use cosmwasm_std::{
14 coin, ensure, to_json_binary, Addr, BankMsg, Binary, Coin, Decimal, Deps, DepsMut, Empty, Env,
15 Event, MessageInfo, Order, Reply, ReplyOn, StdError, StdResult, Timestamp, WasmMsg,
16};
17use cw2::set_contract_version;
18use cw_utils::{may_pay, maybe_addr, nonpayable, parse_reply_instantiate_data};
19use open_edition_factory::msg::{OpenEditionMinterCreateMsg, ParamsResponse};
20use open_edition_factory::state::OpenEditionMinterParams;
21use open_edition_factory::types::NftMetadataType;
22use semver::Version;
23use sg1::distribute_mint_fees;
24use sg2::query::Sg2QueryMsg;
25use sg4::{MinterConfig, Status, StatusResponse, SudoMsg};
26use sg721::{ExecuteMsg as Sg721ExecuteMsg, InstantiateMsg as Sg721InstantiateMsg};
27use sg_std::StargazeMsgWrapper;
28use sg_whitelist::msg::{
29 ConfigResponse as WhitelistConfigResponse, HasMemberResponse, QueryMsg as WhitelistQueryMsg,
30};
31use url::Url;
32
33pub type Response = cosmwasm_std::Response<StargazeMsgWrapper>;
34pub type SubMsg = cosmwasm_std::SubMsg<StargazeMsgWrapper>;
35
36const CONTRACT_NAME: &str = "crates.io:sg-open-edition-minter";
38const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
39
40const INSTANTIATE_SG721_REPLY_ID: u64 = 1;
41
42#[cfg_attr(not(feature = "library"), entry_point)]
43pub fn instantiate(
44 deps: DepsMut,
45 env: Env,
46 info: MessageInfo,
47 mut msg: OpenEditionMinterCreateMsg,
48) -> Result<Response, ContractError> {
49 set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
50
51 let factory = info.sender.clone();
52
53 let factory_response: ParamsResponse = deps
56 .querier
57 .query_wasm_smart(factory.clone(), &Sg2QueryMsg::Params {})?;
58 let factory_params = factory_response.params;
59
60 STATUS.save(deps.storage, &Status::default())?;
62
63 match msg.init_msg.nft_data.nft_data_type {
64 NftMetadataType::OffChainMetadata => {
66 let base_token_uri = msg
67 .init_msg
68 .nft_data
69 .token_uri
70 .as_ref()
71 .map(|uri| uri.trim().to_string())
72 .map_or_else(|| Err(ContractError::InvalidBaseTokenURI {}), Ok)?;
73 Url::parse(&base_token_uri).map_err(|_| ContractError::InvalidBaseTokenURI {})?;
75 msg.init_msg.nft_data.token_uri = Some(base_token_uri);
76 }
77 NftMetadataType::OnChainMetadata => {
79 let base_img_url = msg
80 .init_msg
81 .nft_data
82 .extension
83 .as_ref()
84 .and_then(|ext| ext.image.as_ref().map(|img| img.trim()))
85 .map(Url::parse)
86 .transpose()?
87 .map(|url| url.to_string());
88 if let Some(ext) = msg.init_msg.nft_data.extension.as_mut() {
89 ext.image = base_img_url;
90 }
91 }
92 }
93
94 let whitelist_addr = msg
99 .init_msg
100 .whitelist
101 .and_then(|w| deps.api.addr_validate(w.as_str()).ok());
102
103 if let Some(whitelist) = whitelist_addr.clone() {
104 let res: WhitelistConfigResponse = deps
106 .querier
107 .query_wasm_smart(whitelist, &WhitelistQueryMsg::Config {})?;
108 if res.is_active {
109 return Err(ContractError::WhitelistAlreadyStarted {});
110 }
111 }
112
113 let mut collection_info = msg.collection_params.info.clone();
115 let offset = factory_params.max_trading_offset_secs;
116 let default_start_time_with_offset = msg.init_msg.start_time.plus_seconds(offset);
117 if let Some(start_trading_time) = msg.collection_params.info.start_trading_time {
118 if start_trading_time > default_start_time_with_offset {
120 return Err(ContractError::InvalidStartTradingTime(
121 start_trading_time,
122 default_start_time_with_offset,
123 ));
124 }
125 }
126 let start_trading_time = msg
127 .collection_params
128 .info
129 .start_trading_time
130 .or(Some(default_start_time_with_offset));
131 collection_info.start_trading_time = start_trading_time;
132
133 let config = Config {
134 factory: factory.clone(),
135 collection_code_id: msg.collection_params.code_id,
136 extension: ConfigExtension {
137 admin: deps
138 .api
139 .addr_validate(&msg.collection_params.info.creator)?,
140 payment_address: maybe_addr(deps.api, msg.init_msg.payment_address)?,
141 per_address_limit: msg.init_msg.per_address_limit,
142 start_time: msg.init_msg.start_time,
143 end_time: msg.init_msg.end_time,
144 nft_data: msg.init_msg.nft_data,
145 num_tokens: msg.init_msg.num_tokens,
146 whitelist: whitelist_addr,
147 },
148 mint_price: msg.init_msg.mint_price,
149 };
150
151 CONFIG.save(deps.storage, &config)?;
152
153 TOTAL_MINT_COUNT.save(deps.storage, &0)?;
155
156 if let Some(max_num_tokens) = msg.init_msg.num_tokens {
158 MINTABLE_NUM_TOKENS.save(deps.storage, &max_num_tokens)?;
159 } else {
160 MINTABLE_NUM_TOKENS.save(deps.storage, &factory_params.extension.max_token_limit)?;
161 }
162
163 let submsg = SubMsg {
165 msg: WasmMsg::Instantiate {
166 code_id: msg.collection_params.code_id,
167 msg: to_json_binary(&Sg721InstantiateMsg {
168 name: msg.collection_params.name.clone(),
169 symbol: msg.collection_params.symbol,
170 minter: env.contract.address.to_string(),
171 collection_info,
172 })?,
173 funds: info.funds,
174 admin: Some(config.extension.admin.to_string()),
175 label: format!("SG721-{}", msg.collection_params.name.trim()),
176 }
177 .into(),
178 id: INSTANTIATE_SG721_REPLY_ID,
179 gas_limit: None,
180 reply_on: ReplyOn::Success,
181 };
182
183 Ok(Response::new()
184 .add_attribute("action", "instantiate")
185 .add_attribute("contract_name", CONTRACT_NAME)
186 .add_attribute("contract_version", CONTRACT_VERSION)
187 .add_attribute("sender", factory)
188 .add_submessage(submsg))
189}
190
191#[cfg_attr(not(feature = "library"), entry_point)]
192pub fn execute(
193 deps: DepsMut,
194 env: Env,
195 info: MessageInfo,
196 msg: ExecuteMsg,
197) -> Result<Response, ContractError> {
198 match msg {
199 ExecuteMsg::Mint {} => execute_mint_sender(deps, env, info),
200 ExecuteMsg::Purge {} => execute_purge(deps, env, info),
201 ExecuteMsg::UpdateMintPrice { price } => execute_update_mint_price(deps, env, info, price),
202 ExecuteMsg::UpdateStartTime(time) => execute_update_start_time(deps, env, info, time),
203 ExecuteMsg::UpdateEndTime(time) => execute_update_end_time(deps, env, info, time),
204 ExecuteMsg::UpdateStartTradingTime(time) => {
205 execute_update_start_trading_time(deps, env, info, time)
206 }
207 ExecuteMsg::UpdatePerAddressLimit { per_address_limit } => {
208 execute_update_per_address_limit(deps, env, info, per_address_limit)
209 }
210 ExecuteMsg::MintTo { recipient } => execute_mint_to(deps, env, info, recipient),
211 ExecuteMsg::SetWhitelist { whitelist } => {
212 execute_set_whitelist(deps, env, info, &whitelist)
213 }
214 ExecuteMsg::BurnRemaining {} => execute_burn_remaining(deps, env, info),
215 }
216}
217
218pub fn execute_purge(
221 deps: DepsMut,
222 env: Env,
223 info: MessageInfo,
224) -> Result<Response, ContractError> {
225 nonpayable(&info)?;
226
227 let end_time = CONFIG.load(deps.storage)?.extension.end_time;
229 if let Some(end_time_u) = end_time {
230 if env.block.time <= end_time_u {
231 return Err(ContractError::MintingHasNotYetEnded {});
232 }
233 }
234
235 let mintable_num_tokens = MINTABLE_NUM_TOKENS.may_load(deps.storage)?;
237 if let Some(mintable_nb_tokens) = mintable_num_tokens {
238 if mintable_nb_tokens != 0 && end_time.is_none() {
239 return Err(ContractError::NotSoldOut {});
240 }
241 }
242
243 let keys = MINTER_ADDRS
244 .keys(deps.storage, None, None, Order::Ascending)
245 .collect::<Vec<_>>();
246 for key in keys {
247 MINTER_ADDRS.remove(deps.storage, &key?);
248 }
249
250 Ok(Response::new()
251 .add_attribute("action", "purge")
252 .add_attribute("contract", env.contract.address.to_string())
253 .add_attribute("sender", info.sender))
254}
255
256pub fn execute_set_whitelist(
257 deps: DepsMut,
258 env: Env,
259 info: MessageInfo,
260 whitelist: &str,
261) -> Result<Response, ContractError> {
262 nonpayable(&info)?;
263 let mut config = CONFIG.load(deps.storage)?;
264 let MinterConfig {
265 factory,
266 extension:
267 ConfigExtension {
268 whitelist: existing_whitelist,
269 admin,
270 start_time,
271 ..
272 },
273 ..
274 } = config.clone();
275 ensure!(
276 admin == info.sender,
277 ContractError::Unauthorized("Sender is not an admin".to_owned())
278 );
279
280 ensure!(
281 env.block.time < start_time,
282 ContractError::AlreadyStarted {}
283 );
284
285 if let Some(whitelist) = existing_whitelist {
286 let res: WhitelistConfigResponse = deps
287 .querier
288 .query_wasm_smart(whitelist, &WhitelistQueryMsg::Config {})?;
289
290 ensure!(!res.is_active, ContractError::WhitelistAlreadyStarted {});
291 }
292
293 let new_wl = deps.api.addr_validate(whitelist)?;
294 config.extension.whitelist = Some(new_wl.clone());
295 let WhitelistConfigResponse {
297 is_active: wl_is_active,
298 mint_price: wl_mint_price,
299 ..
300 } = deps
301 .querier
302 .query_wasm_smart(new_wl, &WhitelistQueryMsg::Config {})?;
303
304 ensure!(!wl_is_active, ContractError::WhitelistAlreadyStarted {});
305
306 ensure!(
307 wl_mint_price.denom == config.mint_price.denom,
308 ContractError::InvalidDenom {
309 expected: config.mint_price.denom,
310 got: wl_mint_price.denom,
311 }
312 );
313
314 let ParamsResponse {
316 params:
317 OpenEditionMinterParams {
318 min_mint_price: factory_min_mint_price,
319 ..
320 },
321 } = deps
322 .querier
323 .query_wasm_smart(factory, &Sg2QueryMsg::Params {})?;
324
325 ensure!(
326 factory_min_mint_price.amount <= wl_mint_price.amount,
327 ContractError::InsufficientWhitelistMintPrice {
328 expected: factory_min_mint_price.amount.into(),
329 got: wl_mint_price.amount.into(),
330 }
331 );
332
333 ensure!(
335 factory_min_mint_price.denom == wl_mint_price.denom,
336 ContractError::InvalidDenom {
337 expected: factory_min_mint_price.denom,
338 got: wl_mint_price.denom,
339 }
340 );
341
342 CONFIG.save(deps.storage, &config)?;
343
344 Ok(Response::default()
345 .add_attribute("action", "set_whitelist")
346 .add_attribute("whitelist", whitelist.to_string()))
347}
348
349pub fn execute_mint_sender(
350 deps: DepsMut,
351 env: Env,
352 info: MessageInfo,
353) -> Result<Response, ContractError> {
354 let config = CONFIG.load(deps.storage)?;
355 let action = "mint_sender";
356
357 if is_public_mint(deps.as_ref(), &info)? && (env.block.time < config.extension.start_time) {
360 return Err(ContractError::BeforeMintStartTime {});
361 }
362 if let Some(end_time) = config.extension.end_time {
363 if env.block.time >= end_time {
364 return Err(ContractError::AfterMintEndTime {});
365 }
366 }
367
368 if matches!(mint_count_per_addr(deps.as_ref(), &info)?, count if count >= config.extension.per_address_limit)
370 {
371 return Err(ContractError::MaxPerAddressLimitExceeded {});
372 }
373
374 _execute_mint(deps, env, info, action, false, None)
375}
376
377fn is_public_mint(deps: Deps, info: &MessageInfo) -> Result<bool, ContractError> {
380 let config = CONFIG.load(deps.storage)?;
381
382 if config.extension.whitelist.is_none() {
384 return Ok(true);
385 }
386
387 let whitelist = config.extension.whitelist.unwrap();
388
389 let wl_config: WhitelistConfigResponse = deps
390 .querier
391 .query_wasm_smart(whitelist.clone(), &WhitelistQueryMsg::Config {})?;
392
393 if !wl_config.is_active {
394 return Ok(true);
395 }
396
397 let res: HasMemberResponse = deps.querier.query_wasm_smart(
398 whitelist,
399 &WhitelistQueryMsg::HasMember {
400 member: info.sender.to_string(),
401 },
402 )?;
403 if !res.has_member {
404 return Err(ContractError::NotWhitelisted {
405 addr: info.sender.to_string(),
406 });
407 }
408
409 let mint_count = mint_count(deps, info)?;
411 if mint_count >= wl_config.per_address_limit {
412 return Err(ContractError::MaxPerAddressLimitExceeded {});
413 }
414
415 Ok(false)
416}
417
418fn mint_count(deps: Deps, info: &MessageInfo) -> Result<u32, StdError> {
419 let mint_count = MINTER_ADDRS
420 .key(&info.sender)
421 .may_load(deps.storage)?
422 .unwrap_or(0);
423 Ok(mint_count)
424}
425
426pub fn execute_mint_to(
427 deps: DepsMut,
428 env: Env,
429 info: MessageInfo,
430 recipient: String,
431) -> Result<Response, ContractError> {
432 let recipient = deps.api.addr_validate(&recipient)?;
433 let config = CONFIG.load(deps.storage)?;
434 let action = "mint_to";
435
436 if info.sender != config.extension.admin {
438 return Err(ContractError::Unauthorized(
439 "Sender is not an admin".to_owned(),
440 ));
441 }
442
443 if let Some(end_time) = config.extension.end_time {
444 if env.block.time >= end_time {
445 return Err(ContractError::AfterMintEndTime {});
446 }
447 }
448
449 _execute_mint(deps, env, info, action, true, Some(recipient))
450}
451
452fn _execute_mint(
456 deps: DepsMut,
457 _env: Env,
458 info: MessageInfo,
459 action: &str,
460 is_admin: bool,
461 recipient: Option<Addr>,
462) -> Result<Response, ContractError> {
463 let mintable_num_tokens = MINTABLE_NUM_TOKENS.may_load(deps.storage)?;
464 if let Some(mintable_nb_tokens) = mintable_num_tokens {
465 if mintable_nb_tokens == 0 {
466 return Err(ContractError::SoldOut {});
467 }
468 }
469 let config = CONFIG.load(deps.storage)?;
470
471 let sg721_address = SG721_ADDRESS.load(deps.storage)?;
472
473 let recipient_addr = match recipient {
474 Some(some_recipient) => some_recipient,
475 None => info.sender.clone(),
476 };
477
478 let mint_price: Coin = mint_price(deps.as_ref(), is_admin)?;
479 let payment = may_pay(&info, &mint_price.denom)?;
481 if payment != mint_price.amount {
482 return Err(ContractError::IncorrectPaymentAmount(
483 coin(payment.u128(), &config.mint_price.denom),
484 mint_price,
485 ));
486 }
487
488 let mut res = Response::new();
489
490 let factory: ParamsResponse = deps
491 .querier
492 .query_wasm_smart(config.factory, &Sg2QueryMsg::Params {})?;
493 let factory_params = factory.params;
494
495 let mint_fee = if is_admin {
499 Decimal::bps(factory_params.extension.airdrop_mint_fee_bps)
500 } else {
501 Decimal::bps(factory_params.mint_fee_bps)
502 };
503 let network_fee = mint_price.amount * mint_fee;
504
505 if !network_fee.is_zero() {
506 distribute_mint_fees(
507 coin(network_fee.u128(), mint_price.clone().denom),
508 &mut res,
509 false,
510 Some(
511 deps.api
512 .addr_validate(&factory_params.extension.dev_fee_address)?,
513 ),
514 )?;
515 }
516
517 let token_id = increment_token_index(deps.storage)?.to_string();
519
520 let msg = mint_nft_msg(
522 sg721_address,
523 token_id.clone(),
524 recipient_addr.clone(),
525 match config.extension.nft_data.nft_data_type {
526 NftMetadataType::OnChainMetadata => config.extension.nft_data.extension,
527 NftMetadataType::OffChainMetadata => None,
528 },
529 match config.extension.nft_data.nft_data_type {
530 NftMetadataType::OnChainMetadata => None,
531 NftMetadataType::OffChainMetadata => config.extension.nft_data.token_uri,
532 },
533 )?;
534 res = res.add_message(msg);
535
536 let new_mint_count = mint_count_per_addr(deps.as_ref(), &info)? + 1;
538 MINTER_ADDRS.save(deps.storage, &info.sender, &new_mint_count)?;
539
540 TOTAL_MINT_COUNT.update(
542 deps.storage,
543 |mut updated_mint_count| -> Result<_, ContractError> {
544 updated_mint_count += 1u32;
545 Ok(updated_mint_count)
546 },
547 )?;
548
549 if let Some(mintable_nb_tokens) = mintable_num_tokens {
551 MINTABLE_NUM_TOKENS.save(deps.storage, &(mintable_nb_tokens - 1))?;
552 }
553
554 let seller_amount = {
555 let amount = mint_price.amount.checked_sub(network_fee)?;
557 let payment_address = config.extension.payment_address;
558 let seller = config.extension.admin;
559 if !amount.is_zero() {
561 let msg = BankMsg::Send {
562 to_address: payment_address.unwrap_or(seller).to_string(),
563 amount: vec![coin(amount.u128(), mint_price.clone().denom)],
564 };
565 res = res.add_message(msg);
566 }
567 amount
568 };
569
570 Ok(res
571 .add_attribute("action", action)
572 .add_attribute("sender", info.sender)
573 .add_attribute("recipient", recipient_addr)
574 .add_attribute("token_id", token_id)
575 .add_attribute(
576 "network_fee",
577 coin(network_fee.into(), mint_price.clone().denom).to_string(),
578 )
579 .add_attribute("mint_price", mint_price.to_string())
580 .add_attribute(
581 "seller_amount",
582 coin(seller_amount.into(), mint_price.denom).to_string(),
583 ))
584}
585
586pub fn execute_update_mint_price(
587 deps: DepsMut,
588 env: Env,
589 info: MessageInfo,
590 price: u128,
591) -> Result<Response, ContractError> {
592 nonpayable(&info)?;
593 let mut config = CONFIG.load(deps.storage)?;
594 if info.sender != config.extension.admin {
595 return Err(ContractError::Unauthorized(
596 "Sender is not an admin".to_owned(),
597 ));
598 }
599
600 if let Some(end_time) = config.extension.end_time {
601 if env.block.time >= end_time {
602 return Err(ContractError::AfterMintEndTime {});
603 }
604 }
605
606 if env.block.time >= config.extension.start_time && price >= config.mint_price.amount.u128() {
608 return Err(ContractError::UpdatedMintPriceTooHigh {
609 allowed: config.mint_price.amount.u128(),
610 updated: price,
611 });
612 }
613
614 let factory: ParamsResponse = deps
615 .querier
616 .query_wasm_smart(config.clone().factory, &Sg2QueryMsg::Params {})?;
617 let factory_params = factory.params;
618
619 if factory_params.min_mint_price.amount.u128() > price {
620 return Err(ContractError::InsufficientMintPrice {
621 expected: factory_params.min_mint_price.amount.u128(),
622 got: price,
623 });
624 }
625
626 if config.extension.num_tokens.is_none() {
627 ensure!(price != 0, ContractError::NoTokenLimitWithZeroMintPrice {})
628 }
629
630 config.mint_price = coin(price, config.mint_price.denom);
631 CONFIG.save(deps.storage, &config)?;
632 Ok(Response::new()
633 .add_attribute("action", "update_mint_price")
634 .add_attribute("sender", info.sender)
635 .add_attribute("mint_price", config.mint_price.to_string()))
636}
637
638pub fn execute_update_start_time(
639 deps: DepsMut,
640 env: Env,
641 info: MessageInfo,
642 start_time: Timestamp,
643) -> Result<Response, ContractError> {
644 nonpayable(&info)?;
645 let mut config = CONFIG.load(deps.storage)?;
646 if info.sender != config.extension.admin {
647 return Err(ContractError::Unauthorized(
648 "Sender is not an admin".to_owned(),
649 ));
650 }
651 if env.block.time >= config.extension.start_time {
653 return Err(ContractError::AlreadyStarted {});
654 }
655
656 if env.block.time > start_time {
658 return Err(ContractError::InvalidStartTime(start_time, env.block.time));
659 }
660
661 if let Some(end_time) = config.extension.end_time {
663 if start_time > end_time {
664 return Err(ContractError::InvalidStartTime(end_time, start_time));
665 }
666 }
667
668 config.extension.start_time = start_time;
669 CONFIG.save(deps.storage, &config)?;
670 Ok(Response::new()
671 .add_attribute("action", "update_start_time")
672 .add_attribute("sender", info.sender)
673 .add_attribute("start_time", start_time.to_string()))
674}
675
676pub fn execute_update_end_time(
677 deps: DepsMut,
678 env: Env,
679 info: MessageInfo,
680 end_time: Timestamp,
681) -> Result<Response, ContractError> {
682 nonpayable(&info)?;
683 let mut config = CONFIG.load(deps.storage)?;
684 if info.sender != config.extension.admin {
685 return Err(ContractError::Unauthorized(
686 "Sender is not an admin".to_owned(),
687 ));
688 }
689 if let Some(end_time_u) = config.extension.end_time {
691 if env.block.time >= end_time_u {
692 return Err(ContractError::AfterMintEndTime {});
693 }
694 } else {
695 return Err(ContractError::NoEndTimeInitiallyDefined {});
697 }
698
699 if env.block.time > end_time {
701 return Err(ContractError::InvalidEndTime(end_time, env.block.time));
702 }
703
704 if end_time < config.extension.start_time {
706 return Err(ContractError::InvalidEndTime(
707 end_time,
708 config.extension.start_time,
709 ));
710 }
711
712 config.extension.end_time = Some(end_time);
713 CONFIG.save(deps.storage, &config)?;
714 Ok(Response::new()
715 .add_attribute("action", "update_end_time")
716 .add_attribute("sender", info.sender)
717 .add_attribute("end_time", end_time.to_string()))
718}
719
720pub fn execute_update_start_trading_time(
721 deps: DepsMut,
722 env: Env,
723 info: MessageInfo,
724 start_time: Option<Timestamp>,
725) -> Result<Response, ContractError> {
726 nonpayable(&info)?;
727 let config = CONFIG.load(deps.storage)?;
728 let sg721_contract_addr = SG721_ADDRESS.load(deps.storage)?;
729
730 if info.sender != config.extension.admin {
731 return Err(ContractError::Unauthorized(
732 "Sender is not an admin".to_owned(),
733 ));
734 }
735
736 let factory_params: ParamsResponse = deps
738 .querier
739 .query_wasm_smart(config.factory.clone(), &Sg2QueryMsg::Params {})?;
740 let default_start_time_with_offset = config
741 .extension
742 .start_time
743 .plus_seconds(factory_params.params.max_trading_offset_secs);
744
745 if let Some(start_trading_time) = start_time {
746 if env.block.time > start_trading_time {
747 return Err(ContractError::InvalidStartTradingTime(
748 env.block.time,
749 start_trading_time,
750 ));
751 }
752 if start_trading_time > default_start_time_with_offset {
754 return Err(ContractError::InvalidStartTradingTime(
755 start_trading_time,
756 default_start_time_with_offset,
757 ));
758 }
759 }
760
761 let msg = WasmMsg::Execute {
763 contract_addr: sg721_contract_addr.to_string(),
764 msg: to_json_binary(&Sg721ExecuteMsg::<Empty, Empty>::UpdateStartTradingTime(
765 start_time,
766 ))?,
767 funds: vec![],
768 };
769
770 Ok(Response::new()
771 .add_attribute("action", "update_start_trading_time")
772 .add_attribute("sender", info.sender)
773 .add_message(msg))
774}
775
776pub fn execute_update_per_address_limit(
777 deps: DepsMut,
778 _env: Env,
779 info: MessageInfo,
780 per_address_limit: u32,
781) -> Result<Response, ContractError> {
782 nonpayable(&info)?;
783 let mut config = CONFIG.load(deps.storage)?;
784 if info.sender != config.extension.admin {
785 return Err(ContractError::Unauthorized(
786 "Sender is not an admin".to_owned(),
787 ));
788 }
789
790 let factory: ParamsResponse = deps
791 .querier
792 .query_wasm_smart(config.factory.clone(), &Sg2QueryMsg::Params {})?;
793 let factory_params = factory.params;
794
795 if per_address_limit == 0 || per_address_limit > factory_params.extension.max_per_address_limit
796 {
797 return Err(ContractError::InvalidPerAddressLimit {
798 max: factory_params.extension.max_per_address_limit,
799 min: 1,
800 got: per_address_limit,
801 });
802 }
803
804 config.extension.per_address_limit = per_address_limit;
805 CONFIG.save(deps.storage, &config)?;
806 Ok(Response::new()
807 .add_attribute("action", "update_per_address_limit")
808 .add_attribute("sender", info.sender)
809 .add_attribute("limit", per_address_limit.to_string()))
810}
811
812pub fn mint_price(deps: Deps, is_admin: bool) -> Result<Coin, StdError> {
816 let config = CONFIG.load(deps.storage)?;
817
818 if is_admin {
819 let factory: ParamsResponse = deps
820 .querier
821 .query_wasm_smart(config.factory, &Sg2QueryMsg::Params {})?;
822 let factory_params = factory.params;
823 if factory_params.extension.airdrop_mint_price.amount.is_zero() {
824 ensure!(
825 config.extension.num_tokens.is_some(),
826 StdError::generic_err(
827 "Open Edition collections should have a non-zero airdrop price"
828 )
829 );
830 }
831 Ok(coin(
832 factory_params.extension.airdrop_mint_price.amount.u128(),
833 factory_params.extension.airdrop_mint_price.denom,
834 ))
835 } else {
836 if config.extension.whitelist.is_none() {
837 return Ok(config.mint_price.clone());
838 }
839 let whitelist = config.extension.whitelist.unwrap();
840 let whitelist_config: WhitelistConfigResponse = deps
841 .querier
842 .query_wasm_smart(whitelist, &WhitelistQueryMsg::Config {})?;
843
844 if whitelist_config.is_active {
845 Ok(whitelist_config.mint_price)
846 } else {
847 Ok(config.mint_price.clone())
848 }
849 }
850}
851
852pub fn execute_burn_remaining(
853 deps: DepsMut,
854 env: Env,
855 info: MessageInfo,
856) -> Result<Response, ContractError> {
857 nonpayable(&info)?;
858 let config = CONFIG.load(deps.storage)?;
859 if info.sender != config.extension.admin {
861 return Err(ContractError::Unauthorized(
862 "Sender is not an admin".to_owned(),
863 ));
864 }
865
866 if let Some(end_time) = config.extension.end_time {
868 if env.block.time <= end_time {
869 return Err(ContractError::MintingHasNotYetEnded {});
870 }
871 }
872
873 let mintable_num_tokens = MINTABLE_NUM_TOKENS.may_load(deps.storage)?;
875 if let Some(mintable_nb_tokens) = mintable_num_tokens {
876 if mintable_nb_tokens == 0 {
877 return Err(ContractError::SoldOut {});
878 }
879 }
880
881 if mintable_num_tokens.is_some() {
883 MINTABLE_NUM_TOKENS.save(deps.storage, &0)?;
884 }
885
886 let event = Event::new("burn-remaining")
887 .add_attribute("sender", info.sender)
888 .add_attribute("tokens_burned", mintable_num_tokens.unwrap().to_string())
889 .add_attribute("minter", env.contract.address.to_string());
890 Ok(Response::new().add_event(event))
891}
892
893fn mint_count_per_addr(deps: Deps, info: &MessageInfo) -> Result<u32, StdError> {
894 let mint_count = (MINTER_ADDRS.key(&info.sender).may_load(deps.storage)?).unwrap_or(0);
895 Ok(mint_count)
896}
897
898#[cfg_attr(not(feature = "library"), entry_point)]
899pub fn sudo(deps: DepsMut, _env: Env, msg: SudoMsg) -> Result<Response, ContractError> {
900 match msg {
901 SudoMsg::UpdateStatus {
902 is_verified,
903 is_blocked,
904 is_explicit,
905 } => update_status(deps, is_verified, is_blocked, is_explicit)
906 .map_err(|_| ContractError::UpdateStatus {}),
907 }
908}
909
910pub fn update_status(
912 deps: DepsMut,
913 is_verified: bool,
914 is_blocked: bool,
915 is_explicit: bool,
916) -> StdResult<Response> {
917 let mut status = STATUS.load(deps.storage)?;
918 status.is_verified = is_verified;
919 status.is_blocked = is_blocked;
920 status.is_explicit = is_explicit;
921
922 Ok(Response::new().add_attribute("action", "sudo_update_status"))
923}
924
925#[cfg_attr(not(feature = "library"), entry_point)]
926pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
927 match msg {
928 QueryMsg::Config {} => to_json_binary(&query_config(deps)?),
929 QueryMsg::Status {} => to_json_binary(&query_status(deps)?),
930 QueryMsg::StartTime {} => to_json_binary(&query_start_time(deps)?),
931 QueryMsg::EndTime {} => to_json_binary(&query_end_time(deps)?),
932 QueryMsg::MintPrice {} => to_json_binary(&query_mint_price(deps)?),
933 QueryMsg::MintCount { address } => {
934 to_json_binary(&query_mint_count_per_address(deps, address)?)
935 }
936 QueryMsg::TotalMintCount {} => to_json_binary(&query_mint_count(deps)?),
937 QueryMsg::MintableNumTokens {} => to_json_binary(&query_mintable_num_tokens(deps)?),
938 }
939}
940
941fn query_config(deps: Deps) -> StdResult<ConfigResponse> {
942 let config = CONFIG.load(deps.storage)?;
943 let sg721_address = SG721_ADDRESS.load(deps.storage)?;
944
945 Ok(ConfigResponse {
946 admin: config.extension.admin.to_string(),
947 nft_data: config.extension.nft_data,
948 payment_address: config.extension.payment_address,
949 per_address_limit: config.extension.per_address_limit,
950 num_tokens: config.extension.num_tokens,
951 end_time: config.extension.end_time,
952 sg721_address: sg721_address.to_string(),
953 sg721_code_id: config.collection_code_id,
954 start_time: config.extension.start_time,
955 mint_price: config.mint_price,
956 factory: config.factory.to_string(),
957 whitelist: config.extension.whitelist.map(|w| w.to_string()),
958 })
959}
960
961pub fn query_status(deps: Deps) -> StdResult<StatusResponse> {
962 let status = STATUS.load(deps.storage)?;
963
964 Ok(StatusResponse { status })
965}
966
967fn query_mint_count_per_address(deps: Deps, address: String) -> StdResult<MintCountResponse> {
968 let addr = deps.api.addr_validate(&address)?;
969 let mint_count = (MINTER_ADDRS.key(&addr).may_load(deps.storage)?).unwrap_or(0);
970 Ok(MintCountResponse {
971 address: addr.to_string(),
972 count: mint_count,
973 })
974}
975
976fn query_mint_count(deps: Deps) -> StdResult<TotalMintCountResponse> {
977 let mint_count = TOTAL_MINT_COUNT.load(deps.storage)?;
978 Ok(TotalMintCountResponse { count: mint_count })
979}
980
981fn query_mintable_num_tokens(deps: Deps) -> StdResult<MintableNumTokensResponse> {
982 let count = MINTABLE_NUM_TOKENS.may_load(deps.storage)?;
983 Ok(MintableNumTokensResponse { count })
984}
985
986fn query_start_time(deps: Deps) -> StdResult<StartTimeResponse> {
987 let config = CONFIG.load(deps.storage)?;
988 Ok(StartTimeResponse {
989 start_time: config.extension.start_time.to_string(),
990 })
991}
992
993fn query_end_time(deps: Deps) -> StdResult<EndTimeResponse> {
994 let config = CONFIG.load(deps.storage)?;
995 let end_time_response = config
996 .extension
997 .end_time
998 .map(|end_time| EndTimeResponse {
999 end_time: Some(end_time.to_string()),
1000 })
1001 .unwrap_or(EndTimeResponse { end_time: None });
1002
1003 Ok(end_time_response)
1004}
1005
1006fn query_mint_price(deps: Deps) -> StdResult<MintPriceResponse> {
1007 let config = CONFIG.load(deps.storage)?;
1008
1009 let factory: ParamsResponse = deps
1010 .querier
1011 .query_wasm_smart(config.factory, &Sg2QueryMsg::Params {})?;
1012
1013 let factory_params = factory.params;
1014
1015 let current_price = mint_price(deps, false)?;
1016 let public_price = config.mint_price.clone();
1017 let whitelist_price: Option<Coin> = if let Some(whitelist) = config.extension.whitelist {
1018 let wl_config: WhitelistConfigResponse = deps
1019 .querier
1020 .query_wasm_smart(whitelist, &WhitelistQueryMsg::Config {})?;
1021 Some(wl_config.mint_price)
1022 } else {
1023 None
1024 };
1025 let airdrop_price = coin(
1026 factory_params.extension.airdrop_mint_price.amount.u128(),
1027 config.mint_price.denom,
1028 );
1029 Ok(MintPriceResponse {
1030 public_price,
1031 airdrop_price,
1032 whitelist_price,
1033 current_price,
1034 })
1035}
1036
1037#[cfg_attr(not(feature = "library"), entry_point)]
1039pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result<Response, ContractError> {
1040 if msg.id != INSTANTIATE_SG721_REPLY_ID {
1041 return Err(ContractError::InvalidReplyID {});
1042 }
1043
1044 let reply = parse_reply_instantiate_data(msg);
1045 match reply {
1046 Ok(res) => {
1047 let sg721_address = res.contract_address;
1048 SG721_ADDRESS.save(deps.storage, &Addr::unchecked(sg721_address.clone()))?;
1049 Ok(Response::default()
1050 .add_attribute("action", "instantiate_sg721_reply")
1051 .add_attribute("sg721_address", sg721_address))
1052 }
1053 Err(_) => Err(ContractError::InstantiateSg721Error {}),
1054 }
1055}
1056
1057#[cfg_attr(not(feature = "library"), entry_point)]
1058pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> Result<Response, ContractError> {
1059 let current_version = cw2::get_contract_version(deps.storage)?;
1060 if current_version.contract != CONTRACT_NAME {
1061 return Err(StdError::generic_err("Cannot upgrade to a different contract").into());
1062 }
1063 let version: Version = current_version
1064 .version
1065 .parse()
1066 .map_err(|_| StdError::generic_err("Invalid contract version"))?;
1067 let new_version: Version = CONTRACT_VERSION
1068 .parse()
1069 .map_err(|_| StdError::generic_err("Invalid contract version"))?;
1070
1071 if version > new_version {
1072 return Err(StdError::generic_err("Cannot upgrade to a previous contract version").into());
1073 }
1074 if version == new_version {
1076 return Ok(Response::new());
1077 }
1078
1079 set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
1081 Ok(Response::new())
1082}