MANTRA Chain
  • Introduction
    • Overview
    • Why MANTRA Chain
    • Building on MANTRA Chain
    • MANTRA RWA Suite
  • Using MANTRA Chain
    • MANTRA Chain Wallet Setup
      • Connect to Testnet
      • Connect to Mainnet
      • Testnet Faucet
    • MANTRA Zone
    • MANTRA Bridge
      • MANTRA Bridge User Guide
      • MANTRA Bridge FAQs
    • Stake
      • Stake User Guide
      • Stake FAQs
    • MANTRA Swap
      • MANTRA Swap User Guide
      • Liquidity Pools User Guide
      • MANTRA Swap FAQs
    • Claiming Journey
      • Check If You're Eligible
      • Claiming Journey User Guide
      • I've redeemed my rewards, now what?
    • Chakra Pool
      • What can I do with my USDY
      • Chakra Pool Deposit Guide
      • How KARMA works for Chakra Pool
        • How much KARMA do I get for depositing into Chakra Pool?
        • Can I make multiple deposits and how will KARMA be calculated then?
        • My KARMA is not updated even though I make multiple deposits?
        • Will I get daily KARMA for my deposit on USDY Chakra Pool launched on Hongbai earlier?
      • Chakra Pool FAQs
    • MANTRA Zone Leaderboard
      • MANTRA Zone Leaderboard User Guide
        • Mission Guides
          • Pledge your OM 🕉️
          • Claim OM Staking Rewards
          • OMly Drip 💧
      • MANTRA Zone Leaderboard FAQs
  • Developing on MANTRA Chain
    • Getting Started
      • DuKong Testnet
      • Install Prerequisites
      • Setting Up Dev Environment
      • Compiling a Contract
      • Deployment and Interaction
    • CosmWasm Quick Start Guide
      • CosmWasm Contracts
      • Understanding CosmWasm File Structure
    • DAPP Tooling
      • Cosmos SDK
      • Important Libraries
      • Developer Resources
    • Developer FAQs
  • MANTRA Smart Contracts
    • Overview
    • MANTRA Dex
      • 🚢Deployments
      • 📚Common Types
      • 💰Fee Collector
      • ⌛Epoch Manager
      • 🌊Pool Manager
      • 🎁Farm Manager
    • Claimdrop Contract
    • Audits
  • Node & Validator Operations
    • Overview
    • Node Setup & Deployment
      • Node System Requirements
      • Running a Node
      • How to run Mantrachain with Systemd
      • Validator Nodes
        • Connect sidecar
    • Validator Architecture Recommendations
      • Secure Validator
    • Governance
      • Draft a Proposal
        • Text Proposal
        • Community Spend Proposal
        • Software Upgrade
        • Cancel Software Upgrade
      • Submitting a Proposal
      • Verifying your transaction
      • Depositing funds
      • Voting on a Proposal
    • Download nodes snapshots
  • MANTRA Chain with EVM (Alpha)
    • Getting Started on OMSTEAD Testnet
  • Setup Instructions
  • Using Foundry
    • Deployment with Foundry
  • Using Hardhat
    • Deployment with Hardhat
  • Mainnet OM Information
    • Background
    • Updated OM Tokenomics
    • OM Distribution Updates
    • Inflation & Vesting Schedule
    • Legacy tokens to mainnet staking tokens
  • Appendix
    • Frequently Asked Questions
      • General FAQs
        • What is MANTRA Chain?
        • What are the key features/ modules available to builders on MANTRA Chain?
        • What support is available for developers and validators that join the ecosystem
        • Where can i find MANTRA Social Accounts & Communities?
        • How can an Issue or a bug be reported?
        • How can I identify official MANTRA communication and websites?
      • Validator FAQs
        • What does a new validator’s journey look like?
        • What are my key responsibilities as a validator?
        • What is uptime and how is it maintained?
        • What are the hardware requirements?
        • How to get validator keys?
        • How can I connect a validator from an older testnet to the newest testnet?
        • How do I create a backup snapshot of a node?
        • How do I restore a node from a snapshot?
        • What are the different states a validator can be in?
        • How do I unjail a validator?
        • How do I edit a validator's description?
        • What is self-delegation?
        • Is there a minimum amount of OM that must be delegated to be an active validator?
      • Developer FAQs
        • What is MANTRA Chain, and how does it utilize the Cosmos ecosystem?
        • How does MANTRA Chain ensure interoperability with other blockchains in the Cosmos ecosystem?
        • What is the Cosmos ecosystem, and how does it differ from other blockchain platforms?
        • Which programming languages are commonly used for developing on the Cosmos ecosystem?
        • How do I get started with development on the Cosmos ecosystem?
        • What are Cosmos SDK and Tendermint, and how do they work together?
        • What are Cosmos zones and how do they interact?
        • How do I build and deploy smart contracts on the Cosmos ecosystem?
        • How do I interact with Cosmos chains and applications programmatically?
        • What is the best way to learn about developing for the MANTRA Chain and Cosmos ecosystem?
      • Hongbai FAQs
        • Where do I sign up for Hongbai?
        • Why do I need to provide 2 wallets during signup?
        • How can I Login?
        • What should I do if I forgot my password?
        • What can I do if i forget my registered email?
        • Where do i verify my email?
        • How to connect my Metamask wallet?
        • Is Hongbai app a testnet or live app?
        • Can I change my wallet ?
        • Error “invalid user credentials“
        • Why can i not Invest?
      • How to Guides
        • Install Keplr Wallet
        • Keplr and Ledger
        • Add DuKong Testnet to Keplr
        • Get Tokens from Faucet
        • Track activity on Leaderboard
      • More Help & Support
      • MANTRA Zone FAQs
        • MANTRA Zone General FAQs
          • What wallets are currently supported on the MANTRA Zone?
          • How do I connect my wallet to the MANTRA Zone?
          • How do I copy my connected wallet’s address?
          • How do I connect a different wallet to the MANTRA Zone?
          • Can I disconnect my wallet from MANTRA Zone?
          • How can I resolve seeing multiple entires of the same amount of testnet OM in my Keplr wallet?
          • The question that I have is not covered by the FAQ, what should I do?
        • MANTRA Bridge FAQs
          • What is MANTRA Bridge?
          • Why should I use the MANTRA Bridge?
          • Is there a fee for using the MANTRA Bridge?
          • Do I have to pay gas fees?
          • What tokens will I receive?
          • How does the migration process work?
          • How long should the MANTRA Bridge order take?
          • How can I track the status of my MANTRA Bridge order?
          • What happens to my ERC20 OM tokens once I use the MANTRA Bridge?
          • Which network can I use on the MANTRA Bridge?
          • What should I do if I see ‘Wrong network’ error on the app?
          • Will there be a two-way bridge in the future?
          • My MANTRA Bridge order is stuck on Ethereum?! How can I fix this?
          • Who can I contact for support during the migration process?
          • Which EVM chains can I bridge from?
          • Are the OM tokens I receive wrapped tokens or the native MANTRA Chain OM tokens?
          • Is the MANTRA Bridge a non-custodial solution?
          • What happens if the MANTRA Bridge is hacked or goes offline?
          • Are there any risks associated with using the MANTRA Bridge?
          • How does the MANTRA Bridge ensure the security of my tokens during migration?
          • Can I use my existing Ethereum wallet to access the MANTRA Bridge?
          • How does wallet linking and airdrop participation work?
        • Stake FAQs
          • What is Stake?
          • What are validators, and how do I choose one?
          • What rewards can I earn by staking $OM?
          • How are staking rewards calculated?
          • Can I choose to unstake my $OM coins?
          • What is the unbonding period?
          • Can I change validators after staking $OM?
          • Can I stake $OM with multiple validators?
          • Are there any risks involved with staking $OM?
          • Will my rewards gets automatically restaked after redemption?
          • Do I lose my claimable rewards when performing actions with a validator with unclaimed rewards?
          • My wallet is showing a 0 balance even though I have funds. What should I do?
          • How do I know if my $OM is staked?
        • Chakra Pool FAQs
          • What are the Chakra Pool rewards?
          • What is USDY?
          • How do I get the yield from the treasury bonds by holding USDY?
          • Can I change my linked wallet?
          • I don’t remember my linked wallet - what can I do?
          • What are the bonus rewards and where I can see my rewards?
          • How are the USDY rewards calculated and when can I redeem my USDY rewards?
          • How are the $OM rewards calculated and when can I redeem my $OM rewards?
          • How are the $ONDO rewards calculated and when can I redeem my $ONDO rewards?
          • What can I do with my USDY and $OM rewards after redemption?
        • MANTRA Zone Leaderboard FAQs
          • I have someone that wants to participate in the MANTRA Zone Leaderboard but they don’t know how.
          • What does the ⚡icon and ‘Get + x KARMA’ text under the ‘KARMA’ section refer to?
          • The KARMA on the Zone Leaderboard only lists MANTRA Chain wallets, how to check my EVM wallet rank?
          • Which wallet IDs are visible on the Leaderboard Ranking Table?
          • Why are there three different categories for the Leaderboard Ranking Table?
          • How can I get the Leaderboard Ranking Table to show where my connected wallet ranks currently?
          • How can I search where a specific wallet ID ranks in the Leaderboard Ranking Table?
          • How can I search what wallet ID currently ranks in a specific ranking number?
          • My KARMA and/or multiplier amount did not update on the Leaderboard Ranking Table, why is that?
          • What is the calculation for the KARMA?
          • I’m unable to see the OMly Faucet on my Keplr/Cosmostation wallet
          • I'm unable to see my OMly Faucet Claim transaction on the explorer
          • The question that I have is not covered by the FAQ, what should I do?
        • MANTRA Swap FAQs
          • What is Swap?
          • How can I swap?
          • I have connected my EVM wallet, but I’m still unable to swap. Why?
          • Where can I view my past transactions?
          • Why is my swap button disabled?
          • Why can’t I swap with a slippage more than 5%?
          • What is Exchange Rate?
          • How do I get OM tokens in my wallet?
          • How do I get other tokens in my wallet?
          • Will the swap fee be deducted from my sell or buy token?
          • Why can’t I see the balance of tokens in my wallet but on the Swap panel?
          • How do I add liquidity to a pool?
          • I am unable to provide liquidity to the pool.
          • While adding liquidity, why are the two tokens USD value not equal?
          • How do I withdraw my funds from Liquidity Pools?
          • Why can I not see my tokens for withdrawal?
          • Why can I not remove liquidity?
          • Why is my deposited value of the two tokens changing in my position?
          • Do liquidity pools have a bonding period?
          • What are LP tokens?
          • What can I do with my LP tokens?
          • Why can I not see any LP token balance?
          • I am connected with my MANTRA wallet, but still cannot view my positions?
          • I provided liquidity, but I can’t see my position. What should I do?
          • Why is my transaction not successful?
    • Glossary
    • MANTRA's Incentivised Testnet
      • Testnet Phase 1
      • Hongbai - Testnet Phase 2
  • Third Party Bridges
    • Base Bridge
    • Polygon Bridge
Powered by GitBook
On this page
  1. Developing on MANTRA Chain
  2. CosmWasm Quick Start Guide

Writing & Deploying CW721 Contract

In the previous sections, we have covered setting up the development environment, configuring your wallet, and obtaining testnet OM tokens for development purposes. We have also walked through the steps for writing and deploying CW20 contracts on MANTRA Chain. Now, we will shift our focus to writing NFT CosmWasm contracts.

Overview

Before diving into the steps of creating an NFT on MANTRA Chain, let's understand the contract structure and flow:

  1. CW-721 Base Contract: All NFT data is stored and instantiated here. This contract handles the creation and management of individual NFTs.

  2. CW-721 Factory Contract: Facilitates the creation of multiple NFT instances. Deployed once, it handles the heavy lifting for NFT creation.

Prerequisites

If you encounter any issues with your development environment setup or need to refresh your setup, follow these steps:

  1. Install Rust

First, you need to install Rust, a programming language used for developing smart contracts. You can do this by running the following command in your terminal:

curl --proto '=https' --tlsv1.2 <https://sh.rustup.rs> -sSf | sh

When prompted, choose the first option for standard installation. This will install Rust and its package manager, Cargo.

  1. Set Up Rust and Cargo

After installing Rust, set up Rust to use the stable version and verify the installation:

rustup default stable

cargo version

rustup target add wasm32-unknown-unknown

This ensures that you have the latest stable version of Rust and adds the WebAssembly target required for smart contract development.

  1. Install cargo-generate

cargo-generate is a tool that helps you to generate a new Rust project from a template. Install it using the following command:

cargo install cargo-generate --features vendored-openssl
  1. Install mantrachaind

Download and extract the pre-built mantrachaind binary:

On Linux:

# Download the CLI
curl -LO <https://github.com/MANTRA-Finance/public/raw/main/mantrachain-testnet/mantrachaind-linux-amd64.zip>

# Unzip the CLI
unzip mantrachaind-linux-amd64.zip

On MacOS:

# Download the CLI for Intel chips
curl -LO <https://github.com/MANTRA-Finance/public/raw/main/mantrachain-hongbai/mantrachaind-static-darwin-amd64.tar.gz>

# Download the CLI for Silicon chips (M1, M2...)
curl -LO <https://github.com/MANTRA-Finance/public/raw/main/mantrachain-hongbai/mantrachaind-static-darwin-arm64.tar.gz>

# Extract the CLI
tar -xzvf mantrachaind-static-darwin-*.tar.gz

If you encounter a missing libwasmvm.x86_64.so error, download the library:

sudo wget -P /usr/lib <https://github.com/CosmWasm/wasmvm/releases/download/v2.1.0/libwasmvm.x86_64.so>

Getting Started with NFT CosmWasm Contract

Now that your development environment is set up, let's begin with a boilerplate codebase for NFT CosmWasm contracts. It contains a starting template for CW721 NFT contracts tailored for deployment on MANTRA Chain.

Use any your preferred code editor, such as Visual Studio Code. Open its terminal,then clone the following repository:

git clone <https://github.com/MANTRA-Finance/cw721_contract_sample.git>

Open the cloned project in VS Code to proceed with customizing and building your NFT contract.

Adding Dependencies

Navigate to Cargo.toml in your project and add the following dependencies under the [dependencies] section:

# ...

[dependencies]
cw721-base = { version = "0.18.0", features = ["library"] }
cw-utils = "1.0.3"
cosmwasm-std = "1.5.7"
cw-storage-plus = "1.2.0"
cw2 = "1.1.2"
schemars = "0.8.10"
serde = { version = "1.0.207", default-features = false, features = ["derive"] }
thiserror = { version = "1.0.63" }
cosmwasm-schema = "2.1.3"
prost = "0.13.1"

# ...

State Definition (state.rs)

Navigate to the src folder where your contract's source code resides.

In your state.rs file, define the initial state structure for your NFT contract:

use cosmwasm_schema::cw_serde;

use cw721_base::Extension;

use cosmwasm_std::{Addr, Uint128};
use cw_storage_plus::Item;

#[cw_serde]
pub struct Config {
    pub owner: Addr,
    pub cw20_address: Addr,
    pub cw721_address: Option<Addr>,
    pub max_tokens: u32,
    pub unit_price: Uint128,
    pub name: String,
    pub symbol: String,
    pub token_uri: String,
    pub extension: Extension,
    pub unused_token_id: u32,
}

pub const CONFIG: Item<Config> = Item::new("config");

Instantiating Your NFT CosmWasm Contract

When deploying contracts on the chain, they must be instantiated. This step involves initializing the contract's state and linking it with a CW721 contract to handle NFTs. You will create a Config struct to store the necessary information and define the logic for submessages and handling replies. This ensures that our contract can interact seamlessly with the CW721 contract.

The following code should be added to src/contract.rs to handle the instantiation and reply logic. The instantiate function sets up the initial configuration and sends a submessage to instantiate the CW721 contract. The reply function processes the response from this submessage, linking the CW721 contract to our main contract.

Add the following code in src/contract.rs:

use std::marker::PhantomData;

use cosmwasm_std::{ensure, entry_point, to_json_binary};
use cosmwasm_std::{
    Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Reply, ReplyOn, Response, StdResult,
    SubMsg, Uint128, WasmMsg,
};
use cw2::set_contract_version;
use cw721_base::helpers::Cw721Contract;
use cw721_base::{ExecuteMsg as Cw721ExecuteMsg, Extension, InstantiateMsg as Cw721InstantiateMsg};
use cw_utils::{must_pay, parse_reply_instantiate_data};

use crate::error::ContractError;
use crate::msg::{ConfigResponse, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
use crate::state::{Config, CONFIG};

// version info for migration info
const CONTRACT_NAME: &str = "crates.io:aura-nft";
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");

const INSTANTIATE_TOKEN_REPLY_ID: u64 = 1;

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

    ensure!(
        msg.unit_price.amount > Uint128::zero(),
        ContractError::InvalidUnitPrice {}
    );
    ensure!(msg.max_tokens > 0, ContractError::InvalidMaxTokens {});

    let config = Config {
        cw721_address: None,
        unit_price: msg.unit_price,
        max_tokens: msg.max_tokens,
        owner: info.sender,
        name: msg.name.clone(),
        symbol: msg.symbol.clone(),
        token_uri: msg.token_uri.clone(),
        extension: msg.extension.clone(),
        unused_token_id: 0,
    };

    CONFIG.save(deps.storage, &config)?;

    let sub_msg: Vec<SubMsg> = vec![SubMsg {
        msg: WasmMsg::Instantiate {
            code_id: msg.token_code_id,
            msg: to_json_binary(&Cw721InstantiateMsg {
                name: msg.name.clone(),
                symbol: msg.symbol,
                minter: env.contract.address.to_string(),
            })?,
            funds: vec![],
            admin: None,
            label: String::from("Instantiate fixed price NFT contract"),
        }
        .into(),
        id: INSTANTIATE_TOKEN_REPLY_ID,
        gas_limit: None,
        reply_on: ReplyOn::Success,
    }];

    Ok(Response::new().add_submessages(sub_msg))
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result<Response, ContractError> {
    let mut config: Config = CONFIG.load(deps.storage)?;

    ensure!(
        config.cw721_address == None,
        ContractError::Cw721AlreadyLinked {}
    );
    ensure!(
        msg.id == INSTANTIATE_TOKEN_REPLY_ID,
        ContractError::InvalidTokenReplyId {}
    );

    let reply = parse_reply_instantiate_data(msg).unwrap();
    config.cw721_address = Addr::unchecked(reply.contract_address).into();
    CONFIG.save(deps.storage, &config)?;

    Ok(Response::new())
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(_deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result<Response, ContractError> {
    match msg {
        //No migrate implemented
    }
}

Add the following code in src/msg.rs:

use cosmwasm_schema::{cw_serde, QueryResponses};

use cosmwasm_std::{Addr, Binary, Coin, Uint128};
use cw721_base::Extension;

#[cw_serde]
pub struct InstantiateMsg {
    pub owner: String,
    pub max_tokens: u32,
    pub unit_price: Coin,
    pub name: String,
    pub symbol: String,
    pub token_code_id: u64,
    pub token_uri: String,
    pub extension: Extension,
}

Writing the Execution Logic

Now let's define the ExecuteMsg in src/msg.rs:

#[cw_serde]
pub enum ExecuteMsg {
    Mint,
}

#[cw_serde]
pub enum MigrateMsg {}

Next, integrate the execution logic into src/contract.rs, below the handling replies contract code. This includes the execute function, which handles incoming messages, and the execute_receive function, which processes transactions and mints NFTs.


/// Handling contract execution
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
    deps: DepsMut,
    _env: Env,
    info: MessageInfo,
    msg: ExecuteMsg,
) -> Result<Response, ContractError> {
    match msg {
        ExecuteMsg::Mint => execute_mint(deps, info),
    }
}

pub fn execute_mint(deps: DepsMut, info: MessageInfo) -> Result<Response, ContractError> {
    let mut config = CONFIG.load(deps.storage)?;
    if config.cw721_address == None {
        return Err(ContractError::Uninitialized {});
    }

    if config.unused_token_id >= config.max_tokens {
        return Err(ContractError::SoldOut {});
    }

    let amount = must_pay(&info, &config.unit_price.denom)?;

    if amount != config.unit_price.amount {
        return Err(ContractError::WrongPaymentAmount {});
    }

    let mint_msg = Cw721ExecuteMsg::<Extension, Empty>::Mint {
        token_id: config.unused_token_id.to_string(),
        owner: info.sender.into_string(),
        token_uri: config.token_uri.clone().into(),
        extension: config.extension.clone(),
    };

    match config.cw721_address.clone() {
        Some(cw721) => {
            let callback =
                Cw721Contract::<Empty, Empty>(cw721, PhantomData, PhantomData).call(mint_msg)?;
            config.unused_token_id += 1;
            CONFIG.save(deps.storage, &config)?;

            Ok(Response::new().add_message(callback))
        }
        None => Err(ContractError::Cw721NotLinked {}),
    }
}

Here, the execute function dispatches incoming messages to the appropriate handler. The execute_mint function accepts a native coin, checks it against the configuration set on instantiation, and if the conditions for minting a NFT are right, sends a mint message to the linked CW721 contract. The callback used here is a CosmosMsg message to trigger the mint function on the CW721 contract linked to our contract after instantiation.

Error Handling Logic

In this step, you will delete the src/helpers.rs file from our project setup. You will also create an errors.rs file in the src folder if it doesn't already exist. This file will handle the error logic for running our contracts.

Create the src/errors.rs file and add the following code:

use cosmwasm_std::StdError;
use cw_utils::PaymentError;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ContractError {
    #[error("{0}")]
    Std(#[from] StdError),

    #[error("Unauthorized")]
    Unauthorized {},

    #[error("Custom Error val: {val:?}")]
    CustomError { val: String },

    #[error("InvalidUnitPrice")]
    InvalidUnitPrice {},

    #[error("InvalidMaxTokens")]
    InvalidMaxTokens {},

    #[error("InvalidTokenReplyId")]
    InvalidTokenReplyId {},

    #[error("Cw721AlreadyLinked")]
    Cw721AlreadyLinked {},

    #[error("SoldOut")]
    SoldOut {},

    #[error("UnauthorizedTokenContract")]
    UnauthorizedTokenContract {},

    #[error("Uninitialized")]
    Uninitialized {},

    #[error("WrongPaymentAmount")]
    WrongPaymentAmount {},

    #[error("Cw721NotLinked")]
    Cw721NotLinked {},

    #[error("{0}")]
    PaymentError(#[from] PaymentError),
}

Writing Query Logic

Querying is an essential part of contract interactions, allowing you to retrieve data stored on the blockchain. In this step, we'll add the necessary query logic to our contract.

Add the following code to msg.rs:

#[cw_serde]
#[derive(QueryResponses)]
pub enum QueryMsg {
    #[returns(ConfigResponse)]
    GetConfig {},
}

#[cw_serde]
pub struct ConfigResponse {
    pub owner: Addr,
    pub cw721_address: Option<Addr>,
    pub max_tokens: u32,
    pub unit_price: Coin,
    pub name: String,
    pub symbol: String,
    pub token_uri: String,
    pub extension: Extension,
    pub unused_token_id: u32,
}

Add the following code to contract.rs:


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

fn query_config(deps: Deps) -> StdResult<ConfigResponse> {
    let config = CONFIG.load(deps.storage)?;
    Ok(ConfigResponse {
        owner: config.owner,
        cw721_address: config.cw721_address,
        max_tokens: config.max_tokens,
        unit_price: config.unit_price,
        name: config.name,
        symbol: config.symbol,
        token_uri: config.token_uri,
        extension: config.extension,
        unused_token_id: config.unused_token_id,
    })
}

#[cfg(test)]
mod tests {
    use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MOCK_CONTRACT_ADDR};
    use cosmwasm_std::{from_json, to_json_binary, Coin, SubMsgResponse, SubMsgResult};
    use prost::Message;

    use super::*;

    const NFT_CONTRACT_ADDR: &str = "nftcontract";

    #[derive(Clone, PartialEq, Message)]
    struct MsgInstantiateContractResponse {
        #[prost(string, tag = "1")]
        pub contract_address: ::prost::alloc::string::String,
        #[prost(bytes, tag = "2")]
        pub data: ::prost::alloc::vec::Vec<u8>,
    }

    #[test]
    fn initialization() {
        let mut deps = mock_dependencies();
        let msg = InstantiateMsg {
            owner: "owner".to_string(),
            max_tokens: 1,
            unit_price: Coin {
                denom: "uom".to_string(),
                amount: Uint128::one(),
            },
            name: String::from("FirstFT"),
            symbol: String::from("FFT"),
            token_code_id: 10u64,
            token_uri: String::from("<https://ipfs.io/ipfs/Q>"),
            extension: None,
        };

        let info = mock_info("owner", &[]);
        let res = instantiate(deps.as_mut(), mock_env(), info.clone(), msg.clone()).unwrap();

        instantiate(deps.as_mut(), mock_env(), info, msg.clone()).unwrap();

        assert_eq!(
            res.messages,
            vec![SubMsg {
                msg: WasmMsg::Instantiate {
                    code_id: msg.token_code_id,
                    msg: to_json_binary(&Cw721InstantiateMsg {
                        name: msg.name.clone(),
                        symbol: msg.symbol.clone(),
                        minter: MOCK_CONTRACT_ADDR.to_string(),
                    })
                    .unwrap(),
                    funds: vec![],
                    admin: None,
                    label: String::from("Instantiate fixed price NFT contract"),
                }
                .into(),
                id: INSTANTIATE_TOKEN_REPLY_ID,
                gas_limit: None,
                reply_on: ReplyOn::Success,
            }]
        );

        let instantiate_reply = MsgInstantiateContractResponse {
            contract_address: "nftcontract".to_string(),
            data: vec![2u8; 32769],
        };
        let mut encoded_instantiate_reply =
            Vec::<u8>::with_capacity(instantiate_reply.encoded_len());
        instantiate_reply
            .encode(&mut encoded_instantiate_reply)
            .unwrap();

        let reply_msg = Reply {
            id: INSTANTIATE_TOKEN_REPLY_ID,
            result: SubMsgResult::Ok(SubMsgResponse {
                events: vec![],
                data: Some(encoded_instantiate_reply.into()),
            }),
        };
        reply(deps.as_mut(), mock_env(), reply_msg).unwrap();

        let query_msg = QueryMsg::GetConfig {};
        let res = query(deps.as_ref(), mock_env(), query_msg).unwrap();
        let config: Config = from_json(&res).unwrap();
        assert_eq!(
            config,
            Config {
                owner: Addr::unchecked("owner"),
                cw721_address: Some(Addr::unchecked(NFT_CONTRACT_ADDR)),
                max_tokens: msg.max_tokens,
                unit_price: msg.unit_price,
                name: msg.name,
                symbol: msg.symbol,
                token_uri: msg.token_uri,
                extension: None,
                unused_token_id: 0,
            }
        );
    }
}

In this code, the query function handles incoming query messages and dispatches them to the appropriate handler. The query_config function retrieves the contract's configuration data from storage and returns it as a ConfigResponse.

You have successfully written a complete NFT CosmWasm contract . This contract can be deployed on MANTRA Chain or any other Cosmos-based chain. It includes functionalities for initialization, execution, and querying, enabling the minting and management of NFTs.

Make sure to thoroughly test your contract on a testnet before deploying it on the Hongbai Testnet. Customize the contract further to meet your specific requirements and ensure it fits your project's needs.

Step 4: Build Artifacts and Wasm file

To build the artifact, we need to run the rust-optimizer with docker:

docker run --rm -v "$(pwd)":/code \\
  --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \\
  --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \\
  cosmwasm/optimizer:0.16.0

Or if you are on MacOS:

docker run --rm -v "$(pwd)":/code \\
  --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \\
  --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \\
  cosmwasm/optimizer-arm64:0.16.0

This command generates a wasm file moni.wasm under the artifacts folder.

Step 5: Deploy the contract

We are going to deploy our contract using the mantrachaind cli:

RES=$(mantrachaind tx wasm store artifacts/moni.wasm --from <wallet> --node <https://rpc.hongbai.mantrachain.io:443> --chain-id mantra-hongbai-1 --gas-prices 0.35uom --gas auto --gas-adjustment 1.4 -y --output json)
TX_HASH=$(echo $RES | jq -r .txhash)
CODE_ID=$(mantrachaind query tx $TX_HASH --node <https://rpc.hongbai.mantrachain.io:443> -o json| jq -r '.logs[0].events[] | select(.type == "store_code") | .attributes[] | select(.key == "code_id") | .value')
echo $CODE_ID

Once we get the code_id after storing the contract on chain, we can create an instance of the contract like this:

MSG='{
  "owner": "", // add your wallet
  "max_tokens": 10000,
  "unit_price": {
    "denom": "uom",
    "amount": "1000"
  },
  "name": "Mantra NFT",
  "symbol": "MANTRANFT",
  "token_code_id": 507, // the code id of a cw721_base contract
  "token_uri": "<https://ipfs.io/ipfs/QmZ>"
}'

mantrachaind tx wasm instantiate <code_id> "$MSG" --from <wallet> --node <https://rpc.hongbai.mantrachain.io:443> --chain-id mantra-hongbai-1 --label "MANTRAcw20" --no-admin --gas-prices 0.35uom --gas auto --gas-adjustment 1.4 -y --output json

This will produce an output like the following:

gas estimate: 217659
{"height":"0","txhash":"BB3A0FEF8370B0A407D8AB93919212A4E39E8BA4D52D84836E2E27D76F707D0A","codespace":"","code":0,"data":"","raw_log":"[]","logs":[],"info":"","gas_wanted":"0","gas_used":"0","tx":null,"timestamp":"","events":[]}

We can get the contract address by exploring the transaction hash using jq:

hamantrachaind q tx BB3A0FEF8370B0A407D8AB93919212A4E39E8BA4D52D84836E2E27D76F707D0A --node <https://rpc.hongbai.mantrachain.io:443> -o json | jq -r '.logs[] | .events[] | select(.type == "instantiate") | .attributes[] | select(.key == "_contract_address") | .value'

Happy Coding!

Last updated 8 months ago

For more details on setting up the development environment, check the section .

Note that the token_code_id value on the InstantiateMsg needs to be a valid cw721_base contract code_id. Feel free to compile and deploy your own from the repo, but you can reuse the code_id 507 which is one we have deployed on Hongbai Testnet.

Once deployed, you will receive the wallet address and confirmation of contract instantiation. Verify your deployment on the MANTRA Chain Explorer at.

Install Prerequisites
cw-nfts
http://explorer.hongbai.mantrachain.io