# CosmWasm on Juno The four operations: **store**, **instantiate**, **execute**, **query**. Plus schema discovery, address derivation (`instantiate2`), and migration. Juno has run CosmWasm since launch — this is the canonical home for the ecosystem. ## Mental model A CosmWasm contract is an account address (a `juno1...` bech32 like any other) with WebAssembly bytecode attached. The chain stores: - **Code** — uploaded wasm bytes, identified by `code_id` (a u64). - **Contracts** — instances of a code_id, with their own address, admin, and persistent state. Lifecycle: 1. **Store** the wasm → get a `code_id`. 2. **Instantiate** the code_id with an `InstantiateMsg` → get a contract address. 3. **Execute** messages against the contract → tx that may mutate state, emit events, call other contracts. 4. **Query** the contract → free reads against its `QueryMsg`. Optional: **migrate** to a new code_id (admin-only). ## Store — upload wasm ```bash junod tx wasm store /path/to/contract.wasm \ --from \ --chain-id juno-1 --node $RPC \ --gas auto --gas-adjustment 1.5 \ --gas-prices 0.075ujuno \ --keyring-backend test --keyring-dir \ --note 'descriptive: contract X v1.2.3, sha256 abc...' \ --yes ``` **Cost:** wasm store gas scales linearly with bytecode size. A 400KB wasm costs ~0.3–0.5 JUNO on mainnet. Use `--gas-adjustment 1.5` (or 1.6) — `auto` simulation tends to underestimate store-code gas. **Capture the code_id from the tx events:** ```bash TXHASH= sleep 6 junod query tx "$TXHASH" --node $RPC -o json \ | jq -r '.events[] | select(.type == "store_code") | .attributes[] | select(.key == "code_id") | .value' # Or the easier (but newer) path: junod query tx "$TXHASH" --node $RPC -o json \ | jq -r '.logs[0].events[] | select(.type == "store_code") | .attributes[] | select(.key == "code_id") | .value' ``` Some SDK versions return events under `.logs[*].events` and others under top-level `.events`. Try both. **Permissioning:** by default on `juno-1`, `wasm store` is *permissioned* — only specific addresses can upload. Check current state: ```bash junod query wasm params --node $RPC -o json | jq # Look at .params.code_upload_access (Everybody / Nobody / AnyOfAddresses) ``` If permissioning is restrictive, you need either: - A governance proposal to upload (chain-level x/gov) - An allowed-address upload (typically a foundation multisig) - An on-allowed-list upload (some chains list trusted addresses) `uni-7` testnet is usually permissionless for uploads. ## Instantiate — spawn a contract ```bash junod tx wasm instantiate '' \ --from \ --chain-id juno-1 --node $RPC \ --label "human-readable label" \ --no-admin \ --gas auto --gas-adjustment 1.5 \ --gas-prices 0.075ujuno \ --keyring-backend test --keyring-dir \ --yes ``` | Flag | Meaning | |---|---| | `--label` | Required. Free-form human-readable name visible in explorers. | | `--admin ` | Sets the contract admin (who can migrate). | | `--no-admin` | No admin (immutable contract). Mutually exclusive with `--admin`. | | `--amount` | Initial funds sent at instantiate (e.g. `1000000ujuno`). Optional. | **The InstantiateMsg is JSON specific to the contract.** Get its shape from the contract's `schema/` directory or via: ```bash junod query wasm code-info --node $RPC -o json | jq # (returns hash, creator, instantiate-permission, but not the InstantiateMsg shape itself) ``` For known contracts, look in the source repo's `schema/*.json` files or in `dao-dao-ui/packages/state/contracts/` for TypeScript clients. **Address discovery after instantiate:** ```bash TXHASH= sleep 6 junod query tx "$TXHASH" --node $RPC -o json \ | jq -r '.events[] | select(.type == "instantiate") | .attributes[] | select(.key == "_contract_address") | .value' ``` For long-running deployments, the address is also listed in: ```bash junod query wasm list-contract-by-code --reverse --limit 5 --node $RPC -o json | jq ``` ## Instantiate2 — deterministic addresses If you need the contract address *before* broadcasting (e.g., to announce it in advance, or to wire two contracts that reference each other): ```bash SALT_HEX=$(echo -n "my-deterministic-salt-string" | xxd -p) junod tx wasm instantiate2 '' "$SALT_HEX" \ --from --label "..." --no-admin \ --chain-id juno-1 --node $RPC \ --gas auto --gas-adjustment 1.5 --gas-prices 0.075ujuno \ --yes ``` The address is `instantiate2_address(checksum, creator, salt)`. You can predict it offline: ```python import hashlib, base64 from bech32 import bech32_encode, convertbits def predict_address(checksum_hex, creator_bech32, salt_bytes): creator_addr = bytes.fromhex(...) # decode bech32 → 20 bytes canonical = b"\x00\x21" + bytes.fromhex(checksum_hex) + b"\x14" + creator_addr + b"\x00\x14" + salt_bytes digest = hashlib.sha256(b"module" + hashlib.sha256(b"wasm").digest() + canonical).digest() five_bit = convertbits(digest, 8, 5) return bech32_encode("juno", five_bit) ``` (The exact derivation is `wasmd`'s `BuildContractAddressPredictable` — Go reference in `CosmWasm/wasmd/x/wasm/keeper/keeper.go`.) Use `instantiate2` for: pre-announced addresses, paired-contract bootstrap, cw-vesting + recipient pairings, anything where the address must exist in a script before the contract is live. ## Execute — call a contract ```bash junod tx wasm execute '' \ --from \ --chain-id juno-1 --node $RPC \ --gas auto --gas-adjustment 1.4 \ --gas-prices 0.075ujuno \ --keyring-backend test --keyring-dir \ --yes # To send funds along with the call: junod tx wasm execute '' \ --amount 1000000ujuno \ ... ``` The ExecuteMsg JSON matches the contract's `ExecuteMsg` enum. Common shapes: ```json // cw20 transfer {"transfer": {"recipient": "juno1...", "amount": "1000000"}} // DAO DAO vote {"vote": {"proposal_id": 42, "vote": "yes"}} // DAO DAO propose {"propose": {"title": "...", "description": "...", "msgs": [...]}} ``` **Multiple messages in one tx** — for atomic ops, sometimes the contract exposes a `batch` or `multi` variant; otherwise build a multi-msg tx with `--generate-only` then sign and broadcast. ## Query — read contract state ```bash junod query wasm contract-state smart '' \ --node $RPC -o json | jq ``` Smart queries: ```json // cw20 balance {"balance": {"address": "juno1..."}} // DAO DAO list_proposals {"list_proposals": {"limit": 10, "start_after": 5}} // Generic config / info {"config": {}} {"info": {}} ``` For low-level raw reads (key-value pairs) when you know the storage layout: ```bash junod query wasm contract-state raw --node $RPC junod query wasm contract-state all --node $RPC -o json | jq ``` `contract-state all` dumps everything; useful for debugging or extracting state for analysis. ## Schema discovery The cleanest way to know what messages a contract accepts: 1. **Look in the source repo.** Most CosmWasm contracts ship `schema/.json` — JSON Schema for InstantiateMsg, ExecuteMsg, QueryMsg, and response types. Regenerate locally with `cargo run --bin schema` or the contract's equivalent. 2. **Check `dao-dao-ui/packages/state/contracts/.ts`** — generated TypeScript clients with full type info. 3. **`junod query wasm contract-info`** gives metadata (creator, admin, label, code_id) but not the schema. 4. **Reflection via the contract** — most contracts don't have an introspection query. The schema files are the source of truth. When deploying a new contract, generate and commit the schema *before* the contract is uploaded — downstream consumers (UIs, indexers, other contracts) rely on it. ## Migration Contracts can be migrated to a new code_id if they have an admin. The new code must implement a `MigrateMsg` handler. ```bash junod tx wasm migrate '' \ --from \ --chain-id juno-1 --node $RPC \ --gas auto --gas-adjustment 1.5 \ --gas-prices 0.075ujuno \ --keyring-backend test --keyring-dir \ --yes ``` **Only the admin can migrate.** If the contract was instantiated with `--no-admin`, it's immutable forever. If the admin is a DAO, migration requires a passed proposal. The MigrateMsg shape is contract-specific — for many DAO DAO contracts it's `{}` (empty), but read the migrate handler before assuming. ## Contract admin transfer ```bash junod tx wasm set-contract-admin \ --from \ ... # Or drop admin entirely (make immutable): junod tx wasm clear-contract-admin \ --from \ ... ``` Common patterns: - Instantiate with `--admin `, develop, then transfer admin to the DAO core for production. - Instantiate with `--admin `, so the DAO governs migrations from day one. ## Funds and the contract balance A contract has its own balance just like any account: ```bash junod query bank balances --node $RPC ``` Funds arrive when: - Sent in `--amount` at instantiate. - Sent in `--amount` along with execute calls. - Transferred via `MsgSend` from another account (some contracts intentionally reject this and refund via reply hooks). - Returned to the contract from a `BankMsg::Send` from another contract. Contracts can hold any denom — native (`ujuno`, IBC denoms) and cw20 (which are themselves contracts; balances live in their state). ## Cw20 — token contracts cw20 is the CosmWasm equivalent of ERC-20. A cw20 token is a contract; balances live in its state, not in `x/bank`. ```bash # Balance junod query wasm contract-state smart \ '{"balance":{"address":"juno1..."}}' --node $RPC # Total supply junod query wasm contract-state smart '{"token_info":{}}' --node $RPC # Transfer junod tx wasm execute \ '{"transfer":{"recipient":"juno1...","amount":"1000000"}}' \ --from ... # Send cw20 to a contract that implements the cw20 receiver hook junod tx wasm execute \ '{"send":{"contract":"juno1...","amount":"1000000","msg":""}}' \ --from ... ``` The `send` pattern (instead of `transfer`) triggers the receiver's `Receive` hook — used for things like "deposit cw20 into a DAO" or "swap cw20 for another asset." ## Native token-factory (alternative to cw20) Juno has `x/tokenfactory`. Native tokens are cheaper to transfer (bank, not wasm execute) and indexable as bank events. Many newer projects prefer tokenfactory denoms over cw20. ```bash # Create a denom junod tx tokenfactory create-denom mytoken \ --from ... --yes # Resulting denom: factory/juno1/mytoken # Mint junod tx tokenfactory mint 1000000factory/juno1/mytoken \ --from ... --yes # Burn junod tx tokenfactory burn 100factory/juno1/mytoken \ --from ... --yes ``` ## Common gotchas 1. **Base64 encoding for nested messages.** When a top-level msg contains a sub-message (e.g., DAO DAO `propose` containing `wasm.execute` msgs), the sub-msg's `msg` field is **base64-encoded JSON**, not raw JSON. Encode with `jq -c . | base64 -w 0`. 2. **Gas underestimation on store.** Use `--gas-adjustment 1.5–1.6` for wasm store; `auto` simulation runs against a single block and storage cost can spike. 3. **Sequence racing.** Sending many wasm execute txs in a tight loop hits sequence mismatches. Wait for the previous tx's inclusion before the next, or batch into one multi-msg tx. 4. **wasm-execute simulation lies.** A simulated execute uses the latest block state; another tx between simulation and broadcast can change contract state and cause your tx to fail in a way the simulation didn't predict. Mitigate with `--gas-adjustment 1.5+` for execute on hot contracts. 5. **JSON quoting in shells.** When embedding `'{"key":"value"}'` in a bash command, single-quote the outer string. For long JSON, put it in a file and pass `"$(cat msg.json)"`. ## Going further - DAO DAO contract operations → [`dao-dao.md`](dao-dao.md). DAO DAO is built on these primitives; the patterns there are the most common real-world contract operations on Juno. - IBC channel-level interactions (sending tokens cross-chain via a contract) — beyond this skill; look at the `ibc-hooks` middleware and `polytone` for cross-chain contract calls.