In a recent post , Vitalik Buterin mentioned the need to ‘split’ Ether funds in order to prevent replay attacks and pointed to a splitter contract.

While this splitter contract has been verified and works fine, it leaves room for user errors as some unfortunately discovered by loosing some of their funds. I feel very sorry for the users who ran into this issue and I decided to improve the existing solution. If you did not split yet, read on as the new contract may help you stay safe.

At first, the issues may not be obvious unless you double check both chains. Your funds may go to the expected address on one fork and go to the dreaded 0x00000000000… address on the other fork. That is something we can address.

First problem: Spending transaction fee for nothing

The first issue is a minor one: it is possible to call the split method without sending any funds, asking the contract to split 0.00 Ether.

Contract called without any ETH to split

After clicking on the execute button, we see in the following dialog that the contract does not see any issue with the call.

Splitting ZERO Ether is accepted

While this is not a big problem, calling the method without sending any Ether means that the user will lose the small amount (transaction fee) required by a transaction that is useless.

Major problem: Losing funds

A bigger problem occurs when the contract is called with only one address (I am not aware of users calling the contract with no address at all and that would be the worst but the old contract would allow it! Do NOT try that at home!!!).

It has been mentioned in several place that the addresses should be verified and not swapped but it seems that some user fail to double check and sometimes leave one of the address field empty as shown below.

The contract should NOT be called with a blank address

Once again, the contract allows this. It is a serious issue as you may end up sending Ether on one chain to the 0x00000… address, effectively loosing those funds. Once this is done, you won´t be able to recover those Ether, no matter on what chain they are.

If the contract allows calling with blank address

While a contract can hardly be 100% user proof, there are solutions to improve the existing contract one step further.

The improvement I am proposing consists in using the same oracle used by the initial splitter contract. In other words, there is no change in the way the contract figures out whether it is on the fork or not.

However, instead of using the original contract, a more user-proof version can be used. This version will prevent the following errors:

Missing amount
Missing fork address
Missing NoFork address

When an erroneous call to the Split function of the contract is made, the call is made invalid. Not only it will allow the user saving on transaction fees for useless calls but also prevent him/her from doing a transaction that will result in a irreversible loss.

Invalid transactions will not be possible

The new version of the contract can be used exactly like the previous one. The JSON Interface is exactly the same. The oracle used to figure out what the current chain is remains the same. Only the address of the ReplaySafeSplit v2 contract is obviously different.


Users can follow the step by step explanation from Pauls and simply use the contract address above instead of the initial address (it was 0xaa1a6e3e6ef20068f7f8d8c835d2d22fd5116444).

The contract has been verified on Etherscan for Ethereum Core a well as on Classic Explorer for Ethereum Classic. You can see the verified code there.

The main differences with the initial contract are:

if (targetFork == 0) throw;
if (targetNoFork == 0) throw;

This is what prevents calling the Split function without defining BOTH the target addresses. If you fail providing the 2 addresses, you will not even be able to send the transaction and you will not be charged for any transaction fee.

To prevent users from calling the Split function with no funds, the RequiringFunds contract is inherited by the ReplaySafeSplit v2 contract:

    contract RequiringFunds {
        modifier NeedEth () {
            if (msg.value <= 0 ) throw;

We see the inheritance here:

contract ReplaySafeSplit is RequiringFunds {

And the use of the modifier here:

function split(address targetFork, address targetNoFork) NeedEth returns(bool) { …​.

I would strongly recommend to first test with a small amount of Ether, then check on BOTH chain that things happened the way you expected. If, and only if, you are satisfied with the result, then go ahead and send the rest of your funds.

I did all I could on my side to get this contract code available and verified. If you are able to read Smart Contact code, please review this contract and provide some feedback to help others trusting it.

If you feel like this contract saved you from a big mistake, you may very well invite me for a coffee at the address mentioned below!

Credits: Many thanks to Matt from Etherscan and Jordi who tremendously helped me to get this contract verified.

Disclaimer: Users are responsible for reading the contract´s code before executing it. If you fail doing so, do at least a first test with a small amount of Ether! I take no responsibility of any sort. It is still possible for a user to enter the wrong address in the wrong field, the solution I propose would not prevent that. Don´t screw up!


ETH: 0x0e04AfEaF6103a9530341D39137607A72e06C1a4

Wilfried Kopp aka. Chevdor
Building Blockchains & Decentralized Solutions

I build decentralized solutions and tooling to support them. I am developing Smart Contracts and dApps on Ethereum and Substrate (Polkadot & Kusama) while aspiring at becoming more proficient with Rust. I am using Docker extensively and above all I like efficiency. GPG Fingerprint 15AF C574 D3F9 F1C3 CCDD E31E 2DCE C4DC 506E 6475.