unionlabs/union · error · anyhow::Error
no balance for denom
Error message
no balance for denom {} What it means
`CosmosClient::get_balance` issues a `/cosmos.bank.v1beta1.Query/Balance` grpc_abci_query and unwraps `resp.balance`. Cosmos SDK returns an empty balance when the account holds no Coin for that denom, and this code converts that None into an error rather than zero.
Solutions
- List the account's actual balances (`/cosmos.bank.v1beta1.Query/AllBalances`) and use the exact denom string returned
- Fund the account (faucet or bank send) before the step that requires the balance
- If zero is a legitimate state in your flow, branch on the Option instead of calling this helper
Example fix
// before
let coin = client.get_balance(&addr, "uatom").await?;
// after
let amount = client
.get_balance(&addr, denom)
.await
.ok()
.flatten_amount_if_any() // Option<Coin>
.unwrap_or_else(|| Coin::default_for(denom)); Defensive patterns
Strategy: validation
Validate before calling
// list real denoms before querying a specific one
let balances: QueryAllBalancesResponse = client
.rpc.client()
.grpc_abci_query("/cosmos.bank.v1beta1.Query/AllBalances", &QueryAllBalancesRequest { address: addr.clone().into(), ..Default::default() }, None, false)
.await?.into_result()?.unwrap();
anyhow::ensure!(balances.balances.iter().any(|c| c.denom == denom), "denom {denom} not held by {addr}"); Try / catch
match client.get_balance(&addr, denom).await {
Ok(coin) => Ok(coin),
Err(e) if e.to_string().starts_with("no balance for denom") => {
// decide explicitly: is zero acceptable, or should the account be funded first?
fund_if_needed(&addr, denom).await?;
client.get_balance(&addr, denom).await
}
Err(e) => Err(e),
} Prevention
- Fund accounts in test setup and assert on balances before the logic that consumes them
- Use exact base denominations copied from AllBalances output, not display denominations
When it happens
Trigger: Querying a denom the account has never held (zero balances are omitted by the bank module); a misspelled or differently-formatted denom (uatom vs atom, factory/ibc denom prefixes); the correct denom but an unfunded wallet.
Common situations: Test setup forgets to fund the account; faucet mint hasn't landed; base vs display denomination confusion on testnets; address copied for the wrong network.
Related errors
- client info not found
- config file must be specified
- error while creating a new delegation period record
- error while incrementing period
- Expected revert 0x , but transaction succeeded
AI-assisted analysis of unionlabs/union@031785bb6d (2026-08-16).
Data as JSON: /api/errors/3c56c726dbbf9c95.
Report an issue: GitHub.
Appendix: source
Thrown at tools/union-test/src/cosmos.rs:625
pub async fn get_balance(
&self,
address: impl Into<String>,
denom: &str,
) -> anyhow::Result<protos::cosmos::base::v1beta1::Coin> {
let req = QueryBalanceRequest {
address: address.into(),
denom: denom.to_string(),
};
let resp: QueryBalanceResponse = self
.rpc
.client()
.grpc_abci_query("/cosmos.bank.v1beta1.Query/Balance", &req, None, false)
.await?
.into_result()?
.unwrap();
resp.balance
.ok_or_else(|| anyhow::anyhow!("no balance for denom {}", denom))
}
pub async fn send_cosmwasm_transaction_with_retry(
&self,
contract: Addr,
msg: (Vec<u8>, Vec<Coin>),
signer: &LocalSigner,
) -> Option<Result<TxResponse, BroadcastTxCommitError>> {
let max_retries = 5;
for attempt in 1..=max_retries {
let outcome = self
.send_cosmwasm_transaction(contract.clone(), msg.clone(), signer)
.await;
if let Some(Ok(_)) = &outcome {
return outcome;
}
View on GitHub (pinned to 031785bb6d)