Repository: public-awesome/launchpad
Pull request #351 (2022-08-20 21:19:58)

Factory + minter refactoring
Fixes most of the comments from @yubrew code review.

* Move minter status from factory -> minter
* Remove factory controller in favor of common code in base factory
* Remove reply that's not needed after the minter status change



Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 0b6d0e82..55d45ced 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -832,6 +832,7 @@ dependencies = [
  "serde",
  "sg-std",
  "sg2",
+ "sg3",
  "thiserror",
 ]
 
@@ -1157,6 +1158,7 @@ dependencies = [
 name = "vending-factory"
 version = "0.1.0"
 dependencies = [
+ "base-factory",
  "cosmwasm-schema",
  "cosmwasm-std",
  "cosmwasm-storage",
diff --git a/contracts/base-factory/schema/sg2_query_msg.json b/contracts/base-factory/schema/sg2_query_msg.json
index 0dc3c482..e951e40f 100644
--- a/contracts/base-factory/schema/sg2_query_msg.json
+++ b/contracts/base-factory/schema/sg2_query_msg.json
@@ -14,27 +14,6 @@
         }
       },
       "additionalProperties": false
-    },
-    {
-      "description": "Returns a `MinterStatusResponse`",
-      "type": "object",
-      "required": [
-        "minter_status"
-      ],
-      "properties": {
-        "minter_status": {
-          "type": "object",
-          "required": [
-            "minter"
-          ],
-          "properties": {
-            "minter": {
-              "type": "string"
-            }
-          }
-        }
-      },
-      "additionalProperties": false
     }
   ]
 }
diff --git a/contracts/base-factory/src/contract.rs b/contracts/base-factory/src/contract.rs
index 4a66b91b..5d1374e8 100644
--- a/contracts/base-factory/src/contract.rs
+++ b/contracts/base-factory/src/contract.rs
@@ -1,19 +1,22 @@
 #[cfg(not(feature = "library"))]
 use cosmwasm_std::entry_point;
-use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, StdResult, WasmMsg};
+use cosmwasm_std::{
+    ensure_eq, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, StdResult, WasmMsg,
+};
 use cw2::set_contract_version;
 use cw_utils::must_pay;
 use sg1::checked_fair_burn;
+use sg2::msg::UpdateMinterParamsMsg;
 use sg2::query::Sg2QueryMsg;
+use sg2::MinterParams;
 use sg_std::NATIVE_DENOM;
 
 use crate::error::ContractError;
 use crate::msg::{
     BaseMinterCreateMsg, BaseSudoMsg, BaseUpdateParamsMsg, ExecuteMsg, InstantiateMsg,
-    ParamsResponse, Response, SubMsg, SudoMsg,
+    ParamsResponse, Response, SudoMsg,
 };
 use crate::state::SUDO_PARAMS;
-use sg_controllers::{handle_reply, query_minter_status, update_params, upsert_minter_status};
 
 // version info for migration info
 const CONTRACT_NAME: &str = "crates.io:sg-base-factory";
@@ -59,36 +62,23 @@ pub fn execute_create_base_minter(
     let mut res = Response::new();
     checked_fair_burn(&info, params.creation_fee.amount.u128(), None, &mut res)?;
 
-    let wasm_msg = WasmMsg::Instantiate {
+    let msg = WasmMsg::Instantiate {
         admin: Some(info.sender.to_string()),
         code_id: params.code_id,
         msg: to_binary(&msg)?,
         funds: vec![],
         label: format!("BaseMinter-{}", msg.collection_params.name),
     };
-    let submsg = SubMsg::reply_on_success(wasm_msg, params.code_id);
 
     Ok(res
         .add_attribute("action", "create_minter")
-        .add_submessage(submsg))
-}
-
-/// Reply callback triggered from creation above (minter init)
-#[cfg_attr(not(feature = "library"), entry_point)]
-pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result<Response, ContractError> {
-    handle_reply(deps, msg).map_err(|e| e.into())
+        .add_message(msg))
 }
 
 #[cfg_attr(not(feature = "library"), entry_point)]
 pub fn sudo(deps: DepsMut, env: Env, msg: BaseSudoMsg) -> Result<Response, ContractError> {
     match msg {
         SudoMsg::UpdateParams(params_msg) => sudo_update_params(deps, env, *params_msg),
-        SudoMsg::UpdateMinterStatus {
-            minter,
-            verified,
-            blocked,
-        } => upsert_minter_status(deps, minter, verified, blocked)
-            .map_err(|_| ContractError::MinterFactoryError {}),
     }
 }
 
@@ -107,11 +97,40 @@ pub fn sudo_update_params(
     Ok(Response::new().add_attribute("action", "sudo_update_params"))
 }
 
+/// Base update params that can be used by other minter factories
+pub fn update_params<T, C>(
+    params: &mut MinterParams<C>,
+    param_msg: UpdateMinterParamsMsg<T>,
+) -> Result<(), ContractError> {
+    params.code_id = param_msg.code_id.unwrap_or(params.code_id);
+
+    if let Some(creation_fee) = param_msg.creation_fee {
+        ensure_eq!(
+            &creation_fee.denom,
+            &NATIVE_DENOM,
+            ContractError::InvalidDenom {}
+        );
+        params.creation_fee = creation_fee;
+    }
+
+    if let Some(min_mint_price) = param_msg.min_mint_price {
+        ensure_eq!(
+            &min_mint_price.denom,
+            &NATIVE_DENOM,
+            ContractError::InvalidDenom {}
+        );
+        params.min_mint_price = min_mint_price;
+    }
+
+    params.mint_fee_bps = param_msg.mint_fee_bps.unwrap_or(params.mint_fee_bps);
+
+    Ok(())
+}
+
 #[cfg_attr(not(feature = "library"), entry_point)]
 pub fn query(deps: Deps, _env: Env, msg: Sg2QueryMsg) -> StdResult<Binary> {
     match msg {
         Sg2QueryMsg::Params {} => to_binary(&query_params(deps)?),
-        Sg2QueryMsg::MinterStatus { minter } => to_binary(&query_minter_status(deps, minter)?),
     }
 }
 
diff --git a/contracts/base-factory/src/error.rs b/contracts/base-factory/src/error.rs
index c8055989..68fe7b1f 100644
--- a/contracts/base-factory/src/error.rs
+++ b/contracts/base-factory/src/error.rs
@@ -1,7 +1,6 @@
 use cosmwasm_std::StdError;
 use cw_utils::PaymentError;
 use sg1::FeeError;
-use sg_controllers::MinterFactoryError;
 use thiserror::Error;
 
 #[derive(Error, Debug, PartialEq)]
@@ -15,12 +14,9 @@ pub enum ContractError {
     #[error("{0}")]
     Payment(#[from] PaymentError),
 
-    #[error("{0}")]
-    MinterFactory(#[from] MinterFactoryError),
-
     #[error("Unauthorized")]
     Unauthorized {},
 
-    #[error("MinterFactoryError")]
-    MinterFactoryError {},
+    #[error("InvalidDenom")]
+    InvalidDenom {},
 }
diff --git a/contracts/base-factory/src/msg.rs b/contracts/base-factory/src/msg.rs
index 0de6aab1..f0128d24 100644
--- a/contracts/base-factory/src/msg.rs
+++ b/contracts/base-factory/src/msg.rs
@@ -26,11 +26,6 @@ pub type BaseUpdateParamsMsg = UpdateMinterParamsMsg<Extension>;
 #[serde(rename_all = "snake_case")]
 pub enum SudoMsg<T> {
     UpdateParams(Box<T>),
-    UpdateMinterStatus {
-        minter: String,
-        verified: bool,
-        blocked: bool,
-    },
 }
 pub type BaseSudoMsg = SudoMsg<BaseUpdateParamsMsg>;
 
diff --git a/contracts/base-minter/schema/query_msg.json b/contracts/base-minter/schema/query_msg.json
index 3df1ec13..cc86a34c 100644
--- a/contracts/base-minter/schema/query_msg.json
+++ b/contracts/base-minter/schema/query_msg.json
@@ -13,6 +13,18 @@
         }
       },
       "additionalProperties": false
+    },
+    {
+      "type": "object",
+      "required": [
+        "status"
+      ],
+      "properties": {
+        "status": {
+          "type": "object"
+        }
+      },
+      "additionalProperties": false
     }
   ]
 }
diff --git a/contracts/base-minter/src/contract.rs b/contracts/base-minter/src/contract.rs
index 65573f77..10e931cb 100644
--- a/contracts/base-minter/src/contract.rs
+++ b/contracts/base-minter/src/contract.rs
@@ -1,14 +1,14 @@
 use crate::error::ContractError;
-use crate::msg::{ConfigResponse, ExecuteMsg, QueryMsg};
-use crate::state::{increment_token_index, Config, COLLECTION_ADDRESS, CONFIG};
+use crate::msg::{ConfigResponse, ExecuteMsg, QueryMsg, SudoMsg};
+use crate::state::{increment_token_index, Config, COLLECTION_ADDRESS, CONFIG, STATUS};
 
 use base_factory::msg::{BaseMinterCreateMsg, Extension, ParamsResponse};
 
 #[cfg(not(feature = "library"))]
 use cosmwasm_std::entry_point;
 use cosmwasm_std::{
-    to_binary, Addr, Binary, CosmosMsg, Deps, DepsMut, Empty, Env, MessageInfo, Reply, ReplyOn,
-    StdResult, WasmMsg,
+    to_binary, Addr, Binary, CosmosMsg, Deps, DepsMut, Empty, Env, MessageInfo, Reply, StdResult,
+    WasmMsg,
 };
 
 use cw2::set_contract_version;
@@ -17,6 +17,7 @@ use cw_utils::{must_pay, parse_reply_instantiate_data};
 
 use sg1::checked_fair_burn;
 use sg2::query::Sg2QueryMsg;
+use sg3::{Status, StatusResponse};
 use sg721::{ExecuteMsg as Sg721ExecuteMsg, InstantiateMsg as Sg721InstantiateMsg};
 use sg_std::math::U64Ext;
 use sg_std::{StargazeMsgWrapper, NATIVE_DENOM};
@@ -25,10 +26,8 @@ use url::Url;
 pub type Response = cosmwasm_std::Response<StargazeMsgWrapper>;
 pub type SubMsg = cosmwasm_std::SubMsg<StargazeMsgWrapper>;
 
-// version info for migration info
 const CONTRACT_NAME: &str = "crates.io:sg-base-minter";
 const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
-
 const INSTANTIATE_SG721_REPLY_ID: u64 = 1;
 
 #[cfg_attr(not(feature = "library"), entry_point)]
@@ -42,6 +41,9 @@ pub fn instantiate(
 
     let factory = info.sender.clone();
 
+    // set default status so it can be queried without failing
+    STATUS.save(deps.storage, &Status::default())?;
+
     // Make sure the sender is the factory contract
     // This will fail if the sender cannot parse a response from the factory contract
     let res: ParamsResponse = deps
@@ -59,25 +61,19 @@ pub fn instantiate(
 
     CONFIG.save(deps.storage, &config)?;
 
-    // Submessage to instantiate sg721 contract
-    let submsg = SubMsg {
-        msg: WasmMsg::Instantiate {
-            code_id: msg.collection_params.code_id,
-            msg: to_binary(&Sg721InstantiateMsg {
-                name: msg.collection_params.name.clone(),
-                symbol: msg.collection_params.symbol,
-                minter: env.contract.address.to_string(),
-                collection_info: msg.collection_params.info,
-            })?,
-            funds: info.funds,
-            admin: None,
-            label: format!("SG721-{}", msg.collection_params.name),
-        }
-        .into(),
-        id: INSTANTIATE_SG721_REPLY_ID,
-        gas_limit: None,
-        reply_on: ReplyOn::Success,
+    let wasm_msg = WasmMsg::Instantiate {
+        code_id: msg.collection_params.code_id,
+        msg: to_binary(&Sg721InstantiateMsg {
+            name: msg.collection_params.name.clone(),
+            symbol: msg.collection_params.symbol,
+            minter: env.contract.address.to_string(),
+            collection_info: msg.collection_params.info,
+        })?,
+        funds: info.funds,
+        admin: None,
+        label: format!("SG721-{}", msg.collection_params.name),
     };
+    let submsg = SubMsg::reply_on_success(wasm_msg, INSTANTIATE_SG721_REPLY_ID);
 
     Ok(Response::new()
         .add_attribute("action", "instantiate")
@@ -150,10 +146,38 @@ pub fn execute_mint_sender(
         .add_attribute("network_fee", network_fee.to_string()))
 }
 
+#[cfg_attr(not(feature = "library"), entry_point)]
+pub fn sudo(deps: DepsMut, _env: Env, msg: SudoMsg) -> Result<Response, ContractError> {
+    match msg {
+        SudoMsg::UpdateStatus {
+            is_verified,
+            is_blocked,
+            is_explicit,
+        } => update_status(deps, is_verified, is_blocked, is_explicit)
+            .map_err(|_| ContractError::UpdateStatus {}),
+    }
+}
+
+/// Only governance can update contract params
+pub fn update_status(
+    deps: DepsMut,
+    is_verified: bool,
+    is_blocked: bool,
+    is_explicit: bool,
+) -> StdResult<Response> {
+    let mut status = STATUS.load(deps.storage)?;
+    status.is_verified = is_verified;
+    status.is_blocked = is_blocked;
+    status.is_explicit = is_explicit;
+
+    Ok(Response::new().add_attribute("action", "sudo_update_status"))
+}
+
 #[cfg_attr(not(feature = "library"), entry_point)]
 pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
     match msg {
         QueryMsg::Config {} => to_binary(&query_config(deps)?),
+        QueryMsg::Status {} => to_binary(&query_status(deps)?),
     }
 }
 
@@ -167,6 +191,12 @@ fn query_config(deps: Deps) -> StdResult<ConfigResponse> {
     })
 }
 
+pub fn query_status(deps: Deps) -> StdResult<StatusResponse> {
+    let status = STATUS.load(deps.storage)?;
+
+    Ok(StatusResponse { status })
+}
+
 // Reply callback triggered from sg721 contract instantiation in instantiate()
 #[cfg_attr(not(feature = "library"), entry_point)]
 pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result<Response, ContractError> {
diff --git a/contracts/base-minter/src/error.rs b/contracts/base-minter/src/error.rs
index 08dae360..d45ec2c8 100644
--- a/contracts/base-minter/src/error.rs
+++ b/contracts/base-minter/src/error.rs
@@ -21,6 +21,9 @@ pub enum ContractError {
     #[error("Unauthorized: {0}")]
     Unauthorized(String),
 
+    #[error("UpdateStatus")]
+    UpdateStatus {},
+
     #[error("InvalidMintPrice")]
     InvalidMintPrice {},
 
diff --git a/contracts/base-minter/src/msg.rs b/contracts/base-minter/src/msg.rs
index ffaf1f93..3010fbad 100644
--- a/contracts/base-minter/src/msg.rs
+++ b/contracts/base-minter/src/msg.rs
@@ -20,6 +20,17 @@ pub enum ExecuteMsg {
 #[serde(rename_all = "snake_case")]
 pub enum QueryMsg {
     Config {},
+    Status {},
 }
 
 pub type ConfigResponse = MinterConfigResponse<Empty>;
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum SudoMsg {
+    UpdateStatus {
+        is_verified: bool,
+        is_blocked: bool,
+        is_explicit: bool,
+    },
+}
diff --git a/contracts/base-minter/src/state.rs b/contracts/base-minter/src/state.rs
index 2c78cab4..59c9b572 100644
--- a/contracts/base-minter/src/state.rs
+++ b/contracts/base-minter/src/state.rs
@@ -1,6 +1,6 @@
 use cosmwasm_std::{Addr, Empty, StdResult, Storage};
 use cw_storage_plus::Item;
-use sg3::MinterConfig;
+use sg3::{MinterConfig, Status};
 
 pub type Config = MinterConfig<Empty>;
 
@@ -10,6 +10,8 @@ pub const CONFIG: Item<Config> = Item::new("config");
 /// This is saved after handling a reply in instantiation. Therefore it's not in `Config`.
 pub const COLLECTION_ADDRESS: Item<Addr> = Item::new("collection_address");
 
+pub const STATUS: Item<Status> = Item::new("status");
+
 /// This keeps track of the token index for the token_ids
 pub const TOKEN_INDEX: Item<u64> = Item::new("token_index");
 
diff --git a/contracts/sg721-base/src/contract.rs b/contracts/sg721-base/src/contract.rs
index a7162657..91c394a1 100644
--- a/contracts/sg721-base/src/contract.rs
+++ b/contracts/sg721-base/src/contract.rs
@@ -1,5 +1,3 @@
-#[cfg(not(feature = "library"))]
-use cosmwasm_std::entry_point;
 use cw721_base::state::TokenInfo;
 use cw721_base::Extension;
 use url::Url;
@@ -102,8 +100,6 @@ pub fn ready(
     _env: Env,
     info: MessageInfo,
 ) -> Result<Response, ContractError> {
-    println!("ready");
-
     let minter = Cw721Base::default().minter.load(deps.storage)?;
     if minter != info.sender {
         return Err(ContractError::Unauthorized {});
diff --git a/contracts/sg721-base/src/lib.rs b/contracts/sg721-base/src/lib.rs
index 3e34a385..baf569c6 100644
--- a/contracts/sg721-base/src/lib.rs
+++ b/contracts/sg721-base/src/lib.rs
@@ -7,7 +7,7 @@ pub mod integration_tests;
 pub mod msg;
 pub mod state;
 pub use crate::error::ContractError;
-pub use cw721_base::Extension;
+use cw721_base::Extension;
 use sg721::InstantiateMsg;
 use sg_std::StargazeMsgWrapper;
 
diff --git a/contracts/sg721-nt/Cargo.toml b/contracts/sg721-nt/Cargo.toml
index 80bc3d6b..fdadd42f 100644
--- a/contracts/sg721-nt/Cargo.toml
+++ b/contracts/sg721-nt/Cargo.toml
@@ -5,7 +5,7 @@ authors = ["Shane Vitarana <s@noreply.publicawesome.com>"]
 edition = "2018"
 description = "Stargaze Non-transferrable NFT collection contract"
 license = "Apache-2.0"
-repository = "https://github.com/public-awesome/stargaze-contracts"
+repository = "https://github.com/public-awesome/launchpad"
 homepage = "https://stargaze.zone"
 
 exclude = [
@@ -36,8 +36,8 @@ serde = { version = "1.0.133", default-features = false, features = ["derive"] }
 thiserror = { version = "1.0.30" }
 sg-std = { path = "../../packages/sg-std" }
 url = "2.2.2"
-cw721 = "0.13.4"
-cw721-base = { version = "0.13.4", features = ["library"] }
+cw721 = "0.13.2"
+cw721-base = { version = "0.13.2", features = ["library"] }
 sg721 = { path = "../../packages/sg721" }
 sg721-base = { path = "../sg721-base", features = ["library"] }
 
diff --git a/contracts/sg721-nt/src/contract.rs b/contracts/sg721-nt/src/contract.rs
index 276d1f0b..e80e3166 100644
--- a/contracts/sg721-nt/src/contract.rs
+++ b/contracts/sg721-nt/src/contract.rs
@@ -6,14 +6,14 @@ use cw2::set_contract_version;
 use sg721::InstantiateMsg;
 use sg_std::Response;
 
-use crate::Sg721Base;
+use crate::Cw721Base;
 
 // version info for migration info
 const CONTRACT_NAME: &str = "crates.io:sg721-nt";
 const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
 
 pub fn _instantiate(
-    contract: Sg721Base,
+    contract: Cw721Base,
     deps: DepsMut,
     _env: Env,
     info: MessageInfo,
diff --git a/contracts/sg721-nt/src/lib.rs b/contracts/sg721-nt/src/lib.rs
index 9c6995ea..a9d0f58a 100644
--- a/contracts/sg721-nt/src/lib.rs
+++ b/contracts/sg721-nt/src/lib.rs
@@ -3,10 +3,11 @@ use cosmwasm_std::entry_point;
 
 pub mod contract;
 pub mod msg;
+use cw721_base::Extension;
 use sg721::InstantiateMsg;
-pub use sg721_base::Extension;
+use sg_std::StargazeMsgWrapper;
 
-pub type Sg721Base<'a> = sg721_base::Cw721Base<'a>;
+pub type Cw721Base<'a> = cw721_base::Cw721Contract<'a, Extension, StargazeMsgWrapper>;
 
 pub mod entry {
     use super::*;
@@ -14,8 +15,8 @@ pub mod entry {
         contract::_instantiate,
         msg::{ExecuteMsg, QueryMsg},
     };
-    use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, StdResult};
-    use sg721_base::contract::{burn, mint, ready};
+    use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, StdResult};
+    use sg721_base::contract::{burn, mint, query_collection_info, ready};
     use sg_std::Response;
 
     #[cfg_attr(not(feature = "library"), entry_point)]
@@ -25,7 +26,7 @@ pub mod entry {
         info: MessageInfo,
         msg: InstantiateMsg,
     ) -> Result<Response, sg721_base::ContractError> {
-        let tract = Sg721Base::default();
+        let tract = Cw721Base::default();
         _instantiate(tract, deps, env, info, msg)
     }
 
@@ -36,7 +37,7 @@ pub mod entry {
         info: MessageInfo,
         msg: ExecuteMsg<Extension>,
     ) -> Result<Response, sg721_base::ContractError> {
-        let tract = Sg721Base::default();
+        let tract = Cw721Base::default();
         match msg {
             ExecuteMsg::_Ready {} => ready(tract, deps, env, info),
             ExecuteMsg::Burn { token_id } => burn(tract, deps, env, info, token_id),
@@ -46,6 +47,9 @@ pub mod entry {
 
     #[cfg_attr(not(feature = "library"), entry_point)]
     pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
-        Sg721Base::default().query(deps, env, msg.into())
+        match msg {
+            QueryMsg::CollectionInfo {} => to_binary(&query_collection_info(deps)?),
+            _ => Cw721Base::default().query(deps, env, msg.into()),
+        }
     }
 }
diff --git a/contracts/vending-factory/Cargo.toml b/contracts/vending-factory/Cargo.toml
index 656804c5..63741301 100644
--- a/contracts/vending-factory/Cargo.toml
+++ b/contracts/vending-factory/Cargo.toml
@@ -35,6 +35,7 @@ sg-std = { path = "../../packages/sg-std" }
 sg721 = { path = "../../packages/sg721" }
 sg-controllers = { path = "../../packages/controllers" }
 sg2 = { path = "../../packages/sg2" }
+base-factory = { path = "../base-factory", features = ["library"] }
 
 [dev-dependencies]
 cosmwasm-schema = "1.0.0"
diff --git a/contracts/vending-factory/schema/sg2_query_msg.json b/contracts/vending-factory/schema/sg2_query_msg.json
index 0dc3c482..e951e40f 100644
--- a/contracts/vending-factory/schema/sg2_query_msg.json
+++ b/contracts/vending-factory/schema/sg2_query_msg.json
@@ -14,27 +14,6 @@
         }
       },
       "additionalProperties": false
-    },
-    {
-      "description": "Returns a `MinterStatusResponse`",
-      "type": "object",
-      "required": [
-        "minter_status"
-      ],
-      "properties": {
-        "minter_status": {
-          "type": "object",
-          "required": [
-            "minter"
-          ],
-          "properties": {
-            "minter": {
-              "type": "string"
-            }
-          }
-        }
-      },
-      "additionalProperties": false
     }
   ]
 }
diff --git a/contracts/vending-factory/src/contract.rs b/contracts/vending-factory/src/contract.rs
index a3ea3c21..d57266ef 100644
--- a/contracts/vending-factory/src/contract.rs
+++ b/contracts/vending-factory/src/contract.rs
@@ -1,7 +1,9 @@
+use base_factory::contract::update_params;
+use base_factory::ContractError as BaseContractError;
 #[cfg(not(feature = "library"))]
 use cosmwasm_std::entry_point;
 use cosmwasm_std::{
-    ensure_eq, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, StdResult, WasmMsg,
+    ensure_eq, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, StdResult, WasmMsg,
 };
 use cw2::set_contract_version;
 use cw_utils::must_pay;
@@ -11,11 +13,10 @@ use sg_std::NATIVE_DENOM;
 
 use crate::error::ContractError;
 use crate::msg::{
-    ExecuteMsg, InstantiateMsg, ParamsResponse, Response, SubMsg, SudoMsg, VendingMinterCreateMsg,
+    ExecuteMsg, InstantiateMsg, ParamsResponse, Response, SudoMsg, VendingMinterCreateMsg,
     VendingUpdateParamsMsg,
 };
 use crate::state::SUDO_PARAMS;
-use sg_controllers::{handle_reply, query_minter_status, update_params, upsert_minter_status};
 
 // version info for migration info
 const CONTRACT_NAME: &str = "crates.io:vending-factory";
@@ -81,7 +82,7 @@ pub fn execute_create_vending_minter(
     }
 
     if NATIVE_DENOM != msg.init_msg.unit_price.denom {
-        return Err(ContractError::InvalidDenom {});
+        return Err(ContractError::BaseError(BaseContractError::InvalidDenom {}));
     }
 
     // Check that the price is greater than the minimum
@@ -99,29 +100,16 @@ pub fn execute_create_vending_minter(
         funds: vec![],
         label: format!("VendingMinter-{}", msg.collection_params.name),
     };
-    let submsg = SubMsg::reply_on_success(wasm_msg, params.code_id);
 
     Ok(res
         .add_attribute("action", "create_minter")
-        .add_submessage(submsg))
-}
-
-/// Reply callback triggered from creation above (minter init)
-#[cfg_attr(not(feature = "library"), entry_point)]
-pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result<Response, ContractError> {
-    handle_reply(deps, msg).map_err(|e| e.into())
+        .add_message(wasm_msg))
 }
 
 #[cfg_attr(not(feature = "library"), entry_point)]
 pub fn sudo(deps: DepsMut, env: Env, msg: SudoMsg) -> Result<Response, ContractError> {
     match msg {
         SudoMsg::UpdateParams(params_msg) => sudo_update_params(deps, env, *params_msg),
-        SudoMsg::UpdateMinterStatus {
-            minter,
-            verified,
-            blocked,
-        } => upsert_minter_status(deps, minter, verified, blocked)
-            .map_err(|_| ContractError::MinterFactoryError {}),
     }
 }
 
@@ -148,7 +136,7 @@ pub fn sudo_update_params(
         ensure_eq!(
             &airdrop_mint_price.denom,
             &NATIVE_DENOM,
-            ContractError::InvalidDenom {}
+            ContractError::BaseError(BaseContractError::InvalidDenom {})
         );
         params.extension.airdrop_mint_price = airdrop_mint_price;
     }
@@ -162,7 +150,7 @@ pub fn sudo_update_params(
         ensure_eq!(
             &shuffle_fee.denom,
             &NATIVE_DENOM,
-            ContractError::InvalidDenom {}
+            ContractError::BaseError(BaseContractError::InvalidDenom {})
         );
         params.extension.shuffle_fee = shuffle_fee;
     }
@@ -176,7 +164,6 @@ pub fn sudo_update_params(
 pub fn query(deps: Deps, _env: Env, msg: Sg2QueryMsg) -> StdResult<Binary> {
     match msg {
         Sg2QueryMsg::Params {} => to_binary(&query_params(deps)?),
-        Sg2QueryMsg::MinterStatus { minter } => to_binary(&query_minter_status(deps, minter)?),
     }
 }
 
diff --git a/contracts/vending-factory/src/error.rs b/contracts/vending-factory/src/error.rs
index 7d30bf07..f3c6c619 100644
--- a/contracts/vending-factory/src/error.rs
+++ b/contracts/vending-factory/src/error.rs
@@ -1,7 +1,7 @@
+use base_factory::ContractError as BaseContractError;
 use cosmwasm_std::StdError;
 use cw_utils::PaymentError;
 use sg1::FeeError;
-use sg_controllers::MinterFactoryError;
 use thiserror::Error;
 
 #[derive(Error, Debug, PartialEq)]
@@ -15,24 +15,18 @@ pub enum ContractError {
     #[error("{0}")]
     Payment(#[from] PaymentError),
 
-    #[error("{0}")]
-    MinterFactory(#[from] MinterFactoryError),
-
     #[error("Unauthorized")]
     Unauthorized {},
 
-    #[error("MinterFactoryError")]
-    MinterFactoryError {},
-
     #[error("InvalidNumTokens {max}, min: 1")]
     InvalidNumTokens { max: u32, min: u32 },
 
     #[error("Invalid minting limit per address. max: {max}, min: 1, got: {got}")]
     InvalidPerAddressLimit { max: u32, min: u32, got: u32 },
 
-    #[error("InvalidDenom")]
-    InvalidDenom {},
-
     #[error("Minimum network mint price {expected} got {got}")]
     InsufficientMintPrice { expected: u128, got: u128 },
+
+    #[error("{0}")]
+    BaseError(#[from] BaseContractError),
 }
diff --git a/contracts/vending-factory/src/msg.rs b/contracts/vending-factory/src/msg.rs
index 51760936..d43a5fd3 100644
--- a/contracts/vending-factory/src/msg.rs
+++ b/contracts/vending-factory/src/msg.rs
@@ -32,11 +32,6 @@ pub type ExecuteMsg = Sg2ExecuteMsg<VendingMinterInitMsgExtension>;
 #[serde(rename_all = "snake_case")]
 pub enum SudoMsg {
     UpdateParams(Box<VendingUpdateParamsMsg>),
-    UpdateMinterStatus {
-        minter: String,
-        verified: bool,
-        blocked: bool,
-    },
 }
 
 /// Message for params so they can be updated invidiually by governance
diff --git a/contracts/vending-minter/src/contract.rs b/contracts/vending-minter/src/contract.rs
index 9a799060..4ed6094a 100644
--- a/contracts/vending-minter/src/contract.rs
+++ b/contracts/vending-minter/src/contract.rs
@@ -17,7 +17,7 @@ use cosmwasm_std::{
 };
 use cw2::set_contract_version;
 use cw721_base::{msg::ExecuteMsg as Cw721ExecuteMsg, MintMsg};
-use cw_utils::{may_pay, nonpayable, parse_reply_instantiate_data};
+use cw_utils::{may_pay, parse_reply_instantiate_data};
 use rand_core::{RngCore, SeedableRng};
 use rand_xoshiro::Xoshiro128PlusPlus;
 use sg1::checked_fair_burn;
@@ -231,7 +231,6 @@ pub fn execute_withdraw(
     env: Env,
     info: MessageInfo,
 ) -> Result<Response, ContractError> {
-    nonpayable(&info)?;
     let config = CONFIG.load(deps.storage)?;
     if config.extension.admin != info.sender {
         return Err(ContractError::Unauthorized(
@@ -264,7 +263,6 @@ pub fn execute_set_whitelist(
     info: MessageInfo,
     whitelist: &str,
 ) -> Result<Response, ContractError> {
-    nonpayable(&info)?;
     let mut config = CONFIG.load(deps.storage)?;
     if config.extension.admin != info.sender {
         return Err(ContractError::Unauthorized(
@@ -596,7 +594,6 @@ pub fn execute_update_start_time(
     info: MessageInfo,
     start_time: Timestamp,
 ) -> Result<Response, ContractError> {
-    nonpayable(&info)?;
     let mut config = CONFIG.load(deps.storage)?;
     if info.sender != config.extension.admin {
         return Err(ContractError::Unauthorized(
@@ -633,7 +630,6 @@ pub fn execute_update_per_address_limit(
     info: MessageInfo,
     per_address_limit: u32,
 ) -> Result<Response, ContractError> {
-    nonpayable(&info)?;
     let mut config = CONFIG.load(deps.storage)?;
     if info.sender != config.extension.admin {
         return Err(ContractError::Unauthorized(
diff --git a/packages/controllers/Cargo.toml b/packages/controllers/Cargo.toml
index d91af28a..37ae30c5 100644
--- a/packages/controllers/Cargo.toml
+++ b/packages/controllers/Cargo.toml
@@ -20,3 +20,4 @@ sg-std = { path = "../sg-std" }
 thiserror = { version = "1.0.21" }
 cw-utils = "0.13.4"
 sg2 = { path = "../sg2" }
+sg3 = { path = "../sg3" }
diff --git a/packages/controllers/src/lib.rs b/packages/controllers/src/lib.rs
index 831b9fd0..8715d23b 100644
--- a/packages/controllers/src/lib.rs
+++ b/packages/controllers/src/lib.rs
@@ -1,8 +1,3 @@
 mod hooks;
-mod minter_factory;
 
 pub use hooks::{HookError, Hooks, HooksResponse};
-pub use minter_factory::{
-    handle_reply, query_minter_status, update_params, upsert_minter_status, MinterFactoryError,
-    MINTERS,
-};
diff --git a/packages/controllers/src/minter_factory.rs b/packages/controllers/src/minter_factory.rs
deleted file mode 100644
index 70528631..00000000
--- a/packages/controllers/src/minter_factory.rs
+++ /dev/null
@@ -1,89 +0,0 @@
-use cosmwasm_std::{ensure_eq, Addr, Deps, DepsMut, Reply, StdError, StdResult};
-use cw_storage_plus::Map;
-use cw_utils::parse_reply_instantiate_data;
-use sg2::{msg::UpdateMinterParamsMsg, query::MinterStatusResponse, Minter, MinterParams};
-use sg_std::{Response, NATIVE_DENOM};
-use thiserror::Error;
-
-pub const MINTERS: Map<&Addr, Minter> = Map::new("m");
-
-#[derive(Error, Debug, PartialEq)]
-pub enum MinterFactoryError {
-    #[error("{0}")]
-    Std(#[from] StdError),
-
-    #[error("InstantiateMinterError")]
-    InstantiateMinterError {},
-
-    #[error("InvalidDenom")]
-    InvalidDenom {},
-}
-
-/// Only governance can update contract params
-pub fn upsert_minter_status(
-    deps: DepsMut,
-    minter: String,
-    verified: bool,
-    blocked: bool,
-) -> StdResult<Response> {
-    let minter_addr = deps.api.addr_validate(&minter)?;
-
-    let _: StdResult<Minter> = MINTERS.update(deps.storage, &minter_addr, |m| match m {
-        None => Ok(Minter { verified, blocked }),
-        Some(mut m) => {
-            m.verified = verified;
-            m.blocked = blocked;
-            Ok(m)
-        }
-    });
-
-    Ok(Response::new().add_attribute("action", "sudo_update_minter_status"))
-}
-
-pub fn handle_reply(deps: DepsMut, msg: Reply) -> Result<Response, MinterFactoryError> {
-    let reply = parse_reply_instantiate_data(msg);
-
-    match reply {
-        Ok(res) => {
-            let minter = res.contract_address;
-            upsert_minter_status(deps, minter, false, false)?;
-            Ok(Response::default().add_attribute("action", "instantiate_minter_reply"))
-        }
-        Err(_) => Err(MinterFactoryError::InstantiateMinterError {}),
-    }
-}
-
-pub fn update_params<T, C>(
-    params: &mut MinterParams<C>,
-    param_msg: UpdateMinterParamsMsg<T>,
-) -> Result<(), MinterFactoryError> {
-    params.code_id = param_msg.code_id.unwrap_or(params.code_id);
-
-    if let Some(creation_fee) = param_msg.creation_fee {
-        ensure_eq!(
-            &creation_fee.denom,
-            &NATIVE_DENOM,
-            MinterFactoryError::InvalidDenom {}
-        );
-        params.creation_fee = creation_fee;
-    }
-
-    if let Some(min_mint_price) = param_msg.min_mint_price {
-        ensure_eq!(
-            &min_mint_price.denom,
-            &NATIVE_DENOM,
-            MinterFactoryError::InvalidDenom {}
-        );
-        params.min_mint_price = min_mint_price;
-    }
-
-    params.mint_fee_bps = param_msg.mint_fee_bps.unwrap_or(params.mint_fee_bps);
-
-    Ok(())
-}
-
-pub fn query_minter_status(deps: Deps, minter_addr: String) -> StdResult<MinterStatusResponse> {
-    let minter = MINTERS.load(deps.storage, &deps.api.addr_validate(&minter_addr)?)?;
-
-    Ok(MinterStatusResponse { minter })
-}
diff --git a/packages/sg2/src/lib.rs b/packages/sg2/src/lib.rs
index 6470ba23..13dfd36f 100644
--- a/packages/sg2/src/lib.rs
+++ b/packages/sg2/src/lib.rs
@@ -16,9 +16,3 @@ pub struct MinterParams<T> {
     pub mint_fee_bps: u64,
     pub extension: T,
 }
-
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
-pub struct Minter {
-    pub verified: bool,
-    pub blocked: bool,
-}
diff --git a/packages/sg2/src/query.rs b/packages/sg2/src/query.rs
index c1db7e78..d9cb2ace 100644
--- a/packages/sg2/src/query.rs
+++ b/packages/sg2/src/query.rs
@@ -1,4 +1,4 @@
-use crate::{Minter, MinterParams};
+use crate::MinterParams;
 
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
@@ -8,17 +8,9 @@ use serde::{Deserialize, Serialize};
 pub enum Sg2QueryMsg {
     /// Returns `ParamsResponse`
     Params {},
-    /// Returns a `MinterStatusResponse`
-    MinterStatus { minter: String },
 }
 
 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
 pub struct ParamsResponse<T> {
     pub params: MinterParams<T>,
 }
-
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub struct MinterStatusResponse {
-    pub minter: Minter,
-}
diff --git a/packages/sg3/src/lib.rs b/packages/sg3/src/lib.rs
index 1356f736..9dd131f0 100644
--- a/packages/sg3/src/lib.rs
+++ b/packages/sg3/src/lib.rs
@@ -17,9 +17,23 @@ pub struct MinterConfigResponse<T> {
     pub collection_address: String,
 }
 
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, Default)]
+pub struct Status {
+    pub is_verified: bool,
+    pub is_blocked: bool,
+    pub is_explicit: bool,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+pub struct StatusResponse {
+    pub status: Status,
+}
+
 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
 #[serde(rename_all = "snake_case")]
 pub enum Sg3QueryMsg {
     /// Returns `MinterConfigResponse<T>`
     Config {},
+    /// Returns `StatusResponse`
+    Status {},
 }


Test patch:
diff --git a/contracts/base-factory/src/integration_tests.rs b/contracts/base-factory/src/integration_tests.rs
index 95a4b88b..0af27e49 100644
--- a/contracts/base-factory/src/integration_tests.rs
+++ b/contracts/base-factory/src/integration_tests.rs
@@ -13,8 +13,7 @@ mod tests {
             crate::contract::instantiate,
             crate::contract::query,
         )
-        .with_sudo(crate::contract::sudo)
-        .with_reply(crate::contract::reply);
+        .with_sudo(crate::contract::sudo);
         Box::new(contract)
     }
 
@@ -31,7 +30,7 @@ mod tests {
 
     pub fn mock_params() -> BaseMinterParams {
         BaseMinterParams {
-            code_id: 1,
+            code_id: 2,
             creation_fee: coin(CREATION_FEE, NATIVE_DENOM),
             min_mint_price: coin(MIN_MINT_PRICE, NATIVE_DENOM),
             mint_fee_bps: MINT_FEE_BPS,
diff --git a/contracts/base-minter/src/integration_tests.rs b/contracts/base-minter/src/integration_tests.rs
index 0d0e609e..f6474891 100644
--- a/contracts/base-minter/src/integration_tests.rs
+++ b/contracts/base-minter/src/integration_tests.rs
@@ -23,8 +23,7 @@ pub fn contract_factory() -> Box<dyn Contract<StargazeMsgWrapper>> {
         base_factory::contract::execute,
         base_factory::contract::instantiate,
         base_factory::contract::query,
-    )
-    .with_reply(base_factory::contract::reply);
+    );
     Box::new(contract)
 }
 
diff --git a/contracts/sg721-base/src/integration_tests.rs b/contracts/sg721-base/src/integration_tests.rs
index dc873957..39773dc6 100644
--- a/contracts/sg721-base/src/integration_tests.rs
+++ b/contracts/sg721-base/src/integration_tests.rs
@@ -19,8 +19,7 @@ mod tests {
             vending_factory::contract::execute,
             vending_factory::contract::instantiate,
             vending_factory::contract::query,
-        )
-        .with_reply(vending_factory::contract::reply);
+        );
         Box::new(contract)
     }
 
@@ -138,6 +137,7 @@ mod tests {
         let bal = app.wrap().query_all_balances(ADMIN).unwrap();
         assert_eq!(bal, vec![creation_fee.clone()]);
 
+        // this should create the minter + sg721
         let cosmos_msg = factory_contract.call_with_funds(msg, creation_fee).unwrap();
 
         let res = app.execute(Addr::unchecked(ADMIN), cosmos_msg);
diff --git a/contracts/vending-factory/src/integration_tests.rs b/contracts/vending-factory/src/integration_tests.rs
index 337988f0..dd29e0aa 100644
--- a/contracts/vending-factory/src/integration_tests.rs
+++ b/contracts/vending-factory/src/integration_tests.rs
@@ -14,8 +14,7 @@ mod tests {
             crate::contract::instantiate,
             crate::contract::query,
         )
-        .with_sudo(crate::contract::sudo)
-        .with_reply(crate::contract::reply);
+        .with_sudo(crate::contract::sudo);
         Box::new(contract)
     }
 
diff --git a/contracts/vending-minter/src/integration_tests.rs b/contracts/vending-minter/src/integration_tests.rs
index 833225aa..0c1021cf 100644
--- a/contracts/vending-minter/src/integration_tests.rs
+++ b/contracts/vending-minter/src/integration_tests.rs
@@ -45,8 +45,7 @@ pub fn contract_factory() -> Box<dyn Contract<StargazeMsgWrapper>> {
         vending_factory::contract::execute,
         vending_factory::contract::instantiate,
         vending_factory::contract::query,
-    )
-    .with_reply(vending_factory::contract::reply);
+    );
     Box::new(contract)
 }
 
@@ -592,8 +591,6 @@ fn mint_count_query() {
         .query_wasm_smart(sg721_addr.clone(), &tokens_msg)
         .unwrap();
     let sold_token_id: u32 = res.tokens[1].parse::<u32>().unwrap();
-    println!("sold token id: {}", sold_token_id);
-
     // Buyer transfers NFT to creator
     // random mint token id: 8
     let transfer_msg: Cw721ExecuteMsg<Empty> = Cw721ExecuteMsg::TransferNft {
@@ -704,13 +701,18 @@ fn whitelist_can_update_before_start() {
             creator.clone(),
             minter_addr.clone(),
             &set_whitelist_msg,
-            &[],
+            &coins(UNIT_PRICE, NATIVE_DENOM),
         )
         .unwrap();
 
     // can set twice before starting
     router
-        .execute_contract(creator.clone(), minter_addr, &set_whitelist_msg, &[])
+        .execute_contract(
+            creator.clone(),
+            minter_addr,
+            &set_whitelist_msg,
+            &coins(UNIT_PRICE, NATIVE_DENOM),
+        )
         .unwrap();
 }
 
@@ -982,7 +984,7 @@ fn check_per_address_limit() {
         creator.clone(),
         minter_addr.clone(),
         &per_address_limit_msg,
-        &[],
+        &coins(UNIT_PRICE, NATIVE_DENOM),
     );
     assert!(res.is_ok());
 
@@ -1068,7 +1070,6 @@ fn mint_for_token_id_addr() {
         .wrap()
         .query_all_balances(minter_addr.clone())
         .unwrap();
-    println!("minter_balance: {:?}", minter_balance);
     assert_eq!(0, minter_balance.len());
 
     // Mint fails, invalid token_id