Abstract:
This post serves as a developer’s guide to deploying a CosmWasm smart contract onto the Stargaze testnet. It covers essential steps including setting up the Stargaze CLI (starsd
), configuring a wallet, obtaining testnet tokens, and managing Rust versions for compatibility. The guide also walks through uploading the contract, obtaining its code ID, and instantiating it on the testnet.
Estimated reading time: 15 minutes
Deploying a Smart Contract to Stargaze Testnet
Introduction
Recently, I needed to deploy a smart contract to Stargaze. Here are some key things I learned:
- Stargaze is a Cosmos IBC Chain.
- I needed to deploy to the Stargaze testnet.
- The deployment process has two separate steps:
- Uploading the contract to the chain.
- Instantiating it (which is like a factory creating an instance).
- CosmWasm v2.2.2 is available, but I couldn’t make it work with newer Rust versions. I had to use an older version, Rust 1.81.0.
The documentation is spread across different Cosmos and Stargaze resources. This meant I had to switch between them often.
Setting Up the Environment
Setting up the Stargaze CLI (starsd
)
First, I needed to set up the Stargaze command-line interface (starsd
):
1
2
# Clone the Stargaze repository
gh repo clone public-awesome/stargaze
Note: Their
git clone
command did not work as shown in their documentation.
Next, I needed to configure the CLI to connect to the Stargaze testnet:
1
2
3
4
5
6
7
8
# Set the testnet chain ID
starsd config set client chain-id elgafar-1
# Set the RPC endpoint
starsd config set client node https://rpc.elgafar-1.stargaze-apis.com:443
# Check the configuration
starsd config view client
The output should be similar to this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
###############################################################################
### Client Configuration ###
###############################################################################
# The network chain ID
chain-id = "elgafar-1"
# The keyring's backend, where the keys are stored (os|file|kwallet|pass|test|memory)
keyring-backend = "os"
# CLI output format (text|json)
output = "text"
# <host>:<port> to CometBFT RPC interface for this chain
node = "https://rpc.elgafar-1.stargaze-apis.com:443"
# Transaction broadcasting mode (sync|async)
broadcast-mode = "sync"
Setting Up a Wallet
I imported my wallet using my BIP39 recovery phrase (mnemonic):
1
2
3
4
5
# Import your private key via the bip39 mnemonic
starsd keys add imported-wallet --recover
# Check it has been added to your keyring
starsd keys list
Example output:
1
2
3
4
5
Enter keyring passphrase (attempt 1/3):
- address: stars1t50gf5zarvpjh4h27epxydz5ewq5klw5mrtr8c
name: imported-wallet
pubkey: '{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"An9Ykmdeu9Kn/PGGnoe+d/vnpCX1LDqygPdMgAgzS979"}'
type: local
Getting Testnet Starz
You can get testnet STARS from the faucet at: https://testnet.ping.pub/stargaze/faucet
The faucet gives 5 testnet STARS each time. It seems you can only request once per day.
Setting Up Rust
Next, I needed the correct Rust version:
Important Note: Rust compiler versions 1.82 and newer have a feature called “reference types” turned on by default. This causes compatibility problems. Even though CosmWasm v2.2.2 is available and should support reference types, I could not get it to work correctly. To compile and deploy my contracts successfully, I had to use an older Rust version, 1.81.0.
1
2
3
4
5
6
7
8
# Install Rust 1.81.0
rustup install 1.81.0
# Set it as the default
rustup default 1.81.0
# Add the wasm target for this specific version
rustup target add wasm32-unknown-unknown --toolchain 1.81.0
Verifying the Installation
1
2
3
4
5
# Check current Rust version
rustc --version # Should show 1.81.0
# Check available targets
rustup target list --installed # Should include wasm32-unknown-unknown
Contract Verification
Clone the cw-plus contracts:
1
gh repo clone CosmWasm/cw-plus
I used the cw1_whitelist
contract:
1
cd contracts/cw1-whitelist && cargo wasm
If you encounter any issues, try running cargo clean
, then cargo update
, and finally cargo wasm
.
Before uploading or instantiating a contract, I verified it with the compatibility check:
1
cosmwasm-check ../../target/wasm32-unknown-unknown/release/cw1_whitelist.wasm
Uploading the Contract
The store
command uploads the compiled WASM contract:
1
2
3
4
5
starsd tx wasm store cw1_whitelist.wasm \
--from stars1t50gf5zarvpjh4h27epxydz5ewq5klw5mrtr8c \
--gas-prices 0.025ustars \
--gas-adjustment 1.7 \
--gas auto
Save the transaction hash from this command’s output; you will need it later to find the code_id
.
Getting the Contract Code ID
Using the transaction hash (from the store
command output):
1
starsd q tx 0A1078B8364950CE7CE6F3992248662D9F1284676B912CEC2A64AA5176D63C64 -o json | jq '.. | select(.key? == "code_id")'
Output:
1
2
3
4
5
{
"key": "code_id",
"value": "5196",
"index": true
}
After uploading, you can also get the code_id
by querying the contract address:
1
starsd query wasm contract stars1cq7f2fkv5glhc94r62utemftx6y87cxudl0zvkcr3va9tj8n3mss9xlg3m
Output:
1
2
3
4
5
6
7
8
9
10
11
address: stars1cq7f2fkv5glhc94r62utemftx6y87cxudl0zvkcr3va9tj8n3mss9xlg3m
contract_info:
admin: stars1t50gf5zarvpjh4h27epxydz5ewq5klw5mrtr8c
code_id: "5196"
created:
block_height: "15822799"
tx_index: "0"
creator: stars1t50gf5zarvpjh4h27epxydz5ewq5klw5mrtr8c
extension: null
ibc_port_id: ""
label: StargazeContract
Instantiating the Contract
The message needed to instantiate your smart contract depends on its specific initial settings. For this cw1_whitelist
contract, for example, I needed to define the list of administrators.
First, I checked the contract’s msg.rs
file to identify the expected parameters:
1
2
3
4
pub struct InstantiateMsg {
pub admins: Vec<String>,
pub mutable: bool,
}
Then I prepared the instantiation message:
1
INSTANTIATE_MSG='{"admins": ["stars1t50gf5zarvpjh4h27epxydz5ewq5klw5mrtr8c"], "mutable": true}'
And instantiated the contract:
1
2
3
4
5
6
7
8
starsd tx wasm instantiate 5196 "$INSTANTIATE_MSG" \
--label "StargazeContract" \
--admin stars1t50gf5zarvpjh4h27epxydz5ewq5klw5mrtr8c \
--from imported-wallet \
--amount "1000000ustars" \
--gas-prices 0.025ustars \
--gas-adjustment 1.7 \
--gas auto
Verifying Contract Deployment
Checking Contract Info
1
starsd query wasm contract stars1cq7f2fkv5glhc94r62utemftx6y87cxudl0zvkcr3va9tj8n3mss9xlg3m
Querying Contract State (Admins List)
1
starsd query wasm contract-state smart stars1cq7f2fkv5glhc94r62utemftx6y87cxudl0zvkcr3va9tj8n3mss9xlg3m '{"admin_list":{}}'
Output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
address: stars1cq7f2fkv5glhc94r62utemftx6y87cxudl0zvkcr3va9tj8n3mss9xlg3m
contract_info:
admin: stars1t50gf5zarvpjh4h27epxydz5ewq5klw5mrtr8c
code_id: "5196"
created:
block_height: "15822799"
tx_index: "0"
creator: stars1t50gf5zarvpjh4h27epxydz5ewq5klw5mrtr8c
extension: null
ibc_port_id: ""
label: StargazeContract
data:
admins:
- stars1t50gf5zarvpjh4h27epxydz5ewq5klw5mrtr8c
mutable: true
Executing a function in the Smart Contract
This will withdraw 0.5 STAR to the to_address (in this case the admin account)
1
2
3
4
5
6
7
8
9
10
11
12
13
starsd tx wasm execute stars1cq7f2fkv5glhc94r62utemftx6y87cxudl0zvkcr3va9tj8n3mss9xlg3m \
'{"execute": {"msgs": [{
"bank": {
"send": {
"to_address": "stars1t50gf5zarvpjh4h27epxydz5ewq5klw5mrtr8c",
"amount": [{"denom": "ustars", "amount": "500000"}]
}
}
}]}}' \
--from imported-wallet \
--gas-prices 0.025ustars \
--gas-adjustment 1.7 \
--gas auto
Confirm the updated balance
1
2
3
4
5
6
starsd query bank balances stars1cq7f2fkv5glhc94r62utemftx6y87cxudl0zvkcr3va9tj8n3mss9xlg3m
balances:
- amount: "500000"
denom: ustars
pagination:
total: "1"
Tips for Analyzing a Standard CosmWasm Contract
When analyzing a standard CosmWasm contract, I typically look at the files in this order:
-
msg.rs - First, because it defines the contract’s interface:
- Contains the
InstantiateMsg
,ExecuteMsg
, andQueryMsg
structs - Shows what actions the contract can perform
- Gives you a high-level overview of the contract’s capabilities
- Contains the
-
state.rs - Second, because it defines the contract’s data model:
- Contains the contract’s storage schema
- Shows what data structures are being stored
- Defines key-value store configurations
-
contract.rs - Third, as it contains the main business logic:
- Has the core instantiate/execute/query entry points
- Contains the actual implementation of contract functions
- Shows how the contract processes messages and manages state
-
error.rs - Fourth, to understand error handling:
- Defines custom error types
- Shows what can go wrong
- Helps understand validation and error cases
-
lib.rs - Fifth, as it’s the entry point but usually just re-exports:
- Shows module organization
- Contains crate-level attributes and configurations
- Links all the components together
-
integration_tests.rs - Last, to understand how it all works together:
- Contains end-to-end tests
- Shows real-world usage examples
- Demonstrates expected behaviors
-
bin/schema.rs (in bin directory) - Reference as needed:
- Generates JSON schema for messages
- Useful for client integration
This order helps build understanding from the interface level down to implementation details. The message definitions (msg.rs
) and state management (state.rs
) give you the big picture before diving into the implementation details in contract.rs
.
Macro expansion
The tip https://cosmwasm.cosmos.network/tutorial/setup-environment is slightly wrong.
In vscode/cursor, you need to install the rust-analyzer extension. Then bring up the ctrl-shift-P menu to select the ‘rust analyzer: Expand macro recursively’ option.