use cosmwasm_std::{
    entry_point, to_json_binary, Binary, CosmosMsg, Deps, DepsMut, Empty, Env, MessageInfo,
    Response, Uint128, WasmMsg,
};
use cw2::{get_contract_version, set_contract_version};
use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg};
use cyber_std::CyberMsg;

use crate::error::ContractError;
use crate::msg::{
    ConfigResponse, ExecuteMsg, InstantiateMsg, QueryMsg, TestingOverrides, WrappedSupplyResponse,
};
use crate::state::{WrapConfig, CONFIG, WRAPPED_SUPPLY};

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

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    msg: InstantiateMsg,
) -> Result<Response<CyberMsg>, ContractError> {
    set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;

    let admin = msg
        .admin
        .map(|a| deps.api.addr_validate(&a))
        .transpose()?
        .unwrap_or(info.sender.clone());

    let native_denom = format!("factory/{}/{}", env.contract.address, msg.token_subdenom);

    let config = WrapConfig {
        cw20_contract: deps.api.addr_validate(&msg.cw20_contract)?,
        native_denom: native_denom.clone(),
        admin,
    };
    CONFIG.save(deps.storage, &config)?;
    WRAPPED_SUPPLY.save(deps.storage, &Uint128::zero())?;

    let create_denom = CyberMsg::create_contract_denom(msg.token_subdenom, None);

    Ok(Response::new()
        .add_message(create_denom)
        .add_attribute("action", "instantiate")
        .add_attribute("native_denom", native_denom)
        .add_attribute("admin", config.admin.to_string()))
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
    deps: DepsMut,
    _env: Env,
    info: MessageInfo,
    msg: ExecuteMsg,
) -> Result<Response<CyberMsg>, ContractError> {
    match msg {
        ExecuteMsg::Receive(cw20_msg) => execute_receive(deps, info, cw20_msg),
        ExecuteMsg::UnwrapNative {} => execute_unwrap_native(deps, info),
        ExecuteMsg::UpdateConfig {
            cw20_contract,
            admin,
        } => execute_update_config(deps, info, cw20_contract, admin),
        ExecuteMsg::ApplyTestingOverrides { overrides } => {
            execute_apply_testing_overrides(deps, info, overrides)
        }
    }
}

/// CW-20 โ†’ native wrap. Called when litium-core sends CW-20 tokens to this contract.
fn execute_receive(
    deps: DepsMut,
    info: MessageInfo,
    cw20_msg: Cw20ReceiveMsg,
) -> Result<Response<CyberMsg>, ContractError> {
    let config = CONFIG.load(deps.storage)?;

    // Only accept CW-20 tokens from litium-core
    if info.sender != config.cw20_contract {
        return Err(ContractError::InvalidCw20Sender {});
    }

    let amount = cw20_msg.amount;
    if amount.is_zero() {
        return Err(ContractError::InvalidAmount {});
    }

    let user = cw20_msg.sender;

    WRAPPED_SUPPLY.update(deps.storage, |v| -> Result<_, ContractError> {
        Ok(v + amount)
    })?;

    // Mint native tokens 1:1 (spec ยง8.3: agents can freely convert between representations)
    let mint_msg =
        CyberMsg::mint_contract_tokens(config.native_denom.clone(), amount, user.clone());

    Ok(Response::new()
        .add_message(mint_msg)
        .add_attribute("action", "wrap_cw20_to_native")
        .add_attribute("address", &user)
        .add_attribute("amount", amount.to_string()))
}

/// Native โ†’ CW-20 unwrap. User sends native LI tokens in funds.
fn execute_unwrap_native(
    deps: DepsMut,
    info: MessageInfo,
) -> Result<Response<CyberMsg>, ContractError> {
    let config = CONFIG.load(deps.storage)?;
    let amount = sent_amount_for_denom(&info, &config.native_denom)?;
    if amount.is_zero() {
        return Err(ContractError::InvalidAmount {});
    }

    WRAPPED_SUPPLY.update(deps.storage, |v| -> Result<_, ContractError> {
        v.checked_sub(amount)
            .map_err(|_| ContractError::WrappedSupplyUnderflow {})
    })?;

    // Burn the native tokens
    let burn_msg =
        CyberMsg::burn_contract_tokens(config.native_denom.clone(), amount, String::new());

    // Transfer CW-20 tokens from wrap's balance to user
    let transfer_msg = CosmosMsg::Wasm(WasmMsg::Execute {
        contract_addr: config.cw20_contract.to_string(),
        msg: cosmwasm_std::to_json_binary(&Cw20ExecuteMsg::Transfer {
            recipient: info.sender.to_string(),
            amount,
        })?,
        funds: vec![],
    });

    Ok(Response::new()
        .add_message(burn_msg)
        .add_message(transfer_msg)
        .add_attribute("action", "unwrap_native_to_cw20")
        .add_attribute("address", info.sender.to_string())
        .add_attribute("amount", amount.to_string()))
}

fn execute_update_config(
    deps: DepsMut,
    info: MessageInfo,
    cw20_contract: Option<String>,
    admin: Option<String>,
) -> Result<Response<CyberMsg>, ContractError> {
    let mut config = CONFIG.load(deps.storage)?;
    if info.sender != config.admin {
        return Err(ContractError::Unauthorized {});
    }
    if let Some(c) = cw20_contract {
        config.cw20_contract = deps.api.addr_validate(&c)?;
    }
    if let Some(a) = admin {
        config.admin = deps.api.addr_validate(&a)?;
    }
    CONFIG.save(deps.storage, &config)?;
    Ok(Response::new().add_attribute("action", "update_config"))
}

fn execute_apply_testing_overrides(
    deps: DepsMut,
    info: MessageInfo,
    overrides: TestingOverrides,
) -> Result<Response<CyberMsg>, ContractError> {
    let config = CONFIG.load(deps.storage)?;
    if info.sender != config.admin {
        return Err(ContractError::Unauthorized {});
    }
    if let Some(v) = overrides.wrapped_supply {
        WRAPPED_SUPPLY.save(deps.storage, &v)?;
    }
    Ok(Response::new().add_attribute("action", "apply_testing_overrides"))
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> Result<Response<CyberMsg>, ContractError> {
    let stored = get_contract_version(deps.storage)?;
    let stored_ver = stored
        .version
        .parse::<semver::Version>()
        .map_err(|_| ContractError::MigrationError {})?;
    let current_ver = CONTRACT_VERSION
        .parse::<semver::Version>()
        .map_err(|_| ContractError::MigrationError {})?;
    if stored_ver >= current_ver {
        return Err(ContractError::MigrationError {});
    }
    set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
    Ok(Response::default())
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> cosmwasm_std::StdResult<Binary> {
    match msg {
        QueryMsg::Config {} => to_json_binary(&query_config(deps)?),
        QueryMsg::WrappedSupply {} => to_json_binary(&query_wrapped_supply(deps)?),
    }
}

fn query_config(deps: Deps) -> cosmwasm_std::StdResult<ConfigResponse> {
    let config = CONFIG.load(deps.storage)?;
    Ok(ConfigResponse {
        cw20_contract: config.cw20_contract.to_string(),
        native_denom: config.native_denom,
        admin: config.admin.to_string(),
    })
}

fn query_wrapped_supply(deps: Deps) -> cosmwasm_std::StdResult<WrappedSupplyResponse> {
    let wrapped_supply = WRAPPED_SUPPLY
        .may_load(deps.storage)?
        .unwrap_or_else(Uint128::zero);
    Ok(WrappedSupplyResponse { wrapped_supply })
}

fn sent_amount_for_denom(info: &MessageInfo, denom: &str) -> Result<Uint128, ContractError> {
    let mut amount = Uint128::zero();
    for coin in &info.funds {
        if coin.denom == denom {
            amount += coin.amount;
        } else if !coin.amount.is_zero() {
            return Err(ContractError::UnexpectedFunds {});
        }
    }
    Ok(amount)
}

Dimensions

cw-cyber/contracts/hub-networks/src/contract.rs
cw-cyber/contracts/std-test/src/contract.rs
cw-cyber/contracts/hub-skills/src/contract.rs
cw-cyber/contracts/litium-refer/src/contract.rs
cw-cyber/contracts/cybernet/src/contract.rs
cw-cyber/contracts/hub-libs/src/contract.rs
cw-cyber/contracts/litium-mine/src/contract.rs
cw-cyber/contracts/litium-stake/src/contract.rs
cw-cyber/contracts/hub-protocols/src/contract.rs
cw-cyber/contracts/cw-cyber-passport/src/contract.rs
cw-cyber/contracts/graph-filter/src/contract.rs
cw-cyber/contracts/cw-cyber-subgraph/src/contract.rs
cw-cyber/contracts/litium-core/src/contract.rs
cw-cyber/contracts/hub-channels/src/contract.rs
cw-cyber/contracts/cw-cyber-gift/src/contract.rs
cw-cyber/contracts/hub-tokens/src/contract.rs

Local Graph