If I were designing a bitcoin wallet suitable for an exchange that wanted to be able to safely trade between forks, here's how I would do it:
The fork in this example is a simple block size limit increase with no other consensus rule changes.
The fork creates not two, but
three categories of bitcoins:
- Pre-fork a.k.a undifferentiated coins
- Large block coins
- Small block coins
Large block coins are any utxos who history includes at least one generation output on the large block chain.
Small block coins are any utxos whose history includes at least one generation output on the small block chain after the large block chain has diverged.
Undifferentiated coins are all other utxos.
In order to all the small block and large block coins to be detected, I'd modify the indexing code of the node to track the "most recent origin" for every utxo consisting of the chain height and block hash of the most recent generation output ancestor.
The existing currency symbol the exchange has assigned to Bitcoin would be interpreted as undifferentiated coins. New currency symbols would be created for large block coins and small block coins. No balances would be converted automatically.
Withdrawals of undifferentiated coins are initially broadcast on both chains.
After the exchange has received a deposit of large block coins (small block coins), it then becomes possible to start trading that currency and to allow users to request a withdrawal of their undifferentiated balances as that type of coins. This is accomplished by using the large block coins (small block coins) as inputs to the withdrawal transaction.
If a user wants to internally convert their balance from undifferentiated coins to large or small block coins, they'd be allowed to do so but this would require the exchange to perform an internal on-chain transaction to create the appropriately differentiated utxos.
Throughout the entire process, deposits are credited as the appropriate type based on whether or not they are differentiated.
A bit of attention to detail reveals this talk of a "replay attack" to be nonsense - it only looks like an attack because of the exchanges aren't handling the fork correctly. Reality is more complex than "everybody automatically doubles their coins after a fork".
PS: as far as I know the strategy I described doesn't work in Ethereum because their ledger tracks net balances rather than atomic outputs.