Migration of the SanR dApp from ethers v5 to v6

mqklin

mqklin

Posted on May 31, 2023

Migration of the SanR dApp from ethers v5 to v6

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. 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).

- 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);
Enter fullscreen mode Exit fullscreen mode

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.

- 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)}"]`;
Enter fullscreen mode Exit fullscreen mode

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.

- const L2Provider = new ethers.providers.JsonRpcProvider(L2.url);
+ const L2Provider = new ethers.JsonRpcProvider(L2.url, null, {batchMaxCount: 1});
Enter fullscreen mode Exit fullscreen mode

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.
- 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(
Enter fullscreen mode Exit fullscreen mode

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.

- <SubheaderValue>{L2CompetitionsContract.address}</SubheaderValue>
+ <SubheaderValue>{L2CompetitionsContract.target}</SubheaderValue>
Enter fullscreen mode Exit fullscreen mode

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.

- <JustifyCenter>{el.event === 'Staked' ? 'stake' : el.event === 'Rewarded' ? 'reward' : 'unstake'}</JustifyCenter>
+ <JustifyCenter>{el.fragment.name === 'Staked' ? 'stake' : el.fragment.name === 'Rewarded' ? 'reward' : 'unstake'}</JustifyCenter>
Enter fullscreen mode Exit fullscreen mode

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, has announced plans to rectify this issue, allowing developers to use object validation once again.

- L1SANContract: object.isRequired,
+ L1SANContract: any.isRequired,
Enter fullscreen mode Exit fullscreen mode

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.

- const _token = {...await Contract.followings(tokenId)};
- const token = {..._token};
+ const token = (await Contract.followings(tokenId)).toObject();
Enter fullscreen mode Exit fullscreen mode

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.

- const infoHash = ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(
+ const infoHash = ethers.keccak256(ethers.AbiCoder.defaultAbiCoder().encode(
Enter fullscreen mode Exit fullscreen mode

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.

- const currentPrice = await L2PricesContract.currentPrice(ethers.utils.formatBytes32String('BTC/USD'))).price;
+ const currentPrice = await L2PricesContract.currentPrice(ethers.encodeBytes32String('BTC/USD'))).price;
Enter fullscreen mode Exit fullscreen mode

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.

+ 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
  ;
Enter fullscreen mode Exit fullscreen mode

Asynchronous getSigner Method

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

- 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);
Enter fullscreen mode Exit fullscreen mode

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.

- const tx = await Provider.getTransaction(receipt.transactionHash);
+ const tx = await Provider.getTransaction(receipt.hash);
Enter fullscreen mode Exit fullscreen mode

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.

- const isMined = await Provider.getTransaction(sentTx.hash)).confirmations > 0;
+ const isMined = Boolean(await Provider.getTransactionReceipt(sentTx.hash))
Enter fullscreen mode Exit fullscreen mode

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.

- if (error.data.message.includes('gas required exceeds allowance')) {
+ if (error.reason.includes('gas required exceeds allowance')) {
Enter fullscreen mode Exit fullscreen mode

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 and release notes.

💖 💪 🙅 🚩
mqklin
mqklin

Posted on May 31, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related