1use crate::error::ContractError;
2use crate::msg::{ConfigResponse, ExecuteMsg};
3use crate::state::{increment_token_index, Config, COLLECTION_ADDRESS, CONFIG, STATUS};
4use base_factory::msg::{BaseMinterCreateMsg, ParamsResponse};
5use base_factory::state::Extension;
6#[cfg(not(feature = "library"))]
7use cosmwasm_std::entry_point;
8use cosmwasm_std::{
9 to_json_binary, Addr, Binary, CosmosMsg, Decimal, Deps, DepsMut, Empty, Env, MessageInfo,
10 Reply, StdResult, Timestamp, WasmMsg,
11};
12use cw2::set_contract_version;
13use cw_utils::{must_pay, nonpayable, parse_reply_instantiate_data};
14use sg1::checked_fair_burn;
15use sg2::query::Sg2QueryMsg;
16use sg4::{QueryMsg, Status, StatusResponse, SudoMsg};
17use sg721::{ExecuteMsg as Sg721ExecuteMsg, InstantiateMsg as Sg721InstantiateMsg};
18use sg721_base::msg::{CollectionInfoResponse, QueryMsg as Sg721QueryMsg};
19use sg_std::{Response, SubMsg, NATIVE_DENOM};
20use url::Url;
21
22const CONTRACT_NAME: &str = "crates.io:sg-base-minter";
23const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
24const INSTANTIATE_SG721_REPLY_ID: u64 = 1;
25
26#[cfg_attr(not(feature = "library"), entry_point)]
27pub fn instantiate(
28 deps: DepsMut,
29 env: Env,
30 info: MessageInfo,
31 msg: BaseMinterCreateMsg,
32) -> Result<Response, ContractError> {
33 set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
34
35 let factory = info.sender.clone();
36
37 STATUS.save(deps.storage, &Status::default())?;
39
40 let factory_params: ParamsResponse = deps
43 .querier
44 .query_wasm_smart(factory.clone(), &Sg2QueryMsg::Params {})?;
45
46 let config = Config {
47 factory: factory.clone(),
48 collection_code_id: msg.collection_params.code_id,
49 mint_price: factory_params.params.min_mint_price,
52 extension: Empty {},
53 };
54
55 let mut collection_info = msg.collection_params.info.clone();
57 let offset = factory_params.params.max_trading_offset_secs;
58 let start_trading_time = msg
59 .collection_params
60 .info
61 .start_trading_time
62 .or_else(|| Some(env.block.time.plus_seconds(offset)));
63 collection_info.start_trading_time = start_trading_time;
64
65 CONFIG.save(deps.storage, &config)?;
66
67 let wasm_msg = WasmMsg::Instantiate {
68 code_id: msg.collection_params.code_id,
69 msg: to_json_binary(&Sg721InstantiateMsg {
70 name: msg.collection_params.name.clone(),
71 symbol: msg.collection_params.symbol,
72 minter: env.contract.address.to_string(),
73 collection_info,
74 })?,
75 funds: info.funds,
76 admin: Some(
77 deps.api
78 .addr_validate(&msg.collection_params.info.creator)?
79 .to_string(),
80 ),
81 label: format!(
82 "SG721-{}-{}",
83 msg.collection_params.code_id,
84 msg.collection_params.name.trim()
85 ),
86 };
87 let submsg = SubMsg::reply_on_success(wasm_msg, INSTANTIATE_SG721_REPLY_ID);
88
89 Ok(Response::new()
90 .add_attribute("action", "instantiate")
91 .add_attribute("contract_name", CONTRACT_NAME)
92 .add_attribute("contract_version", CONTRACT_VERSION)
93 .add_attribute("sender", factory)
94 .add_submessage(submsg))
95}
96
97#[cfg_attr(not(feature = "library"), entry_point)]
98pub fn execute(
99 deps: DepsMut,
100 env: Env,
101 info: MessageInfo,
102 msg: ExecuteMsg,
103) -> Result<Response, ContractError> {
104 match msg {
105 ExecuteMsg::Mint { token_uri } => execute_mint_sender(deps, info, token_uri),
106 ExecuteMsg::UpdateStartTradingTime(time) => {
107 execute_update_start_trading_time(deps, env, info, time)
108 }
109 }
110}
111
112pub fn execute_mint_sender(
113 deps: DepsMut,
114 info: MessageInfo,
115 token_uri: String,
116) -> Result<Response, ContractError> {
117 let config = CONFIG.load(deps.storage)?;
118 let collection_address = COLLECTION_ADDRESS.load(deps.storage)?;
119
120 let collection_info: CollectionInfoResponse = deps.querier.query_wasm_smart(
123 collection_address.clone(),
124 &Sg721QueryMsg::CollectionInfo {},
125 )?;
126 if collection_info.creator != info.sender {
128 return Err(ContractError::Unauthorized(
129 "Sender is not sg721 creator".to_owned(),
130 ));
131 };
132
133 Url::parse(&token_uri).map_err(|_| ContractError::InvalidTokenURI {})?;
135
136 let mut res = Response::new();
137
138 let factory: ParamsResponse = deps
139 .querier
140 .query_wasm_smart(config.factory, &Sg2QueryMsg::Params {})?;
141 let factory_params = factory.params;
142
143 let funds_sent = must_pay(&info, NATIVE_DENOM)?;
144
145 let mint_fee_percent = Decimal::bps(factory_params.mint_fee_bps);
147 let network_fee = config.mint_price.amount * mint_fee_percent;
148 if network_fee != funds_sent {
150 return Err(ContractError::InvalidMintPrice {});
151 }
152 checked_fair_burn(&info, network_fee.u128(), None, &mut res)?;
153
154 let mint_msg = Sg721ExecuteMsg::<Extension, Empty>::Mint {
156 token_id: increment_token_index(deps.storage)?.to_string(),
157 owner: info.sender.to_string(),
158 token_uri: Some(token_uri.clone()),
159 extension: None,
160 };
161 let msg = CosmosMsg::Wasm(WasmMsg::Execute {
162 contract_addr: collection_address.to_string(),
163 msg: to_json_binary(&mint_msg)?,
164 funds: vec![],
165 });
166 res = res.add_message(msg);
167
168 Ok(res
169 .add_attribute("action", "mint")
170 .add_attribute("sender", info.sender)
171 .add_attribute("token_uri", token_uri)
172 .add_attribute("network_fee", network_fee.to_string()))
173}
174
175pub fn execute_update_start_trading_time(
176 deps: DepsMut,
177 env: Env,
178 info: MessageInfo,
179 start_time: Option<Timestamp>,
180) -> Result<Response, ContractError> {
181 nonpayable(&info)?;
182 let sg721_contract_addr = COLLECTION_ADDRESS.load(deps.storage)?;
183
184 let collection_info: CollectionInfoResponse = deps.querier.query_wasm_smart(
185 sg721_contract_addr.clone(),
186 &Sg721QueryMsg::CollectionInfo {},
187 )?;
188 if info.sender != collection_info.creator {
189 return Err(ContractError::Unauthorized(
190 "Sender is not creator".to_owned(),
191 ));
192 }
193
194 if let Some(start_time) = start_time {
196 if env.block.time > start_time {
197 return Err(ContractError::InvalidStartTradingTime(
198 env.block.time,
199 start_time,
200 ));
201 }
202 }
203
204 let msg = WasmMsg::Execute {
206 contract_addr: sg721_contract_addr.to_string(),
207 msg: to_json_binary(
208 &Sg721ExecuteMsg::<Extension, Empty>::UpdateStartTradingTime(start_time),
209 )?,
210 funds: vec![],
211 };
212
213 Ok(Response::new()
214 .add_attribute("action", "update_start_time")
215 .add_attribute("sender", info.sender)
216 .add_message(msg))
217}
218
219#[cfg_attr(not(feature = "library"), entry_point)]
220pub fn sudo(deps: DepsMut, _env: Env, msg: SudoMsg) -> Result<Response, ContractError> {
221 match msg {
222 SudoMsg::UpdateStatus {
223 is_verified,
224 is_blocked,
225 is_explicit,
226 } => update_status(deps, is_verified, is_blocked, is_explicit)
227 .map_err(|_| ContractError::UpdateStatus {}),
228 }
229}
230
231pub fn update_status(
233 deps: DepsMut,
234 is_verified: bool,
235 is_blocked: bool,
236 is_explicit: bool,
237) -> StdResult<Response> {
238 let mut status = STATUS.load(deps.storage)?;
239 status.is_verified = is_verified;
240 status.is_blocked = is_blocked;
241 status.is_explicit = is_explicit;
242 STATUS.save(deps.storage, &status)?;
243
244 Ok(Response::new().add_attribute("action", "sudo_update_status"))
245}
246
247#[cfg_attr(not(feature = "library"), entry_point)]
248pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
249 match msg {
250 QueryMsg::Config {} => to_json_binary(&query_config(deps)?),
251 QueryMsg::Status {} => to_json_binary(&query_status(deps)?),
252 }
253}
254
255fn query_config(deps: Deps) -> StdResult<ConfigResponse> {
256 let config = CONFIG.load(deps.storage)?;
257 let collection_address = COLLECTION_ADDRESS.load(deps.storage)?;
258
259 Ok(ConfigResponse {
260 collection_address: collection_address.to_string(),
261 config,
262 })
263}
264
265pub fn query_status(deps: Deps) -> StdResult<StatusResponse> {
266 let status = STATUS.load(deps.storage)?;
267
268 Ok(StatusResponse { status })
269}
270
271#[cfg_attr(not(feature = "library"), entry_point)]
273pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result<Response, ContractError> {
274 if msg.id != INSTANTIATE_SG721_REPLY_ID {
275 return Err(ContractError::InvalidReplyID {});
276 }
277
278 let reply = parse_reply_instantiate_data(msg);
279 match reply {
280 Ok(res) => {
281 let collection_address = res.contract_address;
282 COLLECTION_ADDRESS.save(deps.storage, &Addr::unchecked(collection_address.clone()))?;
283 Ok(Response::default()
284 .add_attribute("action", "instantiate_sg721_reply")
285 .add_attribute("sg721_address", collection_address))
286 }
287 Err(_) => Err(ContractError::InstantiateSg721Error {}),
288 }
289}