Skip to main content

Create your own CW20 token on Aura Network

In this tutorial, you'll create your own CW20 token. And now, you will start working with the Cosmwasm contract code. It may be difficult for you to understand because it's the very first time, but we will not explain each line of code here, you just need to follow it step by step. Try to practice before learning the theory. In later chapters, when we start to write the main contract of this entire series, which can create, send and buy some NFTs, we will explain to you step by step, each concept. Try your best!

Source code​

You can clone source code to take a overview (link).

Prerequisites​

Use the following guides to set up your environment:

You'll also need:

  • An IDE or text editor of your choice. This tutorial will use Visual Studio Code.
  • A command line interface.

Generate the CW20 Token contract​

Now, create your cw20 factory token. Go to the folder in which you want to place it and run command:

beaker new cw20-dapp

When a notification appear, just choose minimal template.

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

You will now have a new folder called cw20-token containing a simple working contract and build system that you can customize.

Create the CW20 Factory Token smart contract​

In this section, you will create contract to build a CW20 token. This contract implements the CW20 Base, along with several custom files.

To create the cw20-token contract, follow the procedure below.

1. Create new contract​

First of all, you need create cw20-token contract, just run:

beaker wasm new cw20-token

2. Add the CW20 base​

Then add the CW20 Base, which implements the base CW20 token functionalities. To add the CW20 Base to the cw20-token contract, do the following.

  1. Navigate to the cw20-token directory.
cd contracts/cw20-token
  1. Open cargo.toml and add this to the dependencies:
# ...

[dependencies]
cw20-base = { version = "0.13.2", features = ["library"] }

# ...

3. Modify the contract files​

Now that you've added the CW20 Base to implement the base CW20 token logic, remove unuse files except for:

  • msg.rs
  • lib.rs
  • contract.rs
  • schemas.rs

Then, you will have a template like this under folder src:

.
β”œβ”€β”€ bin
β”œβ”€β”€β”€β”€schemas.rs
β”œβ”€β”€ msg.rs
β”œβ”€β”€ lib.rs
└── contract.rs

Ok, let's move on! We will modify contract step by step.

  1. Open src/msg.rs in your code editor and paste the following.
src/msg.rs
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct MigrateMsg {}
  1. Open src/lib.rs and paste the following.
src/lib.rs
pub mod contract;
pub mod msg;
  1. Open src/contract.rs and paste the following.
src/contract.rs
#[cfg(not(feature = "library"))]
use cosmwasm_std::entry_point;
use cosmwasm_std::{
to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult,
};
use cw20_base::ContractError;
use cw20_base::enumerable::{query_all_allowances, query_all_accounts};
use cw20_base::msg::{QueryMsg,ExecuteMsg};

use crate::msg::MigrateMsg;
use cw2::set_contract_version;
use cw20_base::allowances::{
execute_decrease_allowance, execute_increase_allowance, execute_send_from,
execute_transfer_from, query_allowance, execute_burn_from,
};
use cw20_base::contract::{
execute_mint, execute_send, execute_transfer, execute_update_marketing,
execute_upload_logo, query_balance, query_token_info, query_minter, query_download_logo, query_marketing_info, execute_burn,
};

// version info for migration info
const CONTRACT_NAME: &str = "crates.io:cw20-token";
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: cw20_base::msg::InstantiateMsg,
) -> Result<Response, ContractError> {
set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;

/* Execute the instantiate method from cw_20_base as the code from that
library is already battle tested we do not have to re-write the full
functionality: https://github.com/CosmWasm/cw-plus/tree/main/contracts/cw20-base*/
Ok(cw20_base::contract::instantiate(deps, env, info, msg)?)
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, cw20_base::ContractError> {
match msg {
ExecuteMsg::Transfer { recipient, amount } => {
execute_transfer(deps, env, info, recipient, amount)
}
ExecuteMsg::Burn { amount } => execute_burn(deps, env, info, amount),
ExecuteMsg::Send {
contract,
amount,
msg,
} => execute_send(deps, env, info, contract, amount, msg),
ExecuteMsg::Mint { recipient, amount } => execute_mint(deps, env, info, recipient, amount),
ExecuteMsg::IncreaseAllowance {
spender,
amount,
expires,
} => execute_increase_allowance(deps, env, info, spender, amount, expires),
ExecuteMsg::DecreaseAllowance {
spender,
amount,
expires,
} => execute_decrease_allowance(deps, env, info, spender, amount, expires),
ExecuteMsg::TransferFrom {
owner,
recipient,
amount,
} => execute_transfer_from(deps, env, info, owner, recipient, amount),
ExecuteMsg::BurnFrom { owner, amount } => execute_burn_from(deps, env, info, owner, amount),
ExecuteMsg::SendFrom {
owner,
contract,
amount,
msg,
} => execute_send_from(deps, env, info, owner, contract, amount, msg),
ExecuteMsg::UpdateMarketing {
project,
description,
marketing,
} => execute_update_marketing(deps, env, info, project, description, marketing),
ExecuteMsg::UploadLogo(logo) => execute_upload_logo(deps, env, info, logo),
}
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
/* Default methods from CW20 Standard with no modifications:
https://github.com/CosmWasm/cw-plus/tree/main/contracts/cw20-base */
QueryMsg::Balance { address } => to_binary(&query_balance(deps, address)?),
QueryMsg::TokenInfo {} => to_binary(&query_token_info(deps)?),
QueryMsg::Minter {} => to_binary(&query_minter(deps)?),
QueryMsg::Allowance { owner, spender } => {
to_binary(&query_allowance(deps, owner, spender)?)
}
QueryMsg::AllAllowances {
owner,
start_after,
limit,
} => to_binary(&query_all_allowances(deps, owner, start_after, limit)?),
QueryMsg::AllAccounts { start_after, limit } => {
to_binary(&query_all_accounts(deps, start_after, limit)?)
}
QueryMsg::MarketingInfo {} => to_binary(&query_marketing_info(deps)?),
QueryMsg::DownloadLogo {} => to_binary(&query_download_logo(deps)?),
}
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Response> {
Ok(Response::default())
}
  1. Open src/bin/schemas.rs and paste the following.
src/bin/schemas.rs
use std::env::current_dir;
use std::fs::create_dir_all;

use cosmwasm_schema::{export_schema, remove_schemas, schema_for};
use cw20_base::msg::{InstantiateMsg, QueryMsg, ExecuteMsg};


fn main() {
let mut out_dir = current_dir().unwrap();
out_dir.push("schema");
create_dir_all(&out_dir).unwrap();
remove_schemas(&out_dir).unwrap();

export_schema(&schema_for!(InstantiateMsg), &out_dir);
export_schema(&schema_for!(ExecuteMsg), &out_dir);
export_schema(&schema_for!(QueryMsg), &out_dir);
}

4. Generate and test the schema​

  1. Generate the new schema.
cd cw20_token
cargo schema
  1. Test the schema.
cd cw20_token
cargo schema
cargo test

5. Build and deploy contract​

Before your contract can be used, it has to be compiled, and then stored on chain. We will show you 2 ways to build and deploy contract.

Use Beaker​

Beaker is powerful toolchain. It make deploy and interact contract easier.

1. Configure beaker.toml​

name = ''
gas_price = '0.025uaura'
gas_adjustment = 1.3
account_prefix = 'aura'
derivation_path = '''m/44'/118'/0'/0/0'''

[networks.serenity]
chain_id = 'auradev_1236-2'
network_variant = 'Shared'
grpc_endpoint = 'https://grpc.serenity.aura.network:9092'
rpc_endpoint = 'https://rpc.serenity.aura.network'

[accounts.signer]
mnemonic = 'your mnemonic'

2. Initial balance​

All you need to do is run one command:

beaker wasm deploy cw20-token --signer-account signer --raw '{"name":"Aura Test Token", "symbol":"ATT", "decimals":18, "initial_balances": [{"amount":"123", "address":"your address"}]}' --network serenity

3. Query balance​

You can query cw20 token balance by calling contract.

beaker wasm query cw20-token --raw '{"balance": { "address" : "your address" }}' --signer-account aloha --network serenity

4. Execute contract​

Other, you can interact with contract.

beaker wasm execute cw20-token --raw '{"burn": { "amount" : "3" }}' --signer-account aloha --network serenity

Use CLI​

1. Compile our contract​

Create scripts/build_contract.sh and paste the following.

# build cargo
cargo build

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

Open your terminal:

bash scripts/build_contract.sh

2. Store code on the blockchain​

Create scripts/store_contract.sh and paste the following.

NODE="https://rpc.euphoria.aura.network:443"
ACCOUNT="my-first-wallet"
CHAINID="aura_6321-3"
CONTRACT_DIR="artifacts/cw20_token.wasm"
SLEEP_TIME="15s"

RES=$(aurad tx wasm store "$CONTRACT_DIR" --from "$ACCOUNT" -y --output json --chain-id "$CHAINID" --node "$NODE" --gas 35000000 --fees 35000ueaura -y --output json)
echo $RES

if [ "$(echo $RES | jq -r .raw_log)" != "[]" ]; then
# exit
echo "ERROR = $(echo $RES | jq .raw_log)"
exit 1
else
echo "STORE SUCCESS"
fi

TXHASH=$(echo $RES | jq -r .txhash)

echo $TXHASH

# sleep for chain to update
sleep "$SLEEP_TIME"

RAW_LOG=$(aurad query tx "$TXHASH" --chain-id "$CHAINID" --node "$NODE" -o json | jq -r .raw_log)

echo $RAW_LOG

CODE_ID=$(echo $RAW_LOG | jq -r .[0].events[1].attributes[0].value)

echo $CODE_ID

Open your terminal:

bash scripts/store_contract.sh

After running the above bash command, if successful, your terminal will print something similar to this:

{"height":"0","txhash":"C421B976484B58832C9D399B7D9CC94872598C6B37F16551C087362419B579F0","codespace":"","code":0,"data":"","raw_log":"[]","logs":[],"info":"","gas_wanted":"0","gas_used":"0","tx":null,"timestamp":"","events":[]}
STORE SUCCESS
C421B976484B58832C9D399B7D9CC94872598C6B37F16551C087362419B579F0
[{"events":[{"type":"message","attributes":[{"key":"action","value":"/cosmwasm.wasm.v1.MsgStoreCode"},{"key":"module","value":"wasm"},{"key":"sender","value":"aura1t0wc2swe77hah62lul0zwlpfje842w4csrcq4t"}]},{"type":"store_code","attributes":[{"key":"code_id","value":"69"}]}]}]
69

Please remember the printed CODE_ID in terminal.

3. Finally, deploy your first contract​

Create scripts/deploy_contract.sh and paste the following.

NODE="https://rpc.euphoria.aura.network:443"
ACCOUNT="my-first-wallet"
CHAINID="aura_6321-3"
CONTRACT_DIR="artifacts/cw20_token.wasm"
SLEEP_TIME="15s"

CODE_ID="$1"

INIT="{\"name\":\"Aura Test Token\", \"symbol\":\"ATT\", \"decimals\":18, \"initial_balances\": [{\"amount\":\"123\", \"address\":\"$(aurad keys show $ACCOUNT -a)\"}]}"
INIT_JSON=$(aurad tx wasm instantiate "$CODE_ID" "$INIT" --from "$ACCOUNT" --label "cw20-token" -y --chain-id "$CHAINID" --node "$NODE" --no-admin --gas 180000 --fees 35000ueaura -o json)

echo "INIT_JSON = $INIT_JSON"

if [ "$(echo $INIT_JSON | jq -r .raw_log)" != "[]" ]; then
# exit
echo "ERROR = $(echo $INIT_JSON | jq .raw_log)"
exit 1
else
echo "INSTANTIATE SUCCESS"
fi

# sleep for chain to update
sleep "$SLEEP_TIME"

RAW_LOG=$(aurad query tx "$(echo $INIT_JSON | jq -r .txhash)" --chain-id "$CHAINID" --node "$NODE" --output json | jq -r .raw_log)

echo "RAW_LOG = $RAW_LOG"

CONTRACT_ADDRESS=$(echo $RAW_LOG | jq -r .[0].events[0].attributes[0].value)

echo "CONTRACT ADDRESS = $CONTRACT_ADDRESS"

Open your terminal:

bash scripts/deploy_contract.sh [CODE_ID]

After running the above bash command, if successful, your terminal will print something similar to this:

INIT_JSON = {"height":"0","txhash":"1980CC6172C749038E81927A99DF956F24E5133C1BDB44DD5293564A56B611C4","codespace":"","code":0,"data":"","raw_log":"[]","logs":[],"info":"","gas_wanted":"0","gas_used":"0","tx":null,"timestamp":"","events":[]}
INSTANTIATE SUCCESS
RAW_LOG = [{"events":[{"type":"instantiate","attributes":[{"key":"_contract_address","value":"aura1e30u7tkfmq9fueyuwtmnep3wl8rrdcj2gusvylsruzpn7cfaavtqhx76sz"},{"key":"code_id","value":"69"}]},{"type":"message","attributes":[{"key":"action","value":"/cosmwasm.wasm.v1.MsgInstantiateContract"},{"key":"module","value":"wasm"},{"key":"sender","value":"aura1t0wc2swe77hah62lul0zwlpfje842w4csrcq4t"}]}]}]
CONTRACT ADDRESS = aura1e30u7tkfmq9fueyuwtmnep3wl8rrdcj2gusvylsruzpn7cfaavtqhx76sz

Congratulations! So you have successfully built your first CW contract on Aura. After successful deployment, you can check your contract on Aurascan. For example, with the contract that I deployed while writing this series:

https://euphoria.aurascan.io/contracts/aura1e30u7tkfmq9fueyuwtmnep3wl8rrdcj2gusvylsruzpn7cfaavtqhx76sz

*Note: The entire source code of this contract, is stored here. You can use it if you encounter any mistakes while studying the chapters below.

6. Create dApp​