{"record":{"id":"a51dcb733dcafbd3","repo":"nautechsystems/nautilus_trader","slug":"wallet-balance-snapshot-contains-duplicate-token-a","errorCode":null,"errorMessage":"Wallet balance snapshot contains duplicate token addresses: {}","messagePattern":"Wallet balance snapshot contains duplicate token addresses: (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/model/src/defi/wallet.rs","lineNumber":226,"sourceCode":"        }\n\n        Ok(balances)\n    }\n\n    fn validate_token_addresses(&self) -> anyhow::Result<()> {\n        let mut token_addresses = HashSet::with_capacity(self.token_balances.len());\n        let mut duplicates = Vec::new();\n\n        for balance in &self.token_balances {\n            if !token_addresses.insert(balance.token.address) {\n                duplicates.push(balance.token.address);\n            }\n        }\n\n        if !duplicates.is_empty() {\n            duplicates.sort_unstable();\n            duplicates.dedup();\n            anyhow::bail!(\n                \"Wallet balance snapshot contains duplicate token addresses: {}\",\n                format_addresses(&duplicates)\n            );\n        }\n\n        let mut missing = self\n            .token_universe\n            .difference(&token_addresses)\n            .copied()\n            .collect::<Vec<_>>();\n        let mut unexpected = token_addresses\n            .difference(&self.token_universe)\n            .copied()\n            .collect::<Vec<_>>();\n        missing.sort_unstable();\n        unexpected.sort_unstable();\n\n        match (missing.is_empty(), unexpected.is_empty()) {","sourceCodeStart":208,"sourceCodeEnd":244,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/model/src/defi/wallet.rs#L208-L244","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nlet mut balances = Vec::new();\nfor feed in feeds { balances.extend(feed.balances()); }\n// after\nuse std::collections::HashMap;\nlet mut balances: HashMap<Address, _> = HashMap::new();\nfor feed in feeds {\n    for (addr, bal) in feed.balances() {\n        *balances.entry(addr).or_default() += bal;\n    }\n}","handlingStrategy":"validation","validationCode":"fn has_no_duplicates(addrs: &[Address]) -> bool {\n    let unique: std::collections::HashSet<_> = addrs.iter().collect();\n    unique.len() == addrs.len()\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Checksum-normalize addresses before any comparison or insertion.","Use a map keyed by address when aggregating balances instead of a list."],"tags":["defi","validation","duplicate-data"],"backgroundTag":"schema-validation-failed","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}