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.
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)
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
- error while incrementing period: %w
- error while creating a new delegation period record: %w
- expected validator, not found
- failed to read upgrade info from disk %s
- newValAddr is not of type bytes.HexBytes
AI-assisted analysis of unionlabs/union@031785bb6d (2026-08-16).
Data as JSON: /api/errors/3c56c726dbbf9c95.
Report an issue: GitHub.