Skip to main content

Create cw721-dapp project

This article will guide you in setting up an NFT smart contract, and show you how to build, test and deploy your NFT contract by using beaker toolchain. You can clone source code at cw721-repository.

Prerequisites

To complete this tutorial successfully, you'll need:

  • Beaker toolchain.
  • An Aura account.
  • NFT Storage Account.

I think you familiar with Beaker and know how to create an Aura account. There are a new thing I want to introduce, it is NFT Storage. Instead of store NFT image in public storage such like Google Drive, .. we will use third party to store NFT image distributive is IPFS. You can use free service such like NFT Storage.

Create project

Yeahhh, it's finally time to start coding our main contract! Let's use beaker to create cw721-dapp:

beaker new cw721-dapp

When a notification appear, just choose minimal template.

? 🤷   Which starting template would you like to use? 
❯ minimal
counter-example

Now we have basically project. But there are nothing contract at here. Just create our contract:

cd cw721-dapp
beaker wasm new cw721-factory

Now, source code for this contract can be find in contracts/cw721-factory. We will start in the src/state.rs file. Upon opening it you should see code like the following:

src/state.rs
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use cosmwasm_std::Addr;
use cw_storage_plus::Item;

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct State {
pub count: i32,
pub owner: Addr,
}

pub const STATE: Item<State> = Item::new("state");

Let's talk through this section by section.

The first 5 lines of code are importing other structs, interfaces and functions from other packages. To give a quick overview without getting bogged down too much:

  • JsonSchema allows structs to be serialized and deserialized to and from JSON.
  • Deserialize and Serialize provide the serialization described above.
  • Addr is a Cosmos address, under the hood it is simply a string.
  • Item is a helper provided by storage plus. It effectively means we can store an item in storage. In this case, the STATE variable is an Item that stores a singular State struct.

We want to make some changes to this, let's rename it accordingly. I prefer our global state to be called Config as it is the configuration of our contract. Let's do this now and remove the count and owner variable from the struct and rename it to Config. Let's also rename STATE to CONFIG.

It should look like this:

src/state.rs
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use cosmwasm_std::Addr;
use cw_storage_plus::Item;

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct Config {

}

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

Now let's think about what global configs we want, we probably want an NFT Minter Contract that can mint some amazing NFTs for buyer with fixed price fee. Let's add some necessary fields representing the configuration parameters of our NFT minter contract.

src/state.rs
// Previous code omitted
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
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,
}
// Following code omitted

Now I'll explain to you about the above data fields.

  • The owner field specifies which account is the owner of your contract.
  • The cw20_address field specifies the address of which cw20 token contract used as a payment method. We will use the contract deployed in chapter 3.
  • The cw721_address field specifies the address of the cw721 token contract we will use. But the cw721 is created dynamically during contract instantiation, so there's no need to instantiate a cw721 token contract separately.
  • The max_tokens field specifies the maximum mint token amount of this contract.
  • The unit_price field specifies the unit price for each NFT.
  • name, symbol, token_uri, extension fields specifies the NFT token info and metadata. Here, the extension is of type cw721_base::Extension. So you need to add a line like the following in the declaration of the library used by Rust.
use cw721_base::Extension;

Also need to declare the use of cw721-base as a dependency in Cargo.toml. Open Cargo.toml and add this to the dependencies:

# ...

[dependencies]
cw721-base = { version = "0.15.0", features = ["library"] }
cw-utils = "0.12.1"

# ...
  • The unused_token_id field is used to store the total number of NFT tokens have been minted. The reason it is named 'unused_token_id' is because, when executing the mint action, the NFT token will be assigned a token_id equal to the value of the current 'unused_token_id'.

Ok, here we have done all the necessary work. In the next chapter, we will move on to working in the src/contract.rs.

Disclaimer!

If you currently run any command such as cargo test or cargo wasm the build will break! Don't worry this is expected as we have modified our storage code without changing code on the contract side.

We'll fix this in next chapters.