Migration: From Ethers v4

This document only covers the features present in v4 which have changed in some important way in v5.

It does not cover all the new additional features that have been added and mainly aims to help those updating their older scripts and applications to retain functional parity.

If you encounter any missing changes, please let me know and I'll update this guide.

BigNumber

Namespace

Since BigNumber is used quite frequently, it has been moved to the top level of the umbrella package.

// v4 ethers.utils.BigNumber ethers.utils.BigNumberish // v5 ethers.BigNumber ethers.BigNumberish

Creating Instances

The bigNumberify method was always preferred over the constructor since it could short-circuit an object instantiation for [[BigNumber] objects (since they are immutable). This has been moved to a static from class method.

// v4 new ethers.utils.BigNumber(someValue) ethers.utils.bigNumberify(someValue); // v5 // - Constructor is private // - Removed `bigNumberify` ethers.BigNumber.from(someValue)

Contracts

ENS Name Resolution

The name of the resolved address has changed. If the address passed into the constructor was an ENS name, the address will be resolved before any calls are made to the contract.

The name of the property where the resolved address has changed from addressPromise to resolvedAddress.

Resolved ENS Names
// v4 contract.addressPromise // v5 contract.resolvedAddress

Gas Estimation

The only difference in gas estimation is that the bucket has changed its name from estimate to estimateGas.

Gas Estimation
// v4 contract.estimate.transfer(toAddress, amount) // v5 contract.estimateGas.transfer(toAddress, amount)

Functions

In a contract in ethers, there is a functions bucket, which exposes all the methods of a contract.

All these functions are available on the root contract itself as well and historically there was no difference between contact.foo and contract.functions.foo. The original reason for the functions bucket was to help when there were method names that collided with other buckets, which is rare.

In v5, the functions bucket is now intended to help with frameworks and for the new error recovery API, so most users should use the methods on the root contract.

The main difference will occur when a contract method only returns a single item. The root method will dereference this automatically while the functions bucket will preserve it as an Result.

If a method returns multiple items, there is no difference.

This helps when creating a framework, since the result will always be known to have the same number of components as the Fragment outputs, without having to handle the special case of a single return value.

Functions Bucket
const abi = [ // Returns a single value "function single() view returns (uint8)", // Returns two values "function double() view returns (uint8, uint8)", ]; // v4 await contract.single() // 123 await contract.functions.single() // 123 // v5 (notice the change in the .function variant) await contract.single() // 123 await contract.functions.single() // [ 123 ] // v4 await contract.double() // [ 123, 5 ] await contract.functions.double() // [ 123, 5 ] // v5 (no difference from v4) await contract.double() // [ 123, 5 ] await contract.functions.double() // [ 123, 5 ]

Errors

Namespace

All errors now belong to the Logger class and the related functions have been moved to Logger instances, which can include a per-package version string.

Global error functions have been moved to Logger class methods.

// v4 ethers.errors.UNKNOWN_ERROR ethers.errors.* errors.setCensorship(censorship, permanent) errors.setLogLevel(logLevel) errors.checkArgumentCount(count, expectedCount, suffix) errors.checkNew(self, kind) errors.checkNormalize() errors.throwError(message, code, params) errors.warn(...) errors.info(...) // v5 ethers.utils.Logger.errors.UNKNOWN_ERROR ethers.utils.Logger.errors.* Logger.setCensorship(censorship, permanent) Logger.setLogLevel(logLevel) const logger = new ethers.utils.Logger(version); logger.checkArgumentCount(count, expectedCount, suffix) logger.checkNew(self, kind) logger.checkNormalize() logger.throwError(message, code, params) logger.warn(...) logger.info(...)

Interface

The Interface object has undergone the most dramatic changes.

It is no longer a meta-class and now has methods that simplify handling contract interface operations without the need for object inspection and special edge cases.

Functions

// v4 (example: "transfer(address to, uint amount)") interface.functions.transfer.encode(to, amount) interface.functions.transfer.decode(callData) // v5 interface.encodeFunctionData("transfer", [ to, amount ]) interface.decodeFunctionResult("transfer", data) // Or you can use any compatible signature or Fragment objects. // Notice that signature normalization is performed for you, // e.g. "uint" and "uint256" will be automatically converted interface.encodeFunctionData("transfer(address,uint)", [ to, amount ]) interface.decodeFunctionResult("transfer(address to, uint256 amount)", data)

Events

// v4 (example: Transfer(address indexed, address indexed, uint256) interface.events.Transfer.encodeTopics(values) interface.events.Transfer.decode(data, topics) // v5 interface.encodeFilterTopics("Transfer", values) interface.decodeEventLog("Transfer", data, topics)

Inspection

Interrogating properties about a function or event can now (mostly) be done directly on the Fragment object.

// v4 interface.functions.transfer.name interface.functions.transfer.inputs interface.functions.transfer.outputs interface.functions.transfer.payable interface.functions.transfer.gas // v5 const functionFragment = interface.getFunction("transfer") functionFragment.name functionFragment.inputs functionFragment.outputs functionFragment.payable functionFragment.gas // v4; type is "call" or "transaction" interface.functions.transfer.type // v5; constant is true (i.e. "call") or false (i.e. "transaction") functionFragment.constant // v4 interface.events.Transfer.anonymous interface.events.Transfer.inputs interface.events.Transfer.name // v5 const eventFragment = interface.getEvent("Transfer"); eventFragment.anonymous eventFragment.inputs eventFragment.name // v4 const functionSig = interface.functions.transfer.signature const sighash = interface.functions.transfer.sighash const eventSig = interface.events.Transfer.signature const topic = interface.events.Transfer.topic // v5 const functionSig = functionFragment.format() const sighash = interface.getSighash(functionFragment) const eventSig = eventFragment.format() const topic = interface.getTopic(eventFragment)

Wallet

Mnemonic Phrases

The mnemonic phrase and related properties have been merged into a single mnemonic object, which also now includes the locale.

// v4 wallet.mnemonic wallet.path // v5 // - Mnemonic phrase and path are a Mnemonic object // - Note: wallet.mnemonic is null if there is no mnemonic wallet.mnemonic.phrase wallet.mnemonic.path