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

  1. Deduplicate token_balances by currency before calling account_balances, merging or picking the latest balance
  2. Normalize token addresses/casing when constructing Currency objects so equal tokens map to one Currency
  3. 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

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


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/8ac6caffbd668b98. Report an issue: GitHub.