nautechsystems/nautilus_trader · error · anyhow::Error
Wallet balance snapshot contains duplicate currency {}
Error message
Wallet balance snapshot contains duplicate currency {} What it means
Wallet::account_balances builds an AccountBalances from native plus token balances, inserting each currency into a set to guarantee uniqueness. If the snapshot contains the same currency twice (duplicate token entry), building AccountBalances would be invalid, so it bails.
Source
Thrown at crates/model/src/defi/wallet.rs:198
self.validate_token_addresses()?;
let native_currency = self
.native_currency
.ok_or_else(|| anyhow::anyhow!("Wallet balance snapshot has no native currency"))?;
let mut currencies = HashSet::new();
currencies.insert(native_currency.currency);
let mut balances = Vec::with_capacity(self.token_balances.len() + 1);
balances.push(AccountBalance::new_checked(
native_currency,
Money::zero(native_currency.currency),
native_currency,
)?);
for token_balance in token_balances {
let total = token_balance.as_money()?;
if !currencies.insert(total.currency) {
anyhow::bail!(
"Wallet balance snapshot contains duplicate currency {}",
total.currency
);
}
balances.push(AccountBalance::new_checked(
total,
Money::zero(total.currency),
total,
)?);
}
Ok(balances)
}
fn validate_token_addresses(&self) -> anyhow::Result<()> {
let mut token_addresses = HashSet::with_capacity(self.token_balances.len());
let mut duplicates = Vec::new();
View on GitHub (pinned to 18893faf8b)
Solutions
- Deduplicate token_balances by currency before calling account_balances, merging or picking the latest balance
- Normalize token addresses/casing when constructing Currency objects so equal tokens map to one Currency
- Fix the snapshot producer so each currency appears exactly once
Example fix
// before
let balances = wallet.account_balances(&token_balances)?;
// after
let mut seen = HashMap::new();
for tb in token_balances {
seen.insert(tb.token.clone(), tb); // keeps last entry
}
let deduped: Vec<_> = seen.into_values().collect();
let balances = wallet.account_balances(&deduped)?; Defensive patterns
Strategy: validation
Validate before calling
fn has_duplicate_currencies(balances: &[TokenBalance]) -> bool {
let mut seen = HashSet::new();
balances.iter().any(|b| !seen.insert(b.token.clone()))
} Try / catch
let balances = wallet.account_balances(&token_balances)
.map_err(|e| MyError::SnapshotCorrupt(e.to_string()))?; Prevention
- Deduplicate token balances by currency before building snapshots
- Normalize token addresses so identical tokens produce identical Currency values
- Replace (not append) balances on refresh
When it happens
Trigger: Calling account_balances (directly or via as_account_balances) when token_balances contains two entries with the same Currency — e.g. the same token appearing twice in a wallet snapshot with different balances.
Common situations: Data aggregation bugs merging multiple RPC balance responses without deduplication; case-mismatched token addresses producing logically identical currencies; caching layers appending refreshed balances instead of replacing.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Wallet balance snapshot has no native currency
- Native currency not specified for chain {}
- Must have the `chain` field set
- `InstrumentId` not applicable to `Block`
- Invalid factory address for DEX {name} on chain {chain} for
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/8ac6caffbd668b98.
Report an issue: GitHub.