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.
- Navigate to the
cw20-token
directory.
cd contracts/cw20-token
- 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.
- Open
src/msg.rs
in your code editor and paste the following.
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct MigrateMsg {}
- Open
src/lib.rs
and paste the following.
pub mod contract;
pub mod msg;
- Open
src/contract.rs
and paste the following.
#[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())
}
- Open
src/bin/schemas.rs
and paste the following.
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β
- Generate the new schema.
cd cw20_token
cargo schema
- 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:
*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.