From f79723be4073c60b25c92906c8a626d22c2b133a Mon Sep 17 00:00:00 2001
From: Andres Martin Aiello <50411235+andresaiello@users.noreply.github.com>
Date: Fri, 2 Feb 2024 13:22:18 -0300
Subject: [PATCH] feat: implement feature to disable invitation (#148)

* feat: implement feature to disable invitation

* audit recommendation

* add expiration

* typo

* update addresses

* add pk
---
 package.json                                  |   2 +-
 .../zeta-app-contracts/data/addresses.json    |   6 +-
 packages/zeta-app-contracts/hardhat.config.ts |   5 +-
 .../contracts/disperse/Disperse.sol           |   9 +-
 .../zeta-points/InvitationManager.sol         |  46 ++++--
 .../zevm-app-contracts/data/addresses.json    |  10 +-
 packages/zevm-app-contracts/hardhat.config.ts |   2 +-
 .../zevm-app-contracts/test/Disperse.spec.ts  |  16 ++-
 .../test/zeta-points/InvitationManager.ts     |  62 ++++++--
 .../test/zeta-points/test.helpers.ts          |   8 +-
 yarn.lock                                     | 132 +++++++++++++-----
 11 files changed, 214 insertions(+), 84 deletions(-)

diff --git a/package.json b/package.json
index e7a00b32..569e7040 100644
--- a/package.json
+++ b/package.json
@@ -29,8 +29,8 @@
   },
   "devDependencies": {
     "@changesets/cli": "^2.23.1",
+    "@nomicfoundation/hardhat-verify": "2.0.3",
     "@nomiclabs/hardhat-ethers": "^2.0.5",
-    "@nomiclabs/hardhat-etherscan": "3.0.3",
     "@nomiclabs/hardhat-waffle": "^2.0.3",
     "@typechain/ethers-v5": "^10.0.0",
     "@typechain/hardhat": "^6.0.0",
diff --git a/packages/zeta-app-contracts/data/addresses.json b/packages/zeta-app-contracts/data/addresses.json
index 27c51e4a..eba55858 100644
--- a/packages/zeta-app-contracts/data/addresses.json
+++ b/packages/zeta-app-contracts/data/addresses.json
@@ -8,7 +8,7 @@
     },
     "bsc_mainnet": {
       "multiChainSwap": "",
-      "multiChainValue": "",
+      "multiChainValue": "0x33e5fCFfe910B99DB46c259804fCA1317EA0Aa89",
       "zetaTokenConsumerUniV2": "",
       "zetaTokenConsumerUniV3": ""
     },
@@ -26,7 +26,7 @@
     },
     "eth_mainnet": {
       "multiChainSwap": "",
-      "multiChainValue": "",
+      "multiChainValue": "0x910966E1C0Bc9FD74f499723c19Ff9799fE258a5",
       "zetaTokenConsumerUniV2": "",
       "zetaTokenConsumerUniV3": ""
     },
@@ -44,7 +44,7 @@
     },
     "zeta_testnet": {
       "multiChainSwap": "",
-      "multiChainValue": "0x82aC45D07dEe4DBDe050e838beF345347DEd99a8",
+      "multiChainValue": "0x36Cfb6dCd6926dFb749dc8E4b28efc73f3e6FAe3",
       "zetaTokenConsumerUniV2": "",
       "zetaTokenConsumerUniV3": ""
     }
diff --git a/packages/zeta-app-contracts/hardhat.config.ts b/packages/zeta-app-contracts/hardhat.config.ts
index 8d3851c6..fe201701 100644
--- a/packages/zeta-app-contracts/hardhat.config.ts
+++ b/packages/zeta-app-contracts/hardhat.config.ts
@@ -1,4 +1,4 @@
-import "@nomiclabs/hardhat-etherscan";
+import "@nomicfoundation/hardhat-verify";
 import "@nomiclabs/hardhat-waffle";
 import "@typechain/hardhat";
 import "hardhat-gas-reporter";
@@ -11,11 +11,14 @@ import type { HardhatUserConfig } from "hardhat/types";
 
 dotenv.config();
 
+const PRIVATE_KEYS = process.env.PRIVATE_KEY !== undefined ? [`0x${process.env.PRIVATE_KEY}`] : [];
+
 const config: HardhatUserConfig = {
   //@ts-ignore
   etherscan: {
     apiKey: {
       // BSC
+      bsc: process.env.BSCSCAN_API_KEY || "",
       bscTestnet: process.env.BSCSCAN_API_KEY || "",
       // ETH
       goerli: process.env.ETHERSCAN_API_KEY || "",
diff --git a/packages/zevm-app-contracts/contracts/disperse/Disperse.sol b/packages/zevm-app-contracts/contracts/disperse/Disperse.sol
index 21efac64..943ce30c 100644
--- a/packages/zevm-app-contracts/contracts/disperse/Disperse.sol
+++ b/packages/zevm-app-contracts/contracts/disperse/Disperse.sol
@@ -2,8 +2,11 @@
 pragma solidity 0.8.7;
 
 import "@openzeppelin/contracts/interfaces/IERC20.sol";
+import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
 
 contract Disperse {
+    using SafeERC20 for IERC20;
+
     bool private locked;
 
     event FundsDispersed(address indexed token, address indexed from, address indexed recipient, uint256 value);
@@ -38,9 +41,9 @@ contract Disperse {
     ) external noReentrancy {
         uint256 total = 0;
         for (uint256 i = 0; i < recipients.length; i++) total += values[i];
-        require(token.transferFrom(msg.sender, address(this), total));
+        token.safeTransferFrom(msg.sender, address(this), total);
         for (uint256 i = 0; i < recipients.length; i++) {
-            require(token.transfer(recipients[i], values[i]));
+            token.safeTransfer(recipients[i], values[i]);
             emit FundsDispersed(address(token), msg.sender, recipients[i], values[i]);
         }
     }
@@ -51,7 +54,7 @@ contract Disperse {
         uint256[] calldata values
     ) external noReentrancy {
         for (uint256 i = 0; i < recipients.length; i++) {
-            require(token.transferFrom(msg.sender, recipients[i], values[i]));
+            token.safeTransferFrom(msg.sender, recipients[i], values[i]);
             emit FundsDispersed(address(token), msg.sender, recipients[i], values[i]);
         }
     }
diff --git a/packages/zevm-app-contracts/contracts/zeta-points/InvitationManager.sol b/packages/zevm-app-contracts/contracts/zeta-points/InvitationManager.sol
index 50f43173..aad0d270 100644
--- a/packages/zevm-app-contracts/contracts/zeta-points/InvitationManager.sol
+++ b/packages/zevm-app-contracts/contracts/zeta-points/InvitationManager.sol
@@ -8,6 +8,10 @@ contract InvitationManager {
         bytes32 r;
         bytes32 s;
     }
+
+    // Indicate if invitation is still available. The default value is true.
+    mapping(address => bool) public invitationEnabled;
+
     // Records the timestamp when a particular user gets verified.
     mapping(address => uint256) public userVerificationTimestamps;
 
@@ -24,23 +28,37 @@ contract InvitationManager {
     mapping(address => mapping(uint256 => uint256)) public totalInvitesByInviterByDay;
 
     error UserAlreadyVerified();
+    error UserNotVerified();
     error UnrecognizedInvitation();
     error IndexOutOfBounds();
     error CanNotInviteYourself();
 
-    event UserVerified(address indexed userAddress, uint256 verifiedAt);
-    event InvitationAccepted(address indexed inviter, address indexed invitee, uint256 index, uint256 acceptedAt);
+    event UserVerified(address indexed userAddress, uint256 verifiedAt, uint256 unix_timestamp);
+    event InvitationAccepted(
+        address indexed inviter,
+        address indexed invitee,
+        uint256 index,
+        uint256 expiration,
+        uint256 acceptedAt,
+        uint256 unix_timestamp
+    );
 
     function _markAsVerified(address user) internal {
         // Check if the user is already verified
         if (userVerificationTimestamps[user] > 0) revert UserAlreadyVerified();
 
         userVerificationTimestamps[user] = block.timestamp;
-        emit UserVerified(user, block.timestamp);
+        emit UserVerified(user, block.timestamp, block.timestamp);
     }
 
     function markAsVerified() external {
         _markAsVerified(msg.sender);
+        invitationEnabled[msg.sender] = true;
+    }
+
+    function updateInvitationStatus(bool value) external {
+        if (userVerificationTimestamps[msg.sender] == 0) revert UserNotVerified();
+        invitationEnabled[msg.sender] = value;
     }
 
     function hasBeenVerified(address userAddress) external view returns (bool) {
@@ -51,18 +69,21 @@ contract InvitationManager {
         return userVerificationTimestamps[userAddress];
     }
 
-    function _verifySignature(address inviter, Signature calldata signature) private pure {
-        bytes32 payloadHash = keccak256(abi.encode(inviter));
+    function _verifySignature(address inviter, uint256 expiration, Signature calldata signature) private pure {
+        bytes32 payloadHash = keccak256(abi.encode(inviter, expiration));
         bytes32 messageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", payloadHash));
 
         address messageSigner = ecrecover(messageHash, signature.v, signature.r, signature.s);
         if (inviter != messageSigner) revert UnrecognizedInvitation();
     }
 
-    function confirmAndAcceptInvitation(address inviter, Signature calldata signature) external {
+    function confirmAndAcceptInvitation(address inviter, uint256 expiration, Signature calldata signature) external {
         if (inviter == msg.sender) revert CanNotInviteYourself();
-        if (userVerificationTimestamps[inviter] == 0) revert UnrecognizedInvitation();
-        _verifySignature(inviter, signature);
+        if (!invitationEnabled[inviter]) revert UnrecognizedInvitation();
+
+        _verifySignature(inviter, expiration, signature);
+
+        if (expiration < block.timestamp) revert UnrecognizedInvitation();
 
         acceptedInvitationsTimestamp[inviter][msg.sender] = block.timestamp;
         _markAsVerified(msg.sender);
@@ -75,7 +96,14 @@ contract InvitationManager {
         totalInvitesByDay[dayStartTimestamp]++;
         totalInvitesByInviterByDay[inviter][dayStartTimestamp]++;
 
-        emit InvitationAccepted(inviter, msg.sender, inviteeLists[inviter].length - 1, block.timestamp);
+        emit InvitationAccepted(
+            inviter,
+            msg.sender,
+            inviteeLists[inviter].length - 1,
+            expiration,
+            block.timestamp,
+            block.timestamp
+        );
     }
 
     function getInviteeCount(address inviter) external view returns (uint256) {
diff --git a/packages/zevm-app-contracts/data/addresses.json b/packages/zevm-app-contracts/data/addresses.json
index e040ed8f..bd53a842 100644
--- a/packages/zevm-app-contracts/data/addresses.json
+++ b/packages/zevm-app-contracts/data/addresses.json
@@ -1,11 +1,11 @@
 {
   "zevm": {
     "zeta_testnet": {
-      "disperse": "0xf394dc01879E39f19eDA533EFD10C82eEee5B2b1",
-      "rewardDistributorFactory": "0x667e4C493d40015256BDC89E3ba750B2F90359E1",
-      "zetaSwap": "0x44D1F1f9289DBA1Cf5824bd667184cEBE020aA1c",
-      "zetaSwapBtcInbound": "0x008b393933D5CA2457Df570CA5D628380FFf6da4",
-      "invitationManager": "0xF4cF881A3d23936e3710ef2Cbbe93f71C4389918"
+      "disperse": "0x23ce409Ea60c3d75827d04D9db3d52F3af62e44d",
+      "rewardDistributorFactory": "0xB9dc665610CF5109cE23aBBdaAc315B41FA094c1",
+      "zetaSwap": "0xA8168Dc495Ed61E70f5c1941e2860050AB902cEF",
+      "zetaSwapBtcInbound": "0x358E2cfC0E16444Ba7D3164Bbeeb6bEA7472c559",
+      "invitationManager": "0x3649C03C472B698213926543456E9c21081e529d"
     }
   }
 }
\ No newline at end of file
diff --git a/packages/zevm-app-contracts/hardhat.config.ts b/packages/zevm-app-contracts/hardhat.config.ts
index d495af66..3ac46d5e 100644
--- a/packages/zevm-app-contracts/hardhat.config.ts
+++ b/packages/zevm-app-contracts/hardhat.config.ts
@@ -1,4 +1,4 @@
-import "@nomiclabs/hardhat-etherscan";
+import "@nomicfoundation/hardhat-verify";
 import "@nomiclabs/hardhat-waffle";
 import "@typechain/hardhat";
 import "hardhat-gas-reporter";
diff --git a/packages/zevm-app-contracts/test/Disperse.spec.ts b/packages/zevm-app-contracts/test/Disperse.spec.ts
index 20b7ca2c..3002619b 100644
--- a/packages/zevm-app-contracts/test/Disperse.spec.ts
+++ b/packages/zevm-app-contracts/test/Disperse.spec.ts
@@ -1,6 +1,7 @@
 import { parseUnits } from "@ethersproject/units";
 import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
 import { expect } from "chai";
+import { parseEther } from "ethers/lib/utils";
 import { ethers, network } from "hardhat";
 
 import { Disperse, Disperse__factory, MockZRC20, MockZRC20__factory } from "../typechain-types";
@@ -23,18 +24,23 @@ describe("Disperse tests", () => {
 
   describe("Disperse", () => {
     it("Should disperse ETH", async () => {
-      const amount = parseUnits("10");
+      const count = 500;
+      const amount = parseEther("0.01");
       const balance0 = await ethers.provider.getBalance(accounts[0].address);
       const balance1 = await ethers.provider.getBalance(accounts[1].address);
-      await disperseContract.disperseEther([accounts[0].address, accounts[1].address], [amount, amount.mul(2)], {
-        value: amount.mul(3),
+
+      const bigArrayAddress = new Array(count).fill(accounts[0].address);
+      const bigArrayAmount = new Array(count).fill(amount);
+
+      await disperseContract.disperseEther(bigArrayAddress, bigArrayAmount, {
+        value: amount.mul(count),
       });
 
       const balance0After = await ethers.provider.getBalance(accounts[0].address);
       const balance1After = await ethers.provider.getBalance(accounts[1].address);
 
-      expect(balance0After.sub(balance0)).to.be.eq(amount);
-      expect(balance1After.sub(balance1)).to.be.eq(amount.mul(2));
+      expect(balance0After.sub(balance0)).to.be.eq(amount.mul(count));
+      expect(balance1After.sub(balance1)).to.be.eq(0);
     });
 
     it("Should disperse ETH with surplus", async () => {
diff --git a/packages/zevm-app-contracts/test/zeta-points/InvitationManager.ts b/packages/zevm-app-contracts/test/zeta-points/InvitationManager.ts
index 2c9d5279..0eb30a06 100644
--- a/packages/zevm-app-contracts/test/zeta-points/InvitationManager.ts
+++ b/packages/zevm-app-contracts/test/zeta-points/InvitationManager.ts
@@ -21,6 +21,13 @@ describe("InvitationManager Contract test", () => {
     await invitationManager.markAsVerified();
   });
 
+  const getTomorrowTimestamp = async () => {
+    const block = await ethers.provider.getBlock("latest");
+    const now = block.timestamp;
+    const tomorrow = now + 24 * 60 * 60;
+    return tomorrow;
+  };
+
   describe("True", () => {
     it("Should be true", async () => {
       expect(true).to.equal(true);
@@ -29,12 +36,16 @@ describe("InvitationManager Contract test", () => {
 
   describe("Invitations test", () => {
     it("Should verify an invitation and store it", async () => {
-      const sig = await getInvitationSig(inviter);
+      const expirationDate = await getTomorrowTimestamp();
+
+      const sig = await getInvitationSig(inviter, expirationDate);
 
       const hasBeenVerifiedBefore = await invitationManager.hasBeenVerified(invitee.address);
       await expect(hasBeenVerifiedBefore).to.be.eq(false);
 
-      const tx = await invitationManager.connect(invitee).confirmAndAcceptInvitation(inviter.address, sig);
+      const tx = await invitationManager
+        .connect(invitee)
+        .confirmAndAcceptInvitation(inviter.address, expirationDate, sig);
       const rec = await tx.wait();
 
       const block = await ethers.provider.getBlock(rec.blockNumber);
@@ -50,30 +61,47 @@ describe("InvitationManager Contract test", () => {
     });
 
     it("Should revert if invitation is invalid", async () => {
-      const sig = await getInvitationSig(inviter);
-      const tx = invitationManager.connect(invitee).confirmAndAcceptInvitation(addrs[0].address, sig);
+      const expirationDate = await getTomorrowTimestamp();
+      const sig = await getInvitationSig(inviter, expirationDate);
+      const tx = invitationManager.connect(invitee).confirmAndAcceptInvitation(addrs[0].address, expirationDate, sig);
+      await expect(tx).to.be.revertedWith("UnrecognizedInvitation");
+    });
+
+    it("Should revert if invitation is expired", async () => {
+      const expirationDate = await getTomorrowTimestamp();
+      const yesterdayTimestamp = expirationDate - 24 * 60 * 60;
+      const sig = await getInvitationSig(inviter, expirationDate);
+      const tx = invitationManager
+        .connect(invitee)
+        .confirmAndAcceptInvitation(inviter.address, yesterdayTimestamp, sig);
       await expect(tx).to.be.revertedWith("UnrecognizedInvitation");
     });
 
     it("Should revert if inviter has not been verified", async () => {
-      const sig = await getInvitationSig(addrs[0]);
-      const tx = invitationManager.connect(invitee).confirmAndAcceptInvitation(addrs[0].address, sig);
+      const expirationDate = await getTomorrowTimestamp();
+      const sig = await getInvitationSig(addrs[0], expirationDate);
+      const tx = invitationManager.connect(invitee).confirmAndAcceptInvitation(addrs[0].address, expirationDate, sig);
       await expect(tx).to.be.revertedWith("UnrecognizedInvitation");
     });
 
     it("Should revert if invitation is already accepted", async () => {
-      const sig = await getInvitationSig(inviter);
-      await invitationManager.connect(invitee).confirmAndAcceptInvitation(inviter.address, sig);
-      const tx = invitationManager.connect(invitee).confirmAndAcceptInvitation(inviter.address, sig);
+      const expirationDate = await getTomorrowTimestamp();
+      const sig = await getInvitationSig(inviter, expirationDate);
+      await invitationManager.connect(invitee).confirmAndAcceptInvitation(inviter.address, expirationDate, sig);
+      const tx = invitationManager.connect(invitee).confirmAndAcceptInvitation(inviter.address, expirationDate, sig);
       await expect(tx).to.be.revertedWith("UserAlreadyVerified");
     });
 
     it("Should count only for today if I just accepted", async () => {
-      const sig = await getInvitationSig(inviter);
-      const tx = await invitationManager.connect(invitee).confirmAndAcceptInvitation(inviter.address, sig);
+      const expirationDate = await getTomorrowTimestamp();
+      const sig = await getInvitationSig(inviter, expirationDate);
+      const tx = await invitationManager
+        .connect(invitee)
+        .confirmAndAcceptInvitation(inviter.address, expirationDate, sig);
       const rec = await tx.wait();
 
       const block = await ethers.provider.getBlock(rec.blockNumber);
+      const now = block.timestamp;
 
       const invitation = await invitationManager.acceptedInvitationsTimestamp(inviter.address, invitee.address);
       await expect(invitation).to.be.eq(block.timestamp);
@@ -81,7 +109,6 @@ describe("InvitationManager Contract test", () => {
       const invitationCount = await invitationManager.getInviteeCount(inviter.address);
       await expect(invitationCount).to.be.eq(1);
 
-      const now = block.timestamp;
       const todayTimestamp = Math.floor(now / 86400) * 86400;
       const invitationCountToday = await invitationManager.getTotalInvitesOnDay(todayTimestamp);
       await expect(invitationCountToday).to.be.eq(1);
@@ -104,12 +131,15 @@ describe("InvitationManager Contract test", () => {
     });
 
     it("Should emit the right event when invitation is accepted", async () => {
-      const sig = await getInvitationSig(inviter);
+      const expirationDate = await getTomorrowTimestamp();
+      const sig = await getInvitationSig(inviter, expirationDate);
 
       const hasBeenVerifiedBefore = await invitationManager.hasBeenVerified(invitee.address);
       await expect(hasBeenVerifiedBefore).to.be.eq(false);
 
-      const tx = await invitationManager.connect(invitee).confirmAndAcceptInvitation(inviter.address, sig);
+      const tx = await invitationManager
+        .connect(invitee)
+        .confirmAndAcceptInvitation(inviter.address, expirationDate, sig);
       const rec = await tx.wait();
       const event = rec.events?.find((e) => e.event === "InvitationAccepted");
       const block = await ethers.provider.getBlock(rec.blockNumber);
@@ -121,7 +151,9 @@ describe("InvitationManager Contract test", () => {
       const inviteeByIndex = await invitationManager.getInviteeAtIndex(inviter.address, event?.args?.index);
       expect(inviteeByIndex).to.be.eq(invitee.address);
 
-      const tx2 = await invitationManager.connect(addrs[0]).confirmAndAcceptInvitation(inviter.address, sig);
+      const tx2 = await invitationManager
+        .connect(addrs[0])
+        .confirmAndAcceptInvitation(inviter.address, expirationDate, sig);
       const rec2 = await tx2.wait();
       const event2 = rec2.events?.find((e) => e.event === "InvitationAccepted");
       const block2 = await ethers.provider.getBlock(rec2.blockNumber);
diff --git a/packages/zevm-app-contracts/test/zeta-points/test.helpers.ts b/packages/zevm-app-contracts/test/zeta-points/test.helpers.ts
index cb758210..f60e9a5f 100644
--- a/packages/zevm-app-contracts/test/zeta-points/test.helpers.ts
+++ b/packages/zevm-app-contracts/test/zeta-points/test.helpers.ts
@@ -1,12 +1,12 @@
 import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
 import { ethers } from "hardhat";
 
-export const getInvitationSig = async (signer: SignerWithAddress) => {
-  let payload = ethers.utils.defaultAbiCoder.encode(["address"], [signer.address]);
+export const getInvitationSig = async (signer: SignerWithAddress, expirationDate: number) => {
+  const payload = ethers.utils.defaultAbiCoder.encode(["address", "uint256"], [signer.address, expirationDate]);
 
-  let payloadHash = ethers.utils.keccak256(payload);
+  const payloadHash = ethers.utils.keccak256(payload);
 
   // This adds the message prefix
-  let signature = await signer.signMessage(ethers.utils.arrayify(payloadHash));
+  const signature = await signer.signMessage(ethers.utils.arrayify(payloadHash));
   return ethers.utils.splitSignature(signature);
 };
diff --git a/yarn.lock b/yarn.lock
index d0be129f..9f6ae3ab 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1773,6 +1773,25 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@nomicfoundation/hardhat-verify@npm:2.0.3":
+  version: 2.0.3
+  resolution: "@nomicfoundation/hardhat-verify@npm:2.0.3"
+  dependencies:
+    "@ethersproject/abi": ^5.1.2
+    "@ethersproject/address": ^5.0.2
+    cbor: ^8.1.0
+    chalk: ^2.4.2
+    debug: ^4.1.1
+    lodash.clonedeep: ^4.5.0
+    semver: ^6.3.0
+    table: ^6.8.0
+    undici: ^5.14.0
+  peerDependencies:
+    hardhat: ^2.0.4
+  checksum: 5cc3513d3385eb45c19081e79b1b2a67b13376f0d2c5dc2da07ee41886f82110b0af5b46e7fae6204db8a9d4dcda7e568a630a254bd5567779068bc29c22c86f
+  languageName: node
+  linkType: hard
+
 "@nomicfoundation/solidity-analyzer-darwin-arm64@npm:0.1.1":
   version: 0.1.1
   resolution: "@nomicfoundation/solidity-analyzer-darwin-arm64@npm:0.1.1"
@@ -1892,23 +1911,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@nomiclabs/hardhat-etherscan@npm:3.0.3":
-  version: 3.0.3
-  resolution: "@nomiclabs/hardhat-etherscan@npm:3.0.3"
-  dependencies:
-    "@ethersproject/abi": ^5.1.2
-    "@ethersproject/address": ^5.0.2
-    cbor: ^5.0.2
-    debug: ^4.1.1
-    fs-extra: ^7.0.1
-    semver: ^6.3.0
-    undici: ^4.14.1
-  peerDependencies:
-    hardhat: ^2.0.4
-  checksum: 6ce0856ad989c4cff5c90f7673d80fb7778ac1941db2bdf0998afca31c2439ed2e840fbad123a6f5958768243179af33a56f7b45919a598c8361b9a7cfdbaea6
-  languageName: node
-  linkType: hard
-
 "@nomiclabs/hardhat-waffle@npm:^2.0.3":
   version: 2.0.6
   resolution: "@nomiclabs/hardhat-waffle@npm:2.0.6"
@@ -3008,6 +3010,18 @@ __metadata:
   languageName: node
   linkType: hard
 
+"ajv@npm:^8.0.1":
+  version: 8.12.0
+  resolution: "ajv@npm:8.12.0"
+  dependencies:
+    fast-deep-equal: ^3.1.1
+    json-schema-traverse: ^1.0.0
+    require-from-string: ^2.0.2
+    uri-js: ^4.2.2
+  checksum: 4dc13714e316e67537c8b31bc063f99a1d9d9a497eb4bbd55191ac0dcd5e4985bbb71570352ad6f1e76684fb6d790928f96ba3b2d4fd6e10024be9612fe3f001
+  languageName: node
+  linkType: hard
+
 "amdefine@npm:>=0.0.4":
   version: 1.0.1
   resolution: "amdefine@npm:1.0.1"
@@ -3285,6 +3299,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"astral-regex@npm:^2.0.0":
+  version: 2.0.0
+  resolution: "astral-regex@npm:2.0.0"
+  checksum: 876231688c66400473ba505731df37ea436e574dd524520294cc3bbc54ea40334865e01fa0d074d74d036ee874ee7e62f486ea38bc421ee8e6a871c06f011766
+  languageName: node
+  linkType: hard
+
 "async-eventemitter@npm:^0.2.4":
   version: 0.2.4
   resolution: "async-eventemitter@npm:0.2.4"
@@ -3418,7 +3439,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"bignumber.js@npm:^9.0.0, bignumber.js@npm:^9.0.1":
+"bignumber.js@npm:^9.0.0":
   version: 9.1.2
   resolution: "bignumber.js@npm:9.1.2"
   checksum: 582c03af77ec9cb0ebd682a373ee6c66475db94a4325f92299621d544aa4bd45cb45fd60001610e94aef8ae98a0905fa538241d9638d4422d57abbeeac6fadaf
@@ -3893,13 +3914,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"cbor@npm:^5.0.2":
-  version: 5.2.0
-  resolution: "cbor@npm:5.2.0"
+"cbor@npm:^8.1.0":
+  version: 8.1.0
+  resolution: "cbor@npm:8.1.0"
   dependencies:
-    bignumber.js: ^9.0.1
-    nofilter: ^1.0.4
-  checksum: b3c39dae64370f361526dbec88f51d0f1b47027224cdd21dbd64c228f0fe7eaa945932d349ec5324068a6c6dcdbb1e3b46242852524fd53c526d14cb60514bdc
+    nofilter: ^3.1.0
+  checksum: a90338435dc7b45cc01461af979e3bb6ddd4f2a08584c437586039cd5f2235014c06e49d664295debbfb3514d87b2f06728092ab6aa6175e2e85e9cd7dc0c1fd
   languageName: node
   linkType: hard
 
@@ -7686,6 +7706,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"json-schema-traverse@npm:^1.0.0":
+  version: 1.0.0
+  resolution: "json-schema-traverse@npm:1.0.0"
+  checksum: 02f2f466cdb0362558b2f1fd5e15cce82ef55d60cd7f8fa828cf35ba74330f8d767fcae5c5c2adb7851fa811766c694b9405810879bc4e1ddd78a7c0e03658ad
+  languageName: node
+  linkType: hard
+
 "json-schema@npm:0.4.0, json-schema@npm:^0.4.0":
   version: 0.4.0
   resolution: "json-schema@npm:0.4.0"
@@ -8084,6 +8111,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"lodash.clonedeep@npm:^4.5.0":
+  version: 4.5.0
+  resolution: "lodash.clonedeep@npm:4.5.0"
+  checksum: 92c46f094b064e876a23c97f57f81fbffd5d760bf2d8a1c61d85db6d1e488c66b0384c943abee4f6af7debf5ad4e4282e74ff83177c9e63d8ff081a4837c3489
+  languageName: node
+  linkType: hard
+
 "lodash.isequal@npm:^4.5.0":
   version: 4.5.0
   resolution: "lodash.isequal@npm:4.5.0"
@@ -8112,6 +8146,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"lodash.truncate@npm:^4.4.2":
+  version: 4.4.2
+  resolution: "lodash.truncate@npm:4.4.2"
+  checksum: b463d8a382cfb5f0e71c504dcb6f807a7bd379ff1ea216669aa42c52fc28c54e404bfbd96791aa09e6df0de2c1d7b8f1b7f4b1a61f324d38fe98bc535aeee4f5
+  languageName: node
+  linkType: hard
+
 "lodash@npm:^4.17.11, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.21":
   version: 4.17.21
   resolution: "lodash@npm:4.17.21"
@@ -8956,10 +8997,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"nofilter@npm:^1.0.4":
-  version: 1.0.4
-  resolution: "nofilter@npm:1.0.4"
-  checksum: 54d864f745de5c3312994e880cf2d4f55e34830d6adc8275dce3731507ca380d21040336e4a277a4901551c07f04c452fbeffd57fad1dc8f68a2943eaf894a04
+"nofilter@npm:^3.1.0":
+  version: 3.1.0
+  resolution: "nofilter@npm:3.1.0"
+  checksum: 58aa85a5b4b35cbb6e42de8a8591c5e338061edc9f3e7286f2c335e9e9b9b8fa7c335ae45daa8a1f3433164dc0b9a3d187fa96f9516e04a17a1f9ce722becc4f
   languageName: node
   linkType: hard
 
@@ -9953,7 +9994,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"require-from-string@npm:^2.0.0":
+"require-from-string@npm:^2.0.0, require-from-string@npm:^2.0.2":
   version: 2.0.2
   resolution: "require-from-string@npm:2.0.2"
   checksum: a03ef6895445f33a4015300c426699bc66b2b044ba7b670aa238610381b56d3f07c686251740d575e22f4c87531ba662d06937508f0f3c0f1ddc04db3130560b
@@ -10574,6 +10615,17 @@ __metadata:
   languageName: node
   linkType: hard
 
+"slice-ansi@npm:^4.0.0":
+  version: 4.0.0
+  resolution: "slice-ansi@npm:4.0.0"
+  dependencies:
+    ansi-styles: ^4.0.0
+    astral-regex: ^2.0.0
+    is-fullwidth-code-point: ^3.0.0
+  checksum: 4a82d7f085b0e1b070e004941ada3c40d3818563ac44766cca4ceadd2080427d337554f9f99a13aaeb3b4a94d9964d9466c807b3d7b7541d1ec37ee32d308756
+  languageName: node
+  linkType: hard
+
 "smart-buffer@npm:^4.2.0":
   version: 4.2.0
   resolution: "smart-buffer@npm:4.2.0"
@@ -11083,6 +11135,19 @@ __metadata:
   languageName: node
   linkType: hard
 
+"table@npm:^6.8.0":
+  version: 6.8.1
+  resolution: "table@npm:6.8.1"
+  dependencies:
+    ajv: ^8.0.1
+    lodash.truncate: ^4.4.2
+    slice-ansi: ^4.0.0
+    string-width: ^4.2.3
+    strip-ansi: ^6.0.1
+  checksum: 08249c7046125d9d0a944a6e96cfe9ec66908d6b8a9db125531be6eb05fa0de047fd5542e9d43b4f987057f00a093b276b8d3e19af162a9c40db2681058fd306
+  languageName: node
+  linkType: hard
+
 "tar@npm:^4.0.2":
   version: 4.4.19
   resolution: "tar@npm:4.4.19"
@@ -11647,13 +11712,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"undici@npm:^4.14.1":
-  version: 4.16.0
-  resolution: "undici@npm:4.16.0"
-  checksum: 5e88c2b3381085e25ed1d1a308610ac7ee985f478ac705af7a8e03213536e10f73ef8dd8d85e6ed38948d1883fa0ae935e04357c317b0f5d3d3c0211d0c8c393
-  languageName: node
-  linkType: hard
-
 "undici@npm:^5.14.0":
   version: 5.28.2
   resolution: "undici@npm:5.28.2"
@@ -12880,8 +12938,8 @@ __metadata:
   resolution: "zetachain@workspace:."
   dependencies:
     "@changesets/cli": ^2.23.1
+    "@nomicfoundation/hardhat-verify": 2.0.3
     "@nomiclabs/hardhat-ethers": ^2.0.5
-    "@nomiclabs/hardhat-etherscan": 3.0.3
     "@nomiclabs/hardhat-waffle": ^2.0.3
     "@typechain/ethers-v5": ^10.0.0
     "@typechain/hardhat": ^6.0.0