nautechsystems/nautilus_trader · error · anyhow::Error
Wallet balance snapshot contains duplicate token addresses:
Error message
Wallet balance snapshot contains duplicate token addresses: {} What it means
WalletBalanceSnapshot::validate_token_addresses rejects a snapshot whose token holdings contain the same token address more than once. Duplicate addresses make balance attribution ambiguous, so the snapshot is considered corrupt and an anyhow error is raised instead of returning balances.
Source
Thrown at crates/model/src/defi/wallet.rs:226
}
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();
for balance in &self.token_balances {
if !token_addresses.insert(balance.token.address) {
duplicates.push(balance.token.address);
}
}
if !duplicates.is_empty() {
duplicates.sort_unstable();
duplicates.dedup();
anyhow::bail!(
"Wallet balance snapshot contains duplicate token addresses: {}",
format_addresses(&duplicates)
);
}
let mut missing = self
.token_universe
.difference(&token_addresses)
.copied()
.collect::<Vec<_>>();
let mut unexpected = token_addresses
.difference(&self.token_universe)
.copied()
.collect::<Vec<_>>();
missing.sort_unstable();
unexpected.sort_unstable();
match (missing.is_empty(), unexpected.is_empty()) {View on GitHub (pinned to 18893faf8b)
Solutions
- Normalize all token addresses (same casing/checksum) and deduplicate before constructing the snapshot.
- When merging balance sources, fold duplicate addresses into one entry by summing or replacing balances instead of appending.
- Log and inspect the deduplicated address list from the error message to find which source introduced the duplicate.
Example fix
// before
let mut balances = Vec::new();
for feed in feeds { balances.extend(feed.balances()); }
// after
use std::collections::HashMap;
let mut balances: HashMap<Address, _> = HashMap::new();
for feed in feeds {
for (addr, bal) in feed.balances() {
*balances.entry(addr).or_default() += bal;
}
} Defensive patterns
Strategy: validation
Validate before calling
fn has_no_duplicates(addrs: &[Address]) -> bool {
let unique: std::collections::HashSet<_> = addrs.iter().collect();
unique.len() == addrs.len()
} Prevention
- Checksum-normalize addresses before any comparison or insertion.
- Use a map keyed by address when aggregating balances instead of a list.
When it happens
Trigger: Calling WalletBalanceSnapshot::account_balances (which invokes validate_token_addresses) when the snapshot's balances map was built with the same token address inserted twice — e.g. aggregating balances from two sources that both include the same ERC-20 address.
Common situations: Merging balance feeds from multiple RPC providers or chains, manually constructing a snapshot from JSON where one token appears under duplicate keys that were normalized to the same address, or case-insensitive hex addresses not being checksum-normalized before deduplication.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- RPC snapshot contains duplicate ticks
- Invalid tick range: {tick_lower} >= {tick_upper}
- Ticks {tick_lower} and {tick_upper} must be multiples of the
- Invalid tick bounds for {tick_lower} and {tick_upper}
- Slippage {actual_slippage} bps exceeds tolerance {max_slippa
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/a51dcb733dcafbd3.
Report an issue: GitHub.