When a swap produces multiple ERC-20 token transfers with the same tx
hash (e.g. send WETH + receive USDC), only the last transfer was kept
because txsByHash was keyed by bare tx hash. This caused the
address-token view to show no transactions for tokens obtained via swap.
Use a composite key (hash:contractAddress) so all token transfers are
preserved. The bare-hash normal-tx entry is removed when token transfers
replace it to avoid duplication.
Closes#72