TL;DR
We ran 6 real on-chain transactions on the provider testnet to document the complete lifecycle of a token created through a CosmWasm contract using the x/tokenfactory module in Gaia v26.
Key finding: when a denom is created via a CosmWasm contract, the contract, not the user wallet, becomes the denom admin. All privileged operations (mint, burn, modify-metadata) must be routed through the contract unless admin is explicitly transferred.
Background
Gaia v26 introduced the x/tokenfactory module to the Cosmos Hub, enabling any account to create native tokens in a fully permissionless manner, no governance proposal required. Tokens are namespaced by creator address:
factory/{creator_address}/{subdenom}
In this study we created CUM (Cumulo Validator Community Token), a limited-supply token, to explore the full operational lifecycle through a CosmWasm contract, including admin control, metadata registration, and supply management.
Operations Tested
| Step | Operation | TX Hash |
|---|---|---|
| 1 | Instantiate contract (code 360) | C613BBE9… |
| 2 | Create denom CUM | 819BA821… |
| 3 | Mint 1000 CUM | 75DB7FC4… |
| 4 | Transfer admin: contract → wallet | D92F7AA5… |
| 5 | Set token metadata | 7CF4C6B2… |
| 6 | Burn 100 CUM | E080A82B… |
Explorer: cumulo.pro/services/cosmos_testnet
The Admin Hierarchy
This is the most critical concept to understand when working with tokenfactory through CosmWasm:
User wallet (cumwallet)
└── owner of the wasm contract
└── admin of the CUM denom in x/tokenfactory
After creating the denom through the contract, querying on-chain confirms:
json
{
"authority_metadata": {
"admin": "cosmos1ujttrfpkslrgcml078hpeh9l6f3ssn3u8m44vyzyaahkxeecpw6s23fxvx"
}
}
That address is the contract, not the wallet. Without transferring admin, you cannot call burn, force-transfer, or modify-metadata directly, those operations require wallet-level admin.
Transferring Admin to the Wallet
The demo contract exposes change_admin as an execute message. After calling it:
json
{
"authority_metadata": {
"admin": "cosmos1cdekug2t0rzjjw96yaytw4ryt4u0mzwyaskz3m"
}
}
With the wallet as direct admin, we registered metadata (symbol, description, 6-decimal precision) and burned 100 CUM, permanently reducing supply from 1000 to 900 units.
Key Findings
1. Contract is denom admin, not user wallet. When a denom is created through a CosmWasm contract, the contract address becomes the admin. Any privileged operation must be routed through the contract unless admin is explicitly transferred.
2. The minimal demo contract (code ID 360) doesn’t expose all tokenfactory operations. Only create_denom, mint_tokens, and change_admin are available as contract messages. Operations like burn, force-transfer, and modify-metadata require wallet-level admin.
3. Admin renouncement is irreversible. Calling change-admin with "" permanently removes all admin control. Supply becomes fixed forever, no minting, burning, or metadata updates possible. This can be a desirable property for a community token seeking credible supply scarcity.
4. get_denom is a local computation, not an on-chain lookup. The contract constructs the denom string from the creator_address argument provided, not from chain state. If you pass your wallet address, you get back a denom that doesn’t actually exist on-chain (the real denom uses the contract’s address as creator).
5. subdenom is case-sensitive. CUM and cum would be registered as two distinct denoms.
Complete Command Reference
Via wasm contract execute messages (contract is admin):
bash
# Create denom (10 ATOM fee required)
gaiad tx wasm execute <contract> '{"create_denom": {"subdenom": "<name>"}}' --amount 10000000uatom
# Mint to any address
gaiad tx wasm execute <contract> '{"mint_tokens": {"amount": "<n>", "denom": "<denom>", "mint_to_address": "<addr>"}}'
# Transfer admin
gaiad tx wasm execute <contract> '{"change_admin": {"denom": "<denom>", "new_admin_address": "<addr>"}}'
Via gaiad tokenfactory (wallet must be admin):
bash
gaiad tx tokenfactory mint "<amount><denom>" --from <key>
gaiad tx tokenfactory burn "<amount><denom>" --from <key>
gaiad tx tokenfactory burn-from "<amount><denom>" <address> --from <key>
gaiad tx tokenfactory force-transfer "<amount><denom>" <from> <to> --from <key>
gaiad tx tokenfactory change-admin <denom> <new_admin> --from <key>
gaiad tx tokenfactory change-admin <denom> "" --from <key> # IRREVERSIBLE renouncement
gaiad tx tokenfactory modify-metadata <denom> <symbol> "<description>" <exponent> --from <key>
Useful queries:
bash
gaiad q tokenfactory denoms-from-admin <address>
gaiad q tokenfactory denom-authority-metadata <denom>
gaiad q bank denom-metadata <denom>
gaiad q bank balance <address> <denom>
Resources
| Resource | Link |
|---|---|
| Task specification | cosmos/testnets - demoday25 |
| Sample contract source | cosmos/tokenfactory - wasm-demo |
| CosmWasm token-factory bindings | CosmWasm/token-factory |
| Osmosis tokenfactory module docs | docs.osmosis.zone/tokenfactory |
| Cosmos Developer Documentation | docs.cosmos.network |
| Full study with all TX details | github.com/Cumulo-pro/cumulo-cosmoshub-infra |
| Explorer | cumulo.pro/services/cosmos_testnet |
Cumulo · Cosmos Hub Validator · Testnet Tuesday April 2026