Migration Guides

Migrating from Web3 to ethers v4

Todo: This is coming soon.


Migrating from ethers v3 to ethers v4

A lot of the functionality has remained the same, but there has been some slight refactoring and improved paradigms.


Deploying Contracts

Deploying contracts has undergone some significant change. The new API is more seamless and reduces the amount of low-level understanding that is required, such as the details of how init transaction behave.

More complex and complete objects are also returned through-out the process, so there are far less calls to utility functions after a deployment required to populate databases and track status.

ethers v3
let tx = Contract.getDeployTransaction(bytecode, abi, arg1, arg2);

// overrides
tx.gasLimit = 1500000;

wallet.sendTransaction(tx).then((tx) => {
    let contractAddress = ethers.utils.getContractAddress(tx);
    let contract = new ethers.Contract(contractAddress, abi, wallet);
    provider.waitForTransaction(tx).then((tx) => {
        provider.getTransactionReceipt(tx.hash).then((receipt) => {
            if (receipt.status === 0) {
                throw new Error('failed to deploy');
            }
            contract.someFunction();
        });
    });
});
Deploy a Contract — ethers v4
// Construct a Contract Factory
let factory = new ethers.ContractFactory(abi, bytecode, signer);

// Deploy automatically detects gasLimit and all other parameters
// Overrides can optionally be passed as an extra parameter

// Optional; all unspecified values will queried from the network
let overrides = { };

factory.deploy(arg1, arg2, overrides).then((contract) => {
    // The contract is returned immediately; it has not been mined yet

    // The contract known its address (before it is even mined)
    console.log(contract.address);

    // You can access the in-flight transaction that is currently waiting to be mined
    console.log(contract.deployTransaction);
    // A full transaction with:
    // - from
    // - nonce
    // - hash
    // - all other Transaction Response fields
    // - wait() => Promise that resolves to the TransactionReceipt

    // The "deployed()" function will return a Promise that resolves
    // to the contract, once it has been mined. If the contract fails
    // to deploy, the Promise will reject.
    contract.deployed().then((contract) => {
        // The contract is now deployed
        contract.someFunction();

    }, (error) => {
        // The transaction failed to deploy
        console.log('Failed to deploy in TX:', error.transactionHash);
    });
});
Deploy a Contract with async/await — ethers v4
async function() {
    let factory = new ethers.ContractFactory(abi, bytecode, signer);

    let contract = await factory.deploy(arg1, arg2);

    try {
        await contract.deployed();

    } catch (error) {
        console.log('Failed to deploy in TX:', error.transactionHash);
        throw error;
    }

    contract.someFunction();
}
Get Deployment Transaction — ethers v4
// If you still need the deployment transaction, and don't wish to
// actually deploy, you can much more easily just use the Interface
// object without the need for a provider or signer.

let factory = new ethers.ContractFactory(abi, bytecode);

let tx = factory.getDeployTransaction(arg1, arg2);

Encrypted Wallets

Checking JSON Wallets — ethers v3
let isJsonWallet = ethers.Wallet.isEncryptedWallet(json);
Checking JSON Wallets — ethers v4
let address = ethers.utils.getJsonWalletAddress(json);
let isJsonWallet = (address !== null)

Events

Events now behave like a modern JavaScript Event Emitter, rather than a 1995 web browser.

The events now play nicer with the arrow operator (i.e. () => { ... }), since rather than modifying the this inside the callbacks, an additional rich object is passed along.

Events — ethers v3
// Solidity: event SomeEvent(address foo, uint256 bar)
contract.onsomeevent = function(foo, bar) {
    console.log(foo, bar);
    // The Log was available at this:
    // - this.event
    // - this.removeListener()
};
Listening to an Event — ethers v4
// Solidity: event SomeEvent(address foo, uint256 bar)
contract.on('SomeEvent', (foo, bar, eventInfo) => {
    console.log(foo, bar);
    // eventInfo
    //  - Log (blockNumber, blockHash, txHash, removed, address, data, etc.)
    //  - args: [ foo, bar ]
    //  - decode: (data, topics) => [ foo, bar ]
    //  - event: "SomeEvent"
    //  - eventSignature: "SomeEvent(address,uint256)"
    //  - removeListener: () => removes this listener
    //  - getBlock: () => returns a Promise of the block
    //  - getTransaction: () => returns a Promise of transaction
    //  - getTransactionReceipt: () => returns a Promise of the receipt
});
Indexed Events — ethers v3
// Detecting a parameters is an indexed hash, and not a value
contract.someEvent = function(foo) {
    if (foo.indexed) {
        // The value is just a hash to filter by
    }
}
Indexed Events — ethers v4
let Indexed = ethers.types.Indexed;

// Detecting a parameters is an indexed hash, and not a value
contract.someEvent = function(foo) {
    if (Indexed.isIndexed(foo)) {
        // The value is just a hash to filter by
    }
}
Filtering Events — ethers v4
// Solidity: event SomeEvent(address indexed foo, uint256 bar)
let address = '0x8B40a2E27C5E87aa66DfA7F80BF675176F49DCA7';
let filter = contract.filters.SomeEvent(address, null);

console.log(filter);
// {
//    address: contract.address,
//    topics: [
//        0xdde371250dcd21c331edbb965b9163f4898566e8c60e28868533281edf66ab03,
//        0x0000000000000000000000008b40a2e27c5e87aa66dfa7f80bf675176f49dca7
//    ]
// }

contract.on(filter, (foo, bar, eventInfo) => {
    console.log(foo === address);
    // true
});

If there are multiple events with the same name:

Event Name Collision — ethers v4
// Solidity
// - event SomeEvent(address foo, uint256 bar)
// - event SomeEvent(address foo, address bar)

contract.on('SomeEvent(address, uint256)', (foo, bar, eventInfo) => {
    // ...
});

contract.on('SomeEvent(address, address)', (foo, bar, eventInfo) => {
    // ...
});

Fetching JSON

The JSON fetching routine, since it was mostly used for Providers was on the Provider object in v3. In v4, it has moved to utils, since there are other common cases where it may be useful.

Fetching JSON — ethers v3
ethers.providers.Provider.fetchJson(url).then((object) => {
    // ...
});
Fetching JSON — ethers v4
ethers.utils.fetchJson(url).then((object) => {
    // ...
});

Interfaces

This has always been a fairly low-level API, and mostly available for framework developers and other tools that require quite specific access to working with an ABI. Most of the changes are to simplify the interaction, so while there will probably be changes required, if you use this class, the complexity and size of your code should be reduced.

Interface — ethers v3
let iface = ethers.Interface(address, abi, providerOrSigner);
Interface — ethers v4
let iface = ethers.utils.Interface(address, abi, providerOrSigner);
Function Description — ethers v3
let address = "0x8B40a2E27C5E87aa66DfA7F80BF675176F49DCA7";
let value = 1000;

// Solidity: function someFunc(address foo, uint256 bar) constant returns (address result)
let functionCallable = iface.functionst.someFunc
// functionInfo
// - inputs: { names: [ "foo", "bar" ], types: [ "address", "uint256" ] }
// - outputs: { names: [ "result" ], types: [ "address" ] }
// - payable: false
// - parseResult: (data) => any
// - signature: "someFunc(address,uint256)"
// - sighash: "0xd90a6a67"

let data = functionCallable(address, value);
let result = functionCallable.parseResult(callResultData);
Function Description — ethers v4
let address = "0x8B40a2E27C5E87aa66DfA7F80BF675176F49DCA7";
let value = 1000;

// Solidity: function someFunc(address foo, uint256 bar) constant returns (address result)
let functionInfo = iface.functions.someFunc;
// functionInfo
// - type: "call" (or "transaction" for non-constant functions)
// - name: "someFunc"
// - signature: "someFunc(address,uint256)"
// - sighash: "0xd90a6a67"
// - inputs: [ { name: foo", type: "bar" }, { name: "bar", type: "uint256" } ]
// - outputs: [ { name: "result", type: "address" } ]
// - payable: false
// - encode: (params) => string
// - decode: (data) => any

// Note: This takes in an array; it no longer uses ...args
let data = functionInfo.encode([ address, value ]);
let result = functionInfo.decode(callResultData);
Event Description — ethers v3
// Solidity: event SomeEvent(address foo, uint256 bar)
let eventInfo = iface.events.SomeEvent;
// eventInfo
// - topics: [ ethers.utils.id("SomeEvent(address,uint256)") ]
// - anonymous: false
// - name: "SomeEvent"
// - signature: "SomeEvent(address,uint256)"
// - type: "event"
// - inputs: { names: [ 'foo', 'bar' ], types: [ 'address', 'uint256' ] }
// - parse: (topics, data) => Result
Event Description — ethers v4
// Solidity: event SomeEvent(address foo, uint256 bar)
let eventInfo = iface.events.SomeEvent;
// eventInfo
// - name: "SomeEvent"
// - signature: "SomeEvent(address,bar)"
// - inputs: [ { name: "foo", type: "address" }, { name: "bar", type: "uint256" } ]
// - anonymous: false
// - topic: ethers.utils.id("SomeEvent(address,uint256)")
// - encodeTopics: (Array<any>) => Array<string>
// - decode: (data, topics) => Result

// Create event filter topics
let address = '0x8B40a2E27C5E87aa66DfA7F80BF675176F49DCA7';
eventInfo.encodeTopics(address, null)
// topics: [
//   "0xdde371250dcd21c331edbb965b9163f4898566e8c60e28868533281edf66ab03",
//   "0x0000000000000000000000008b40a2e27c5e87aa66dfa7f80bf675176f49dca7"
// ]

Networks

Getting Network Info - ethers v3
let network = ethers.providers.getNetwork('ropsten')
Getting Network Info - ethers v4
let network = ethers.utils.getNetwork('ropsten');

// Networks may now also be found by their network ID
let network = ethers.utils.getNetwork(3);

// And networks can be validated as an object
let network = ethers.utils.getNetwork({ name: "custom", chainId: 1337 });

// Validation fails; this will throw an error, since Rinkeby is not
// chain ID 1 (homestead is)
let network = ethers.utils.getNetwork({ name: "rinkeby", chainId: 1 });

Parsing Transactions

The transaction parsing was moved out of the Wallet and into its own class in the utilities, along with a general serialization API.

ethers v3
let tx = ethers.Wallet.parseTransaction(rawTransaction);
ethers v4
let tx = ethers.utils.parseTransaction(rawTransaction);

Custom Signer

Custome Signer — ethers v3
let signer = {
    // Required
    getAddress: function() { ... },
    provider: provider,

    // Optional
    estimateGas: function(tx) { ... },
    sendTransaction: function(tx) { ... },
    sign: function(tx) { ... },
};
Custom Signer — JavaScript — ethers v4
function CustomSigner {
    ethers.Signer.call(this);

    // Optional
    this.provider = provider;
}
inherits(CustomSigner, ethers.Signer);

// Required
CustomSigner.prototype.getAddress = () => { ... };
CustomSigner.prototype.sendTransaction = (tx) => { ... };
CustomSigner.prototype.signMessage = (message) => { ... };

// Optional
CustomSigner.prototype.connect = (provider) => { ... };
Custom Signer — TypeScript — ethers v4
import {
    Signer,
    utils
} from 'ethers';

import {
    Provider,
    TransactionRequest,
    TransactionReqponse
} from 'ethers/providers';

class CustomSigner extends Signer {
    this.provider = provider;
    readony provider: Provider;
    constructor(provider) {
         super();

         // Optional
         this.provider = // ...
    }

    getAddress() {
        // Return a promise to the address
    };

    sendTransaction = (transaction: TransactionRequest): Promise<TransaxctinResponse> {
        // This will popualte missing properties, like nonce, gasLimit, etc
        return utils.populateTransaction(transaction).then((tx) => {
             // Send the transaction and resolve the transaction
        });
    };

    signMessage(message: string | ethers.utils.Arrayish): Promise<string> {
        let dataToSign: Uint8Array = utils.hashMessage(message);;
        // Sign ths dataToSign and resolve teh flat-format signature
    };

    // Optional; highly recommended
    connect(provider: Provider): CustomSigner {
        return new CustomSigner(provider);
    }
}

Default Provider

Default Provider — ethers v3
let provider = ethers.providers.getDefaultProvider();
Default Provider — ethers v4
let provider = ethers.getDefaultProvider();

Big Number

isBigNumber — ethers v3
let checkBigNumber = ethers.utils.isBigNumber(value);
isBigNumber — ethers v4
let checkBigNumber = BigNumber.isBigNumber(value);

JsonRpcProvider

Connecting — ethers v3
let url = "http://localhost:8545";
let network = "rinkeby";
let provider = new ethers.providers.JsonRpcProvider(url, network);

// Even if the network was wrong, this would mostly seem to work and
// fail once the chain ID was attempted

// The network was a synchronous property on Provider
let network = provider.network;
Connecting — ethers v4
// In v4, the network is automatically determined; you may override
// the network, but it must match, or it will fail early

let url = "http://localhost:8545";
let provider = new ethers.providers.JsonRpcProvider(url);

// The network is now an asynchronous property:
provider.getNetwork().then((network) => {
    // ...
});

// One useful and common exception it that, if any async call from
// the provider has ever succeeded, the synchronous network property
// is then valid. The network property is populated before any
// async call is made, so once an async call has finished, the network
// property is available synchronously.

async function() {
    await provider.getBlockNumber();

    let network = provider.network;
}
Sending Transactions — ethers v3
provider.sendTransaction(rawTransaction).then((hash) => {
    // Just a transaction hash
});
Sending Transactions — ethers v4
provider.sendTransaction(rawTransaction).then((transaction) => {
    // A full Transaction Response is returned
    // - from
    // - to
    // - gasLimit, gasPrice
    // - nonce
    // - r, s, v
    // - wait() => Promise that resolves the Transaction Receipt once mined
    //             and rejects with an error is the stats is 0; the error
    //             will have a transactionHash property as well as a
    //             transaction property.

    let hash = transaction.hash;
});

Verifying Messages

The message verification was moved from a static class on the Wallet to the utilities, along with a few other functions of the elliptic curve cryptographic exposed.

ethers v3
let signingAddress = ethers.Wallet.verifyMessage(message, signature);
ethers v4
let signingAddress = ethers.utils.verifyMessage(message, signature);

Waiting for Transactions

In v3, the transaction.wait() returned a Promise which would resolve to the TransactionResponse once it is mined.

In v4, the transaction.wait() returned a Promise which would resolve to the TransactionReceipt once it is mined.