The logic: the instantiate/execute/query entry points and every handler behind them. This is where the rules actually run.
Live source from contracts/names/src/contract.rs (1046 lines). Generated from the deployed contract code.
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{
    entry_point, to_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, Deps, DepsMut, Env,
    MessageInfo, Order, Response, StdResult, Uint128,
};
use cw2::set_contract_version;
use cw_storage_plus::Bound;

use crate::error::ContractError;
use crate::msg::{
    ExecuteMsg, InstantiateMsg, MigrateMsg, OwnedByResponse, PricesUpdate, PricingResponse,
    QueryMsg, ResolveResponse, ReverseResponse,
};
use crate::state::{Config, Record, CONFIG, NAMES, OWNED, PRIMARY};

const CONTRACT_NAME: &str = "crates.io:bwick-names";
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");

const DEFAULT_MIN_LENGTH: u8 = 3;
const DEFAULT_MAX_LENGTH: u8 = 32;
const DEFAULT_TLD: &str = ".bwick";
const DEFAULT_PRICE_3: u128 = 100_000_000; // $100
const DEFAULT_PRICE_4: u128 = 25_000_000; // $25
const DEFAULT_PRICE_5_8: u128 = 10_000_000; // $10
const DEFAULT_PRICE_9_PLUS: u128 = 1_000_000; // $1

const BWICK_DENOM: &str = "ubwick";
const DEFAULT_LIMIT: u32 = 30;
const MAX_LIMIT: u32 = 100;

// ── Oracle integration ────────────────────────────────────────────────────────
//
// Mirror of the bwick-oracle contract's QueryMsg::Price response.

#[cw_serde]
struct OraclePriceQueryMsg {
    price: OraclePriceArgs,
}

#[cw_serde]
struct OraclePriceArgs {
    require_fresh: Option<bool>,
}

#[cw_serde]
struct OraclePriceResponse {
    bwick_usd_price: Uint128,
    sol_usd_price: Uint128,
    last_update_height: u64,
    last_update_timestamp: u64,
    source: String,
    fresh: bool,
}

/// Returns the current BWICK/USD price in micro-USD (6 decimals) from the oracle.
/// Returns 0 if no source provides a price.
fn get_bwick_usd_price(deps: Deps, config: &Config) -> u128 {
    if config.oracle_contract.as_str().is_empty() {
        return 0;
    }
    let req = OraclePriceQueryMsg {
        price: OraclePriceArgs { require_fresh: None },
    };
    deps.querier
        .query_wasm_smart::<OraclePriceResponse>(config.oracle_contract.as_str(), &req)
        .map(|r| r.bwick_usd_price.u128())
        .unwrap_or(0)
}

// ── Name validation ───────────────────────────────────────────────────────────

fn validate_name(name: &str, min_length: u8, max_length: u8) -> Result<(), ContractError> {
    let len = name.len();
    if len < min_length as usize {
        return Err(ContractError::InvalidName {
            reason: format!("name too short ({} < {})", len, min_length),
        });
    }
    if len > max_length as usize {
        return Err(ContractError::InvalidName {
            reason: format!("name too long ({} > {})", len, max_length),
        });
    }
    let bytes = name.as_bytes();
    if bytes[0] == b'-' || bytes[bytes.len() - 1] == b'-' {
        return Err(ContractError::InvalidName {
            reason: "leading or trailing hyphen not allowed".into(),
        });
    }
    let mut prev_hyphen = false;
    for &b in bytes {
        let ok = matches!(b, b'a'..=b'z' | b'0'..=b'9' | b'-');
        if !ok {
            return Err(ContractError::InvalidName {
                reason: "only lowercase a-z, 0-9, and hyphen are allowed".into(),
            });
        }
        if b == b'-' {
            if prev_hyphen {
                return Err(ContractError::InvalidName {
                    reason: "double hyphen not allowed".into(),
                });
            }
            prev_hyphen = true;
        } else {
            prev_hyphen = false;
        }
    }
    Ok(())
}

/// Strip the configured TLD if present, otherwise return the name as-is.
fn canonicalize<'a>(raw: &'a str, tld: &str) -> &'a str {
    if !tld.is_empty() && raw.ends_with(tld) {
        &raw[..raw.len() - tld.len()]
    } else {
        raw
    }
}

fn is_free_period(env: &Env, config: &Config) -> bool {
    env.block.time.seconds() <= config.free_until_timestamp
}

fn tier_usd_for(name: &str, config: &Config) -> u128 {
    match name.len() {
        3 => config.price_3,
        4 => config.price_4,
        5..=8 => config.price_5_8,
        _ => config.price_9_plus,
    }
}

/// Compute the BWICK fee (in ubwick) for a name given the current oracle price
/// and price tier. Returns (fee_ubwick, tier_usd).
///
/// fee_ubwick = ceil(tier_usd * 10^6 / bwick_usd_price)
///
/// Both `tier_usd` and `bwick_usd_price` are in micro-USD, so:
///   tier_usd / bwick_usd_price = whole BWICK
///   * 10^6 = ubwick
fn compute_bwick_fee(tier_usd: u128, bwick_usd_price: u128) -> Result<u128, ContractError> {
    if bwick_usd_price == 0 {
        return Err(ContractError::OracleUnavailable {});
    }
    let numerator = tier_usd
        .checked_mul(1_000_000)
        .ok_or_else(|| ContractError::Std(cosmwasm_std::StdError::generic_err("overflow")))?;
    // Round up so we never charge less than the listed tier.
    let fee = numerator.div_ceil(bwick_usd_price);
    Ok(fee)
}

fn extract_bwick_from_funds(funds: &[Coin]) -> u128 {
    funds
        .iter()
        .filter(|c| c.denom == BWICK_DENOM)
        .map(|c| c.amount.u128())
        .sum()
}

// ── Entry points ──────────────────────────────────────────────────────────────

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
    deps: DepsMut,
    _env: Env,
    info: MessageInfo,
    msg: InstantiateMsg,
) -> Result<Response, ContractError> {
    let admin = match msg.admin {
        Some(a) => deps.api.addr_validate(&a)?,
        None => info.sender.clone(),
    };
    let treasury = deps.api.addr_validate(&msg.treasury)?;
    let oracle_contract = if msg.oracle_contract.is_empty() {
        Addr::unchecked("")
    } else {
        deps.api.addr_validate(&msg.oracle_contract)?
    };

    let min_length = msg.min_length.unwrap_or(DEFAULT_MIN_LENGTH);
    let max_length = msg.max_length.unwrap_or(DEFAULT_MAX_LENGTH);
    if min_length == 0 || min_length > max_length {
        return Err(ContractError::InvalidConfig {
            reason: format!("bad length bounds {}..={}", min_length, max_length),
        });
    }

    let config = Config {
        admin,
        treasury,
        oracle_contract,
        free_until_timestamp: msg.free_until_timestamp,
        min_length,
        max_length,
        tld: msg.tld.unwrap_or_else(|| DEFAULT_TLD.to_string()),
        price_3: msg.price_3.map(|x| x.u128()).unwrap_or(DEFAULT_PRICE_3),
        price_4: msg.price_4.map(|x| x.u128()).unwrap_or(DEFAULT_PRICE_4),
        price_5_8: msg
            .price_5_8
            .map(|x| x.u128())
            .unwrap_or(DEFAULT_PRICE_5_8),
        price_9_plus: msg
            .price_9_plus
            .map(|x| x.u128())
            .unwrap_or(DEFAULT_PRICE_9_PLUS),
    };
    CONFIG.save(deps.storage, &config)?;
    set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;

    Ok(Response::new()
        .add_attribute("action", "instantiate")
        .add_attribute("admin", config.admin.as_str())
        .add_attribute("treasury", config.treasury.as_str())
        .add_attribute("oracle_contract", config.oracle_contract.as_str())
        .add_attribute(
            "free_until_timestamp",
            config.free_until_timestamp.to_string(),
        ))
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    msg: ExecuteMsg,
) -> Result<Response, ContractError> {
    match msg {
        ExecuteMsg::RegisterName { name, owner } => {
            execute_register(deps, env, info, name, owner)
        }
        ExecuteMsg::TransferName { name, recipient } => {
            execute_transfer(deps, env, info, name, recipient)
        }
        ExecuteMsg::ReleaseName { name } => execute_release(deps, env, info, name),
        ExecuteMsg::SetPrimaryName { name } => execute_set_primary(deps, env, info, name),
        ExecuteMsg::UpdateConfig {
            admin,
            treasury,
            oracle_contract,
            free_until_timestamp,
            prices,
        } => execute_update_config(
            deps,
            info,
            admin,
            treasury,
            oracle_contract,
            free_until_timestamp,
            prices,
        ),
    }
}

fn execute_register(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    raw_name: String,
    owner: String,
) -> Result<Response, ContractError> {
    let config = CONFIG.load(deps.storage)?;
    let name_lower = raw_name.to_lowercase();
    let name = canonicalize(&name_lower, &config.tld).to_string();

    validate_name(&name, config.min_length, config.max_length)?;

    if NAMES.may_load(deps.storage, name.as_str())?.is_some() {
        return Err(ContractError::NameTaken { name });
    }

    let owner_addr = deps.api.addr_validate(&owner)?;
    let now = env.block.time.seconds();
    let free = is_free_period(&env, &config);

    let mut messages: Vec<CosmosMsg> = Vec::new();
    let mut tier_usd: u128 = 0;
    let mut fee_ubwick: u128 = 0;

    if !free {
        tier_usd = tier_usd_for(&name, &config);
        let price = get_bwick_usd_price(deps.as_ref(), &config);
        fee_ubwick = compute_bwick_fee(tier_usd, price)?;

        let sent = extract_bwick_from_funds(&info.funds);
        if sent < fee_ubwick {
            return Err(ContractError::InsufficientFunds {
                required: fee_ubwick,
                sent,
            });
        }

        // Forward fee to treasury.
        messages.push(CosmosMsg::Bank(BankMsg::Send {
            to_address: config.treasury.to_string(),
            amount: vec![Coin {
                denom: BWICK_DENOM.into(),
                amount: Uint128::from(fee_ubwick),
            }],
        }));

        // Refund excess to msg.sender.
        let refund = sent - fee_ubwick;
        if refund > 0 {
            messages.push(CosmosMsg::Bank(BankMsg::Send {
                to_address: info.sender.to_string(),
                amount: vec![Coin {
                    denom: BWICK_DENOM.into(),
                    amount: Uint128::from(refund),
                }],
            }));
        }
    } else {
        // Free period: refund any funds attached so callers don't lose them.
        let sent = extract_bwick_from_funds(&info.funds);
        if sent > 0 {
            messages.push(CosmosMsg::Bank(BankMsg::Send {
                to_address: info.sender.to_string(),
                amount: vec![Coin {
                    denom: BWICK_DENOM.into(),
                    amount: Uint128::from(sent),
                }],
            }));
        }
    }

    let record = Record {
        owner: owner_addr.clone(),
        registered_at: now,
    };
    NAMES.save(deps.storage, name.as_str(), &record)?;
    OWNED.save(deps.storage, (&owner_addr, name.as_str()), &())?;

    // Auto-set primary if the owner has none yet.
    if PRIMARY.may_load(deps.storage, &owner_addr)?.is_none() {
        PRIMARY.save(deps.storage, &owner_addr, &name)?;
    }

    Ok(Response::new()
        .add_messages(messages)
        .add_attribute("action", "register_name")
        .add_attribute("name", &name)
        .add_attribute("owner", owner_addr.as_str())
        .add_attribute("free", free.to_string())
        .add_attribute("fee_ubwick", fee_ubwick.to_string())
        .add_attribute("tier_usd", tier_usd.to_string()))
}

fn execute_transfer(
    deps: DepsMut,
    _env: Env,
    info: MessageInfo,
    raw_name: String,
    recipient: String,
) -> Result<Response, ContractError> {
    let config = CONFIG.load(deps.storage)?;
    let name_lower = raw_name.to_lowercase();
    let name = canonicalize(&name_lower, &config.tld).to_string();

    let mut record = NAMES
        .may_load(deps.storage, name.as_str())?
        .ok_or_else(|| ContractError::NameNotFound { name: name.clone() })?;

    if record.owner != info.sender {
        return Err(ContractError::Unauthorized {});
    }

    let new_owner = deps.api.addr_validate(&recipient)?;

    // Update primary mapping for the previous owner if this was their primary.
    if let Some(prim) = PRIMARY.may_load(deps.storage, &info.sender)? {
        if prim == name {
            PRIMARY.remove(deps.storage, &info.sender);
        }
    }

    OWNED.remove(deps.storage, (&info.sender, name.as_str()));
    OWNED.save(deps.storage, (&new_owner, name.as_str()), &())?;

    record.owner = new_owner.clone();
    NAMES.save(deps.storage, name.as_str(), &record)?;

    Ok(Response::new()
        .add_attribute("action", "transfer_name")
        .add_attribute("name", &name)
        .add_attribute("from", info.sender.as_str())
        .add_attribute("to", new_owner.as_str()))
}

fn execute_release(
    deps: DepsMut,
    _env: Env,
    info: MessageInfo,
    raw_name: String,
) -> Result<Response, ContractError> {
    let config = CONFIG.load(deps.storage)?;
    let name_lower = raw_name.to_lowercase();
    let name = canonicalize(&name_lower, &config.tld).to_string();

    let record = NAMES
        .may_load(deps.storage, name.as_str())?
        .ok_or_else(|| ContractError::NameNotFound { name: name.clone() })?;

    if record.owner != info.sender {
        return Err(ContractError::Unauthorized {});
    }

    NAMES.remove(deps.storage, name.as_str());
    OWNED.remove(deps.storage, (&info.sender, name.as_str()));

    if let Some(prim) = PRIMARY.may_load(deps.storage, &info.sender)? {
        if prim == name {
            PRIMARY.remove(deps.storage, &info.sender);
        }
    }

    Ok(Response::new()
        .add_attribute("action", "release_name")
        .add_attribute("name", &name)
        .add_attribute("owner", info.sender.as_str()))
}

fn execute_set_primary(
    deps: DepsMut,
    _env: Env,
    info: MessageInfo,
    raw_name: String,
) -> Result<Response, ContractError> {
    let config = CONFIG.load(deps.storage)?;
    let name_lower = raw_name.to_lowercase();
    let name = canonicalize(&name_lower, &config.tld).to_string();

    let record = NAMES
        .may_load(deps.storage, name.as_str())?
        .ok_or_else(|| ContractError::NameNotFound { name: name.clone() })?;

    if record.owner != info.sender {
        return Err(ContractError::Unauthorized {});
    }

    PRIMARY.save(deps.storage, &info.sender, &name)?;

    Ok(Response::new()
        .add_attribute("action", "set_primary_name")
        .add_attribute("name", &name)
        .add_attribute("owner", info.sender.as_str()))
}

#[allow(clippy::too_many_arguments)]
fn execute_update_config(
    deps: DepsMut,
    info: MessageInfo,
    admin: Option<String>,
    treasury: Option<String>,
    oracle_contract: Option<String>,
    free_until_timestamp: Option<u64>,
    prices: Option<PricesUpdate>,
) -> Result<Response, ContractError> {
    let mut config = CONFIG.load(deps.storage)?;
    if info.sender != config.admin {
        return Err(ContractError::Unauthorized {});
    }
    if let Some(a) = admin {
        config.admin = deps.api.addr_validate(&a)?;
    }
    if let Some(t) = treasury {
        config.treasury = deps.api.addr_validate(&t)?;
    }
    if let Some(o) = oracle_contract {
        config.oracle_contract = if o.is_empty() {
            Addr::unchecked("")
        } else {
            deps.api.addr_validate(&o)?
        };
    }
    if let Some(ts) = free_until_timestamp {
        config.free_until_timestamp = ts;
    }
    if let Some(p) = prices {
        if let Some(v) = p.price_3 {
            config.price_3 = v.u128();
        }
        if let Some(v) = p.price_4 {
            config.price_4 = v.u128();
        }
        if let Some(v) = p.price_5_8 {
            config.price_5_8 = v.u128();
        }
        if let Some(v) = p.price_9_plus {
            config.price_9_plus = v.u128();
        }
    }
    CONFIG.save(deps.storage, &config)?;

    Ok(Response::new().add_attribute("action", "update_config"))
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(
    deps: DepsMut,
    _env: Env,
    msg: MigrateMsg,
) -> Result<Response, ContractError> {
    let mut config = CONFIG.load(deps.storage)?;
    if let Some(a) = msg.admin {
        config.admin = deps.api.addr_validate(&a)?;
    }
    if let Some(t) = msg.treasury {
        config.treasury = deps.api.addr_validate(&t)?;
    }
    if let Some(o) = msg.oracle_contract {
        config.oracle_contract = if o.is_empty() {
            Addr::unchecked("")
        } else {
            deps.api.addr_validate(&o)?
        };
    }
    if let Some(ts) = msg.free_until_timestamp {
        config.free_until_timestamp = ts;
    }
    CONFIG.save(deps.storage, &config)?;
    set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
    Ok(Response::new().add_attribute("action", "migrate"))
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
    match msg {
        QueryMsg::Resolve { name } => to_binary(&query_resolve(deps, name)?),
        QueryMsg::Reverse { address } => to_binary(&query_reverse(deps, address)?),
        QueryMsg::OwnedBy {
            address,
            start_after,
            limit,
        } => to_binary(&query_owned_by(deps, address, start_after, limit)?),
        QueryMsg::Pricing { name } => to_binary(&query_pricing(deps, env, name)?),
        QueryMsg::Config {} => to_binary(&CONFIG.load(deps.storage)?),
    }
}

fn query_resolve(deps: Deps, raw_name: String) -> StdResult<ResolveResponse> {
    let config = CONFIG.load(deps.storage)?;
    let name_lower = raw_name.to_lowercase();
    let name = canonicalize(&name_lower, &config.tld);
    match NAMES.may_load(deps.storage, name)? {
        Some(rec) => Ok(ResolveResponse {
            owner: Some(rec.owner),
            registered_at: Some(rec.registered_at),
        }),
        None => Ok(ResolveResponse {
            owner: None,
            registered_at: None,
        }),
    }
}

fn query_reverse(deps: Deps, address: String) -> StdResult<ReverseResponse> {
    let addr = deps.api.addr_validate(&address)?;
    Ok(ReverseResponse {
        primary: PRIMARY.may_load(deps.storage, &addr)?,
    })
}

fn query_owned_by(
    deps: Deps,
    address: String,
    start_after: Option<String>,
    limit: Option<u32>,
) -> StdResult<OwnedByResponse> {
    let addr = deps.api.addr_validate(&address)?;
    let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
    let start = start_after.as_deref().map(Bound::exclusive);

    let names: Vec<String> = OWNED
        .prefix(&addr)
        .range(deps.storage, start, None, Order::Ascending)
        .take(limit)
        .filter_map(|item| item.ok().map(|(k, _)| k))
        .collect();

    Ok(OwnedByResponse { names })
}

fn query_pricing(deps: Deps, env: Env, raw_name: String) -> StdResult<PricingResponse> {
    let config = CONFIG.load(deps.storage)?;
    let name_lower = raw_name.to_lowercase();
    let name = canonicalize(&name_lower, &config.tld);

    if validate_name(name, config.min_length, config.max_length).is_err() {
        // Not a valid name — return zero/free without erroring so the UI can still
        // show why a name is unsupported separately via the validation rules.
        return Ok(PricingResponse {
            fee_ubwick: Uint128::zero(),
            free: is_free_period(&env, &config),
            tier_usd: 0,
        });
    }

    if is_free_period(&env, &config) {
        return Ok(PricingResponse {
            fee_ubwick: Uint128::zero(),
            free: true,
            tier_usd: 0,
        });
    }

    let tier_usd = tier_usd_for(name, &config);
    let price = get_bwick_usd_price(deps, &config);
    let fee = if price == 0 {
        // Oracle unavailable; surface zero (UI should treat this as "price unknown").
        0
    } else {
        compute_bwick_fee(tier_usd, price).unwrap_or(0)
    };

    Ok(PricingResponse {
        fee_ubwick: Uint128::from(fee),
        free: false,
        tier_usd,
    })
}

// ── Tests ─────────────────────────────────────────────────────────────────────

#[cfg(test)]
mod tests {
    use super::*;
    use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
    use cosmwasm_std::{from_binary, ContractResult, OwnedDeps, SystemResult, WasmQuery};

    fn default_config_msg(now: u64, free_until_offset: i64) -> InstantiateMsg {
        let free_until = if free_until_offset >= 0 {
            now + free_until_offset as u64
        } else {
            now.saturating_sub((-free_until_offset) as u64)
        };
        InstantiateMsg {
            admin: None,
            treasury: "treasury0".into(),
            oracle_contract: "oracle0".into(),
            free_until_timestamp: free_until,
            min_length: None,
            max_length: None,
            tld: None,
            price_3: None,
            price_4: None,
            price_5_8: None,
            price_9_plus: None,
        }
    }

    fn setup(
        free_offset: i64,
    ) -> (
        OwnedDeps<
            cosmwasm_std::MemoryStorage,
            cosmwasm_std::testing::MockApi,
            cosmwasm_std::testing::MockQuerier,
        >,
        Env,
    ) {
        let mut deps = mock_dependencies();
        let env = mock_env();
        let info = mock_info("admin0", &[]);
        let msg = default_config_msg(env.block.time.seconds(), free_offset);
        instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
        (deps, env)
    }

    /// Mocks an oracle that returns a fixed BWICK/USD price in micro-USD.
    fn install_oracle_price(
        deps: &mut OwnedDeps<
            cosmwasm_std::MemoryStorage,
            cosmwasm_std::testing::MockApi,
            cosmwasm_std::testing::MockQuerier,
        >,
        bwick_usd_price: u128,
    ) {
        deps.querier.update_wasm(move |q| match q {
            WasmQuery::Smart { .. } => {
                let resp = OraclePriceResponse {
                    bwick_usd_price: Uint128::from(bwick_usd_price),
                    sol_usd_price: Uint128::zero(),
                    last_update_height: 1,
                    last_update_timestamp: 0,
                    source: "mock".into(),
                    fresh: true,
                };
                SystemResult::Ok(ContractResult::Ok(to_binary(&resp).unwrap()))
            }
            _ => SystemResult::Ok(ContractResult::Err("unimpl".into())),
        });
    }

    #[test]
    fn test_validate_name_rules() {
        // ok cases
        assert!(validate_name("abc", 3, 32).is_ok());
        assert!(validate_name("a-b-c", 3, 32).is_ok());
        assert!(validate_name("a1b2c3", 3, 32).is_ok());
        // boundary length
        assert!(validate_name("ab", 3, 32).is_err());
        assert!(validate_name(&"a".repeat(33), 3, 32).is_err());
        assert!(validate_name(&"a".repeat(32), 3, 32).is_ok());
        // forbidden chars
        assert!(validate_name("ABC", 3, 32).is_err());
        assert!(validate_name("ab.c", 3, 32).is_err());
        assert!(validate_name("ab_c", 3, 32).is_err());
        assert!(validate_name("ab c", 3, 32).is_err());
        // hyphen rules
        assert!(validate_name("-abc", 3, 32).is_err());
        assert!(validate_name("abc-", 3, 32).is_err());
        assert!(validate_name("a--b", 3, 32).is_err());
    }

    #[test]
    fn test_register_during_free_period() {
        let (mut deps, env) = setup(60);
        let info = mock_info("anyone", &[]);
        let res = execute_register(
            deps.as_mut(),
            env.clone(),
            info,
            "chef".into(),
            "owner1".into(),
        )
        .unwrap();
        // No bank messages during free period when zero funds attached.
        assert!(res.messages.is_empty());

        // Resolve should now return owner.
        let q = query_resolve(deps.as_ref(), "chef".into()).unwrap();
        assert_eq!(q.owner.as_ref().map(|a| a.as_str()), Some("owner1"));

        // Auto-primary should be set.
        let r = query_reverse(deps.as_ref(), "owner1".into()).unwrap();
        assert_eq!(r.primary.as_deref(), Some("chef"));
    }

    #[test]
    fn test_double_registration_rejected() {
        let (mut deps, env) = setup(60);
        execute_register(
            deps.as_mut(),
            env.clone(),
            mock_info("a", &[]),
            "chef".into(),
            "owner1".into(),
        )
        .unwrap();
        let err = execute_register(
            deps.as_mut(),
            env.clone(),
            mock_info("b", &[]),
            "chef".into(),
            "owner2".into(),
        )
        .unwrap_err();
        match err {
            ContractError::NameTaken { .. } => {}
            other => panic!("expected NameTaken, got {:?}", other),
        }
    }

    #[test]
    fn test_post_free_pricing_via_oracle() {
        // BWICK price = $0.50 = 500_000 micro-USD per BWICK.
        // For a 5-8 char name, tier = $10 = 10_000_000 micro-USD.
        // fee_ubwick = ceil(10_000_000 * 1_000_000 / 500_000) = 20_000_000 ubwick = 20 BWICK.
        let (mut deps, env) = setup(-10); // free period in the past
        install_oracle_price(&mut deps, 500_000);

        let pricing = query_pricing(deps.as_ref(), env.clone(), "chefoo".into()).unwrap();
        assert!(!pricing.free);
        assert_eq!(pricing.tier_usd, 10_000_000);
        assert_eq!(pricing.fee_ubwick.u128(), 20_000_000);

        // Insufficient funds rejected.
        let info = mock_info("anyone", &[Coin::new(1_000, BWICK_DENOM)]);
        let err = execute_register(
            deps.as_mut(),
            env.clone(),
            info,
            "chefoo".into(),
            "owner1".into(),
        )
        .unwrap_err();
        match err {
            ContractError::InsufficientFunds { required, sent } => {
                assert_eq!(required, 20_000_000);
                assert_eq!(sent, 1_000);
            }
            other => panic!("expected InsufficientFunds, got {:?}", other),
        }

        // Exact-amount succeeds; one bank message to treasury.
        let info = mock_info("anyone", &[Coin::new(20_000_000, BWICK_DENOM)]);
        let res = execute_register(
            deps.as_mut(),
            env.clone(),
            info,
            "chefoo".into(),
            "owner1".into(),
        )
        .unwrap();
        assert_eq!(res.messages.len(), 1);

        // Overpay refunds.
        let info = mock_info("anyone", &[Coin::new(25_000_000, BWICK_DENOM)]);
        let res = execute_register(
            deps.as_mut(),
            env,
            info,
            "chefoo2".into(),
            "owner1".into(),
        )
        .unwrap();
        assert_eq!(res.messages.len(), 2); // treasury + refund
    }

    #[test]
    fn test_transfer_clears_primary_when_was_primary() {
        let (mut deps, env) = setup(60);
        execute_register(
            deps.as_mut(),
            env.clone(),
            mock_info("anyone", &[]),
            "chef".into(),
            "owner1".into(),
        )
        .unwrap();
        // owner1's primary should currently be "chef"
        assert_eq!(
            query_reverse(deps.as_ref(), "owner1".into())
                .unwrap()
                .primary
                .as_deref(),
            Some("chef")
        );

        execute_transfer(
            deps.as_mut(),
            env.clone(),
            mock_info("owner1", &[]),
            "chef".into(),
            "owner2".into(),
        )
        .unwrap();

        // owner1 no longer has primary
        assert!(query_reverse(deps.as_ref(), "owner1".into())
            .unwrap()
            .primary
            .is_none());
        // owner2 not auto-set
        assert!(query_reverse(deps.as_ref(), "owner2".into())
            .unwrap()
            .primary
            .is_none());
        // resolve points to owner2
        assert_eq!(
            query_resolve(deps.as_ref(), "chef".into())
                .unwrap()
                .owner
                .as_ref()
                .map(|a| a.as_str()),
            Some("owner2")
        );
    }

    #[test]
    fn test_set_primary_only_by_owner() {
        let (mut deps, env) = setup(60);
        execute_register(
            deps.as_mut(),
            env.clone(),
            mock_info("anyone", &[]),
            "chef".into(),
            "owner1".into(),
        )
        .unwrap();
        execute_register(
            deps.as_mut(),
            env.clone(),
            mock_info("anyone", &[]),
            "diner".into(),
            "owner1".into(),
        )
        .unwrap();

        // someone else cannot change owner1's primary
        let err = execute_set_primary(
            deps.as_mut(),
            env.clone(),
            mock_info("intruder", &[]),
            "diner".into(),
            )
        .unwrap_err();
        assert!(matches!(err, ContractError::Unauthorized { .. }));

        // owner1 can switch primary
        execute_set_primary(
            deps.as_mut(),
            env,
            mock_info("owner1", &[]),
            "diner".into(),
        )
        .unwrap();
        assert_eq!(
            query_reverse(deps.as_ref(), "owner1".into())
                .unwrap()
                .primary
                .as_deref(),
            Some("diner")
        );
    }

    #[test]
    fn test_release_clears_state() {
        let (mut deps, env) = setup(60);
        execute_register(
            deps.as_mut(),
            env.clone(),
            mock_info("anyone", &[]),
            "chef".into(),
            "owner1".into(),
        )
        .unwrap();
        execute_release(
            deps.as_mut(),
            env.clone(),
            mock_info("owner1", &[]),
            "chef".into(),
        )
        .unwrap();
        assert!(query_resolve(deps.as_ref(), "chef".into())
            .unwrap()
            .owner
            .is_none());
        assert!(query_reverse(deps.as_ref(), "owner1".into())
            .unwrap()
            .primary
            .is_none());
    }

    #[test]
    fn test_owned_by_query() {
        let (mut deps, env) = setup(60);
        for n in ["abc", "xyz", "chef", "diner"].iter() {
            execute_register(
                deps.as_mut(),
                env.clone(),
                mock_info("anyone", &[]),
                (*n).into(),
                "owner1".into(),
            )
            .unwrap();
        }
        let owned = query_owned_by(deps.as_ref(), "owner1".into(), None, None).unwrap();
        // Map key order is ascending alphabetic.
        assert_eq!(owned.names, vec!["abc", "chef", "diner", "xyz"]);
    }

    #[test]
    fn test_canonicalize_strips_tld() {
        assert_eq!(canonicalize("chef.bwick", ".bwick"), "chef");
        assert_eq!(canonicalize("chef", ".bwick"), "chef");
        assert_eq!(canonicalize(".bwick", ".bwick"), "");
    }

    #[test]
    fn test_query_resolve_handles_tld_suffix() {
        let (mut deps, env) = setup(60);
        execute_register(
            deps.as_mut(),
            env.clone(),
            mock_info("anyone", &[]),
            "chef".into(),
            "owner1".into(),
        )
        .unwrap();
        let r = query_resolve(deps.as_ref(), "chef.bwick".into()).unwrap();
        assert_eq!(r.owner.as_ref().map(|a| a.as_str()), Some("owner1"));
    }

    #[test]
    fn test_update_config_admin_only() {
        let (mut deps, _env) = setup(60);
        let err = execute_update_config(
            deps.as_mut(),
            mock_info("intruder", &[]),
            None,
            None,
            None,
            Some(42),
            None,
        )
        .unwrap_err();
        assert!(matches!(err, ContractError::Unauthorized { .. }));

        // The admin defaulted to the instantiator ("admin0").
        execute_update_config(
            deps.as_mut(),
            mock_info("admin0", &[]),
            None,
            None,
            None,
            Some(42),
            None,
        )
        .unwrap();
        let cfg = CONFIG.load(deps.as_ref().storage).unwrap();
        assert_eq!(cfg.free_until_timestamp, 42);
    }

    #[test]
    fn test_query_message_round_trip() {
        // Sanity that QueryMsg variants parse cleanly via the entry point.
        let (mut deps, env) = setup(60);
        execute_register(
            deps.as_mut(),
            env.clone(),
            mock_info("anyone", &[]),
            "chef".into(),
            "owner1".into(),
        )
        .unwrap();
        let bin = query(
            deps.as_ref(),
            env.clone(),
            QueryMsg::Resolve {
                name: "chef".into(),
            },
        )
        .unwrap();
        let resp: ResolveResponse = from_binary(&bin).unwrap();
        assert!(resp.owner.is_some());

        let bin = query(deps.as_ref(), env, QueryMsg::Config {}).unwrap();
        let cfg: Config = from_binary(&bin).unwrap();
        assert_eq!(cfg.tld, ".bwick");
    }
}