Migration of the SanR dApp from ethers v5 to v6
Published: 2023-05-31 05:09:25

## Introduction

SanR (https://sanr.app), a decentralized application (dApp) built on the Ethereum blockchain, recently underwent a migration from ethers v5 to ethers v6. This migration involved several changes in the codebase to ensure compatibility and leverage the enhanced features and improvements introduced in the latest version of ethers.js. In this article, we will explore the key modifications made during the migration process and their implications for the SanR dApp.

## Transition from BigNumber to BigInt

One significant change in ethers v6 is the replacement of the BigNumber class with [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt). BigInt provides more precise handling of large numbers and aligns with the JavaScript standard. It is important to note that JavaScript requires explicit conversion from BigInt to perform operations with regular numbers (Number). 

```jsx
- if (ethers.BigNumber.isBigNumber(value[key])) {
+ if (typeof value[key] === 'bigint') {

// token.createdAt is a BigNum now
- const daysPassed = Math.ceil((now - token.createdAt) / 60 / 60 / 24);
+ const daysPassed = Math.ceil((now - Number(token.createdAt)) / 60 / 60 / 24);
```


## Restructuring of Methods

Many methods have been moved from the `utils` and `providers` namespaces directly to the root object in ethers v6. This restructuring simplifies the access and usage of these methods.

```jsx
- const wallet = new Wallet(privateKey, new ethers.providers.JsonRpcProvider('https://sanrchain-node.santiment.net/'));
+ const wallet = new Wallet(privateKey, new ethers.JsonRpcProvider('https://sanrchain-node.santiment.net/'));

- await adminWallet.sendTransaction({to: wallet.address, value: ethers.utils.parseEther('1.0')});
+ await adminWallet.sendTransaction({to: wallet.address, value: ethers.parseEther('1.0')});

- const salt = ethers.utils.keccak256(ethers.utils.toUtf8Bytes('default'));
+ const salt = ethers.keccak256(ethers.toUtf8Bytes('default'));

- filter += `, "issuerAddress":["${ethers.utils.getAddress(issuerAddress)}"]`;
+ filter += `, "issuerAddress":["${ethers.getAddress(issuerAddress)}"]`;
```

## **Introduction of Transaction Batching**

In ethers v6, transaction batching allows developers to bundle multiple transactions into a single batch, reducing network costs and improving efficiency. By default, transaction batching is enabled in ethers v6, allowing users to take advantage of this feature. However, in the case of SanR, which utilizes its own blockchain called the SanR Network that currently does not support transaction batching, the feature can be explicitly disabled using the new `batchMaxCount` parameter.

```jsx
- const L2Provider = new ethers.providers.JsonRpcProvider(L2.url);
+ const L2Provider = new ethers.JsonRpcProvider(L2.url, null, {batchMaxCount: 1});
```

Please note that transaction batching was already available in ethers v5, and it remains a valuable feature in ethers v6, with default activation unless explicitly disabled when not needed.

## Method Invocation Changes

Several changes were made regarding method invocations in contract interactions:

- The `populateTransaction` method and the method name have switched positions.
- In ethers v6, there is no longer a need to specify the function signature when a method can accept multiple arguments.

```jsx
- await L2CompetitionsContract.populateTransaction.createGame(
+ await L2CompetitionsContract.createGame.populateTransaction(

- await L2CompetitionsContract.populateTransaction['makeStake(uint256)'](ethers.utils.parseUnits('500')),
+ await L2CompetitionsContract.makeStake.populateTransaction(ethers.parseUnits('500')),

- await L2SignalsContract.populateTransaction['closeSignals(uint256[],bytes32[],bool[],uint256[],uint256[])'](
+ await L2SignalsContract.closeSignals.populateTransaction(
```

## Renaming of Contract Address Field

In ethers v6, the `address` field of a contract has been renamed to `target`. This change brings greater clarity and consistency to the naming conventions within ethers.js.

```jsx
- <SubheaderValue>{L2CompetitionsContract.address}</SubheaderValue>
+ <SubheaderValue>{L2CompetitionsContract.target}</SubheaderValue>
```

## Renaming of Event Field

The `event` field in events has been renamed to `fragment.name` in ethers v6. This modification provides a more descriptive and precise representation of event fragments.

```jsx
- <JustifyCenter>{el.event === 'Staked' ? 'stake' : el.event === 'Rewarded' ? 'reward' : 'unstake'}</JustifyCenter>
+ <JustifyCenter>{el.fragment.name === 'Staked' ? 'stake' : el.fragment.name === 'Rewarded' ? 'reward' : 'unstake'}</JustifyCenter>
```

## Contract Proxy Transition

In ethers v6, the Contract object has transitioned from being an `object` to an element of `Proxy`. This change affected the `React.propTypes` validation for contract objects, requiring them to be replaced with any. However, it is worth noting that the ethers.js main developer, [@ricmoo.eth](https://twitter.com/ricmoo), has announced plans to rectify this [issue](https://github.com/ethers-io/ethers.js/issues/4084), allowing developers to use object validation once again.

```jsx
- L1SANContract: object.isRequired,
+ L1SANContract: any.isRequired,
```

## Copying Response from Contract Calls

The transformation of contracts into Proxy also impacted how responses from contract method calls are shallow copied. Failure to perform this shallow copy could result in missing object properties in the new object. Developers need to ensure proper handling of copying contract call responses to maintain data integrity.

```jsx
- const _token = {...await Contract.followings(tokenId)};
- const token = {..._token};
+ const token = (await Contract.followings(tokenId)).toObject();
```

## Method Changes in defaultAbiCoder

In ethers v6, the `defaultAbiCoder` is now a method instead of a property. Developers should update their code accordingly to utilize this method.

```jsx
- const infoHash = ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(
+ const infoHash = ethers.keccak256(ethers.AbiCoder.defaultAbiCoder().encode(
```

## Renaming of formatBytes32String

The function `formatBytes32String` has been renamed to `encodeBytes32String` in ethers v6. This change aligns with the naming conventions and offers a more descriptive name for the functionality.

```jsx
- const currentPrice = await L2PricesContract.currentPrice(ethers.utils.formatBytes32String('BTC/USD'))).price;
+ const currentPrice = await L2PricesContract.currentPrice(ethers.encodeBytes32String('BTC/USD'))).price;
```

## Provider Renaming and GSN Adjustment

The `Web3Provider` has been renamed to `BrowserProvider` in ethers.js v6. Additionally, if you are using OpenGSN version 2, modifications to the request method are necessary for GSN provider integration.

```jsx
+ const gsnProvider = await RelayProvider.newProvider({
+   provider: window.ethereum,
+   config,
+ }).init();
+ gsnProvider.request = function(payload) {
+   return new Promise((resolve, reject) => {
+     gsnProvider.send(payload, function(error, result) {
+       if (error) {
+         reject(error);
+       }
+       else {
+         resolve(result?.result);
+       }
+     });
+   });
+ };

  const Provider = GSN_CONTRACTS.includes(params.to.toLowerCase())
-   ? new ethers.providers.Web3Provider(
-     await RelayProvider.newProvider({
-       provider: window.ethereum,
-       config,
-     }).init(),
-   )
+   ? new BrowserProvider(gsnProvider)
    : WindowEthereumProvider
  ;
```

## Asynchronous getSigner Method

In ethers v6, the `getSigner method` has been updated to be asynchronous, allowing for more efficient handling of signer-related operations.

```jsx
- const tx = await Provider.getSigner().sendTransaction(params);
+ const tx = await (await Provider.getSigner()).sendTransaction(params);

- const signedMessage = await Provider.getSigner().signMessage(originalMessage);
+ const signedMessage = await (await Provider.getSigner()).signMessage(originalMessage);
```

## Renaming of transactionHash Field

The `transactionHash` field of the transaction receipt has been renamed to `hash` in ethers v6. This change provides a more consistent naming convention across the library.

```jsx
- const tx = await Provider.getTransaction(receipt.transactionHash);
+ const tx = await Provider.getTransaction(receipt.hash);
```

## Removal of confirmations Field

The `confirmations` field has been removed from Transaction objects in ethers v6. To determine the number of confirmations for a mined transaction, developers should access the confirmations property from the `TransactionReceipt` object.

```jsx
- const isMined = await Provider.getTransaction(sentTx.hash)).confirmations > 0;
+ const isMined = Boolean(await Provider.getTransactionReceipt(sentTx.hash))
```

## Renaming of error reason Field

The field containing the error message encountered during the invocation of `sendTransaction` has been moved from `data.message` to `reason`.

```jsx
- if (error.data.message.includes('gas required exceeds allowance')) {
+ if (error.reason.includes('gas required exceeds allowance')) {
```

## Conclusion

The migration of the SanR dApp from ethers.js v5 to v6 involved several notable changes in code structure and functionality. These modifications were necessary to leverage the advancements and improvements offered by ethers.js v6.


Please note that this article is a summary of the changes introduced during the migration process. For more detailed information and guidance, we recommend referring to the ethers.js [documentation](https://docs.ethers.org/v6/migrating/) and [release notes](https://github.com/ethers-io/ethers.js/releases).