diff --git a/contracts/airnode-protocol-v1-contracts.sol b/contracts/airnode-protocol-v1-contracts.sol new file mode 100644 index 00000000..3b897ee1 --- /dev/null +++ b/contracts/airnode-protocol-v1-contracts.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.17; + +import "@api3/airnode-protocol-v1/contracts/access-control-registry/AccessControlRegistry.sol"; +import "@api3/airnode-protocol-v1/contracts/api3-server-v1/Api3ServerV1.sol"; diff --git a/contracts/contract-imports.sol b/contracts/contract-imports.sol deleted file mode 100644 index 4b17618f..00000000 --- a/contracts/contract-imports.sol +++ /dev/null @@ -1,4 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.18; - -import "@api3/dapi-management/contracts/AirseekerRegistry.sol"; diff --git a/contracts/dapi-management-contracts.sol b/contracts/dapi-management-contracts.sol new file mode 100644 index 00000000..6c639465 --- /dev/null +++ b/contracts/dapi-management-contracts.sol @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.17; + +import "api3-contracts/contracts/AirseekerRegistry.sol"; diff --git a/hardhat.config.ts b/hardhat.config.ts index 7df6fd0f..29bebd8c 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -3,7 +3,7 @@ import type { HardhatUserConfig } from 'hardhat/types'; import '@nomicfoundation/hardhat-toolbox'; const config: HardhatUserConfig = { - solidity: { version: '0.8.18' }, + solidity: { compilers: [{ version: '0.8.17' }] }, networks: { localhost: { url: 'http://127.0.0.1:8545/', @@ -14,7 +14,7 @@ const config: HardhatUserConfig = { // flattened version (only the contents of the "src" folder). This is also in anticipation of importing the // Typechain types instead of generating them at build time. outDir: 'src/typechain-types', - target: 'ethers-v5', + target: 'ethers-v6', }, defaultNetwork: 'localhost', }; diff --git a/jest-unit.config.js b/jest-unit.config.js index 1db6da99..de573fd2 100644 --- a/jest-unit.config.js +++ b/jest-unit.config.js @@ -19,4 +19,8 @@ module.exports = { testMatch: ['**/?(*.)+(spec|test).[t]s?(x)'], testPathIgnorePatterns: ['/.build', '/dist/', '/build/'], verbose: true, + + // See: https://github.com/jestjs/jest/issues/11617#issuecomment-1028651059. We can't use "workerThreads" mentioned + // later, because it complains that some of the node internal modules used in commons processing are unavailable. + maxWorkers: 1, }; diff --git a/local-test-configuration/airnode-feed-1/airnode-feed.json b/local-test-configuration/airnode-feed-1/airnode-feed.json index 2bf495c9..06a2a821 100644 --- a/local-test-configuration/airnode-feed-1/airnode-feed.json +++ b/local-test-configuration/airnode-feed-1/airnode-feed.json @@ -94,7 +94,7 @@ } ], "nodeSettings": { - "nodeVersion": "0.2.0", + "nodeVersion": "0.4.0", "airnodeWalletMnemonic": "${AIRNODE_WALLET_MNEMONIC}", "stage": "local-example" } diff --git a/local-test-configuration/airnode-feed-2/airnode-feed.json b/local-test-configuration/airnode-feed-2/airnode-feed.json index 713c03f0..447e494e 100644 --- a/local-test-configuration/airnode-feed-2/airnode-feed.json +++ b/local-test-configuration/airnode-feed-2/airnode-feed.json @@ -94,7 +94,7 @@ } ], "nodeSettings": { - "nodeVersion": "0.2.0", + "nodeVersion": "0.4.0", "airnodeWalletMnemonic": "${AIRNODE_WALLET_MNEMONIC}", "stage": "local-example" } diff --git a/local-test-configuration/monitoring/index.html b/local-test-configuration/monitoring/index.html index f4e3d3c7..90d50fed 100644 --- a/local-test-configuration/monitoring/index.html +++ b/local-test-configuration/monitoring/index.html @@ -18,8 +18,8 @@

Active data feeds


   
   
@@ -188,6 +188,45 @@ 

Active data feeds

name: 'UpdatedSignedApiUrl', type: 'event', }, + { + inputs: [], + name: 'MAXIMUM_BEACON_COUNT_IN_SET', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'MAXIMUM_SIGNED_API_URL_LENGTH', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'MAXIMUM_UPDATE_PARAMETERS_LENGTH', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, { inputs: [], name: 'activeDapiNameCount', @@ -236,6 +275,16 @@

Active data feeds

name: 'dataFeedTimestamp', type: 'uint32', }, + { + internalType: 'int224[]', + name: 'beaconValues', + type: 'int224[]', + }, + { + internalType: 'uint32[]', + name: 'beaconTimestamps', + type: 'uint32[]', + }, { internalType: 'bytes', name: 'updateParameters', @@ -529,7 +578,7 @@

Active data feeds

inputs: [], name: 'renounceOwnership', outputs: [], - stateMutability: 'nonpayable', + stateMutability: 'pure', type: 'function', }, { @@ -648,7 +697,7 @@

Active data feeds

], name: 'transferOwnership', outputs: [], - stateMutability: 'nonpayable', + stateMutability: 'pure', type: 'function', }, { @@ -676,7 +725,6 @@

Active data feeds

type: 'function', }, ]; - // Configuration const urlParams = new URLSearchParams(window.location.search); const rpcUrl = urlParams.get('rpcUrl'), @@ -686,26 +734,34 @@

Active data feeds

if (!airseekerRegistryAddress) throw new Error('airseekerRegistryAddress must be provided as URL parameter'); if (!airseekerMnemonic) throw new Error('airseekerMnemonic must be provided as URL parameter'); + // See: https://github.com/GoogleChromeLabs/jsbi/issues/30#issuecomment-953187833 + BigInt.prototype.toJSON = function () { + return this.toString(); + }; + function deriveBeaconId(airnodeAddress, templateId) { - return ethers.utils.solidityKeccak256(['address', 'bytes32'], [airnodeAddress, templateId]); + return ethers.solidityPackedKeccak256(['address', 'bytes32'], [airnodeAddress, templateId]); } function deriveBeaconSetId(beaconIds) { - return ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(['bytes32[]'], [beaconIds])); + return ethers.keccak256(ethers.AbiCoder.defaultAbiCoder().encode(['bytes32[]'], [beaconIds])); } const decodeDataFeedDetails = (dataFeed) => { if (dataFeed.length === 130) { // (64 [actual bytes] * 2[hex encoding] ) + 2 [for the '0x' preamble] // This is a hex encoded string, the contract works with bytes directly - const [airnodeAddress, templateId] = ethers.utils.defaultAbiCoder.decode(['address', 'bytes32'], dataFeed); + const [airnodeAddress, templateId] = ethers.AbiCoder.defaultAbiCoder().decode(['address', 'bytes32'], dataFeed); const dataFeedId = deriveBeaconId(airnodeAddress, templateId); return { dataFeedId, beacons: [{ beaconId: dataFeedId, airnodeAddress, templateId }] }; } - const [airnodeAddresses, templateIds] = ethers.utils.defaultAbiCoder.decode(['address[]', 'bytes32[]'], dataFeed); + const [airnodeAddresses, templateIds] = ethers.AbiCoder.defaultAbiCoder().decode( + ['address[]', 'bytes32[]'], + dataFeed + ); const beacons = airnodeAddresses.map((airnodeAddress, idx) => { const templateId = templateIds[idx]; @@ -722,7 +778,7 @@

Active data feeds

const decodeUpdateParameters = (updateParameters) => { // https://github.com/api3dao/airnode-protocol-v1/blob/5f861715749e182e334c273d6a52c4f2560c7994/contracts/api3-server-v1/extensions/BeaconSetUpdatesWithPsp.sol#L122 const [deviationThresholdInPercentage, deviationReference, heartbeatInterval] = - ethers.utils.defaultAbiCoder.decode(['uint256', 'int224', 'uint256'], updateParameters); + ethers.AbiCoder.defaultAbiCoder().decode(['uint256', 'int224', 'uint256'], updateParameters); // 2 characters for the '0x' preamble + 3 parameters, 32 * 2 hexadecimals for 32 bytes each if (updateParameters.length !== 2 + 3 * (32 * 2)) { @@ -741,52 +797,46 @@

Active data feeds

const mid = Math.floor(arr.length / 2); const nums = [...arr].sort((a, b) => { - if (a.lt(b)) return -1; - else if (a.gt(b)) return 1; + if (a < b) return -1; + else if (a > b) return 1; else return 0; }); - return arr.length % 2 === 0 ? nums[mid - 1].add(nums[mid]).div(2) : nums[mid]; + return arr.length % 2 === 0 ? (nums[mid - 1] + nums[mid]) / 2n : nums[mid]; }; const decodeBeaconValue = (encodedBeaconValue) => { // Solidity type(int224).min - const INT224_MIN = ethers.BigNumber.from(2).pow(ethers.BigNumber.from(223)).mul(ethers.BigNumber.from(-1)); + const INT224_MIN = 2n ** 223n * -1n; // Solidity type(int224).max - const INT224_MAX = ethers.BigNumber.from(2).pow(ethers.BigNumber.from(223)).sub(ethers.BigNumber.from(1)); + const INT224_MAX = 2n ** 223n - 1n; - const decodedBeaconValue = ethers.BigNumber.from( - ethers.utils.defaultAbiCoder.decode(['int256'], encodedBeaconValue)[0] - ); - if (decodedBeaconValue.gt(INT224_MAX) || decodedBeaconValue.lt(INT224_MIN)) { + const decodedBeaconValue = BigInt(ethers.AbiCoder.defaultAbiCoder().decode(['int256'], encodedBeaconValue)[0]); + if (decodedBeaconValue > INT224_MAX || decodedBeaconValue < INT224_MIN) { return null; } return decodedBeaconValue; }; + const abs = (n) => (n < 0n ? -n : n); + const calculateUpdateInPercentage = (initialValue, updatedValue) => { - const delta = updatedValue.sub(initialValue); - const absoluteDelta = delta.abs(); + const delta = updatedValue - initialValue; + const absoluteDelta = abs(delta); // Avoid division by 0 - const absoluteInitialValue = initialValue.isZero() ? ethers.BigNumber.from(1) : initialValue.abs(); - - return absoluteDelta.mul(ethers.BigNumber.from(1e8)).div(absoluteInitialValue); - }; - - const checkDeviationThresholdExceeded = (onChainValue, deviationThreshold, apiValue) => { - const updateInPercentage = calculateUpdateInPercentage(onChainValue, apiValue); + const absoluteInitialValue = initialValue === 0n ? 1n : abs(initialValue); - return updateInPercentage.gt(deviationThreshold); + return (absoluteDelta * BigInt(1e8)) / absoluteInitialValue; }; function deriveWalletPathFromSponsorAddress(sponsorAddress) { - const sponsorAddressBN = ethers.BigNumber.from(sponsorAddress); + const sponsorAddressBN = BigInt(sponsorAddress); const paths = []; for (let i = 0; i < 6; i++) { - const shiftedSponsorAddressBN = sponsorAddressBN.shr(31 * i); - paths.push(shiftedSponsorAddressBN.mask(31).toString()); + const shiftedSponsorAddressBN = sponsorAddressBN >> BigInt(31 * i); + paths.push((shiftedSponsorAddressBN % 2n ** 31n).toString()); } const AIRSEEKER_PROTOCOL_ID = '5'; // From: https://github.com/api3dao/airnode/blob/ef16c54f33d455a1794e7886242567fc47ee14ef/packages/airnode-protocol/src/index.ts#L46 return `${AIRSEEKER_PROTOCOL_ID}/${paths.join('/')}`; @@ -795,12 +845,15 @@

Active data feeds

const deriveSponsorWallet = (sponsorWalletMnemonic, dapiNameOrDataFeedId) => { // Hash the dAPI name or data feed ID because we need to take the first 20 bytes of it which could result in // collisions for dAPIs with the same prefix. - const hashedDapiNameOrDataFeedId = ethers.utils.keccak256(dapiNameOrDataFeedId); + const hashedDapiNameOrDataFeedId = ethers.keccak256(dapiNameOrDataFeedId); // Take first 20 bytes of the hashed dapiName or data feed ID as sponsor address together with the "0x" prefix. - const sponsorAddress = ethers.utils.getAddress(hashedDapiNameOrDataFeedId.slice(0, 42)); - const sponsorWallet = ethers.Wallet.fromMnemonic( + const sponsorAddress = ethers.getAddress(hashedDapiNameOrDataFeedId.slice(0, 42)); + // NOTE: Be sure not to use "ethers.Wallet.fromPhrase(sponsorWalletMnemonic).derivePath" because that produces a + // different result. + const sponsorWallet = ethers.HDNodeWallet.fromPhrase( sponsorWalletMnemonic, + undefined, `m/44'/60'/0'/${deriveWalletPathFromSponsorAddress(sponsorAddress)}` ); @@ -808,7 +861,7 @@

Active data feeds

}; setInterval(async () => { - const provider = new ethers.providers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(rpcUrl); const airseekerRegistry = new ethers.Contract(airseekerRegistryAddress, airseekerRegistryAbi, provider); const activeDataFeedCount = await airseekerRegistry.activeDataFeedCount(); @@ -851,20 +904,16 @@

Active data feeds

} console.info('Signed datas', signedDatas); // For debugging purposes. - const newBeaconSetValue = calculateMedian( - signedDatas.map((signedData) => ethers.BigNumber.from(signedData.value)) - ); - const newBeaconSetTimestamp = calculateMedian( - signedDatas.map((signedData) => ethers.BigNumber.from(signedData.timestamp)) - ).toNumber(); + const newBeaconSetValue = calculateMedian(signedDatas.map((signedData) => BigInt(signedData.value))); + const newBeaconSetTimestamp = calculateMedian(signedDatas.map((signedData) => BigInt(signedData.timestamp))); - const deviationPercentage = calculateUpdateInPercentage(dataFeedValue, newBeaconSetValue).toNumber() / 1e6; - const deviationThresholdPercentage = deviationThresholdInPercentage.toNumber() / 1e6; + const deviationPercentage = Number(calculateUpdateInPercentage(dataFeedValue, newBeaconSetValue)) / 1e6; + const deviationThresholdPercentage = Number(deviationThresholdInPercentage) / 1e6; const sponsorWallet = deriveSponsorWallet(airseekerMnemonic, dapiName ?? dataFeed.decodedDataFeed.dataFeedId); const dataFeedInfo = { dapiName: dapiName, dataFeedId: dataFeed.decodedDataFeed.dataFeedId, - decodedDapiName: ethers.utils.parseBytes32String(dapiName), + decodedDapiName: ethers.decodeBytes32String(dapiName), dataFeedValue: dataFeed.dataFeedValue, offChainValue: { value: newBeaconSetValue.toString(), @@ -874,7 +923,7 @@

Active data feeds

deviationPercentage > deviationThresholdPercentage ? `${deviationPercentage}` : deviationPercentage, deviationThresholdPercentage: deviationThresholdPercentage, sponsorWalletAddress: sponsorWallet.address, - sponsorWalletBalance: ethers.utils.formatEther(await provider.getBalance(sponsorWallet.address)), + sponsorWalletBalance: ethers.formatEther(await provider.getBalance(sponsorWallet.address)), }; newActiveDataFeedsHtml += JSON.stringify(dataFeedInfo, null, 2) + '\n\n'; diff --git a/local-test-configuration/scripts/initialize-chain.ts b/local-test-configuration/scripts/initialize-chain.ts index 336b8b41..b602ce9c 100644 --- a/local-test-configuration/scripts/initialize-chain.ts +++ b/local-test-configuration/scripts/initialize-chain.ts @@ -2,17 +2,17 @@ import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import { encode } from '@api3/airnode-abi'; -import { - AccessControlRegistry__factory as AccessControlRegistryFactory, - Api3ServerV1__factory as Api3ServerV1Factory, -} from '@api3/airnode-protocol-v1'; import dotenv from 'dotenv'; -import type { ContractTransaction, Signer } from 'ethers'; +import type { ContractTransactionResponse, Signer } from 'ethers'; import { ethers } from 'ethers'; import { zip } from 'lodash'; import { interpolateSecrets, parseSecrets } from '../../src/config/utils'; -import { AirseekerRegistry__factory as AirseekerRegistryFactory } from '../../src/typechain-types'; +import { + AirseekerRegistry__factory as AirseekerRegistryFactory, + AccessControlRegistry__factory as AccessControlRegistryFactory, + Api3ServerV1__factory as Api3ServerV1Factory, +} from '../../src/typechain-types'; import { deriveBeaconId, deriveSponsorWallet, encodeDapiName } from '../../src/utils'; interface RawBeaconData { @@ -29,26 +29,26 @@ const deriveBeaconData = (beaconData: RawBeaconData) => { const { endpointId, parameters: parameters, airnodeAddress } = beaconData; const encodedParameters = encode(parameters); - const templateId = ethers.utils.solidityKeccak256(['bytes32', 'bytes'], [endpointId, encodedParameters]); + const templateId = ethers.solidityPackedKeccak256(['bytes32', 'bytes'], [endpointId, encodedParameters]); const beaconId = deriveBeaconId(airnodeAddress, templateId)!; return { endpointId, templateId, encodedParameters, beaconId, parameters, airnodeAddress }; }; export const deriveRootRole = (managerAddress: string) => { - return ethers.utils.solidityKeccak256(['address'], [managerAddress]); + return ethers.solidityPackedKeccak256(['address'], [managerAddress]); }; export const deriveRole = (adminRole: string, roleDescription: string) => { - return ethers.utils.solidityKeccak256( + return ethers.solidityPackedKeccak256( ['bytes32', 'bytes32'], - [adminRole, ethers.utils.solidityKeccak256(['string'], [roleDescription])] + [adminRole, ethers.solidityPackedKeccak256(['string'], [roleDescription])] ); }; // NOTE: This function is not used by the initialization script, but you can use it after finishing Airseeker test on a // public testnet to refund test ETH from sponsor wallets to the funder wallet. -export const refundFunder = async (funderWallet: ethers.Wallet) => { +export const refundFunder = async (funderWallet: ethers.HDNodeWallet) => { const airseekerSecrets = dotenv.parse(readFileSync(join(__dirname, `/../airseeker`, 'secrets.env'), 'utf8')); const airseekerWalletMnemonic = airseekerSecrets.SPONSOR_WALLET_MNEMONIC; if (!airseekerWalletMnemonic) throw new Error('SPONSOR_WALLET_MNEMONIC not found in Airseeker secrets'); @@ -58,20 +58,23 @@ export const refundFunder = async (funderWallet: ethers.Wallet) => { const dapiName = encodeDapiName(beaconSetName); const sponsorWallet = deriveSponsorWallet(airseekerWalletMnemonic, dapiName).connect(funderWallet.provider); - const sponsorWalletBalance = await funderWallet.provider.getBalance(sponsorWallet.address); - console.info('Sponsor wallet balance:', ethers.utils.formatEther(sponsorWalletBalance.toString())); - - const gasPrice = await sponsorWallet.provider.getGasPrice(); - const gasFee = gasPrice.mul(ethers.BigNumber.from(21_000)); - if (sponsorWalletBalance.sub(gasFee).lt(ethers.constants.Zero)) { + const sponsorWalletBalance = await funderWallet.provider!.getBalance(sponsorWallet.address); + console.info('Sponsor wallet balance:', ethers.formatEther(sponsorWalletBalance.toString())); + + const feeData = await sponsorWallet.provider!.getFeeData(); + const { gasPrice } = feeData; + // We assume the legacy gas price will always exist. See: + // https://api3workspace.slack.com/archives/C05TQPT7PNJ/p1699098552350519 + const gasFee = gasPrice! * BigInt(21_000); + if (sponsorWalletBalance < gasFee) { console.info('Sponsor wallet balance is too low, skipping refund'); continue; } const tx = await sponsorWallet.sendTransaction({ to: funderWallet.address, gasPrice, - gasLimit: ethers.BigNumber.from(21_000), - value: sponsorWalletBalance.sub(gasFee), + gasLimit: BigInt(21_000), + value: sponsorWalletBalance - gasFee, }); await tx.wait(); @@ -97,7 +100,7 @@ const loadAirnodeFeedConfig = (airnodeFeedDir: 'airnode-feed-1' | 'airnode-feed- const getBeaconSetNames = () => { const airnodeFeed = loadAirnodeFeedConfig('airnode-feed-1'); - const airnodeFeedWallet = ethers.Wallet.fromMnemonic(airnodeFeed.nodeSettings.airnodeWalletMnemonic); + const airnodeFeedWallet = ethers.Wallet.fromPhrase(airnodeFeed.nodeSettings.airnodeWalletMnemonic); const airnodeFeedBeacons = Object.values(airnodeFeed.templates).map((template: any) => { return deriveBeaconData({ ...template, airnodeAddress: airnodeFeedWallet.address }); }); @@ -105,7 +108,7 @@ const getBeaconSetNames = () => { return airnodeFeedBeacons.map((beacon) => beacon.parameters[0]!.value); }; -export const fundAirseekerSponsorWallet = async (funderWallet: ethers.Wallet) => { +export const fundAirseekerSponsorWallet = async (funderWallet: ethers.HDNodeWallet) => { const airseekerSecrets = dotenv.parse(readFileSync(join(__dirname, `/../airseeker`, 'secrets.env'), 'utf8')); const airseekerWalletMnemonic = airseekerSecrets.SPONSOR_WALLET_MNEMONIC; if (!airseekerWalletMnemonic) throw new Error('SPONSOR_WALLET_MNEMONIC not found in Airseeker secrets'); @@ -115,12 +118,12 @@ export const fundAirseekerSponsorWallet = async (funderWallet: ethers.Wallet) => const dapiName = encodeDapiName(beaconSetName); const sponsorWallet = deriveSponsorWallet(airseekerWalletMnemonic, dapiName); - const sponsorWalletBalance = await funderWallet.provider.getBalance(sponsorWallet.address); - console.info('Sponsor wallet balance:', ethers.utils.formatEther(sponsorWalletBalance.toString())); + const sponsorWalletBalance = await funderWallet.provider!.getBalance(sponsorWallet.address); + console.info('Sponsor wallet balance:', ethers.formatEther(sponsorWalletBalance.toString())); const tx = await funderWallet.sendTransaction({ to: sponsorWallet.address, - value: ethers.utils.parseEther('1'), + value: ethers.parseEther('1'), }); await tx.wait(); @@ -131,7 +134,7 @@ export const fundAirseekerSponsorWallet = async (funderWallet: ethers.Wallet) => } }; -export const deploy = async (funderWallet: ethers.Wallet, provider: ethers.providers.JsonRpcProvider) => { +export const deploy = async (funderWallet: ethers.HDNodeWallet, provider: ethers.JsonRpcProvider) => { // NOTE: It is OK if all of these roles are done via the funder wallet. const deployerAndManager = funderWallet, randomPerson = funderWallet; @@ -139,29 +142,29 @@ export const deploy = async (funderWallet: ethers.Wallet, provider: ethers.provi // Deploy contracts const accessControlRegistryFactory = new AccessControlRegistryFactory(deployerAndManager as Signer); const accessControlRegistry = await accessControlRegistryFactory.deploy(); - await accessControlRegistry.deployTransaction.wait(); + await accessControlRegistry.waitForDeployment(); const api3ServerV1Factory = new Api3ServerV1Factory(deployerAndManager as Signer); const api3ServerV1AdminRoleDescription = 'Api3ServerV1 admin'; const api3ServerV1 = await api3ServerV1Factory.deploy( - accessControlRegistry.address, + accessControlRegistry.getAddress(), api3ServerV1AdminRoleDescription, deployerAndManager.address ); - await api3ServerV1.deployTransaction.wait(); + await api3ServerV1.waitForDeployment(); const airseekerRegistryFactory = new AirseekerRegistryFactory(deployerAndManager as Signer); const airseekerRegistry = await airseekerRegistryFactory.deploy( await (deployerAndManager as Signer).getAddress(), - api3ServerV1.address + api3ServerV1.getAddress() ); - await airseekerRegistry.deployTransaction.wait(); + await airseekerRegistry.waitForDeployment(); // Create templates const airnodeFeed1 = loadAirnodeFeedConfig('airnode-feed-1'); const airnodeFeed2 = loadAirnodeFeedConfig('airnode-feed-2'); - const airnodeFeed1Wallet = ethers.Wallet.fromMnemonic(airnodeFeed1.nodeSettings.airnodeWalletMnemonic).connect( + const airnodeFeed1Wallet = ethers.Wallet.fromPhrase(airnodeFeed1.nodeSettings.airnodeWalletMnemonic).connect( provider ); - const airnodeFeed2Wallet = ethers.Wallet.fromMnemonic(airnodeFeed2.nodeSettings.airnodeWalletMnemonic).connect( + const airnodeFeed2Wallet = ethers.Wallet.fromPhrase(airnodeFeed2.nodeSettings.airnodeWalletMnemonic).connect( provider ); const airnodeFeed1Beacons = Object.values(airnodeFeed1.templates).map((template: any) => { @@ -176,7 +179,7 @@ export const deploy = async (funderWallet: ethers.Wallet, provider: ethers.provi [airnodeFeed1Wallet.address, joinUrl(airnodeFeed1.signedApis[0].url, 'default')], // NOTE: Airnode feed pushes to the "/" of the signed API, but we need to query it additional path. [airnodeFeed2Wallet.address, joinUrl(airnodeFeed2.signedApis[0].url, 'default')], // NOTE: Airnode feed pushes to the "/" of the signed API, but we need to query it additional path. ] as const; - let tx: ContractTransaction; + let tx: ContractTransactionResponse; for (const [airnode, url] of apiTreeValues) { tx = await airseekerRegistry.connect(deployerAndManager).setSignedApiUrl(airnode, url); await tx.wait(); @@ -186,8 +189,8 @@ export const deploy = async (funderWallet: ethers.Wallet, provider: ethers.provi airnodes: [airnodeFeed1Beacon!.airnodeAddress, airnodeFeed2Beacon!.airnodeAddress], templateIds: [airnodeFeed1Beacon!.templateId, airnodeFeed1Beacon!.templateId], dapiName: encodeDapiName(airnodeFeed1Beacon!.parameters[0]!.value), - beaconSetId: ethers.utils.keccak256( - ethers.utils.defaultAbiCoder.encode( + beaconSetId: ethers.keccak256( + ethers.AbiCoder.defaultAbiCoder().encode( ['bytes32[]'], [[airnodeFeed1Beacon!.beaconId, airnodeFeed2Beacon!.beaconId]] ) @@ -197,25 +200,25 @@ export const deploy = async (funderWallet: ethers.Wallet, provider: ethers.provi for (const dapiInfo of dapiInfos) { const { airnodes, templateIds, dapiName, beaconSetId } = dapiInfo; - const encodedBeaconSetData = ethers.utils.defaultAbiCoder.encode( + const encodedBeaconSetData = ethers.AbiCoder.defaultAbiCoder().encode( ['address[]', 'bytes32[]'], [airnodes, templateIds] ); tx = await airseekerRegistry.connect(randomPerson).registerDataFeed(encodedBeaconSetData); await tx.wait(); const HUNDRED_PERCENT = 1e8; - const deviationThresholdInPercentage = ethers.BigNumber.from(HUNDRED_PERCENT / 100); // 1% - const deviationReference = ethers.constants.Zero; // Not used in Airseeker V1 - const heartbeatInterval = ethers.BigNumber.from(86_400); // 24 hrs + const deviationThresholdInPercentage = BigInt(HUNDRED_PERCENT / 100); // 1% + const deviationReference = 0n; // Not used in Airseeker V2 + const heartbeatInterval = BigInt(86_400); // 24 hrs tx = await api3ServerV1.connect(deployerAndManager).setDapiName(dapiName, beaconSetId); await tx.wait(); - await airseekerRegistry.connect(deployerAndManager).setDapiNameToBeActivated(dapiName); + tx = await airseekerRegistry.connect(deployerAndManager).setDapiNameToBeActivated(dapiName); await tx.wait(); tx = await airseekerRegistry .connect(deployerAndManager) .setDapiNameUpdateParameters( dapiName, - ethers.utils.defaultAbiCoder.encode( + ethers.AbiCoder.defaultAbiCoder().encode( ['uint256', 'uint256', 'uint256'], [deviationThresholdInPercentage, deviationReference, heartbeatInterval] ) @@ -242,17 +245,21 @@ async function main() { if (!process.env.FUNDER_MNEMONIC) throw new Error('FUNDER_MNEMONIC not found'); if (!process.env.PROVIDER_URL) throw new Error('PROVIDER_URL not found'); - const provider = new ethers.providers.StaticJsonRpcProvider(process.env.PROVIDER_URL); - const funderWallet = ethers.Wallet.fromMnemonic(process.env.FUNDER_MNEMONIC).connect(provider); + const provider = new ethers.JsonRpcProvider(process.env.PROVIDER_URL, undefined, { + staticNetwork: true, + polling: true, + pollingInterval: 100, + }); + const funderWallet = ethers.Wallet.fromPhrase(process.env.FUNDER_MNEMONIC).connect(provider); await refundFunder(funderWallet); - const balance = await funderWallet.getBalance(); - console.info('Funder balance:', ethers.utils.formatEther(balance.toString())); + const balance = await provider.getBalance(funderWallet.address); + console.info('Funder balance:', ethers.formatEther(balance.toString())); console.info(); const { api3ServerV1, airseekerRegistry } = await deploy(funderWallet, provider); - console.info('Api3ServerV1 deployed at:', api3ServerV1.address); - console.info('AirseekerRegistry deployed at:', airseekerRegistry.address); + console.info('Api3ServerV1 deployed at:', await api3ServerV1.getAddress()); + console.info('AirseekerRegistry deployed at:', await airseekerRegistry.getAddress()); console.info(); await fundAirseekerSponsorWallet(funderWallet); diff --git a/local-test-configuration/signed-api-1/secrets.example.env b/local-test-configuration/signed-api-1/secrets.example.env new file mode 100644 index 00000000..e69de29b diff --git a/local-test-configuration/signed-api-1/signed-api.json b/local-test-configuration/signed-api-1/signed-api.json index 8525a1b4..2aef83ea 100644 --- a/local-test-configuration/signed-api-1/signed-api.json +++ b/local-test-configuration/signed-api-1/signed-api.json @@ -8,5 +8,5 @@ ], "allowedAirnodes": [{ "address": "0xaC0653E412acAE526Da3a33af0135205A34E21AF", "authTokens": null }], "stage": "signed-api-1", - "version": "0.2.0" + "version": "0.4.0" } diff --git a/local-test-configuration/signed-api-2/secrets.example.env b/local-test-configuration/signed-api-2/secrets.example.env new file mode 100644 index 00000000..e69de29b diff --git a/local-test-configuration/signed-api-2/signed-api.json b/local-test-configuration/signed-api-2/signed-api.json index e4494414..caa49ec4 100644 --- a/local-test-configuration/signed-api-2/signed-api.json +++ b/local-test-configuration/signed-api-2/signed-api.json @@ -8,5 +8,5 @@ ], "allowedAirnodes": [{ "address": "0x2Bf0dddA8Daa1C3C0Fae9e85866807A1C2D601B7", "authTokens": null }], "stage": "signed-api-2", - "version": "0.2.0" + "version": "0.4.0" } diff --git a/package.json b/package.json index b1422e67..4d5b00d9 100644 --- a/package.json +++ b/package.json @@ -29,14 +29,24 @@ "test": "jest --config=jest-unit.config.js", "tsc": "tsc --project ." }, + "dependencies": { + "@api3/airnode-protocol-v1": "^3.1.0", + "@api3/commons": "^0.6.0", + "@api3/promise-utils": "^0.4.0", + "api3-contracts": "^0.0.3", + "axios": "^1.6.5", + "dotenv": "^16.3.1", + "ethers": "^6.10.0", + "immer": "^10.0.3", + "lodash": "^4.17.21", + "zod": "^3.22.4" + }, "devDependencies": { "@api3/airnode-abi": "^0.13.0", "@api3/ois": "^2.3.1", - "@nomicfoundation/hardhat-network-helpers": "^1.0.10", - "@nomicfoundation/hardhat-toolbox": "^2.0.2", - "@nomiclabs/hardhat-ethers": "^2.2.3", + "@nomicfoundation/hardhat-ethers": "^3.0.5", + "@nomicfoundation/hardhat-toolbox": "^4.0.0", "@openzeppelin/contracts": "4.9.5", - "@typechain/ethers-v5": "^11.1.2", "@typechain/hardhat": "^9.1.0", "@types/jest": "^29.5.11", "@types/lodash": "^4.14.202", @@ -46,7 +56,6 @@ "eslint": "^8.56.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-jest": "^27.6.1", - "ethers": "^5.7.2", "hardhat": "^2.19.4", "hardhat-deploy": "^0.11.45", "husky": "^8.0.3", @@ -58,19 +67,5 @@ "ts-node": "^10.9.2", "typechain": "^8.3.2", "typescript": "^5.3.3" - }, - "dependencies": { - "@api3/airnode-protocol-v1": "^3.1.0", - "@api3/commons": "^0.6.0", - "@api3/dapi-management": "github:api3dao/dapi-management#bb-edition", - "@api3/promise-utils": "^0.4.0", - "@ethersproject/abi": "^5.7.0", - "@ethersproject/providers": "^5.7.2", - "axios": "^1.6.5", - "dotenv": "^16.3.1", - "ethers": "^5.7.2", - "immer": "^10.0.3", - "lodash": "^4.17.21", - "zod": "^3.22.4" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8cf74fbc..d6173af4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,18 +11,12 @@ dependencies: '@api3/commons': specifier: ^0.6.0 version: 0.6.0(eslint@8.56.0)(jest@29.7.0)(typescript@5.3.3) - '@api3/dapi-management': - specifier: github:api3dao/dapi-management#bb-edition - version: github.com/api3dao/dapi-management/15ca2812b88d374b89d5991efd1f5e465e7ff523(@babel/core@7.23.0) '@api3/promise-utils': specifier: ^0.4.0 version: 0.4.0 - '@ethersproject/abi': - specifier: ^5.7.0 - version: 5.7.0 - '@ethersproject/providers': - specifier: ^5.7.2 - version: 5.7.2 + api3-contracts: + specifier: ^0.0.3 + version: 0.0.3 axios: specifier: ^1.6.5 version: 1.6.5 @@ -30,8 +24,8 @@ dependencies: specifier: ^16.3.1 version: 16.3.1 ethers: - specifier: ^5.7.2 - version: 5.7.2 + specifier: ^6.10.0 + version: 6.10.0 immer: specifier: ^10.0.3 version: 10.0.3 @@ -49,24 +43,18 @@ devDependencies: '@api3/ois': specifier: ^2.3.1 version: 2.3.1 - '@nomicfoundation/hardhat-network-helpers': - specifier: ^1.0.10 - version: 1.0.10(hardhat@2.19.4) + '@nomicfoundation/hardhat-ethers': + specifier: ^3.0.5 + version: 3.0.5(ethers@6.10.0)(hardhat@2.19.4) '@nomicfoundation/hardhat-toolbox': - specifier: ^2.0.2 - version: 2.0.2(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(@nomicfoundation/hardhat-chai-matchers@1.0.6)(@nomicfoundation/hardhat-network-helpers@1.0.10)(@nomiclabs/hardhat-ethers@2.2.3)(@nomiclabs/hardhat-etherscan@3.1.8)(@typechain/ethers-v5@11.1.2)(@typechain/hardhat@9.1.0)(@types/chai@4.3.9)(@types/mocha@10.0.3)(@types/node@20.10.7)(chai@4.3.10)(ethers@5.7.2)(hardhat-gas-reporter@1.0.9)(hardhat@2.19.4)(solidity-coverage@0.8.5)(ts-node@10.9.2)(typechain@8.3.2)(typescript@5.3.3) - '@nomiclabs/hardhat-ethers': - specifier: ^2.2.3 - version: 2.2.3(ethers@5.7.2)(hardhat@2.19.4) + specifier: ^4.0.0 + version: 4.0.0(@nomicfoundation/hardhat-chai-matchers@2.0.3)(@nomicfoundation/hardhat-ethers@3.0.5)(@nomicfoundation/hardhat-network-helpers@1.0.10)(@nomicfoundation/hardhat-verify@2.0.3)(@typechain/ethers-v6@0.5.1)(@typechain/hardhat@9.1.0)(@types/chai@4.3.9)(@types/mocha@10.0.3)(@types/node@20.10.7)(chai@4.3.10)(ethers@6.10.0)(hardhat-gas-reporter@1.0.9)(hardhat@2.19.4)(solidity-coverage@0.8.5)(ts-node@10.9.2)(typechain@8.3.2)(typescript@5.3.3) '@openzeppelin/contracts': specifier: 4.9.5 version: 4.9.5 - '@typechain/ethers-v5': - specifier: ^11.1.2 - version: 11.1.2(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typechain@8.3.2)(typescript@5.3.3) '@typechain/hardhat': specifier: ^9.1.0 - version: 9.1.0(@typechain/ethers-v6@0.5.1)(ethers@5.7.2)(hardhat@2.19.4)(typechain@8.3.2) + version: 9.1.0(@typechain/ethers-v6@0.5.1)(ethers@6.10.0)(hardhat@2.19.4)(typechain@8.3.2) '@types/jest': specifier: ^29.5.11 version: 29.5.11 @@ -133,7 +121,6 @@ packages: /@adraffy/ens-normalize@1.10.0: resolution: {integrity: sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==} - dev: false /@ampproject/remapping@2.2.1: resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} @@ -142,16 +129,6 @@ packages: '@jridgewell/gen-mapping': 0.3.3 '@jridgewell/trace-mapping': 0.3.19 - /@api3/airnode-abi@0.10.1: - resolution: {integrity: sha512-vWpIlzbHZAQgEbBRXpb+NCq6kgq8awHzmqJlRvhhlBuaz5CR5DOjfUDwcqcVpdHIaJGwjhTp+nnAxxZk6raBTA==} - dependencies: - ethers: 5.7.2 - lodash: 4.17.21 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - dev: false - /@api3/airnode-abi@0.13.0: resolution: {integrity: sha512-qWe3w3cGvpwUhONlLOkx8uMJ17m1qbC6b4g7xf9Jo0GZ6/AObcrBnsmPL7/JAlAtbvsTg25wS4o/TZaEA+gpYg==} dependencies: @@ -162,12 +139,6 @@ packages: - utf-8-validate dev: true - /@api3/airnode-protocol-v1@2.10.0: - resolution: {integrity: sha512-5pXI9OUXeyM2IbVYrkW48Eymt7qGT5nfBU8GR2XZsRE8dCRZTIIfp+xdAMH+OFm62GGnUDz9e1uTiAHkRWrtKA==} - dependencies: - '@openzeppelin/contracts': 4.8.2 - dev: false - /@api3/airnode-protocol-v1@3.1.0: resolution: {integrity: sha512-3XOYdQqQPWXC2xSz1cCnk6kzkeugE6NdxgU25C43dzkJStvY+jALyxmS9SBtsUCaq4KA7hYVxci5gkDi7rZbAQ==} dependencies: @@ -911,34 +882,6 @@ packages: engines: {node: '>=14'} dev: true - /@floating-ui/core@1.5.2: - resolution: {integrity: sha512-Ii3MrfY/GAIN3OhXNzpCKaLxHQfJF9qvwq/kEJYdqDxeIHa01K8sldugal6TmeeXl+WMvhv9cnVzUTaFFJF09A==} - dependencies: - '@floating-ui/utils': 0.1.6 - dev: false - - /@floating-ui/dom@1.5.3: - resolution: {integrity: sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==} - dependencies: - '@floating-ui/core': 1.5.2 - '@floating-ui/utils': 0.1.6 - dev: false - - /@floating-ui/react-dom@2.0.4(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - dependencies: - '@floating-ui/dom': 1.5.3 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@floating-ui/utils@0.1.6: - resolution: {integrity: sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==} - dev: false - /@humanwhocodes/config-array@0.11.13: resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==} engines: {node: '>=10.10.0'} @@ -1224,97 +1167,12 @@ packages: tweetnacl-util: 0.15.1 dev: true - /@next/env@13.5.6: - resolution: {integrity: sha512-Yac/bV5sBGkkEXmAX5FWPS9Mmo2rthrOPRQQNfycJPkjUAUclomCPH7QFVCDQ4Mp2k2K1SSM6m0zrxYrOwtFQw==} - dev: false - /@next/eslint-plugin-next@13.5.6: resolution: {integrity: sha512-ng7pU/DDsxPgT6ZPvuprxrkeew3XaRf4LAT4FabaEO/hAbvVx4P7wqnqdbTdDn1kgTvsI4tpIgT4Awn/m0bGbg==} dependencies: glob: 7.1.7 dev: false - /@next/swc-darwin-arm64@13.5.6: - resolution: {integrity: sha512-5nvXMzKtZfvcu4BhtV0KH1oGv4XEW+B+jOfmBdpFI3C7FrB/MfujRpWYSBBO64+qbW8pkZiSyQv9eiwnn5VIQA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - - /@next/swc-darwin-x64@13.5.6: - resolution: {integrity: sha512-6cgBfxg98oOCSr4BckWjLLgiVwlL3vlLj8hXg2b+nDgm4bC/qVXXLfpLB9FHdoDu4057hzywbxKvmYGmi7yUzA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - - /@next/swc-linux-arm64-gnu@13.5.6: - resolution: {integrity: sha512-txagBbj1e1w47YQjcKgSU4rRVQ7uF29YpnlHV5xuVUsgCUf2FmyfJ3CPjZUvpIeXCJAoMCFAoGnbtX86BK7+sg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@next/swc-linux-arm64-musl@13.5.6: - resolution: {integrity: sha512-cGd+H8amifT86ZldVJtAKDxUqeFyLWW+v2NlBULnLAdWsiuuN8TuhVBt8ZNpCqcAuoruoSWynvMWixTFcroq+Q==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@next/swc-linux-x64-gnu@13.5.6: - resolution: {integrity: sha512-Mc2b4xiIWKXIhBy2NBTwOxGD3nHLmq4keFk+d4/WL5fMsB8XdJRdtUlL87SqVCTSaf1BRuQQf1HvXZcy+rq3Nw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@next/swc-linux-x64-musl@13.5.6: - resolution: {integrity: sha512-CFHvP9Qz98NruJiUnCe61O6GveKKHpJLloXbDSWRhqhkJdZD2zU5hG+gtVJR//tyW897izuHpM6Gtf6+sNgJPQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@next/swc-win32-arm64-msvc@13.5.6: - resolution: {integrity: sha512-aFv1ejfkbS7PUa1qVPwzDHjQWQtknzAZWGTKYIAaS4NMtBlk3VyA6AYn593pqNanlicewqyl2jUhQAaFV/qXsg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /@next/swc-win32-ia32-msvc@13.5.6: - resolution: {integrity: sha512-XqqpHgEIlBHvzwG8sp/JXMFkLAfGLqkbVsyN+/Ih1mR8INb6YCc2x/Mbwi6hsAgUnqQztz8cvEbHJUbSl7RHDg==} - engines: {node: '>= 10'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /@next/swc-win32-x64-msvc@13.5.6: - resolution: {integrity: sha512-Cqfe1YmOS7k+5mGu92nl5ULkzpKuxJrP3+4AEuPmrpFZ3BHxTY3TnHmU1On3bFmFFs6FbTcdF58CCUProGpIGQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: false - optional: true - /@noble/curves@1.1.0: resolution: {integrity: sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==} dependencies: @@ -1325,10 +1183,10 @@ packages: resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==} dependencies: '@noble/hashes': 1.3.2 - dev: false /@noble/hashes@1.2.0: resolution: {integrity: sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==} + dev: true /@noble/hashes@1.3.1: resolution: {integrity: sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==} @@ -1341,16 +1199,7 @@ packages: /@noble/secp256k1@1.7.1: resolution: {integrity: sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==} - - /@nodary/utilities@0.7.1: - resolution: {integrity: sha512-o7X57MlHReTiXQ8ER+SNcg1e99USgw9QR9PU3JyifrXCInYaquFFhgDeQPtN0Is+tSIH6EC1UmhV74oi1TZHCg==} - dependencies: - '@api3/airnode-abi': 0.10.1 - ethers: 6.9.1 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - dev: false + dev: true /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -1528,25 +1377,38 @@ packages: - utf-8-validate dev: true - /@nomicfoundation/hardhat-chai-matchers@1.0.6(@nomiclabs/hardhat-ethers@2.2.3)(chai@4.3.10)(ethers@5.7.2)(hardhat@2.19.4): - resolution: {integrity: sha512-f5ZMNmabZeZegEfuxn/0kW+mm7+yV7VNDxLpMOMGXWFJ2l/Ct3QShujzDRF9cOkK9Ui/hbDeOWGZqyQALDXVCQ==} + /@nomicfoundation/hardhat-chai-matchers@2.0.3(@nomicfoundation/hardhat-ethers@3.0.5)(chai@4.3.10)(ethers@6.10.0)(hardhat@2.19.4): + resolution: {integrity: sha512-A40s7EAK4Acr8UP1Yudgi9GGD9Cca/K3LHt3DzmRIje14lBfHtg9atGQ7qK56vdPcTwKmeaGn30FzxMUfPGEMw==} peerDependencies: - '@nomiclabs/hardhat-ethers': ^2.0.0 + '@nomicfoundation/hardhat-ethers': ^3.0.0 chai: ^4.2.0 - ethers: ^5.0.0 + ethers: ^6.1.0 hardhat: ^2.9.4 dependencies: - '@ethersproject/abi': 5.7.0 - '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.19.4) + '@nomicfoundation/hardhat-ethers': 3.0.5(ethers@6.10.0)(hardhat@2.19.4) '@types/chai-as-promised': 7.1.7 chai: 4.3.10 chai-as-promised: 7.1.1(chai@4.3.10) deep-eql: 4.1.3 - ethers: 5.7.2 + ethers: 6.10.0 hardhat: 2.19.4(ts-node@10.9.2)(typescript@5.3.3) ordinal: 1.0.3 dev: true + /@nomicfoundation/hardhat-ethers@3.0.5(ethers@6.10.0)(hardhat@2.19.4): + resolution: {integrity: sha512-RNFe8OtbZK6Ila9kIlHp0+S80/0Bu/3p41HUpaRIoHLm6X3WekTd83vob3rE54Duufu1edCiBDxspBzi2rxHHw==} + peerDependencies: + ethers: ^6.1.0 + hardhat: ^2.0.0 + dependencies: + debug: 4.3.4(supports-color@5.5.0) + ethers: 6.10.0 + hardhat: 2.19.4(ts-node@10.9.2)(typescript@5.3.3) + lodash.isequal: 4.5.0 + transitivePeerDependencies: + - supports-color + dev: true + /@nomicfoundation/hardhat-network-helpers@1.0.10(hardhat@2.19.4): resolution: {integrity: sha512-R35/BMBlx7tWN5V6d/8/19QCwEmIdbnA4ZrsuXgvs8i2qFx5i7h6mH5pBS4Pwi4WigLH+upl6faYusrNPuzMrQ==} peerDependencies: @@ -1556,42 +1418,38 @@ packages: hardhat: 2.19.4(ts-node@10.9.2)(typescript@5.3.3) dev: true - /@nomicfoundation/hardhat-toolbox@2.0.2(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(@nomicfoundation/hardhat-chai-matchers@1.0.6)(@nomicfoundation/hardhat-network-helpers@1.0.10)(@nomiclabs/hardhat-ethers@2.2.3)(@nomiclabs/hardhat-etherscan@3.1.8)(@typechain/ethers-v5@11.1.2)(@typechain/hardhat@9.1.0)(@types/chai@4.3.9)(@types/mocha@10.0.3)(@types/node@20.10.7)(chai@4.3.10)(ethers@5.7.2)(hardhat-gas-reporter@1.0.9)(hardhat@2.19.4)(solidity-coverage@0.8.5)(ts-node@10.9.2)(typechain@8.3.2)(typescript@5.3.3): - resolution: {integrity: sha512-vnN1AzxbvpSx9pfdRHbUzTRIXpMLPXnUlkW855VaDk6N1pwRaQ2gNzEmFAABk4lWf11E00PKwFd/q27HuwYrYg==} + /@nomicfoundation/hardhat-toolbox@4.0.0(@nomicfoundation/hardhat-chai-matchers@2.0.3)(@nomicfoundation/hardhat-ethers@3.0.5)(@nomicfoundation/hardhat-network-helpers@1.0.10)(@nomicfoundation/hardhat-verify@2.0.3)(@typechain/ethers-v6@0.5.1)(@typechain/hardhat@9.1.0)(@types/chai@4.3.9)(@types/mocha@10.0.3)(@types/node@20.10.7)(chai@4.3.10)(ethers@6.10.0)(hardhat-gas-reporter@1.0.9)(hardhat@2.19.4)(solidity-coverage@0.8.5)(ts-node@10.9.2)(typechain@8.3.2)(typescript@5.3.3): + resolution: {integrity: sha512-jhcWHp0aHaL0aDYj8IJl80v4SZXWMS1A2XxXa1CA6pBiFfJKuZinCkO6wb+POAt0LIfXB3gA3AgdcOccrcwBwA==} peerDependencies: - '@ethersproject/abi': ^5.4.7 - '@ethersproject/providers': ^5.4.7 - '@nomicfoundation/hardhat-chai-matchers': ^1.0.0 + '@nomicfoundation/hardhat-chai-matchers': ^2.0.0 + '@nomicfoundation/hardhat-ethers': ^3.0.0 '@nomicfoundation/hardhat-network-helpers': ^1.0.0 - '@nomiclabs/hardhat-ethers': ^2.0.0 - '@nomiclabs/hardhat-etherscan': ^3.0.0 - '@typechain/ethers-v5': ^10.1.0 - '@typechain/hardhat': ^6.1.2 + '@nomicfoundation/hardhat-verify': ^2.0.0 + '@typechain/ethers-v6': ^0.5.0 + '@typechain/hardhat': ^9.0.0 '@types/chai': ^4.2.0 '@types/mocha': '>=9.1.0' - '@types/node': '>=12.0.0' + '@types/node': '>=16.0.0' chai: ^4.2.0 - ethers: ^5.4.7 + ethers: ^6.4.0 hardhat: ^2.11.0 hardhat-gas-reporter: ^1.0.8 solidity-coverage: ^0.8.1 ts-node: '>=8.0.0' - typechain: ^8.1.0 + typechain: ^8.3.0 typescript: '>=4.5.0' dependencies: - '@ethersproject/abi': 5.7.0 - '@ethersproject/providers': 5.7.2 - '@nomicfoundation/hardhat-chai-matchers': 1.0.6(@nomiclabs/hardhat-ethers@2.2.3)(chai@4.3.10)(ethers@5.7.2)(hardhat@2.19.4) + '@nomicfoundation/hardhat-chai-matchers': 2.0.3(@nomicfoundation/hardhat-ethers@3.0.5)(chai@4.3.10)(ethers@6.10.0)(hardhat@2.19.4) + '@nomicfoundation/hardhat-ethers': 3.0.5(ethers@6.10.0)(hardhat@2.19.4) '@nomicfoundation/hardhat-network-helpers': 1.0.10(hardhat@2.19.4) - '@nomiclabs/hardhat-ethers': 2.2.3(ethers@5.7.2)(hardhat@2.19.4) - '@nomiclabs/hardhat-etherscan': 3.1.8(hardhat@2.19.4) - '@typechain/ethers-v5': 11.1.2(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typechain@8.3.2)(typescript@5.3.3) - '@typechain/hardhat': 9.1.0(@typechain/ethers-v6@0.5.1)(ethers@5.7.2)(hardhat@2.19.4)(typechain@8.3.2) + '@nomicfoundation/hardhat-verify': 2.0.3(hardhat@2.19.4) + '@typechain/ethers-v6': 0.5.1(ethers@6.10.0)(typechain@8.3.2)(typescript@5.3.3) + '@typechain/hardhat': 9.1.0(@typechain/ethers-v6@0.5.1)(ethers@6.10.0)(hardhat@2.19.4)(typechain@8.3.2) '@types/chai': 4.3.9 '@types/mocha': 10.0.3 '@types/node': 20.10.7 chai: 4.3.10 - ethers: 5.7.2 + ethers: 6.10.0 hardhat: 2.19.4(ts-node@10.9.2)(typescript@5.3.3) hardhat-gas-reporter: 1.0.9(hardhat@2.19.4) solidity-coverage: 0.8.5(hardhat@2.19.4) @@ -1600,6 +1458,25 @@ packages: typescript: 5.3.3 dev: true + /@nomicfoundation/hardhat-verify@2.0.3(hardhat@2.19.4): + resolution: {integrity: sha512-ESbRu9by53wu6VvgwtMtm108RSmuNsVqXtzg061D+/4R7jaWh/Wl/8ve+p6SdDX7vA1Z3L02hDO1Q3BY4luLXQ==} + peerDependencies: + hardhat: ^2.0.4 + dependencies: + '@ethersproject/abi': 5.7.0 + '@ethersproject/address': 5.7.0 + cbor: 8.1.0 + chalk: 2.4.2 + debug: 4.3.4(supports-color@5.5.0) + hardhat: 2.19.4(ts-node@10.9.2)(typescript@5.3.3) + lodash.clonedeep: 4.5.0 + semver: 6.3.1 + table: 6.8.1 + undici: 5.27.0 + transitivePeerDependencies: + - supports-color + dev: true + /@nomicfoundation/solidity-analyzer-darwin-arm64@0.1.1: resolution: {integrity: sha512-KcTodaQw8ivDZyF+D76FokN/HdpgGpfjc/gFCImdLUyqB6eSWVaZPazMbeAjmfhx3R0zm/NYVzxwAokFKgrc0w==} engines: {node: '>= 10'} @@ -1686,671 +1563,40 @@ packages: engines: {node: '>= 10'} cpu: [x64] os: [win32] - requiresBuild: true - dev: true - optional: true - - /@nomicfoundation/solidity-analyzer@0.1.1: - resolution: {integrity: sha512-1LMtXj1puAxyFusBgUIy5pZk3073cNXYnXUpuNKFghHbIit/xZgbk0AokpUADbNm3gyD6bFWl3LRFh3dhVdREg==} - engines: {node: '>= 12'} - optionalDependencies: - '@nomicfoundation/solidity-analyzer-darwin-arm64': 0.1.1 - '@nomicfoundation/solidity-analyzer-darwin-x64': 0.1.1 - '@nomicfoundation/solidity-analyzer-freebsd-x64': 0.1.1 - '@nomicfoundation/solidity-analyzer-linux-arm64-gnu': 0.1.1 - '@nomicfoundation/solidity-analyzer-linux-arm64-musl': 0.1.1 - '@nomicfoundation/solidity-analyzer-linux-x64-gnu': 0.1.1 - '@nomicfoundation/solidity-analyzer-linux-x64-musl': 0.1.1 - '@nomicfoundation/solidity-analyzer-win32-arm64-msvc': 0.1.1 - '@nomicfoundation/solidity-analyzer-win32-ia32-msvc': 0.1.1 - '@nomicfoundation/solidity-analyzer-win32-x64-msvc': 0.1.1 - dev: true - - /@nomiclabs/hardhat-ethers@2.2.3(ethers@5.7.2)(hardhat@2.19.4): - resolution: {integrity: sha512-YhzPdzb612X591FOe68q+qXVXGG2ANZRvDo0RRUtimev85rCrAlv/TLMEZw5c+kq9AbzocLTVX/h2jVIFPL9Xg==} - peerDependencies: - ethers: ^5.0.0 - hardhat: ^2.0.0 - dependencies: - ethers: 5.7.2 - hardhat: 2.19.4(ts-node@10.9.2)(typescript@5.3.3) - dev: true - - /@nomiclabs/hardhat-etherscan@3.1.8(hardhat@2.19.4): - resolution: {integrity: sha512-v5F6IzQhrsjHh6kQz4uNrym49brK9K5bYCq2zQZ729RYRaifI9hHbtmK+KkIVevfhut7huQFEQ77JLRMAzWYjQ==} - deprecated: The @nomiclabs/hardhat-etherscan package is deprecated, please use @nomicfoundation/hardhat-verify instead - peerDependencies: - hardhat: ^2.0.4 - dependencies: - '@ethersproject/abi': 5.7.0 - '@ethersproject/address': 5.7.0 - cbor: 8.1.0 - chalk: 2.4.2 - debug: 4.3.4(supports-color@5.5.0) - fs-extra: 7.0.1 - hardhat: 2.19.4(ts-node@10.9.2)(typescript@5.3.3) - lodash: 4.17.21 - semver: 6.3.1 - table: 6.8.1 - undici: 5.27.0 - transitivePeerDependencies: - - supports-color - dev: true - - /@openzeppelin/contracts@4.8.2: - resolution: {integrity: sha512-kEUOgPQszC0fSYWpbh2kT94ltOJwj1qfT2DWo+zVttmGmf97JZ99LspePNaeeaLhCImaHVeBbjaQFZQn7+Zc5g==} - dev: false - - /@openzeppelin/contracts@4.9.3: - resolution: {integrity: sha512-He3LieZ1pP2TNt5JbkPA4PNT9WC3gOTOlDcFGJW4Le4QKqwmiNJCRt44APfxMxvq7OugU/cqYuPcSBzOw38DAg==} - dev: false - - /@openzeppelin/contracts@4.9.5: - resolution: {integrity: sha512-ZK+W5mVhRppff9BE6YdR8CC52C8zAvsVAiWhEtQ5+oNxFE6h1WdeWo+FJSF8KKvtxxVYZ7MTP/5KoVpAU3aSWg==} - dev: true - - /@openzeppelin/merkle-tree@1.0.5: - resolution: {integrity: sha512-JkwG2ysdHeIphrScNxYagPy6jZeNONgDRyqU6lbFgE8HKCZFSkcP8r6AjZs+3HZk4uRNV0kNBBzuWhKQ3YV7Kw==} - dependencies: - '@ethersproject/abi': 5.7.0 - ethereum-cryptography: 1.2.0 - dev: false - - /@pkgjs/parseargs@0.11.0: - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - requiresBuild: true - dev: true - optional: true - - /@radix-ui/number@1.0.1: - resolution: {integrity: sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==} - dependencies: - '@babel/runtime': 7.23.2 - dev: false - - /@radix-ui/primitive@1.0.1: - resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==} - dependencies: - '@babel/runtime': 7.23.2 - dev: false - - /@radix-ui/react-arrow@1.0.3(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@radix-ui/react-collection@1.0.3(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - '@radix-ui/react-compose-refs': 1.0.1(react@18.2.0) - '@radix-ui/react-context': 1.0.1(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-slot': 1.0.2(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@radix-ui/react-compose-refs@1.0.1(react@18.2.0): - resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - react: 18.2.0 - dev: false - - /@radix-ui/react-context@1.0.1(react@18.2.0): - resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - react: 18.2.0 - dev: false - - /@radix-ui/react-direction@1.0.1(react@18.2.0): - resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - react: 18.2.0 - dev: false - - /@radix-ui/react-dismissable-layer@1.0.5(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.0.1(react@18.2.0) - '@radix-ui/react-use-escape-keydown': 1.0.3(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@radix-ui/react-focus-guards@1.0.1(react@18.2.0): - resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - react: 18.2.0 - dev: false - - /@radix-ui/react-focus-scope@1.0.4(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - '@radix-ui/react-compose-refs': 1.0.1(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.0.1(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@radix-ui/react-id@1.0.1(react@18.2.0): - resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - '@radix-ui/react-use-layout-effect': 1.0.1(react@18.2.0) - react: 18.2.0 - dev: false - - /@radix-ui/react-popover@1.0.7(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(react@18.2.0) - '@radix-ui/react-context': 1.0.1(react@18.2.0) - '@radix-ui/react-dismissable-layer': 1.0.5(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-focus-guards': 1.0.1(react@18.2.0) - '@radix-ui/react-focus-scope': 1.0.4(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-id': 1.0.1(react@18.2.0) - '@radix-ui/react-popper': 1.1.3(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-portal': 1.0.4(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-presence': 1.0.1(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-slot': 1.0.2(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.0.1(react@18.2.0) - aria-hidden: 1.2.3 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-remove-scroll: 2.5.5(react@18.2.0) - dev: false - - /@radix-ui/react-popper@1.1.3(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - '@floating-ui/react-dom': 2.0.4(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-arrow': 1.0.3(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-compose-refs': 1.0.1(react@18.2.0) - '@radix-ui/react-context': 1.0.1(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.0.1(react@18.2.0) - '@radix-ui/react-use-layout-effect': 1.0.1(react@18.2.0) - '@radix-ui/react-use-rect': 1.0.1(react@18.2.0) - '@radix-ui/react-use-size': 1.0.1(react@18.2.0) - '@radix-ui/rect': 1.0.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@radix-ui/react-portal@1.0.4(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@radix-ui/react-presence@1.0.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - '@radix-ui/react-compose-refs': 1.0.1(react@18.2.0) - '@radix-ui/react-use-layout-effect': 1.0.1(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@radix-ui/react-primitive@1.0.3(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - '@radix-ui/react-slot': 1.0.2(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@radix-ui/react-roving-focus@1.0.4(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-collection': 1.0.3(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-compose-refs': 1.0.1(react@18.2.0) - '@radix-ui/react-context': 1.0.1(react@18.2.0) - '@radix-ui/react-direction': 1.0.1(react@18.2.0) - '@radix-ui/react-id': 1.0.1(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.0.1(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.0.1(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@radix-ui/react-select@2.0.0(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-RH5b7af4oHtkcHS7pG6Sgv5rk5Wxa7XI8W5gvB1N/yiuDGZxko1ynvOiVhFM7Cis2A8zxF9bTOUVbRDzPepe6w==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - '@radix-ui/number': 1.0.1 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-collection': 1.0.3(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-compose-refs': 1.0.1(react@18.2.0) - '@radix-ui/react-context': 1.0.1(react@18.2.0) - '@radix-ui/react-direction': 1.0.1(react@18.2.0) - '@radix-ui/react-dismissable-layer': 1.0.5(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-focus-guards': 1.0.1(react@18.2.0) - '@radix-ui/react-focus-scope': 1.0.4(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-id': 1.0.1(react@18.2.0) - '@radix-ui/react-popper': 1.1.3(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-portal': 1.0.4(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-slot': 1.0.2(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.0.1(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.0.1(react@18.2.0) - '@radix-ui/react-use-layout-effect': 1.0.1(react@18.2.0) - '@radix-ui/react-use-previous': 1.0.1(react@18.2.0) - '@radix-ui/react-visually-hidden': 1.0.3(react-dom@18.2.0)(react@18.2.0) - aria-hidden: 1.2.3 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-remove-scroll: 2.5.5(react@18.2.0) - dev: false - - /@radix-ui/react-slot@1.0.2(react@18.2.0): - resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - '@radix-ui/react-compose-refs': 1.0.1(react@18.2.0) - react: 18.2.0 - dev: false - - /@radix-ui/react-tabs@1.0.4(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-egZfYY/+wRNCflXNHx+dePvnz9FbmssDTJBtgRfDY7e8SE5oIo3Py2eCB1ckAbh1Q7cQ/6yJZThJ++sgbxibog==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-context': 1.0.1(react@18.2.0) - '@radix-ui/react-direction': 1.0.1(react@18.2.0) - '@radix-ui/react-id': 1.0.1(react@18.2.0) - '@radix-ui/react-presence': 1.0.1(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-roving-focus': 1.0.4(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.0.1(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@radix-ui/react-toast@1.1.5(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-fRLn227WHIBRSzuRzGJ8W+5YALxofH23y0MlPLddaIpLpCDqdE0NZlS2NRQDRiptfxDeeCjgFIpexB1/zkxDlw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-collection': 1.0.3(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-compose-refs': 1.0.1(react@18.2.0) - '@radix-ui/react-context': 1.0.1(react@18.2.0) - '@radix-ui/react-dismissable-layer': 1.0.5(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-portal': 1.0.4(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-presence': 1.0.1(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.0.1(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.0.1(react@18.2.0) - '@radix-ui/react-use-layout-effect': 1.0.1(react@18.2.0) - '@radix-ui/react-visually-hidden': 1.0.3(react-dom@18.2.0)(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@radix-ui/react-toggle@1.0.3(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-Pkqg3+Bc98ftZGsl60CLANXQBBQ4W3mTFS9EJvNxKMZ7magklKV69/id1mlAlOFDDfHvlCms0fx8fA4CMKDJHg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.0.1(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@radix-ui/react-tooltip@1.0.7(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(react@18.2.0) - '@radix-ui/react-context': 1.0.1(react@18.2.0) - '@radix-ui/react-dismissable-layer': 1.0.5(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-id': 1.0.1(react@18.2.0) - '@radix-ui/react-popper': 1.1.3(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-portal': 1.0.4(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-presence': 1.0.1(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-slot': 1.0.2(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.0.1(react@18.2.0) - '@radix-ui/react-visually-hidden': 1.0.3(react-dom@18.2.0)(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@radix-ui/react-use-callback-ref@1.0.1(react@18.2.0): - resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - react: 18.2.0 - dev: false - - /@radix-ui/react-use-controllable-state@1.0.1(react@18.2.0): - resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - '@radix-ui/react-use-callback-ref': 1.0.1(react@18.2.0) - react: 18.2.0 - dev: false - - /@radix-ui/react-use-escape-keydown@1.0.3(react@18.2.0): - resolution: {integrity: sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - '@radix-ui/react-use-callback-ref': 1.0.1(react@18.2.0) - react: 18.2.0 - dev: false - - /@radix-ui/react-use-layout-effect@1.0.1(react@18.2.0): - resolution: {integrity: sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - react: 18.2.0 - dev: false - - /@radix-ui/react-use-previous@1.0.1(react@18.2.0): - resolution: {integrity: sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - react: 18.2.0 - dev: false + requiresBuild: true + dev: true + optional: true - /@radix-ui/react-use-rect@1.0.1(react@18.2.0): - resolution: {integrity: sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - '@radix-ui/rect': 1.0.1 - react: 18.2.0 - dev: false + /@nomicfoundation/solidity-analyzer@0.1.1: + resolution: {integrity: sha512-1LMtXj1puAxyFusBgUIy5pZk3073cNXYnXUpuNKFghHbIit/xZgbk0AokpUADbNm3gyD6bFWl3LRFh3dhVdREg==} + engines: {node: '>= 12'} + optionalDependencies: + '@nomicfoundation/solidity-analyzer-darwin-arm64': 0.1.1 + '@nomicfoundation/solidity-analyzer-darwin-x64': 0.1.1 + '@nomicfoundation/solidity-analyzer-freebsd-x64': 0.1.1 + '@nomicfoundation/solidity-analyzer-linux-arm64-gnu': 0.1.1 + '@nomicfoundation/solidity-analyzer-linux-arm64-musl': 0.1.1 + '@nomicfoundation/solidity-analyzer-linux-x64-gnu': 0.1.1 + '@nomicfoundation/solidity-analyzer-linux-x64-musl': 0.1.1 + '@nomicfoundation/solidity-analyzer-win32-arm64-msvc': 0.1.1 + '@nomicfoundation/solidity-analyzer-win32-ia32-msvc': 0.1.1 + '@nomicfoundation/solidity-analyzer-win32-x64-msvc': 0.1.1 + dev: true - /@radix-ui/react-use-size@1.0.1(react@18.2.0): - resolution: {integrity: sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - '@radix-ui/react-use-layout-effect': 1.0.1(react@18.2.0) - react: 18.2.0 + /@openzeppelin/contracts@4.8.2: + resolution: {integrity: sha512-kEUOgPQszC0fSYWpbh2kT94ltOJwj1qfT2DWo+zVttmGmf97JZ99LspePNaeeaLhCImaHVeBbjaQFZQn7+Zc5g==} dev: false - /@radix-ui/react-visually-hidden@1.0.3(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.2 - '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false + /@openzeppelin/contracts@4.9.5: + resolution: {integrity: sha512-ZK+W5mVhRppff9BE6YdR8CC52C8zAvsVAiWhEtQ5+oNxFE6h1WdeWo+FJSF8KKvtxxVYZ7MTP/5KoVpAU3aSWg==} + dev: true - /@radix-ui/rect@1.0.1: - resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==} - dependencies: - '@babel/runtime': 7.23.2 - dev: false + /@pkgjs/parseargs@0.11.0: + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + requiresBuild: true + dev: true + optional: true /@rushstack/eslint-patch@1.5.1: resolution: {integrity: sha512-6i/8UoL0P5y4leBIGzvkZdS85RDMG9y1ihZzmTZQ5LdHUYmZ7pKFoj8X0236s3lusPs1Fa5HTQUpwI+UfTcmeA==} @@ -2358,6 +1604,7 @@ packages: /@scure/base@1.1.3: resolution: {integrity: sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q==} + dev: true /@scure/bip32@1.1.5: resolution: {integrity: sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==} @@ -2365,6 +1612,7 @@ packages: '@noble/hashes': 1.2.0 '@noble/secp256k1': 1.7.1 '@scure/base': 1.1.3 + dev: true /@scure/bip32@1.3.1: resolution: {integrity: sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==} @@ -2379,6 +1627,7 @@ packages: dependencies: '@noble/hashes': 1.2.0 '@scure/base': 1.1.3 + dev: true /@scure/bip39@1.2.1: resolution: {integrity: sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==} @@ -2482,12 +1731,6 @@ packages: antlr4ts: 0.5.0-alpha.4 dev: true - /@swc/helpers@0.5.2: - resolution: {integrity: sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==} - dependencies: - tslib: 2.6.2 - dev: false - /@tsconfig/node10@1.0.9: resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} @@ -2500,39 +1743,21 @@ packages: /@tsconfig/node16@1.0.4: resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - /@typechain/ethers-v5@11.1.2(@ethersproject/abi@5.7.0)(@ethersproject/providers@5.7.2)(ethers@5.7.2)(typechain@8.3.2)(typescript@5.3.3): - resolution: {integrity: sha512-ID6pqWkao54EuUQa0P5RgjvfA3MYqxUQKpbGKERbsjBW5Ra7EIXvbMlPp2pcP5IAdUkyMCFYsP2SN5q7mPdLDQ==} - peerDependencies: - '@ethersproject/abi': ^5.0.0 - '@ethersproject/providers': ^5.0.0 - ethers: ^5.1.3 - typechain: ^8.3.2 - typescript: '>=4.3.0' - dependencies: - '@ethersproject/abi': 5.7.0 - '@ethersproject/providers': 5.7.2 - ethers: 5.7.2 - lodash: 4.17.21 - ts-essentials: 7.0.3(typescript@5.3.3) - typechain: 8.3.2(typescript@5.3.3) - typescript: 5.3.3 - dev: true - - /@typechain/ethers-v6@0.5.1(ethers@5.7.2)(typechain@8.3.2)(typescript@5.3.3): + /@typechain/ethers-v6@0.5.1(ethers@6.10.0)(typechain@8.3.2)(typescript@5.3.3): resolution: {integrity: sha512-F+GklO8jBWlsaVV+9oHaPh5NJdd6rAKN4tklGfInX1Q7h0xPgVLP39Jl3eCulPB5qexI71ZFHwbljx4ZXNfouA==} peerDependencies: ethers: 6.x typechain: ^8.3.2 typescript: '>=4.7.0' dependencies: - ethers: 5.7.2 + ethers: 6.10.0 lodash: 4.17.21 ts-essentials: 7.0.3(typescript@5.3.3) typechain: 8.3.2(typescript@5.3.3) typescript: 5.3.3 dev: true - /@typechain/hardhat@9.1.0(@typechain/ethers-v6@0.5.1)(ethers@5.7.2)(hardhat@2.19.4)(typechain@8.3.2): + /@typechain/hardhat@9.1.0(@typechain/ethers-v6@0.5.1)(ethers@6.10.0)(hardhat@2.19.4)(typechain@8.3.2): resolution: {integrity: sha512-mtaUlzLlkqTlfPwB3FORdejqBskSnh+Jl8AIJGjXNAQfRQ4ofHADPl1+oU7Z3pAJzmZbUXII8MhOLQltcHgKnA==} peerDependencies: '@typechain/ethers-v6': ^0.5.1 @@ -2540,8 +1765,8 @@ packages: hardhat: ^2.9.9 typechain: ^8.3.2 dependencies: - '@typechain/ethers-v6': 0.5.1(ethers@5.7.2)(typechain@8.3.2)(typescript@5.3.3) - ethers: 5.7.2 + '@typechain/ethers-v6': 0.5.1(ethers@6.10.0)(typechain@8.3.2)(typescript@5.3.3) + ethers: 6.10.0 fs-extra: 9.1.0 hardhat: 2.19.4(ts-node@10.9.2)(typescript@5.3.3) typechain: 8.3.2(typescript@5.3.3) @@ -2666,7 +1891,6 @@ packages: /@types/node@18.15.13: resolution: {integrity: sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==} - dev: false /@types/node@20.10.7: resolution: {integrity: sha512-fRbIKb8C/Y2lXxB5eVMj4IU7xpdox0Lh8bUPEdtLysaylsml1hOOx1+STloRs/B9nf7C6kPRmmg/V7aQW7usNg==} @@ -2997,6 +2221,7 @@ packages: /abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + dev: true /abstract-level@1.0.3: resolution: {integrity: sha512-t6jv+xHy+VYwc4xqZMn2Pa9DjcdzvzZmQGRjTFc8spIbRGHgBrEKbPq+rYXc7CCo0lxgYvSgKVg9qZAhpVQSjA==} @@ -3042,7 +2267,6 @@ packages: /aes-js@4.0.0-beta.5: resolution: {integrity: sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==} - dev: false /agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} @@ -3147,6 +2371,16 @@ packages: normalize-path: 3.0.0 picomatch: 2.3.1 + /api3-contracts@0.0.3: + resolution: {integrity: sha512-mj+BGAvVAjIu4WNpfNrkqwjHZ591Q+YcWroMUa0jnM5sUqx1VxvQF+rOnl2zp//aacERABe/EzsDOV3r5EN9Sw==} + engines: {node: ^20.11.0} + dependencies: + ethers: 6.10.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + /arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -3158,13 +2392,6 @@ packages: /argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - /aria-hidden@1.2.3: - resolution: {integrity: sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==} - engines: {node: '>=10'} - dependencies: - tslib: 2.6.2 - dev: false - /aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} dependencies: @@ -3533,13 +2760,6 @@ packages: engines: {node: '>=6'} dev: false - /busboy@1.6.0: - resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} - engines: {node: '>=10.16.0'} - dependencies: - streamsearch: 1.1.0 - dev: false - /bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -3676,12 +2896,6 @@ packages: /cjs-module-lexer@1.2.3: resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} - /class-variance-authority@0.7.0: - resolution: {integrity: sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==} - dependencies: - clsx: 2.0.0 - dev: false - /classic-level@1.3.0: resolution: {integrity: sha512-iwFAJQYtqRTRM0F6L8h4JCt00ZSGdOyqh7yVrhhjrOpFhmBjNlRUey64MCiyo6UmQHMJ+No3c81nujPv+n9yrg==} engines: {node: '>=12'} @@ -3716,10 +2930,6 @@ packages: colors: 1.4.0 dev: true - /client-only@0.0.1: - resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} - dev: false - /cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} dependencies: @@ -3736,16 +2946,6 @@ packages: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - /clsx@2.0.0: - resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==} - engines: {node: '>=6'} - dev: false - - /clsx@2.1.0: - resolution: {integrity: sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==} - engines: {node: '>=6'} - dev: false - /co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} @@ -4031,10 +3231,6 @@ packages: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} - /detect-node-es@1.1.0: - resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} - dev: false - /detect-port@1.5.1: resolution: {integrity: sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ==} hasBin: true @@ -4049,16 +3245,6 @@ packages: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - /diff2html@3.4.46: - resolution: {integrity: sha512-z1SkrH7jDLfmsOYgwJmGiDTdzsbpw7p4vx084kNzeYqER+/Kqp8+v/L7lMsIWpFzlX3NaJDJna280Y/HFqel+Q==} - engines: {node: '>=12'} - dependencies: - diff: 5.1.0 - hogan.js: 3.0.2 - optionalDependencies: - highlight.js: 11.8.0 - dev: false - /diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -4068,11 +3254,6 @@ packages: engines: {node: '>=0.3.1'} dev: true - /diff@5.1.0: - resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==} - engines: {node: '>=0.3.1'} - dev: false - /difflib@0.2.4: resolution: {integrity: sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w==} dependencies: @@ -4761,6 +3942,7 @@ packages: '@noble/secp256k1': 1.7.1 '@scure/bip32': 1.1.5 '@scure/bip39': 1.1.1 + dev: true /ethereum-cryptography@2.1.2: resolution: {integrity: sha512-Z5Ba0T0ImZ8fqXrJbpHcbpAvIswRte2wGNR/KePnu8GbbvgJ47lMxT/ZZPG6i9Jaht4azPDop4HaM00J0J59ug==} @@ -4838,8 +4020,8 @@ packages: - bufferutil - utf-8-validate - /ethers@6.9.1: - resolution: {integrity: sha512-kuV8fGd4/8Gj7wkurbsuUsm1DCG6N5gKGYdw3fnWG/7QGknhy1xtHD7kbkCWQAcbAYmzLCLqCPedS3FYncFkKQ==} + /ethers@6.10.0: + resolution: {integrity: sha512-nMNwYHzs6V1FR3Y4cdfxSQmNgZsRj1RiTU25JwvnJLmyzw9z3SKxNc2XKDuiXXo/v9ds5Mp9m6HBabgYQQ26tA==} engines: {node: '>=14.0.0'} dependencies: '@adraffy/ens-normalize': 1.10.0 @@ -4852,7 +4034,6 @@ packages: transitivePeerDependencies: - bufferutil - utf-8-validate - dev: false /ethjs-unit@0.1.6: resolution: {integrity: sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw==} @@ -4869,6 +4050,7 @@ packages: is-hex-prefixed: 1.0.0 strip-hex-prefix: 1.0.0 dev: true + bundledDependencies: false /evp_bytestokey@1.0.3: resolution: {integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==} @@ -5149,11 +4331,6 @@ packages: has-symbols: 1.0.3 hasown: 2.0.0 - /get-nonce@1.0.1: - resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} - engines: {node: '>=6'} - dev: false - /get-package-type@0.1.0: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} @@ -5199,10 +4376,6 @@ packages: dependencies: is-glob: 4.0.3 - /glob-to-regexp@0.4.1: - resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - dev: false - /glob@10.3.10: resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} engines: {node: '>=16 || 14 >=14.17'} @@ -5519,13 +4692,6 @@ packages: resolution: {integrity: sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==} dev: true - /highlight.js@11.8.0: - resolution: {integrity: sha512-MedQhoqVdr0U6SSnWPzfiadUcDHfN/Wzq25AkXiQv9oiOO/sG0S7XkvpFIqWBl9Yq1UYyYOOVORs5UW2XlPyzg==} - engines: {node: '>=12.0.0'} - requiresBuild: true - dev: false - optional: true - /hmac-drbg@1.0.1: resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} dependencies: @@ -5533,14 +4699,6 @@ packages: minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 - /hogan.js@3.0.2: - resolution: {integrity: sha512-RqGs4wavGYJWE07t35JQccByczmNUXQT0E12ZYV1VKYu5UiAU9lsos/yBAcf840+zrUQQxgVduCR5/B8nNtibg==} - hasBin: true - dependencies: - mkdirp: 0.3.0 - nopt: 1.0.10 - dev: false - /hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} dev: false @@ -5676,12 +4834,6 @@ packages: engines: {node: '>= 0.10'} dev: true - /invariant@2.2.4: - resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} - dependencies: - loose-envify: 1.4.0 - dev: false - /io-ts@1.10.4: resolution: {integrity: sha512-b23PteSnYXSONJ6JQXRAlvJhuw8KOtkqa87W4wDtvMrud/DTJd5X+NpOOI+O/zZwVq6v0VLAaJ+1EDViKEuN9g==} dependencies: @@ -6575,6 +5727,14 @@ packages: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} dev: true + /lodash.clonedeep@4.5.0: + resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + dev: true + + /lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + dev: true + /lodash.memoize@4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} dev: true @@ -6642,14 +5802,6 @@ packages: resolution: {integrity: sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==} dev: true - /lucide-react@0.288.0(react@18.2.0): - resolution: {integrity: sha512-ikhb/9LOkq9orPoLV9lLC4UYyoXQycBhIgH7H59ahOkk0mkcAqkD52m84RXedE/qVqZHW8rEJquInT4xGmsNqw==} - peerDependencies: - react: ^16.5.1 || ^17.0.0 || ^18.0.0 - dependencies: - react: 18.2.0 - dev: false - /make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} @@ -6768,11 +5920,6 @@ packages: engines: {node: '>=16 || 14 >=14.17'} dev: true - /mkdirp@0.3.0: - resolution: {integrity: sha512-OHsdUcVAQ6pOtg5JYWpCBo9W/GySVuwvP9hueRMW7UqshC0tbfzLv8wjySTPm3tfUZ/21CE9E1pJagOA91Pxew==} - deprecated: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.) - dev: false - /mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true @@ -6845,12 +5992,6 @@ packages: hasBin: true dev: true - /nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - dev: false - /napi-macros@2.2.2: resolution: {integrity: sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g==} dev: true @@ -6862,45 +6003,6 @@ packages: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} dev: true - /next@13.5.6(@babel/core@7.23.0)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-Y2wTcTbO4WwEsVb4A8VSnOsG1I9ok+h74q0ZdxkwM3EODqrs4pasq7O0iUxbcS9VtWMicG7f3+HAj0r1+NtKSw==} - engines: {node: '>=16.14.0'} - hasBin: true - peerDependencies: - '@opentelemetry/api': ^1.1.0 - react: ^18.2.0 - react-dom: ^18.2.0 - sass: ^1.3.0 - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - sass: - optional: true - dependencies: - '@next/env': 13.5.6 - '@swc/helpers': 0.5.2 - busboy: 1.6.0 - caniuse-lite: 1.0.30001543 - postcss: 8.4.31 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - styled-jsx: 5.1.1(@babel/core@7.23.0)(react@18.2.0) - watchpack: 2.4.0 - optionalDependencies: - '@next/swc-darwin-arm64': 13.5.6 - '@next/swc-darwin-x64': 13.5.6 - '@next/swc-linux-arm64-gnu': 13.5.6 - '@next/swc-linux-arm64-musl': 13.5.6 - '@next/swc-linux-x64-gnu': 13.5.6 - '@next/swc-linux-x64-musl': 13.5.6 - '@next/swc-win32-arm64-msvc': 13.5.6 - '@next/swc-win32-ia32-msvc': 13.5.6 - '@next/swc-win32-x64-msvc': 13.5.6 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - dev: false - /node-addon-api@2.0.2: resolution: {integrity: sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==} dev: true @@ -6949,6 +6051,7 @@ packages: hasBin: true dependencies: abbrev: 1.1.1 + dev: true /nopt@3.0.6: resolution: {integrity: sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==} @@ -7244,15 +6347,6 @@ packages: engines: {node: '>=4'} dev: false - /postcss@8.4.31: - resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} - engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.7 - picocolors: 1.0.0 - source-map-js: 1.0.2 - dev: false - /prelude-ls@1.1.2: resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} engines: {node: '>= 0.8.0'} @@ -7347,16 +6441,6 @@ packages: unpipe: 1.0.0 dev: true - /react-dom@18.2.0(react@18.2.0): - resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} - peerDependencies: - react: ^18.2.0 - dependencies: - loose-envify: 1.4.0 - react: 18.2.0 - scheduler: 0.23.0 - dev: false - /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} dev: false @@ -7364,62 +6448,6 @@ packages: /react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} - /react-remove-scroll-bar@2.3.4(react@18.2.0): - resolution: {integrity: sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - react: 18.2.0 - react-style-singleton: 2.2.1(react@18.2.0) - tslib: 2.6.2 - dev: false - - /react-remove-scroll@2.5.5(react@18.2.0): - resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - react: 18.2.0 - react-remove-scroll-bar: 2.3.4(react@18.2.0) - react-style-singleton: 2.2.1(react@18.2.0) - tslib: 2.6.2 - use-callback-ref: 1.3.1(react@18.2.0) - use-sidecar: 1.1.2(react@18.2.0) - dev: false - - /react-style-singleton@2.2.1(react@18.2.0): - resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - get-nonce: 1.0.1 - invariant: 2.2.4 - react: 18.2.0 - tslib: 2.6.2 - dev: false - - /react@18.2.0: - resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} - engines: {node: '>=0.10.0'} - dependencies: - loose-envify: 1.4.0 - dev: false - /read-pkg-up@7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} engines: {node: '>=8'} @@ -7703,12 +6731,6 @@ packages: wordwrap: 1.0.0 dev: true - /scheduler@0.23.0: - resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} - dependencies: - loose-envify: 1.4.0 - dev: false - /scrypt-js@3.0.1: resolution: {integrity: sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==} @@ -7896,11 +6918,6 @@ packages: - supports-color dev: true - /source-map-js@1.0.2: - resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} - engines: {node: '>=0.10.0'} - dev: false - /source-map-support@0.5.13: resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} dependencies: @@ -7974,11 +6991,6 @@ packages: engines: {node: '>= 0.8'} dev: true - /streamsearch@1.1.0: - resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} - engines: {node: '>=10.0.0'} - dev: false - /string-format@2.0.0: resolution: {integrity: sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA==} dev: true @@ -8112,24 +7124,6 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - /styled-jsx@5.1.1(@babel/core@7.23.0)(react@18.2.0): - resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} - engines: {node: '>= 12.0.0'} - peerDependencies: - '@babel/core': '*' - babel-plugin-macros: '*' - react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' - peerDependenciesMeta: - '@babel/core': - optional: true - babel-plugin-macros: - optional: true - dependencies: - '@babel/core': 7.23.0 - client-only: 0.0.1 - react: 18.2.0 - dev: false - /supports-color@3.2.3: resolution: {integrity: sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==} engines: {node: '>=0.8.0'} @@ -8195,16 +7189,6 @@ packages: strip-ansi: 6.0.1 dev: true - /tailwind-merge@1.14.0: - resolution: {integrity: sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==} - dev: false - - /tailwindcss-animate@1.0.7: - resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} - peerDependencies: - tailwindcss: '>=3.0.0 || insiders' - dev: false - /tapable@2.2.1: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} @@ -8381,7 +7365,6 @@ packages: /tslib@2.4.0: resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} - dev: false /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} @@ -8582,35 +7565,6 @@ packages: dependencies: punycode: 2.3.0 - /use-callback-ref@1.3.1(react@18.2.0): - resolution: {integrity: sha512-Lg4Vx1XZQauB42Hw3kK7JM6yjVjgFmFC5/Ab797s79aARomD2nEErc4mCgM8EZrARLmmbWpi5DGCadmK50DcAQ==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - react: 18.2.0 - tslib: 2.6.2 - dev: false - - /use-sidecar@1.1.2(react@18.2.0): - resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - detect-node-es: 1.1.0 - react: 18.2.0 - tslib: 2.6.2 - dev: false - /utf8@3.0.0: resolution: {integrity: sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==} dev: true @@ -8646,14 +7600,6 @@ packages: dependencies: makeerror: 1.0.12 - /watchpack@2.4.0: - resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==} - engines: {node: '>=10.13.0'} - dependencies: - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.11 - dev: false - /web3-utils@1.10.3: resolution: {integrity: sha512-OqcUrEE16fDBbGoQtZXWdavsPzbGIDc5v3VrRTZ0XrIpefC/viZ1ZU9bGEemazyS0catk/3rkOOxpzTfY+XsyQ==} engines: {node: '>=8.0.0'} @@ -8846,7 +7792,6 @@ packages: optional: true utf-8-validate: optional: true - dev: false /y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} @@ -8921,45 +7866,3 @@ packages: /zod@3.22.4: resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} - - github.com/api3dao/dapi-management/15ca2812b88d374b89d5991efd1f5e465e7ff523(@babel/core@7.23.0): - resolution: {tarball: https://codeload.github.com/api3dao/dapi-management/tar.gz/15ca2812b88d374b89d5991efd1f5e465e7ff523} - id: github.com/api3dao/dapi-management/15ca2812b88d374b89d5991efd1f5e465e7ff523 - name: '@api3/dapi-management' - version: 0.1.0 - dependencies: - '@api3/airnode-protocol-v1': 2.10.0 - '@api3/promise-utils': 0.4.0 - '@nodary/utilities': 0.7.1 - '@openzeppelin/contracts': 4.9.3 - '@openzeppelin/merkle-tree': 1.0.5 - '@radix-ui/react-popover': 1.0.7(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-select': 2.0.0(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-slot': 1.0.2(react@18.2.0) - '@radix-ui/react-tabs': 1.0.4(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-toast': 1.1.5(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-toggle': 1.0.3(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-tooltip': 1.0.7(react-dom@18.2.0)(react@18.2.0) - class-variance-authority: 0.7.0 - clsx: 2.1.0 - diff2html: 3.4.46 - ethers: 5.7.2 - lodash: 4.17.21 - lucide-react: 0.288.0(react@18.2.0) - next: 13.5.6(@babel/core@7.23.0)(react-dom@18.2.0)(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - tailwind-merge: 1.14.0 - tailwindcss-animate: 1.0.7 - zod: 3.22.4 - transitivePeerDependencies: - - '@babel/core' - - '@opentelemetry/api' - - '@types/react' - - '@types/react-dom' - - babel-plugin-macros - - bufferutil - - sass - - tailwindcss - - utf-8-validate - dev: false diff --git a/renovate.json b/renovate.json index 22f5ac6c..d3a5676d 100644 --- a/renovate.json +++ b/renovate.json @@ -7,7 +7,7 @@ "enabled": false }, { - "matchPackageNames": ["ethers", "@openzeppelin/contracts", "@nomicfoundation/hardhat-toolbox"], + "matchPackageNames": ["@openzeppelin/contracts"], "matchUpdateTypes": ["major"], "enabled": false }, diff --git a/src/config/schema.ts b/src/config/schema.ts index 9b6c55f4..e1ac9989 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -137,7 +137,9 @@ export type DeviationThresholdCoefficient = z.infer ethers.utils.isValidMnemonic(mnemonic), 'Invalid mnemonic'), + sponsorWalletMnemonic: z + .string() + .refine((mnemonic) => ethers.Mnemonic.isValidMnemonic(mnemonic), 'Invalid mnemonic'), chains: chainsSchema, signedDataFetchInterval: z.number().positive(), deviationThresholdCoefficient: deviationThresholdCoefficientSchema, diff --git a/src/constants.ts b/src/constants.ts index 1129457e..48487b94 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,5 +1,3 @@ -import { ethers } from 'ethers'; - export const HTTP_SIGNED_DATA_API_TIMEOUT_MULTIPLIER = 0.9; export const RPC_PROVIDER_TIMEOUT_MS = 120_000; @@ -9,7 +7,7 @@ export const HUNDRED_PERCENT = 1e8; export const AIRSEEKER_PROTOCOL_ID = '5'; // From: https://github.com/api3dao/airnode/blob/ef16c54f33d455a1794e7886242567fc47ee14ef/packages/airnode-protocol/src/index.ts#L46 // Solidity type(int224).min -export const INT224_MIN = ethers.BigNumber.from(2).pow(ethers.BigNumber.from(223)).mul(ethers.BigNumber.from(-1)); +export const INT224_MIN = 2n ** 223n * -1n; // Solidity type(int224).max -export const INT224_MAX = ethers.BigNumber.from(2).pow(ethers.BigNumber.from(223)).sub(ethers.BigNumber.from(1)); +export const INT224_MAX = 2n ** 223n - 1n; diff --git a/src/data-fetcher-loop/signed-data-state.test.ts b/src/data-fetcher-loop/signed-data-state.test.ts index affc5f3e..4b0d0fc8 100644 --- a/src/data-fetcher-loop/signed-data-state.test.ts +++ b/src/data-fetcher-loop/signed-data-state.test.ts @@ -1,7 +1,7 @@ import { ethers } from 'ethers'; import { initializeState } from '../../test/fixtures/mock-config'; -import { allowPartial, generateRandomBytes32, signData } from '../../test/utils'; +import { allowPartial, generateRandomBytes, signData } from '../../test/utils'; import { getState, updateState } from '../state'; import type { SignedData } from '../types'; import { deriveBeaconId } from '../utils'; @@ -10,14 +10,14 @@ import * as signedDataStateModule from './signed-data-state'; describe('signed data state', () => { let testDataPoint: SignedData; - const signer = ethers.Wallet.fromMnemonic('test test test test test test test test test test test junk'); + const signer = ethers.Wallet.fromPhrase('test test test test test test test test test test test junk'); beforeAll(async () => { initializeState(); - const templateId = generateRandomBytes32(); + const templateId = generateRandomBytes(32); const timestamp = Math.floor((Date.now() - 25 * 60 * 60 * 1000) / 1000).toString(); const airnode = signer.address; - const encodedValue = ethers.utils.defaultAbiCoder.encode(['int256'], [ethers.BigNumber.from(1)]); + const encodedValue = ethers.AbiCoder.defaultAbiCoder().encode(['int256'], [1n]); testDataPoint = { airnode, @@ -41,10 +41,10 @@ describe('signed data state', () => { }); it('checks that the timestamp on signed data is not in the future', async () => { - const templateId = generateRandomBytes32(); + const templateId = generateRandomBytes(32); const timestamp = Math.floor((Date.now() + 61 * 60 * 1000) / 1000).toString(); const airnode = signer.address; - const encodedValue = ethers.utils.defaultAbiCoder.encode(['int256'], [ethers.BigNumber.from(1)]); + const encodedValue = ethers.AbiCoder.defaultAbiCoder().encode(['int256'], [1n]); const futureTestDataPoint = { airnode, @@ -59,10 +59,10 @@ describe('signed data state', () => { }); it('checks the signature on signed data', async () => { - const templateId = generateRandomBytes32(); + const templateId = generateRandomBytes(32); const timestamp = Math.floor((Date.now() + 60 * 60 * 1000) / 1000).toString(); const airnode = ethers.Wallet.createRandom().address; - const encodedValue = ethers.utils.defaultAbiCoder.encode(['int256'], [ethers.BigNumber.from(1)]); + const encodedValue = ethers.AbiCoder.defaultAbiCoder().encode(['int256'], [1n]); const badTestDataPoint = { airnode, diff --git a/src/data-fetcher-loop/signed-data-state.ts b/src/data-fetcher-loop/signed-data-state.ts index 40162906..b01992c9 100644 --- a/src/data-fetcher-loop/signed-data-state.ts +++ b/src/data-fetcher-loop/signed-data-state.ts @@ -1,5 +1,5 @@ import { goSync } from '@api3/promise-utils'; -import { BigNumber, ethers } from 'ethers'; +import { ethers } from 'ethers'; import { logger } from '../logger'; import { getState, updateState } from '../state'; @@ -9,11 +9,11 @@ import { deriveBeaconId } from '../utils'; export const verifySignedData = ({ airnode, templateId, timestamp, signature, encodedValue }: SignedData) => { // Verification is wrapped in goSync, because ethers methods can potentially throw on invalid input. const goVerify = goSync(() => { - const message = ethers.utils.arrayify( - ethers.utils.solidityKeccak256(['bytes32', 'uint256', 'bytes'], [templateId, timestamp, encodedValue]) + const message = ethers.getBytes( + ethers.solidityPackedKeccak256(['bytes32', 'uint256', 'bytes'], [templateId, timestamp, encodedValue]) ); - const signerAddr = ethers.utils.verifyMessage(message, signature); + const signerAddr = ethers.verifyMessage(message, signature); if (signerAddr !== airnode) throw new Error('Signer address does not match'); }); @@ -95,7 +95,7 @@ export const saveSignedData = (signedData: SignedData) => { export const getSignedData = (dataFeedId: BeaconId) => getState().signedDatas[dataFeedId]; export const isSignedDataFresh = (signedData: SignedData) => - BigNumber.from(signedData.timestamp).gt(Math.ceil(Date.now() / 1000 - 24 * 60 * 60)); + BigInt(signedData.timestamp) > BigInt(Math.ceil(Date.now() / 1000 - 24 * 60 * 60)); export const purgeOldSignedData = () => { const state = getState(); diff --git a/src/deviation-check/deviation-check.test.ts b/src/deviation-check/deviation-check.test.ts index fc60dd2e..fb13e9b0 100644 --- a/src/deviation-check/deviation-check.test.ts +++ b/src/deviation-check/deviation-check.test.ts @@ -1,5 +1,3 @@ -import { ethers } from 'ethers'; - import { HUNDRED_PERCENT } from '../constants'; import { @@ -10,56 +8,41 @@ import { isDataFeedUpdatable, } from './deviation-check'; -const getDeviationThresholdAsBigNumber = (input: number) => - ethers.BigNumber.from(Math.trunc(input * HUNDRED_PERCENT)).div(ethers.BigNumber.from(100)); +const getDeviationThresholdAsBigInt = (input: number) => BigInt(Math.trunc(input * HUNDRED_PERCENT)) / 100n; describe(isDeviationThresholdExceeded.name, () => { - const onChainValue = ethers.BigNumber.from(500); + const onChainValue = 500n; it('returns true when api value is higher and deviation threshold is reached', () => { - const shouldUpdate = isDeviationThresholdExceeded( - onChainValue, - getDeviationThresholdAsBigNumber(10), - ethers.BigNumber.from(560) - ); + const shouldUpdate = isDeviationThresholdExceeded(onChainValue, getDeviationThresholdAsBigInt(10), 560n); expect(shouldUpdate).toBe(true); }); it('returns true when api value is lower and deviation threshold is reached', () => { - const shouldUpdate = isDeviationThresholdExceeded( - onChainValue, - getDeviationThresholdAsBigNumber(10), - ethers.BigNumber.from(440) - ); + const shouldUpdate = isDeviationThresholdExceeded(onChainValue, getDeviationThresholdAsBigInt(10), 440n); expect(shouldUpdate).toBe(true); }); it('returns false when deviation threshold is not reached', () => { - const shouldUpdate = isDeviationThresholdExceeded( - onChainValue, - getDeviationThresholdAsBigNumber(10), - ethers.BigNumber.from(480) - ); + const shouldUpdate = isDeviationThresholdExceeded(onChainValue, getDeviationThresholdAsBigInt(10), 480n); expect(shouldUpdate).toBe(false); }); it('handles correctly bad JS math', () => { - expect(() => - isDeviationThresholdExceeded(onChainValue, getDeviationThresholdAsBigNumber(0.14), ethers.BigNumber.from(560)) - ).not.toThrow(); + expect(() => isDeviationThresholdExceeded(onChainValue, getDeviationThresholdAsBigInt(0.14), 560n)).not.toThrow(); }); it('checks all update conditions | heartbeat exceeded', () => { const result = isDataFeedUpdatable( - ethers.BigNumber.from(10), - Date.now() / 1000 - 60 * 60 * 24, - ethers.BigNumber.from(10), - Date.now() / 1000, - ethers.BigNumber.from(60 * 60 * 23), - getDeviationThresholdAsBigNumber(2) + 10n, + BigInt(Math.floor(Date.now() / 1000) - 60 * 60 * 24), + 10n, + BigInt(Math.floor(Date.now() / 1000)), + BigInt(60 * 60 * 23), + getDeviationThresholdAsBigInt(2) ); expect(result).toBe(true); @@ -67,12 +50,12 @@ describe(isDeviationThresholdExceeded.name, () => { it('checks all update conditions | no update', () => { const result = isDataFeedUpdatable( - ethers.BigNumber.from(10), - Date.now() / 1000, - ethers.BigNumber.from(10), - Date.now() + 60 * 60 * 23, - ethers.BigNumber.from(60 * 60 * 24), - getDeviationThresholdAsBigNumber(2) + 10n, + BigInt(Math.floor(Date.now() / 1000)), + 10n, + BigInt(Date.now() + 60 * 60 * 23), + BigInt(60 * 60 * 24), + getDeviationThresholdAsBigInt(2) ); expect(result).toBe(false); @@ -81,13 +64,13 @@ describe(isDeviationThresholdExceeded.name, () => { describe(isOnChainDataFresh.name, () => { it('returns true if on chain data timestamp is newer than heartbeat interval', () => { - const isFresh = isOnChainDataFresh(Date.now() / 1000 - 100, ethers.BigNumber.from(200)); + const isFresh = isOnChainDataFresh(BigInt(Math.floor(Date.now() / 1000) - 100), 200n); expect(isFresh).toBe(true); }); it('returns false if on chain data timestamp is older than heartbeat interval', () => { - const isFresh = isOnChainDataFresh(Date.now() / 1000 - 300, ethers.BigNumber.from(200)); + const isFresh = isOnChainDataFresh(BigInt(Math.floor(Date.now() / 1000) - 300), 200n); expect(isFresh).toBe(false); }); @@ -95,95 +78,73 @@ describe(isOnChainDataFresh.name, () => { describe(calculateDeviationPercentage.name, () => { it('calculates zero change', () => { - const updateInPercentage = calculateDeviationPercentage(ethers.BigNumber.from(10), ethers.BigNumber.from(10)); - expect(updateInPercentage).toStrictEqual(ethers.BigNumber.from(0 * HUNDRED_PERCENT)); + const updateInPercentage = calculateDeviationPercentage(10n, 10n); + expect(updateInPercentage).toStrictEqual(BigInt(0 * HUNDRED_PERCENT)); }); it('calculates 100 percent change', () => { - const updateInPercentage = calculateDeviationPercentage(ethers.BigNumber.from(10), ethers.BigNumber.from(20)); - expect(updateInPercentage).toStrictEqual(ethers.BigNumber.from(1 * HUNDRED_PERCENT)); + const updateInPercentage = calculateDeviationPercentage(10n, 20n); + expect(updateInPercentage).toStrictEqual(BigInt(1 * HUNDRED_PERCENT)); }); it('calculates positive to negative change', () => { - const updateInPercentage = calculateDeviationPercentage(ethers.BigNumber.from(10), ethers.BigNumber.from(-5)); - expect(updateInPercentage).toStrictEqual(ethers.BigNumber.from(1.5 * HUNDRED_PERCENT)); + const updateInPercentage = calculateDeviationPercentage(10n, BigInt(-5)); + expect(updateInPercentage).toStrictEqual(BigInt(1.5 * HUNDRED_PERCENT)); }); it('calculates negative to positive change', () => { - const updateInPercentage = calculateDeviationPercentage(ethers.BigNumber.from(-5), ethers.BigNumber.from(5)); - expect(updateInPercentage).toStrictEqual(ethers.BigNumber.from(2 * HUNDRED_PERCENT)); + const updateInPercentage = calculateDeviationPercentage(BigInt(-5), 5n); + expect(updateInPercentage).toStrictEqual(BigInt(2 * HUNDRED_PERCENT)); }); it('calculates initial zero to positive change', () => { - const updateInPercentage = calculateDeviationPercentage(ethers.BigNumber.from(0), ethers.BigNumber.from(5)); - expect(updateInPercentage).toStrictEqual(ethers.BigNumber.from(5 * HUNDRED_PERCENT)); + const updateInPercentage = calculateDeviationPercentage(0n, 5n); + expect(updateInPercentage).toStrictEqual(BigInt(5 * HUNDRED_PERCENT)); }); it('calculates initial zero to negative change', () => { - const updateInPercentage = calculateDeviationPercentage(ethers.BigNumber.from(0), ethers.BigNumber.from(-5)); - expect(updateInPercentage).toStrictEqual(ethers.BigNumber.from(5 * HUNDRED_PERCENT)); + const updateInPercentage = calculateDeviationPercentage(0n, BigInt(-5)); + expect(updateInPercentage).toStrictEqual(BigInt(5 * HUNDRED_PERCENT)); }); it('calculates initial positive to zero change', () => { - const updateInPercentage = calculateDeviationPercentage(ethers.BigNumber.from(5), ethers.BigNumber.from(0)); - expect(updateInPercentage).toStrictEqual(ethers.BigNumber.from(1 * HUNDRED_PERCENT)); + const updateInPercentage = calculateDeviationPercentage(5n, 0n); + expect(updateInPercentage).toStrictEqual(BigInt(1 * HUNDRED_PERCENT)); }); it('calculates initial negative to zero change', () => { - const updateInPercentage = calculateDeviationPercentage(ethers.BigNumber.from(-5), ethers.BigNumber.from(0)); - expect(updateInPercentage).toStrictEqual(ethers.BigNumber.from(1 * HUNDRED_PERCENT)); + const updateInPercentage = calculateDeviationPercentage(BigInt(-5), 0n); + expect(updateInPercentage).toStrictEqual(BigInt(1 * HUNDRED_PERCENT)); }); it('calculates initial negative to negative change', () => { - const updateInPercentage = calculateDeviationPercentage(ethers.BigNumber.from(-5), ethers.BigNumber.from(-1)); - expect(updateInPercentage).toStrictEqual(ethers.BigNumber.from(0.8 * HUNDRED_PERCENT)); + const updateInPercentage = calculateDeviationPercentage(BigInt(-5), BigInt(-1)); + expect(updateInPercentage).toStrictEqual(BigInt(0.8 * HUNDRED_PERCENT)); }); }); describe(calculateMedian.name, () => { describe('for array with odd number of elements', () => { it('calculates median for sorted array', () => { - const arr = [ - ethers.BigNumber.from(10), - ethers.BigNumber.from(11), - ethers.BigNumber.from(24), - ethers.BigNumber.from(30), - ethers.BigNumber.from(47), - ]; - expect(calculateMedian(arr)).toStrictEqual(ethers.BigNumber.from(24)); + const arr = [10n, 11n, 24n, 30n, 47n]; + expect(calculateMedian(arr)).toBe(24n); }); it('calculates median for unsorted array', () => { - const arr = [ - ethers.BigNumber.from(24), - ethers.BigNumber.from(11), - ethers.BigNumber.from(10), - ethers.BigNumber.from(47), - ethers.BigNumber.from(30), - ]; - expect(calculateMedian(arr)).toStrictEqual(ethers.BigNumber.from(24)); + const arr = [24n, 11n, 10n, 47n, 30n]; + expect(calculateMedian(arr)).toBe(24n); }); }); describe('for array with even number of elements', () => { it('calculates median for sorted array', () => { - const arr = [ - ethers.BigNumber.from(10), - ethers.BigNumber.from(11), - ethers.BigNumber.from(24), - ethers.BigNumber.from(30), - ]; - expect(calculateMedian(arr)).toStrictEqual(ethers.BigNumber.from(17)); + const arr = [10n, 11n, 24n, 30n]; + expect(calculateMedian(arr)).toBe(17n); }); it('calculates median for unsorted array', () => { - const arr = [ - ethers.BigNumber.from(24), - ethers.BigNumber.from(11), - ethers.BigNumber.from(10), - ethers.BigNumber.from(30), - ]; - expect(calculateMedian(arr)).toStrictEqual(ethers.BigNumber.from(17)); + const arr = [24n, 11n, 10n, 30n]; + expect(calculateMedian(arr)).toBe(17n); }); }); }); diff --git a/src/deviation-check/deviation-check.ts b/src/deviation-check/deviation-check.ts index 7146083f..0c933398 100644 --- a/src/deviation-check/deviation-check.ts +++ b/src/deviation-check/deviation-check.ts @@ -1,54 +1,49 @@ -import { type BigNumber, ethers } from 'ethers'; - import { HUNDRED_PERCENT } from '../constants'; import { logger } from '../logger'; +import { abs } from '../utils'; -export const calculateDeviationPercentage = (initialValue: ethers.BigNumber, updatedValue: ethers.BigNumber) => { - const delta = updatedValue.sub(initialValue); - const absoluteDelta = delta.abs(); +export const calculateDeviationPercentage = (initialValue: bigint, updatedValue: bigint) => { + const delta = updatedValue - initialValue; + const absoluteDelta = abs(delta); // Avoid division by 0 - const absoluteInitialValue = initialValue.isZero() ? ethers.BigNumber.from(1) : initialValue.abs(); + const absoluteInitialValue = initialValue === 0n ? 1n : abs(initialValue); - return absoluteDelta.mul(ethers.BigNumber.from(HUNDRED_PERCENT)).div(absoluteInitialValue); + return (absoluteDelta * BigInt(HUNDRED_PERCENT)) / absoluteInitialValue; }; -export const calculateMedian = (arr: ethers.BigNumber[]) => { +export const calculateMedian = (arr: bigint[]) => { if (arr.length === 0) throw new Error('Cannot calculate median of empty array'); const mid = Math.floor(arr.length / 2); const nums = [...arr].sort((a, b) => { - if (a.lt(b)) return -1; - else if (a.gt(b)) return 1; + if (a < b) return -1; + else if (a > b) return 1; else return 0; }); - return arr.length % 2 === 0 ? nums[mid - 1]!.add(nums[mid]!).div(2) : nums[mid]!; + return arr.length % 2 === 0 ? (nums[mid - 1]! + nums[mid]!) / 2n : nums[mid]!; }; -export const isDeviationThresholdExceeded = ( - onChainValue: ethers.BigNumber, - deviationThreshold: ethers.BigNumber, - apiValue: ethers.BigNumber -) => { +export const isDeviationThresholdExceeded = (onChainValue: bigint, deviationThreshold: bigint, apiValue: bigint) => { const updateInPercentage = calculateDeviationPercentage(onChainValue, apiValue); - return updateInPercentage.gt(deviationThreshold); + return updateInPercentage > deviationThreshold; }; /** * Returns true when the on chain data timestamp is newer than the heartbeat interval. */ -export const isOnChainDataFresh = (timestamp: number, heartbeatInterval: BigNumber) => - timestamp > Date.now() / 1000 - heartbeatInterval.toNumber(); +export const isOnChainDataFresh = (timestamp: bigint, heartbeatInterval: bigint) => + BigInt(timestamp) > BigInt(Math.floor(Date.now() / 1000)) - heartbeatInterval; export const isDataFeedUpdatable = ( - onChainValue: ethers.BigNumber, - onChainTimestamp: number, - offChainValue: ethers.BigNumber, - offChainTimestamp: number, - heartbeatInterval: BigNumber, - deviationThreshold: BigNumber + onChainValue: bigint, + onChainTimestamp: bigint, + offChainValue: bigint, + offChainTimestamp: bigint, + heartbeatInterval: bigint, + deviationThreshold: bigint ): boolean => { // Check that fulfillment data is newer than on chain data. Update transaction with stale data would revert on chain, // draining the sponsor wallet. See: diff --git a/src/gas-price/gas-price.test.ts b/src/gas-price/gas-price.test.ts index 41b95477..cd048d52 100644 --- a/src/gas-price/gas-price.test.ts +++ b/src/gas-price/gas-price.test.ts @@ -18,10 +18,14 @@ import { const chainId = '31337'; const providerName = 'localhost'; const rpcUrl = 'http://127.0.0.1:8545/'; -const provider = new ethers.providers.StaticJsonRpcProvider(rpcUrl, { - chainId: Number.parseInt(chainId, 10), - name: chainId, -}); +const provider = new ethers.JsonRpcProvider( + rpcUrl, + { + chainId: Number.parseInt(chainId, 10), + name: chainId, + }, + { staticNetwork: true } +); const dateNowMock = 1_696_930_907_351; const timestampMock = Math.floor(dateNowMock / 1000); const sponsorWalletAddress = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'; @@ -51,7 +55,7 @@ describe(calculateScalingMultiplier.name, () => { describe(purgeOldGasPrices.name, () => { it('clears expired gas prices from the state', () => { const oldGasPriceMock = { - price: ethers.utils.parseUnits('5', 'gwei'), + price: ethers.parseUnits('5', 'gwei'), timestamp: timestampMock - gasSettings.sanitizationSamplingWindow - 1, }; jest.spyOn(Date, 'now').mockReturnValue(dateNowMock); @@ -69,10 +73,10 @@ describe(saveGasPrice.name, () => { it('updates state with price data', () => { jest.spyOn(Date, 'now').mockReturnValue(dateNowMock); - saveGasPrice(chainId, providerName, ethers.utils.parseUnits('10', 'gwei')); + saveGasPrice(chainId, providerName, ethers.parseUnits('10', 'gwei')); expect(getState().gasPrices[chainId]![providerName]!.gasPrices).toStrictEqual([ - { price: ethers.utils.parseUnits('10', 'gwei'), timestamp: timestampMock }, + { price: ethers.parseUnits('10', 'gwei'), timestamp: timestampMock }, ]); }); }); @@ -105,19 +109,19 @@ describe(clearSponsorLastUpdateTimestamp.name, () => { describe(getPercentile.name, () => { it('returns correct percentile', () => { const percentile = getPercentile(80, [ - ethers.BigNumber.from('1'), - ethers.BigNumber.from('2'), - ethers.BigNumber.from('3'), - ethers.BigNumber.from('4'), - ethers.BigNumber.from('5'), - ethers.BigNumber.from('6'), - ethers.BigNumber.from('7'), - ethers.BigNumber.from('8'), - ethers.BigNumber.from('9'), - ethers.BigNumber.from('10'), + BigInt('1'), + BigInt('2'), + BigInt('3'), + BigInt('4'), + BigInt('5'), + BigInt('6'), + BigInt('7'), + BigInt('8'), + BigInt('9'), + BigInt('10'), ]); - expect(percentile).toStrictEqual(ethers.BigNumber.from('8')); + expect(percentile).toStrictEqual(BigInt('8')); }); it('returns correct percentile for empty array', () => { @@ -127,29 +131,25 @@ describe(getPercentile.name, () => { }); it('edge cases', () => { - expect( - getPercentile(100, [ethers.BigNumber.from('10'), ethers.BigNumber.from('20'), ethers.BigNumber.from('30')]) - ).toStrictEqual(ethers.BigNumber.from('30')); + expect(getPercentile(100, [BigInt('10'), BigInt('20'), BigInt('30')])).toStrictEqual(BigInt('30')); - expect(getPercentile(0, [ethers.BigNumber.from('10'), ethers.BigNumber.from('20')])).toStrictEqual( - ethers.BigNumber.from('10') - ); + expect(getPercentile(0, [BigInt('10'), BigInt('20')])).toStrictEqual(BigInt('10')); - expect(getPercentile(50, [ethers.BigNumber.from('10')])).toStrictEqual(ethers.BigNumber.from('10')); + expect(getPercentile(50, [BigInt('10')])).toStrictEqual(BigInt('10')); }); }); describe(getRecommendedGasPrice.name, () => { it('uses provider recommended gas price when there are no historical prices', async () => { jest.spyOn(Date, 'now').mockReturnValue(dateNowMock); - jest.spyOn(provider, 'getGasPrice').mockResolvedValueOnce(ethers.utils.parseUnits('10', 'gwei')); + jest.spyOn(provider, 'getFeeData').mockResolvedValueOnce({ gasPrice: ethers.parseUnits('10', 'gwei') } as any); jest.spyOn(logger, 'debug'); const gasPrice = await getRecommendedGasPrice(chainId, providerName, provider, sponsorWalletAddress); - expect(gasPrice).toStrictEqual(ethers.utils.parseUnits('15', 'gwei')); // The price is multiplied by the recommendedGasPriceMultiplier. + expect(gasPrice).toStrictEqual(ethers.parseUnits('15', 'gwei')); // The price is multiplied by the recommendedGasPriceMultiplier. expect(getState().gasPrices[chainId]![providerName]!.gasPrices).toStrictEqual([ - { price: ethers.utils.parseUnits('10', 'gwei'), timestamp: timestampMock }, + { price: ethers.parseUnits('10', 'gwei'), timestamp: timestampMock }, ]); expect(logger.debug).toHaveBeenCalledTimes(3); @@ -163,11 +163,11 @@ describe(getRecommendedGasPrice.name, () => { it('uses the sanitized percentile price from the state if the new price is above the percentile', async () => { const gasPricesMock = Array.from(Array.from({ length: 10 }), (_, i) => ({ - price: ethers.utils.parseUnits(`${i + 1}`, 'gwei'), + price: ethers.parseUnits(`${i + 1}`, 'gwei'), timestamp: timestampMock - 0.9 * gasSettings.sanitizationSamplingWindow - 1, })); jest.spyOn(Date, 'now').mockReturnValue(dateNowMock); - jest.spyOn(provider, 'getGasPrice').mockResolvedValueOnce(ethers.utils.parseUnits('10', 'gwei')); + jest.spyOn(provider, 'getFeeData').mockResolvedValueOnce({ gasPrice: ethers.parseUnits('10', 'gwei') } as any); updateState((draft) => { draft.gasPrices[chainId]![providerName]!.gasPrices = gasPricesMock; }); @@ -176,9 +176,9 @@ describe(getRecommendedGasPrice.name, () => { const gasPrice = await getRecommendedGasPrice(chainId, providerName, provider, sponsorWalletAddress); - expect(gasPrice).toStrictEqual(ethers.utils.parseUnits('12', 'gwei')); // The price is multiplied by the recommendedGasPriceMultiplier. + expect(gasPrice).toStrictEqual(ethers.parseUnits('12', 'gwei')); // The price is multiplied by the recommendedGasPriceMultiplier. expect(getState().gasPrices[chainId]![providerName]!.gasPrices).toStrictEqual([ - { price: ethers.utils.parseUnits('10', 'gwei'), timestamp: timestampMock }, + { price: ethers.parseUnits('10', 'gwei'), timestamp: timestampMock }, ...gasPricesMock, ]); @@ -193,13 +193,13 @@ describe(getRecommendedGasPrice.name, () => { }); it('uses provider recommended gas if it is within the percentile', async () => { - const oldGasPriceValueMock = ethers.utils.parseUnits('11', 'gwei'); + const oldGasPriceValueMock = ethers.parseUnits('11', 'gwei'); const oldGasPriceMock = { price: oldGasPriceValueMock, timestamp: timestampMock, }; jest.spyOn(Date, 'now').mockReturnValue(dateNowMock); - jest.spyOn(provider, 'getGasPrice').mockResolvedValueOnce(ethers.utils.parseUnits('10', 'gwei')); + jest.spyOn(provider, 'getFeeData').mockResolvedValueOnce({ gasPrice: ethers.parseUnits('10', 'gwei') } as any); updateState((draft) => { draft.gasPrices[chainId]![providerName]!.gasPrices.unshift(oldGasPriceMock); }); @@ -207,9 +207,9 @@ describe(getRecommendedGasPrice.name, () => { const gasPrice = await getRecommendedGasPrice(chainId, providerName, provider, sponsorWalletAddress); - expect(gasPrice).toStrictEqual(ethers.utils.parseUnits('15', 'gwei')); // The price is multiplied by the recommendedGasPriceMultiplier. + expect(gasPrice).toStrictEqual(ethers.parseUnits('15', 'gwei')); // The price is multiplied by the recommendedGasPriceMultiplier. expect(getState().gasPrices[chainId]![providerName]!.gasPrices).toStrictEqual([ - { price: ethers.utils.parseUnits('10', 'gwei'), timestamp: timestampMock }, + { price: ethers.parseUnits('10', 'gwei'), timestamp: timestampMock }, oldGasPriceMock, ]); @@ -219,13 +219,13 @@ describe(getRecommendedGasPrice.name, () => { }); it('applies scaling if last update timestamp is past the scaling window', async () => { - const oldGasPriceValueMock = ethers.utils.parseUnits('5', 'gwei'); + const oldGasPriceValueMock = ethers.parseUnits('5', 'gwei'); const oldGasPriceMock = { price: oldGasPriceValueMock, timestamp: timestampMock, }; jest.spyOn(Date, 'now').mockReturnValue(dateNowMock); - jest.spyOn(provider, 'getGasPrice').mockResolvedValueOnce(ethers.utils.parseUnits('10', 'gwei')); + jest.spyOn(provider, 'getFeeData').mockResolvedValueOnce({ gasPrice: ethers.parseUnits('10', 'gwei') } as any); updateState((draft) => { draft.gasPrices[chainId]![providerName]!.gasPrices.unshift(oldGasPriceMock); draft.gasPrices[chainId]![providerName]!.sponsorLastUpdateTimestamp[sponsorWalletAddress] = @@ -236,9 +236,9 @@ describe(getRecommendedGasPrice.name, () => { const gasPrice = await getRecommendedGasPrice(chainId, providerName, provider, sponsorWalletAddress); - expect(gasPrice).toStrictEqual(ethers.utils.parseUnits('20', 'gwei')); // The price is multiplied by the scaling multiplier. + expect(gasPrice).toStrictEqual(ethers.parseUnits('20', 'gwei')); // The price is multiplied by the scaling multiplier. expect(getState().gasPrices[chainId]![providerName]!.gasPrices).toStrictEqual([ - { price: ethers.utils.parseUnits('10', 'gwei'), timestamp: timestampMock }, + { price: ethers.parseUnits('10', 'gwei'), timestamp: timestampMock }, oldGasPriceMock, ]); @@ -250,13 +250,13 @@ describe(getRecommendedGasPrice.name, () => { }); it('does not apply scaling if the lag is not sufficient', async () => { - const oldGasPriceValueMock = ethers.utils.parseUnits('5', 'gwei'); + const oldGasPriceValueMock = ethers.parseUnits('5', 'gwei'); const oldGasPriceMock = { price: oldGasPriceValueMock, timestamp: timestampMock, }; jest.spyOn(Date, 'now').mockReturnValue(dateNowMock); - jest.spyOn(provider, 'getGasPrice').mockResolvedValueOnce(ethers.utils.parseUnits('10', 'gwei')); + jest.spyOn(provider, 'getFeeData').mockResolvedValueOnce({ gasPrice: ethers.parseUnits('10', 'gwei') } as any); updateState((draft) => { draft.gasPrices[chainId]![providerName]!.gasPrices.unshift(oldGasPriceMock); draft.gasPrices[chainId]![providerName]!.sponsorLastUpdateTimestamp[sponsorWalletAddress] = @@ -267,9 +267,9 @@ describe(getRecommendedGasPrice.name, () => { const gasPrice = await getRecommendedGasPrice(chainId, providerName, provider, sponsorWalletAddress); - expect(gasPrice).toStrictEqual(ethers.utils.parseUnits('15', 'gwei')); // The price is multiplied by the recommendedGasPriceMultiplier. + expect(gasPrice).toStrictEqual(ethers.parseUnits('15', 'gwei')); // The price is multiplied by the recommendedGasPriceMultiplier. expect(getState().gasPrices[chainId]![providerName]!.gasPrices).toStrictEqual([ - { price: ethers.utils.parseUnits('10', 'gwei'), timestamp: timestampMock }, + { price: ethers.parseUnits('10', 'gwei'), timestamp: timestampMock }, oldGasPriceMock, ]); @@ -285,7 +285,7 @@ describe(getRecommendedGasPrice.name, () => { }); it('throws and error when getting gas price from RPC provider fails', async () => { - jest.spyOn(provider, 'getGasPrice').mockRejectedValueOnce(new Error('Provider error')); + jest.spyOn(provider, 'getFeeData').mockRejectedValueOnce(new Error('Provider error')); jest.spyOn(logger, 'debug'); jest.spyOn(logger, 'warn'); diff --git a/src/gas-price/gas-price.ts b/src/gas-price/gas-price.ts index 8f1a1e26..7c775621 100644 --- a/src/gas-price/gas-price.ts +++ b/src/gas-price/gas-price.ts @@ -21,7 +21,7 @@ export const initializeGasState = (chainId: string, providerName: string) => * @param providerName * @param gasPrice */ -export const saveGasPrice = (chainId: string, providerName: string, gasPrice: ethers.BigNumber) => +export const saveGasPrice = (chainId: string, providerName: string, gasPrice: bigint) => updateState((draft) => { draft.gasPrices[chainId]![providerName]!.gasPrices.unshift({ price: gasPrice, @@ -76,10 +76,10 @@ export const clearSponsorLastUpdateTimestamp = (chainId: string, providerName: s } }); -export const getPercentile = (percentile: number, array: ethers.BigNumber[]) => { +export const getPercentile = (percentile: number, array: bigint[]) => { if (array.length === 0) return; - array.sort((a, b) => (a.gt(b) ? 1 : -1)); + array.sort((a, b) => (a > b ? 1 : -1)); const index = Math.max(0, Math.ceil(array.length * (percentile / 100)) - 1); return array[index]; }; @@ -113,7 +113,7 @@ export const calculateScalingMultiplier = ( export const getRecommendedGasPrice = async ( chainId: string, providerName: string, - provider: ethers.providers.StaticJsonRpcProvider, + provider: ethers.JsonRpcProvider, sponsorWalletAddress: string ) => { const state = getState(); @@ -131,7 +131,12 @@ export const getRecommendedGasPrice = async ( // Get the provider recommended gas price and save it to the state logger.debug('Fetching gas price and saving it to the state.'); - const goGasPrice = await go(async () => provider.getGasPrice()); + const goGasPrice = await go(async () => { + const feeData = await provider.getFeeData(); + // We assume the legacy gas price will always exist. See: + // https://api3workspace.slack.com/archives/C05TQPT7PNJ/p1699098552350519 + return feeData.gasPrice; + }); let gasPrice = goGasPrice.data; if (!goGasPrice.success) logger.error('Failed to fetch gas price from RPC provider.', goGasPrice.error); if (gasPrice) saveGasPrice(chainId, providerName, gasPrice); @@ -186,7 +191,7 @@ export const getRecommendedGasPrice = async ( } // Log a warning if there is not enough historical data to sanitize the gas price but the price could be sanitized - if (!hasSufficientSanitizationData && gasPrice.gt(percentileGasPrice)) { + if (!hasSufficientSanitizationData && gasPrice > percentileGasPrice) { logger.warn('Gas price could be sanitized but there is not enough historical data.', { gasPrice: gasPrice.toString(), percentileGasPrice: percentileGasPrice.toString(), @@ -194,7 +199,7 @@ export const getRecommendedGasPrice = async ( } // If necessary, sanitize the gas price and log a warning because this should not happen under normal circumstances - if (hasSufficientSanitizationData && gasPrice.gt(percentileGasPrice)) { + if (hasSufficientSanitizationData && gasPrice > percentileGasPrice) { logger.warn('Sanitizing gas price.', { gasPrice: gasPrice.toString(), percentileGasPrice: percentileGasPrice.toString(), diff --git a/src/state/state.test.ts b/src/state/state.test.ts index 91aba7ba..05970f91 100644 --- a/src/state/state.test.ts +++ b/src/state/state.test.ts @@ -1,5 +1,3 @@ -import { BigNumber } from 'ethers'; - import { generateTestConfig, initializeState } from '../../test/fixtures/mock-config'; import { deriveBeaconId } from '../utils'; @@ -11,7 +9,7 @@ const stateMock: State = { gasPrices: { '31337': { localhost: { - gasPrices: [{ price: BigNumber.from(10), timestamp: timestampMock }], + gasPrices: [{ price: 10n, timestamp: timestampMock }], sponsorLastUpdateTimestamp: { '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266': timestampMock }, }, }, diff --git a/src/state/state.ts b/src/state/state.ts index 045e2bde..35e18647 100644 --- a/src/state/state.ts +++ b/src/state/state.ts @@ -1,4 +1,3 @@ -import type { BigNumber } from 'ethers'; import { produce, type Draft } from 'immer'; import type { Config } from '../config/schema'; @@ -14,7 +13,7 @@ import type { } from '../types'; interface GasState { - gasPrices: { price: BigNumber; timestamp: number }[]; + gasPrices: { price: bigint; timestamp: number }[]; sponsorLastUpdateTimestamp: Record; } diff --git a/src/update-feeds-loops/contracts.ts b/src/update-feeds-loops/contracts.ts index 8dfc33ab..5a021a96 100644 --- a/src/update-feeds-loops/contracts.ts +++ b/src/update-feeds-loops/contracts.ts @@ -1,19 +1,21 @@ -import { Api3ServerV1__factory as Api3ServerV1Factory } from '@api3/airnode-protocol-v1'; import { ethers } from 'ethers'; -// NOTE: The contract is not yet published, so we generate the Typechain artifacts locally and import it from there. -import { type AirseekerRegistry, AirseekerRegistry__factory as AirseekerRegistryFactory } from '../typechain-types'; +import { + Api3ServerV1__factory as Api3ServerV1Factory, + type AirseekerRegistry, + AirseekerRegistry__factory as AirseekerRegistryFactory, +} from '../typechain-types'; import type { DecodedDataFeed } from '../types'; import { decodeDapiName, deriveBeaconId, deriveBeaconSetId } from '../utils'; -export const getApi3ServerV1 = (address: string, provider: ethers.providers.StaticJsonRpcProvider) => +export const getApi3ServerV1 = (address: string, provider: ethers.JsonRpcProvider) => Api3ServerV1Factory.connect(address, provider); -export const getAirseekerRegistry = (address: string, provider: ethers.providers.StaticJsonRpcProvider) => +export const getAirseekerRegistry = (address: string, provider: ethers.JsonRpcProvider) => AirseekerRegistryFactory.connect(address, provider); export const verifyMulticallResponse = ( - response: Awaited> + response: Awaited> ) => { const { successes, returndata } = response; @@ -21,17 +23,11 @@ export const verifyMulticallResponse = ( return returndata; }; -export const decodeActiveDataFeedCountResponse = (activeDataFeedCountReturndata: string) => { - return ethers.BigNumber.from(activeDataFeedCountReturndata).toNumber(); -}; +export const decodeActiveDataFeedCountResponse = Number; -export const decodeGetBlockNumberResponse = (getBlockNumberReturndata: string) => { - return ethers.BigNumber.from(getBlockNumberReturndata).toNumber(); -}; +export const decodeGetBlockNumberResponse = Number; -export const decodeGetChainIdResponse = (getChainIdReturndata: string) => { - return ethers.BigNumber.from(getChainIdReturndata).toNumber(); -}; +export const decodeGetChainIdResponse = Number; export const decodeDataFeedDetails = (dataFeed: string): DecodedDataFeed | null => { // The contract returns empty bytes if the data feed is not registered. See: @@ -41,14 +37,17 @@ export const decodeDataFeedDetails = (dataFeed: string): DecodedDataFeed | null // This is a hex encoded string, the contract works with bytes directly // 2 characters for the '0x' preamble + 32 * 2 hexadecimals for 32 bytes + 32 * 2 hexadecimals for 32 bytes if (dataFeed.length === 2 + 32 * 2 + 32 * 2) { - const [airnodeAddress, templateId] = ethers.utils.defaultAbiCoder.decode(['address', 'bytes32'], dataFeed); + const [airnodeAddress, templateId] = ethers.AbiCoder.defaultAbiCoder().decode(['address', 'bytes32'], dataFeed); const dataFeedId = deriveBeaconId(airnodeAddress, templateId)!; return { dataFeedId, beacons: [{ beaconId: dataFeedId, airnodeAddress, templateId }] }; } - const [airnodeAddresses, templateIds] = ethers.utils.defaultAbiCoder.decode(['address[]', 'bytes32[]'], dataFeed); + const [airnodeAddresses, templateIds] = ethers.AbiCoder.defaultAbiCoder().decode( + ['address[]', 'bytes32[]'], + dataFeed + ); const beacons = (airnodeAddresses as string[]).map((airnodeAddress: string, idx: number) => { const templateId = templateIds[idx] as string; @@ -63,17 +62,15 @@ export const decodeDataFeedDetails = (dataFeed: string): DecodedDataFeed | null }; export interface DecodedUpdateParameters { - deviationReference: ethers.BigNumber; - deviationThresholdInPercentage: ethers.BigNumber; - heartbeatInterval: ethers.BigNumber; + deviationReference: bigint; + deviationThresholdInPercentage: bigint; + heartbeatInterval: bigint; } export const decodeUpdateParameters = (updateParameters: string): DecodedUpdateParameters => { // https://github.com/api3dao/airnode-protocol-v1/blob/5f861715749e182e334c273d6a52c4f2560c7994/contracts/api3-server-v1/extensions/BeaconSetUpdatesWithPsp.sol#L122 - const [deviationThresholdInPercentage, deviationReference, heartbeatInterval] = ethers.utils.defaultAbiCoder.decode( - ['uint256', 'int224', 'uint256'], - updateParameters - ); + const [deviationThresholdInPercentage, deviationReference, heartbeatInterval] = + ethers.AbiCoder.defaultAbiCoder().decode(['uint256', 'int224', 'uint256'], updateParameters); // 2 characters for the '0x' preamble + 3 parameters, 32 * 2 hexadecimals for 32 bytes each if (updateParameters.length !== 2 + 3 * (32 * 2)) { throw new Error(`Unexpected trailing data in update parameters`); @@ -90,8 +87,8 @@ export interface DecodedActiveDataFeedResponse { dapiName: string | null; decodedDapiName: string | null; decodedUpdateParameters: DecodedUpdateParameters; - dataFeedValue: ethers.BigNumber; - dataFeedTimestamp: number; + dataFeedValue: bigint; + dataFeedTimestamp: bigint; decodedDataFeed: DecodedDataFeed; signedApiUrls: string[]; } @@ -101,8 +98,8 @@ export const decodeActiveDataFeedResponse = ( activeDataFeedReturndata: string ): DecodedActiveDataFeedResponse | null => { const { dapiName, updateParameters, dataFeedValue, dataFeedTimestamp, dataFeedDetails, signedApiUrls } = - airseekerRegistry.interface.decodeFunctionResult('activeDataFeed', activeDataFeedReturndata) as Awaited< - ReturnType + airseekerRegistry.interface.decodeFunctionResult('activeDataFeed', activeDataFeedReturndata) as unknown as Awaited< + ReturnType >; // https://github.com/api3dao/dapi-management/blob/f3d39e4707c33c075a8f07aa8f8369f8dc07736f/contracts/AirseekerRegistry.sol#L162 diff --git a/src/update-feeds-loops/get-updatable-feeds.test.ts b/src/update-feeds-loops/get-updatable-feeds.test.ts index fd208978..ffff42c3 100644 --- a/src/update-feeds-loops/get-updatable-feeds.test.ts +++ b/src/update-feeds-loops/get-updatable-feeds.test.ts @@ -14,16 +14,20 @@ import * as getUpdatableFeedsModule from './get-updatable-feeds'; const chainId = '31337'; const rpcUrl = 'http://127.0.0.1:8545/'; -const provider = new ethers.providers.StaticJsonRpcProvider(rpcUrl, { - chainId: Number.parseInt(chainId, 10), - name: chainId, -}); +const provider = new ethers.JsonRpcProvider( + rpcUrl, + { + chainId: Number.parseInt(chainId, 10), + name: chainId, + }, + { staticNetwork: true } +); // https://github.com/api3dao/airnode-protocol-v1/blob/fa95f043ce4b50e843e407b96f7ae3edcf899c32/contracts/api3-server-v1/DataFeedServer.sol#L132 const encodeBeaconValue = (numericValue: string) => { - const numericValueAsBigNumber = ethers.BigNumber.from(numericValue); + const numericValueAsBigNumber = BigInt(numericValue); - return ethers.utils.defaultAbiCoder.encode(['int256'], [numericValueAsBigNumber]); + return ethers.AbiCoder.defaultAbiCoder().encode(['int256'], [numericValueAsBigNumber]); }; const feedIds = [ @@ -41,10 +45,10 @@ describe(multicallBeaconValues.name, () => { const tryMulticallMock = jest.fn().mockReturnValue({ successes: [true, true, true], returndata: [ - ethers.utils.defaultAbiCoder.encode(['uint256'], [31_337]), - ethers.utils.defaultAbiCoder.encode(['int224', 'uint32'], [100, 105]), - ethers.utils.defaultAbiCoder.encode(['int224', 'uint32'], [101, 106]), - ethers.utils.defaultAbiCoder.encode(['int224', 'uint32'], [102, 107]), + ethers.AbiCoder.defaultAbiCoder().encode(['uint256'], [31_337]), + ethers.AbiCoder.defaultAbiCoder().encode(['int224', 'uint32'], [100, 105]), + ethers.AbiCoder.defaultAbiCoder().encode(['int224', 'uint32'], [101, 106]), + ethers.AbiCoder.defaultAbiCoder().encode(['int224', 'uint32'], [102, 107]), ], }); @@ -56,8 +60,8 @@ describe(multicallBeaconValues.name, () => { const mockContract = { connect: jest.fn().mockReturnValue({ - callStatic: { - tryMulticall: tryMulticallMock, + tryMulticall: { + staticCall: tryMulticallMock, }, }), interface: { encodeFunctionData: encodeFunctionDataMock }, @@ -69,16 +73,16 @@ describe(multicallBeaconValues.name, () => { expect(callAndParseMulticallPromise).toStrictEqual({ '0xf5c140bcb4814dfec311d38f6293e86c02d32ba1b7da027fe5b5202cae35dbc6': { - timestamp: ethers.BigNumber.from(105), - value: ethers.BigNumber.from(100), + timestamp: 105n, + value: 100n, }, '0xf5c140bcb4814dfec311d38f6293e86c02d32ba1b7da027fe5b5202cae35dbc7': { - timestamp: ethers.BigNumber.from(106), - value: ethers.BigNumber.from(101), + timestamp: 106n, + value: 101n, }, '0xf5c140bcb4814dfec311d38f6293e86c02d32ba1b7da027fe5b5202cae35dbc8': { - timestamp: ethers.BigNumber.from(107), - value: ethers.BigNumber.from(102), + timestamp: 107n, + value: 102n, }, }); expect(tryMulticallMock).toHaveBeenCalledWith(['0xChain', '0xFirst', '0xSecond', '0xThird']); @@ -122,16 +126,16 @@ describe(getUpdatableFeeds.name, () => { // None of the feeds failed to update jest.spyOn(getUpdatableFeedsModule, 'multicallBeaconValues').mockResolvedValue({ '0xf5c140bcb4814dfec311d38f6293e86c02d32ba1b7da027fe5b5202cae35dbc6': { - timestamp: ethers.BigNumber.from(150), - value: ethers.BigNumber.from('400'), + timestamp: 150n, + value: BigInt('400'), }, '0xf5c140bcb4814dfec311d38f6293e86c02d32ba1b7da027fe5b5202cae35dbc7': { - timestamp: ethers.BigNumber.from(160), - value: ethers.BigNumber.from('500'), + timestamp: 160n, + value: BigInt('500'), }, '0xf5c140bcb4814dfec311d38f6293e86c02d32ba1b7da027fe5b5202cae35dbc8': { - timestamp: ethers.BigNumber.from(170), - value: ethers.BigNumber.from('600'), + timestamp: 170n, + value: BigInt('600'), }, }); jest.spyOn(logger, 'info'); @@ -139,11 +143,11 @@ describe(getUpdatableFeeds.name, () => { const batch = allowPartial([ { decodedUpdateParameters: { - deviationThresholdInPercentage: ethers.BigNumber.from(1), - heartbeatInterval: ethers.BigNumber.from(100), + deviationThresholdInPercentage: 1n, + heartbeatInterval: 100n, }, - dataFeedValue: ethers.BigNumber.from(10), - dataFeedTimestamp: 95, + dataFeedValue: 10n, + dataFeedTimestamp: 95n, decodedDataFeed: { dataFeedId: '0x000', beacons: [{ beaconId: feedIds[0] }, { beaconId: feedIds[1] }, { beaconId: feedIds[2] }], @@ -168,8 +172,8 @@ describe(getUpdatableFeeds.name, () => { ], dataFeedInfo: { dapiName: encodeDapiName('test'), - dataFeedValue: ethers.BigNumber.from('10'), - dataFeedTimestamp: 95, + dataFeedValue: BigInt('10'), + dataFeedTimestamp: 95n, decodedDataFeed: { beacons: [ expect.objectContaining({ @@ -185,8 +189,8 @@ describe(getUpdatableFeeds.name, () => { dataFeedId: '0x000', }, decodedUpdateParameters: { - deviationThresholdInPercentage: ethers.BigNumber.from(1), - heartbeatInterval: ethers.BigNumber.from(100), + deviationThresholdInPercentage: 1n, + heartbeatInterval: 100n, }, }, }, @@ -218,16 +222,16 @@ describe(getUpdatableFeeds.name, () => { // None of the feeds failed to update jest.spyOn(getUpdatableFeedsModule, 'multicallBeaconValues').mockResolvedValue({ '0xf5c140bcb4814dfec311d38f6293e86c02d32ba1b7da027fe5b5202cae35dbc6': { - timestamp: ethers.BigNumber.from(150), - value: ethers.BigNumber.from('400'), + timestamp: 150n, + value: BigInt('400'), }, '0xf5c140bcb4814dfec311d38f6293e86c02d32ba1b7da027fe5b5202cae35dbc7': { - timestamp: ethers.BigNumber.from(160), - value: ethers.BigNumber.from('400'), + timestamp: 160n, + value: BigInt('400'), }, '0xf5c140bcb4814dfec311d38f6293e86c02d32ba1b7da027fe5b5202cae35dbc8': { - timestamp: ethers.BigNumber.from(170), - value: ethers.BigNumber.from('400'), + timestamp: 170n, + value: BigInt('400'), }, }); jest.spyOn(logger, 'debug'); @@ -235,11 +239,11 @@ describe(getUpdatableFeeds.name, () => { const batch = allowPartial([ { decodedUpdateParameters: { - deviationThresholdInPercentage: ethers.BigNumber.from(1), - heartbeatInterval: ethers.BigNumber.from(1), + deviationThresholdInPercentage: 1n, + heartbeatInterval: 1n, }, - dataFeedValue: ethers.BigNumber.from(400), - dataFeedTimestamp: 90, + dataFeedValue: 400n, + dataFeedTimestamp: 90n, decodedDataFeed: { dataFeedId: '0x000', beacons: [{ beaconId: feedIds[0] }, { beaconId: feedIds[1] }, { beaconId: feedIds[2] }], @@ -264,8 +268,8 @@ describe(getUpdatableFeeds.name, () => { ], dataFeedInfo: { dapiName: encodeDapiName('test'), - dataFeedValue: ethers.BigNumber.from('400'), - dataFeedTimestamp: 90, + dataFeedValue: BigInt('400'), + dataFeedTimestamp: 90n, decodedDataFeed: { beacons: [ expect.objectContaining({ @@ -281,8 +285,8 @@ describe(getUpdatableFeeds.name, () => { dataFeedId: '0x000', }, decodedUpdateParameters: { - deviationThresholdInPercentage: ethers.BigNumber.from(1), - heartbeatInterval: ethers.BigNumber.from(1), + deviationThresholdInPercentage: 1n, + heartbeatInterval: 1n, }, }, }, @@ -315,11 +319,11 @@ describe(getUpdatableFeeds.name, () => { const batch = allowPartial([ { decodedUpdateParameters: { - deviationThresholdInPercentage: ethers.BigNumber.from(1), - heartbeatInterval: ethers.BigNumber.from(100), + deviationThresholdInPercentage: 1n, + heartbeatInterval: 100n, }, - dataFeedValue: ethers.BigNumber.from(200), - dataFeedTimestamp: 160, + dataFeedValue: 200n, + dataFeedTimestamp: 160n, decodedDataFeed: { dataFeedId: '0x000', beacons: [{ beaconId: feedIds[0] }, { beaconId: feedIds[1] }, { beaconId: feedIds[2] }], @@ -331,16 +335,16 @@ describe(getUpdatableFeeds.name, () => { // Ensure on-chain values don't trigger an update jest.spyOn(getUpdatableFeedsModule, 'multicallBeaconValues').mockResolvedValue({ '0xf5c140bcb4814dfec311d38f6293e86c02d32ba1b7da027fe5b5202cae35dbc6': { - timestamp: ethers.BigNumber.from(150), - value: ethers.BigNumber.from('200'), + timestamp: 150n, + value: BigInt('200'), }, '0xf5c140bcb4814dfec311d38f6293e86c02d32ba1b7da027fe5b5202cae35dbc7': { - timestamp: ethers.BigNumber.from(155), - value: ethers.BigNumber.from('200'), + timestamp: 155n, + value: BigInt('200'), }, '0xf5c140bcb4814dfec311d38f6293e86c02d32ba1b7da027fe5b5202cae35dbc8': { - timestamp: ethers.BigNumber.from(170), - value: ethers.BigNumber.from('200'), + timestamp: 170n, + value: BigInt('200'), }, }); jest.spyOn(logger, 'warn'); @@ -376,16 +380,16 @@ describe(getUpdatableFeeds.name, () => { // None of the feeds failed to update jest.spyOn(getUpdatableFeedsModule, 'multicallBeaconValues').mockResolvedValue({ '0xf5c140bcb4814dfec311d38f6293e86c02d32ba1b7da027fe5b5202cae35dbc6': { - timestamp: ethers.BigNumber.from(150), - value: ethers.BigNumber.from('400'), + timestamp: 150n, + value: BigInt('400'), }, '0xf5c140bcb4814dfec311d38f6293e86c02d32ba1b7da027fe5b5202cae35dbc7': { - timestamp: ethers.BigNumber.from(160), - value: ethers.BigNumber.from('400'), + timestamp: 160n, + value: BigInt('400'), }, '0xf5c140bcb4814dfec311d38f6293e86c02d32ba1b7da027fe5b5202cae35dbc8': { - timestamp: ethers.BigNumber.from(170), - value: ethers.BigNumber.from('400'), + timestamp: 170n, + value: BigInt('400'), }, }); jest.spyOn(logger, 'info'); @@ -393,11 +397,11 @@ describe(getUpdatableFeeds.name, () => { const batch = allowPartial([ { decodedUpdateParameters: { - deviationThresholdInPercentage: ethers.BigNumber.from(1), - heartbeatInterval: ethers.BigNumber.from(100), + deviationThresholdInPercentage: 1n, + heartbeatInterval: 100n, }, - dataFeedValue: ethers.BigNumber.from(400), - dataFeedTimestamp: 140, + dataFeedValue: 400n, + dataFeedTimestamp: 140n, decodedDataFeed: { dataFeedId: '0x000', beacons: [{ beaconId: feedIds[0] }, { beaconId: feedIds[1] }, { beaconId: feedIds[2] }], @@ -417,11 +421,11 @@ describe(getUpdatableFeeds.name, () => { const batch = allowPartial([ { decodedUpdateParameters: { - deviationThresholdInPercentage: ethers.BigNumber.from(1), - heartbeatInterval: ethers.BigNumber.from(100), + deviationThresholdInPercentage: 1n, + heartbeatInterval: 100n, }, - dataFeedValue: ethers.BigNumber.from(10), - dataFeedTimestamp: 95, + dataFeedValue: 10n, + dataFeedTimestamp: 95n, decodedDataFeed: { dataFeedId: '0x000', beacons: [{ beaconId: feedIds[0] }, { beaconId: feedIds[1] }, { beaconId: feedIds[2] }], diff --git a/src/update-feeds-loops/get-updatable-feeds.ts b/src/update-feeds-loops/get-updatable-feeds.ts index 97ca4954..e63f399c 100644 --- a/src/update-feeds-loops/get-updatable-feeds.ts +++ b/src/update-feeds-loops/get-updatable-feeds.ts @@ -16,8 +16,8 @@ import { } from './contracts'; interface BeaconValue { - timestamp: ethers.BigNumber; - value: ethers.BigNumber; + timestamp: bigint; + value: bigint; } export interface UpdatableBeacon { @@ -33,7 +33,7 @@ export interface UpdatableDataFeed { export const getUpdatableFeeds = async ( batch: DecodedActiveDataFeedResponse[], deviationThresholdCoefficient: number, - provider: ethers.providers.StaticJsonRpcProvider, + provider: ethers.JsonRpcProvider, chainId: ChainId ): Promise => { const uniqueBeaconIds = [ @@ -63,11 +63,11 @@ export const getUpdatableFeeds = async ( const signedData = getSignedData(beaconId); const offChainValue: BeaconValue | undefined = signedData ? { - timestamp: ethers.BigNumber.from(signedData.timestamp), + timestamp: BigInt(signedData.timestamp), value: decodeBeaconValue(signedData.encodedValue)!, } : undefined; - const isUpdatable = offChainValue?.timestamp.gt(onChainValue.timestamp); + const isUpdatable = offChainValue && offChainValue?.timestamp > onChainValue.timestamp; return { onChainValue, offChainValue, isUpdatable, signedData, beaconId }; }); @@ -84,7 +84,7 @@ export const getUpdatableFeeds = async ( ); const newBeaconSetValue = calculateMedian(beaconValues.map(({ value }) => value)); - const newBeaconSetTimestamp = calculateMedian(beaconValues.map(({ timestamp }) => timestamp))!.toNumber(); + const newBeaconSetTimestamp = calculateMedian(beaconValues.map(({ timestamp }) => timestamp)); const { decodedUpdateParameters, dataFeedValue, dataFeedTimestamp } = dataFeedInfo; const adjustedDeviationThresholdCoefficient = multiplyBigNumber( @@ -116,7 +116,7 @@ export const getUpdatableFeeds = async ( export const multicallBeaconValues = async ( batch: BeaconId[], - provider: ethers.providers.StaticJsonRpcProvider, + provider: ethers.JsonRpcProvider, chainId: ChainId ): Promise | null> => { const { config } = getState(); @@ -126,11 +126,11 @@ export const multicallBeaconValues = async ( // Calling the dataFeeds contract function is guaranteed not to revert, so we are not checking the multicall successes // and using returndata directly. If the call fails (e.g. timeout or RPC error) we let the parent handle it. const api3ServerV1 = getApi3ServerV1(contracts.Api3ServerV1, provider); - const voidSigner = new ethers.VoidSigner(ethers.constants.AddressZero, provider); + const voidSigner = new ethers.VoidSigner(ethers.ZeroAddress, provider); const returndatas = verifyMulticallResponse( await api3ServerV1 .connect(voidSigner) - .callStatic.tryMulticall([ + .tryMulticall.staticCall([ api3ServerV1.interface.encodeFunctionData('getChainId'), ...batch.map((beaconId) => api3ServerV1.interface.encodeFunctionData('dataFeeds', [beaconId])), ]) @@ -145,8 +145,11 @@ export const multicallBeaconValues = async ( const onChainValues: Record = {}; for (const [idx, beaconId] of batch.entries()) { - const [value, timestamp] = ethers.utils.defaultAbiCoder.decode(['int224', 'uint32'], dataFeedsReturndata[idx]!); - onChainValues[beaconId] = { timestamp: ethers.BigNumber.from(timestamp), value: ethers.BigNumber.from(value) }; + const [value, timestamp] = ethers.AbiCoder.defaultAbiCoder().decode( + ['int224', 'uint32'], + dataFeedsReturndata[idx]! + ); + onChainValues[beaconId] = { timestamp: BigInt(timestamp), value: BigInt(value) }; } return onChainValues; }; diff --git a/src/update-feeds-loops/submit-transactions.test.ts b/src/update-feeds-loops/submit-transactions.test.ts index ce684bb6..e3426a69 100644 --- a/src/update-feeds-loops/submit-transactions.test.ts +++ b/src/update-feeds-loops/submit-transactions.test.ts @@ -1,4 +1,3 @@ -import type { Api3ServerV1 } from '@api3/airnode-protocol-v1'; import { ethers } from 'ethers'; import { generateMockApi3ServerV1 } from '../../test/fixtures/mock-contract'; @@ -6,6 +5,7 @@ import { allowPartial } from '../../test/utils'; import * as gasPriceModule from '../gas-price'; import { logger } from '../logger'; import * as stateModule from '../state'; +import type { Api3ServerV1 } from '../typechain-types'; import * as utilsModule from '../utils'; import type { UpdatableDataFeed } from './get-updatable-feeds'; @@ -14,7 +14,7 @@ import * as submitTransactionsModule from './submit-transactions'; describe(submitTransactionsModule.estimateMulticallGasLimit.name, () => { it('estimates the gas limit for a multicall', async () => { const mockApi3ServerV1 = generateMockApi3ServerV1(); - mockApi3ServerV1.estimateGas.multicall.mockResolvedValueOnce(ethers.BigNumber.from(500_000)); + mockApi3ServerV1.multicall.estimateGas.mockResolvedValueOnce(BigInt(500_000)); const gasLimit = await submitTransactionsModule.estimateMulticallGasLimit( mockApi3ServerV1 as unknown as Api3ServerV1, @@ -22,12 +22,12 @@ describe(submitTransactionsModule.estimateMulticallGasLimit.name, () => { undefined ); - expect(gasLimit).toStrictEqual(ethers.BigNumber.from(550_000)); // Note that the gas limit is increased by 10%. + expect(gasLimit).toStrictEqual(BigInt(550_000)); // Note that the gas limit is increased by 10%. }); it('uses fallback gas limit when dummy data estimation fails', async () => { const mockApi3ServerV1 = generateMockApi3ServerV1(); - mockApi3ServerV1.estimateGas.multicall.mockRejectedValue(new Error('some-error')); + mockApi3ServerV1.multicall.estimateGas.mockRejectedValue(new Error('some-error')); const gasLimit = await submitTransactionsModule.estimateMulticallGasLimit( mockApi3ServerV1 as unknown as Api3ServerV1, @@ -35,12 +35,12 @@ describe(submitTransactionsModule.estimateMulticallGasLimit.name, () => { 2_000_000 ); - expect(gasLimit).toStrictEqual(ethers.BigNumber.from(2_000_000)); + expect(gasLimit).toStrictEqual(BigInt(2_000_000)); }); it('throws an error if no fallback is provided', async () => { const mockApi3ServerV1 = generateMockApi3ServerV1(); - mockApi3ServerV1.estimateGas.multicall.mockRejectedValue(new Error('some-error')); + mockApi3ServerV1.multicall.estimateGas.mockRejectedValue(new Error('some-error')); await expect(async () => submitTransactionsModule.estimateMulticallGasLimit( @@ -300,7 +300,7 @@ describe(submitTransactionsModule.submitTransactions.name, () => { await submitTransactionsModule.submitTransactions( '31337', 'evm-local', - new ethers.providers.StaticJsonRpcProvider(), + new ethers.JsonRpcProvider(), generateMockApi3ServerV1() as unknown as Api3ServerV1, [ allowPartial({ @@ -324,12 +324,11 @@ describe(submitTransactionsModule.submitTransaction.name, () => { jest.spyOn(submitTransactionsModule, 'createUpdateFeedCalldatas').mockReturnValue(['calldata1', 'calldata2']); jest.spyOn(logger, 'debug'); jest.spyOn(logger, 'info'); - jest.spyOn(submitTransactionsModule, 'estimateMulticallGasLimit').mockResolvedValue(ethers.BigNumber.from(500_000)); - jest.spyOn(gasPriceModule, 'getRecommendedGasPrice').mockResolvedValue(ethers.BigNumber.from(100_000_000)); + jest.spyOn(submitTransactionsModule, 'estimateMulticallGasLimit').mockResolvedValue(BigInt(500_000)); + jest.spyOn(gasPriceModule, 'getRecommendedGasPrice').mockResolvedValue(BigInt(100_000_000)); jest.spyOn(submitTransactionsModule, 'hasSponsorPendingTransaction').mockReturnValue(false); const api3ServerV1 = generateMockApi3ServerV1(); jest.spyOn(api3ServerV1, 'connect').mockReturnValue(api3ServerV1); - jest.spyOn(api3ServerV1, 'tryMulticall'); jest.spyOn(stateModule, 'getState').mockReturnValue( allowPartial({ config: { @@ -346,7 +345,7 @@ describe(submitTransactionsModule.submitTransaction.name, () => { jest.spyOn(stateModule, 'updateState').mockImplementation(); const provider = { getTransactionCount: jest.fn().mockResolvedValue(0), - } as unknown as ethers.providers.StaticJsonRpcProvider; + } as unknown as ethers.JsonRpcProvider; await submitTransactionsModule.submitTransaction( '31337', diff --git a/src/update-feeds-loops/submit-transactions.ts b/src/update-feeds-loops/submit-transactions.ts index efb5eb1b..85f1fb26 100644 --- a/src/update-feeds-loops/submit-transactions.ts +++ b/src/update-feeds-loops/submit-transactions.ts @@ -1,10 +1,10 @@ -import type { Api3ServerV1 } from '@api3/airnode-protocol-v1'; import { go } from '@api3/promise-utils'; import { ethers } from 'ethers'; import { getRecommendedGasPrice, setSponsorLastUpdateTimestamp } from '../gas-price'; import { logger } from '../logger'; import { getState, updateState } from '../state'; +import type { Api3ServerV1 } from '../typechain-types'; import type { ChainId, DapiNameOrDataFeedId, ProviderName } from '../types'; import { deriveSponsorWallet } from '../utils'; @@ -45,7 +45,7 @@ export const hasSponsorPendingTransaction = (chainId: string, providerName: stri export const submitTransaction = async ( chainId: ChainId, providerName: ProviderName, - provider: ethers.providers.StaticJsonRpcProvider, + provider: ethers.JsonRpcProvider, api3ServerV1: Api3ServerV1, updatableDataFeed: UpdatableDataFeed, blockNumber: number @@ -111,8 +111,8 @@ export const submitTransaction = async ( api3ServerV1 // When we add the sponsor wallet (signer) without connecting it to the provider, the provider of the // contract will be set to "null". We need to connect the sponsor wallet to the provider of the contract. - .connect(sponsorWallet.connect(api3ServerV1.provider)) - .tryMulticall(dataFeedUpdateCalldatas, { gasPrice, gasLimit, nonce }) + .connect(sponsorWallet.connect(provider)) + .tryMulticall.send(dataFeedUpdateCalldatas, { gasPrice, gasLimit, nonce }) ); }); if (!goMulticall.success) { @@ -144,7 +144,7 @@ export const submitTransaction = async ( export const submitTransactions = async ( chainId: ChainId, providerName: ProviderName, - provider: ethers.providers.StaticJsonRpcProvider, + provider: ethers.JsonRpcProvider, api3ServerV1: Api3ServerV1, updatableDataFeeds: UpdatableDataFeed[], blockNumber: number @@ -160,10 +160,10 @@ export const estimateMulticallGasLimit = async ( calldatas: string[], fallbackGasLimit: number | undefined ) => { - const goEstimateGas = await go(async () => api3ServerV1.estimateGas.multicall(calldatas)); + const goEstimateGas = await go(async () => api3ServerV1.multicall.estimateGas(calldatas)); if (goEstimateGas.success) { // Adding a extra 10% because multicall consumes less gas than tryMulticall - return goEstimateGas.data.mul(ethers.BigNumber.from(Math.round(1.1 * 100))).div(ethers.BigNumber.from(100)); + return (goEstimateGas.data * BigInt(Math.round(1.1 * 100))) / 100n; } logger.warn(`Unable to estimate gas for multicall using provider.`, goEstimateGas.error); @@ -171,7 +171,7 @@ export const estimateMulticallGasLimit = async ( throw new Error('Unable to estimate gas limit'); } - return ethers.BigNumber.from(fallbackGasLimit); + return BigInt(fallbackGasLimit); }; export const getDerivedSponsorWallet = (sponsorWalletMnemonic: string, dapiNameOrDataFeedId: DapiNameOrDataFeedId) => { diff --git a/src/update-feeds-loops/update-feeds-loops.test.ts b/src/update-feeds-loops/update-feeds-loops.test.ts index ceb1ea77..43cccf1f 100644 --- a/src/update-feeds-loops/update-feeds-loops.test.ts +++ b/src/update-feeds-loops/update-feeds-loops.test.ts @@ -17,7 +17,7 @@ import * as updateFeedsLoopsModule from './update-feeds-loops'; const chainId = '31337'; const rpcUrl = 'http://127.0.0.1:8545/'; -const provider = new ethers.providers.StaticJsonRpcProvider(rpcUrl, { +const provider = new ethers.JsonRpcProvider(rpcUrl, { chainId: Number.parseInt(chainId, 10), name: chainId, }); @@ -150,7 +150,7 @@ describe(updateFeedsLoopsModule.runUpdateFeeds.name, () => { jest .spyOn(contractsModule, 'getAirseekerRegistry') .mockReturnValue(airseekerRegistry as unknown as AirseekerRegistry); - airseekerRegistry.callStatic.tryMulticall.mockRejectedValueOnce(new Error('provider-error')); + airseekerRegistry.tryMulticall.staticCall.mockRejectedValueOnce(new Error('provider-error')); jest.spyOn(logger, 'error'); await updateFeedsLoopsModule.runUpdateFeeds( @@ -193,17 +193,17 @@ describe(updateFeedsLoopsModule.runUpdateFeeds.name, () => { .spyOn(contractsModule, 'getAirseekerRegistry') .mockReturnValue(airseekerRegistry as unknown as AirseekerRegistry); airseekerRegistry.interface.decodeFunctionResult.mockImplementation((_fn, value) => value); - const blockNumber = ethers.BigNumber.from(123); - const chainId = ethers.BigNumber.from(31_337); - airseekerRegistry.callStatic.tryMulticall.mockResolvedValueOnce({ + const blockNumber = 123n; + const chainId = BigInt(31_337); + airseekerRegistry.tryMulticall.staticCall.mockResolvedValueOnce({ successes: [true, true, true, true], - returndata: [ethers.BigNumber.from(3), blockNumber, chainId, firstDataFeed], + returndata: [3n, blockNumber, chainId, firstDataFeed], }); - airseekerRegistry.callStatic.tryMulticall.mockResolvedValueOnce({ + airseekerRegistry.tryMulticall.staticCall.mockResolvedValueOnce({ successes: [true, true, false], returndata: [blockNumber, chainId, '0x'], }); - airseekerRegistry.callStatic.tryMulticall.mockResolvedValueOnce({ + airseekerRegistry.tryMulticall.staticCall.mockResolvedValueOnce({ successes: [true, true, true], returndata: [blockNumber, chainId, thirdDataFeed], }); @@ -307,11 +307,11 @@ describe(updateFeedsLoopsModule.runUpdateFeeds.name, () => { .spyOn(contractsModule, 'getAirseekerRegistry') .mockReturnValue(airseekerRegistry as unknown as AirseekerRegistry); airseekerRegistry.interface.decodeFunctionResult.mockImplementation((_fn, value) => value); - const blockNumber = ethers.BigNumber.from(123); - const chainId = ethers.BigNumber.from(31_337); - airseekerRegistry.callStatic.tryMulticall.mockResolvedValueOnce({ + const blockNumber = 123n; + const chainId = BigInt(31_337); + airseekerRegistry.tryMulticall.staticCall.mockResolvedValueOnce({ successes: [true, true, true, true], - returndata: [ethers.BigNumber.from(1), blockNumber, chainId, dataFeed], + returndata: [1n, blockNumber, chainId, dataFeed], }); const testConfig = generateTestConfig(); jest.spyOn(stateModule, 'getState').mockReturnValue( @@ -361,7 +361,7 @@ describe(updateFeedsLoopsModule.processBatch.name, () => { decodedDataFeed, decodedDapiName: utilsModule.decodeDapiName(dataFeed.dapiName), }; - jest.spyOn(Date, 'now').mockReturnValue(dataFeed.dataFeedTimestamp * 1000); + jest.spyOn(Date, 'now').mockReturnValue(Number(dataFeed.dataFeedTimestamp) * 1000); const testConfig = generateTestConfig(); jest.spyOn(stateModule, 'getState').mockReturnValue( allowPartial({ @@ -371,20 +371,20 @@ describe(updateFeedsLoopsModule.processBatch.name, () => { '0xf5c140bcb4814dfec311d38f6293e86c02d32ba1b7da027fe5b5202cae35dbc6': { airnode: '0xc52EeA00154B4fF1EbbF8Ba39FDe37F1AC3B9Fd4', templateId: '0x457a3b3da67e394a895ea49e534a4d91b2d009477bef15eab8cbed313925b010', - encodedValue: ethers.utils.defaultAbiCoder.encode( + encodedValue: ethers.AbiCoder.defaultAbiCoder().encode( ['int256'], [ - ethers.BigNumber.from( - dataFeed.dataFeedValue + BigInt( + (dataFeed.dataFeedValue * // Multiply the new value by the on chain deviationThresholdInPercentage - .mul(decodedUpdateParameters.deviationThresholdInPercentage.add(1 * 1e8)) - .div(1e8) + (decodedUpdateParameters.deviationThresholdInPercentage + 10n ** 8n)) / + 10n ** 8n ), ] ), signature: '0x0fe25ad7debe4d018aa53acfe56d84f35c8bedf58574611f5569a8d4415e342311c093bfe0648d54e0a02f13987ac4b033b24220880638df9103a60d4f74090b1c', - timestamp: (dataFeed.dataFeedTimestamp + 1).toString(), + timestamp: (dataFeed.dataFeedTimestamp + 1n).toString(), }, }, gasPrices: {}, @@ -394,16 +394,16 @@ describe(updateFeedsLoopsModule.processBatch.name, () => { jest.spyOn(logger, 'info'); jest.spyOn(getUpdatableFeedsModule, 'multicallBeaconValues').mockResolvedValue({ '0xf5c140bcb4814dfec311d38f6293e86c02d32ba1b7da027fe5b5202cae35dbc6': { - timestamp: ethers.BigNumber.from(150), - value: ethers.BigNumber.from('400'), + timestamp: 150n, + value: BigInt('400'), }, '0xf5c140bcb4814dfec311d38f6293e86c02d32ba1b7da027fe5b5202cae35dbc7': { - timestamp: ethers.BigNumber.from(160), - value: ethers.BigNumber.from('500'), + timestamp: 160n, + value: BigInt('500'), }, '0xf5c140bcb4814dfec311d38f6293e86c02d32ba1b7da027fe5b5202cae35dbc8': { - timestamp: ethers.BigNumber.from(170), - value: ethers.BigNumber.from('600'), + timestamp: 170n, + value: BigInt('600'), }, }); diff --git a/src/update-feeds-loops/update-feeds-loops.ts b/src/update-feeds-loops/update-feeds-loops.ts index c95cada2..00de4a47 100644 --- a/src/update-feeds-loops/update-feeds-loops.ts +++ b/src/update-feeds-loops/update-feeds-loops.ts @@ -3,7 +3,6 @@ import { ethers } from 'ethers'; import { isError, range, size, zip } from 'lodash'; import type { Chain } from '../config/schema'; -import { RPC_PROVIDER_TIMEOUT_MS } from '../constants'; import { clearSponsorLastUpdateTimestamp, initializeGasState } from '../gas-price'; import { logger } from '../logger'; import { getState, updateState } from '../state'; @@ -97,7 +96,7 @@ export const readActiveDataFeedBatch = async ( ) ); - let returndatas = verifyMulticallResponse(await airseekerRegistry.callStatic.tryMulticall(calldatas, {})); + let returndatas = verifyMulticallResponse(await airseekerRegistry.tryMulticall.staticCall(calldatas)); let activeDataFeedCountReturndata: string | undefined; if (fromIndex === 0) { activeDataFeedCountReturndata = returndatas[0]!; @@ -126,8 +125,9 @@ export const readActiveDataFeedBatch = async ( const batch = batchReturndata .map((dataFeedReturndata) => decodeActiveDataFeedResponse(airseekerRegistry, dataFeedReturndata)) .filter((dataFeed, dataFeedIndex): dataFeed is DecodedActiveDataFeedResponse => { - logger.warn(`Data feed not registered.`, { dataFeedIndex }); - return dataFeed !== null; + const isRegistered = dataFeed !== null; + if (!isRegistered) logger.warn(`Data feed not registered.`, { dataFeedIndex }); + return isRegistered; }); const blockNumber = decodeGetBlockNumberResponse(getBlockNumberReturndata!); @@ -148,9 +148,8 @@ export const runUpdateFeeds = async (providerName: ProviderName, chain: Chain, c const dataFeedUpdateIntervalMs = dataFeedUpdateInterval * 1000; // Create a provider and connect it to the AirseekerRegistry contract. - const provider = new ethers.providers.StaticJsonRpcProvider({ - url: providers[providerName]!.url, - timeout: RPC_PROVIDER_TIMEOUT_MS, + const provider = new ethers.JsonRpcProvider(providers[providerName]!.url, undefined, { + staticNetwork: true, }); const airseekerRegistry = getAirseekerRegistry(contracts.AirseekerRegistry, provider); @@ -253,7 +252,7 @@ export const runUpdateFeeds = async (providerName: ProviderName, chain: Chain, c export const processBatch = async ( batch: DecodedActiveDataFeedResponse[], providerName: ProviderName, - provider: ethers.providers.StaticJsonRpcProvider, + provider: ethers.JsonRpcProvider, chainId: ChainId, blockNumber: number ) => { diff --git a/src/utils.test.ts b/src/utils.test.ts index 564b9282..328a1cd5 100644 --- a/src/utils.test.ts +++ b/src/utils.test.ts @@ -1,3 +1,5 @@ +import { ethers } from 'hardhat'; + import { deriveSponsorWallet, deriveWalletPathFromSponsorAddress, encodeDapiName } from './utils'; describe(deriveSponsorWallet.name, () => { @@ -32,3 +34,11 @@ describe(deriveWalletPathFromSponsorAddress.name, () => { ); }); }); + +test('ethers compatibility for Wallet.fromMnemonic', () => { + const wallet = ethers.Wallet.fromPhrase( + 'arrange actress together floor menu parade dawn abandon say swear excess museum' + ); + + expect(wallet.address).toBe('0xE1f7E4662F92e5DaDB9529Efb2EE61ec63b028e3'); +}); diff --git a/src/utils.ts b/src/utils.ts index aed91b9c..62e47485 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -3,26 +3,28 @@ import { ethers } from 'ethers'; import { AIRSEEKER_PROTOCOL_ID, INT224_MAX, INT224_MIN } from './constants'; +export const abs = (n: bigint) => (n < 0n ? -n : n); + export const sleep = async (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); export function deriveBeaconId(airnodeAddress: string, templateId: string) { - return goSync(() => ethers.utils.solidityKeccak256(['address', 'bytes32'], [airnodeAddress, templateId])).data; + return goSync(() => ethers.solidityPackedKeccak256(['address', 'bytes32'], [airnodeAddress, templateId])).data; } export function deriveBeaconSetId(beaconIds: string[]) { - return goSync(() => ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(['bytes32[]'], [beaconIds]))).data; + return goSync(() => ethers.keccak256(ethers.AbiCoder.defaultAbiCoder().encode(['bytes32[]'], [beaconIds]))).data; } -export const encodeDapiName = (decodedDapiName: string) => ethers.utils.formatBytes32String(decodedDapiName); +export const encodeDapiName = (decodedDapiName: string) => ethers.encodeBytes32String(decodedDapiName); -export const decodeDapiName = (encodedDapiName: string) => ethers.utils.parseBytes32String(encodedDapiName); +export const decodeDapiName = (encodedDapiName: string) => ethers.decodeBytes32String(encodedDapiName); export function deriveWalletPathFromSponsorAddress(sponsorAddress: string) { - const sponsorAddressBN = ethers.BigNumber.from(sponsorAddress); + const sponsorAddressBN = BigInt(sponsorAddress); const paths = []; for (let i = 0; i < 6; i++) { - const shiftedSponsorAddressBN = sponsorAddressBN.shr(31 * i); - paths.push(shiftedSponsorAddressBN.mask(31).toString()); + const shiftedSponsorAddressBN = sponsorAddressBN >> BigInt(31 * i); + paths.push((shiftedSponsorAddressBN % 2n ** 31n).toString()); } return `${AIRSEEKER_PROTOCOL_ID}/${paths.join('/')}`; } @@ -30,27 +32,28 @@ export function deriveWalletPathFromSponsorAddress(sponsorAddress: string) { export const deriveSponsorWallet = (sponsorWalletMnemonic: string, dapiNameOrDataFeedId: string) => { // Hash the dAPI name or data feed ID because we need to take the first 20 bytes of it which could result in // collisions for dAPIs with the same prefix. - const hashedDapiNameOrDataFeedId = ethers.utils.keccak256(dapiNameOrDataFeedId); + const hashedDapiNameOrDataFeedId = ethers.keccak256(dapiNameOrDataFeedId); // Take first 20 bytes of the hashed dapiName or data feed ID as sponsor address together with the "0x" prefix. - const sponsorAddress = ethers.utils.getAddress(hashedDapiNameOrDataFeedId.slice(0, 42)); - const sponsorWallet = ethers.Wallet.fromMnemonic( + const sponsorAddress = ethers.getAddress(hashedDapiNameOrDataFeedId.slice(0, 42)); + // NOTE: Be sure not to use "ethers.Wallet.fromPhrase(sponsorWalletMnemonic).derivePath" because that produces a + // different result. + const sponsorWallet = ethers.HDNodeWallet.fromPhrase( sponsorWalletMnemonic, + undefined, `m/44'/60'/0'/${deriveWalletPathFromSponsorAddress(sponsorAddress)}` ); return sponsorWallet; }; -export const multiplyBigNumber = (bigNumber: ethers.BigNumber, multiplier: number) => - bigNumber.mul(ethers.BigNumber.from(Math.round(multiplier * 100))).div(ethers.BigNumber.from(100)); +export const multiplyBigNumber = (bigNumber: bigint, multiplier: number) => + (bigNumber * BigInt(Math.round(multiplier * 100))) / 100n; // https://github.com/api3dao/airnode-protocol-v1/blob/fa95f043ce4b50e843e407b96f7ae3edcf899c32/contracts/api3-server-v1/DataFeedServer.sol#L132 export const decodeBeaconValue = (encodedBeaconValue: string) => { - const decodedBeaconValue = ethers.BigNumber.from( - ethers.utils.defaultAbiCoder.decode(['int256'], encodedBeaconValue)[0] - ); - if (decodedBeaconValue.gt(INT224_MAX) || decodedBeaconValue.lt(INT224_MIN)) { + const decodedBeaconValue = BigInt(ethers.AbiCoder.defaultAbiCoder().decode(['int256'], encodedBeaconValue)[0]); + if (decodedBeaconValue > INT224_MAX || decodedBeaconValue < INT224_MIN) { return null; } diff --git a/test/e2e/update-feeds.feature.ts b/test/e2e/update-feeds.feature.ts index 9443958f..faede65b 100644 --- a/test/e2e/update-feeds.feature.ts +++ b/test/e2e/update-feeds.feature.ts @@ -1,6 +1,3 @@ -import { ethers } from 'ethers'; -import { omit } from 'lodash'; - import { initializeGasState } from '../../src/gas-price'; import { logger } from '../../src/logger'; import * as stateModule from '../../src/state'; @@ -14,8 +11,6 @@ import { generateSignedData } from '../utils'; const chainId = '31337'; const providerName = 'localhost'; -const rpcUrl = 'http://127.0.0.1:8545/'; -const provider = new ethers.providers.StaticJsonRpcProvider(rpcUrl); it('reads blockchain data', async () => { const { config } = await deployAndUpdate(); @@ -41,24 +36,30 @@ it('updates blockchain data', async () => { krakenAirnodeWallet, binanceAirnodeWallet, airseekerWallet, + provider, } = await deployAndUpdate(); initializeState(config); stateModule.updateState((draft) => { - draft.config.sponsorWalletMnemonic = airseekerWallet.mnemonic.phrase; + draft.config.sponsorWalletMnemonic = airseekerWallet.mnemonic!.phrase; }); initializeGasState(chainId, providerName); - const btcDataFeed = await airseekerRegistry.activeDataFeed(0); + const { dataFeedId, dapiName, dataFeedDetails, dataFeedValue, dataFeedTimestamp, updateParameters, signedApiUrls } = + await airseekerRegistry.activeDataFeed(0); - const decodedDataFeed = decodeDataFeedDetails(btcDataFeed.dataFeedDetails)!; + const decodedDataFeed = decodeDataFeedDetails(dataFeedDetails)!; const activeBtcDataFeed = { - ...omit(btcDataFeed, ['dataFeedDetails', 'updateParameters']), - decodedUpdateParameters: decodeUpdateParameters(btcDataFeed.updateParameters), + dapiName, + dataFeedId, + dataFeedValue, + dataFeedTimestamp, + signedApiUrls, + decodedUpdateParameters: decodeUpdateParameters(updateParameters), decodedDataFeed, - decodedDapiName: decodeDapiName(btcDataFeed.dapiName), + decodedDapiName: decodeDapiName(dapiName), }; - const currentBlock = await airseekerRegistry.provider.getBlock('latest'); - const currentBlockTimestamp = currentBlock.timestamp; + const currentBlock = await provider.getBlock('latest'); + const currentBlockTimestamp = currentBlock!.timestamp; const binanceBtcSignedData = await generateSignedData( binanceAirnodeWallet, binanceBtcBeacon.templateId, diff --git a/test/fixtures/mock-contract.ts b/test/fixtures/mock-contract.ts index 24766a76..af4a1346 100644 --- a/test/fixtures/mock-contract.ts +++ b/test/fixtures/mock-contract.ts @@ -1,18 +1,17 @@ -import type { Api3ServerV1 } from '@api3/airnode-protocol-v1'; import { ethers } from 'ethers'; -import type { AirseekerRegistry } from '../../src/typechain-types'; +import type { AirseekerRegistry, Api3ServerV1 } from '../../src/typechain-types'; import { encodeDapiName } from '../../src/utils'; import { type DeepPartial, encodeBeaconDetails } from '../utils'; export const generateActiveDataFeedResponse = () => ({ dapiName: encodeDapiName('MOCK_FEED'), - updateParameters: ethers.utils.defaultAbiCoder.encode( + updateParameters: ethers.AbiCoder.defaultAbiCoder().encode( ['uint256', 'int224', 'uint256'], [0.5 * 1e8, 0.5 * 1e8, 100] // deviationThresholdInPercentage, deviationReference, heartbeatInterval ), - dataFeedValue: ethers.BigNumber.from(123 * 1e6), - dataFeedTimestamp: 1_629_811_200, + dataFeedValue: BigInt(123 * 1e6), + dataFeedTimestamp: 1_629_811_200n, dataFeedDetails: encodeBeaconDetails({ beaconId: '0xf5c140bcb4814dfec311d38f6293e86c02d32ba1b7da027fe5b5202cae35dbc6', airnodeAddress: '0xc52EeA00154B4fF1EbbF8Ba39FDe37F1AC3B9Fd4', @@ -27,10 +26,7 @@ export const generateMockAirseekerRegistry = () => { encodeFunctionData: jest.fn(), decodeFunctionResult: jest.fn(), }, - callStatic: { - tryMulticall: jest.fn(), - }, - tryMulticall: jest.fn(), + tryMulticall: { staticCall: jest.fn(), send: jest.fn() }, activeDataFeed: jest.fn(), activeDataFeedCount: jest.fn(), } satisfies DeepPartial; @@ -38,15 +34,13 @@ export const generateMockAirseekerRegistry = () => { export const generateMockApi3ServerV1 = () => { return { - estimateGas: { - multicall: jest.fn(), - updateBeaconWithSignedData: jest.fn(), - updateBeaconSetWithBeacons: jest.fn(), - }, + multicall: { estimateGas: jest.fn() }, + updateBeaconWithSignedData: { estimateGas: jest.fn() }, + updateBeaconSetWithBeacons: { estimateGas: jest.fn() }, interface: { encodeFunctionData: jest.fn(), }, connect: jest.fn(), - tryMulticall: jest.fn(), + tryMulticall: { staticCall: jest.fn(), send: jest.fn() }, } satisfies DeepPartial; }; diff --git a/test/setup/contract.ts b/test/setup/contract.ts index f0cff089..86de356c 100644 --- a/test/setup/contract.ts +++ b/test/setup/contract.ts @@ -1,14 +1,13 @@ import { encode } from '@api3/airnode-abi'; +import type { HDNodeWallet, JsonRpcProvider, Signer } from 'ethers'; +import { ethers } from 'hardhat'; + import { + Api3ServerV1__factory as Api3ServerV1Factory, AccessControlRegistry__factory as AccessControlRegistryFactory, + AirseekerRegistry__factory as AirseekerRegistryFactory, type Api3ServerV1, - Api3ServerV1__factory as Api3ServerV1Factory, -} from '@api3/airnode-protocol-v1'; -import type { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; -import type { Signer, Wallet } from 'ethers'; -import { ethers } from 'hardhat'; - -import { AirseekerRegistry__factory as AirseekerRegistryFactory } from '../../src/typechain-types'; +} from '../../src/typechain-types'; import { deriveBeaconId, deriveSponsorWallet, encodeDapiName } from '../../src/utils'; import { generateTestConfig } from '../fixtures/mock-config'; import { signData } from '../utils'; @@ -89,37 +88,37 @@ interface RawBeaconData { const deriveBeaconData = (beaconData: RawBeaconData) => { const { endpoint, templateParameters, airnodeAddress } = beaconData; - const endpointId = ethers.utils.keccak256( - ethers.utils.defaultAbiCoder.encode(['string', 'string'], [endpoint.oisTitle, endpoint.endpointName]) + const endpointId = ethers.keccak256( + ethers.AbiCoder.defaultAbiCoder().encode(['string', 'string'], [endpoint.oisTitle, endpoint.endpointName]) ); const encodedParameters = encode(templateParameters); - const templateId = ethers.utils.solidityKeccak256(['bytes32', 'bytes'], [endpointId, encodedParameters]); + const templateId = ethers.solidityPackedKeccak256(['bytes32', 'bytes'], [endpointId, encodedParameters]); const beaconId = deriveBeaconId(airnodeAddress, templateId)!; return { endpointId, templateId, encodedParameters, beaconId }; }; export const deriveRootRole = (managerAddress: string) => { - return ethers.utils.solidityKeccak256(['address'], [managerAddress]); + return ethers.solidityPackedKeccak256(['address'], [managerAddress]); }; export const deriveRole = (adminRole: string, roleDescription: string) => { - return ethers.utils.solidityKeccak256( + return ethers.solidityPackedKeccak256( ['bytes32', 'bytes32'], - [adminRole, ethers.utils.solidityKeccak256(['string'], [roleDescription])] + [adminRole, ethers.solidityPackedKeccak256(['string'], [roleDescription])] ); }; const initializeBeacon = async ( api3ServerV1: Api3ServerV1, - airnodeWallet: Wallet, - sponsorWalletMnemonic: SignerWithAddress | Wallet, + airnodeWallet: HDNodeWallet, + sponsorWalletMnemonic: HDNodeWallet, templateId: string, apiValue: number ) => { - const block = await api3ServerV1.provider.getBlock('latest'); - const dataFeedTimestamp = (block.timestamp + 1).toString(); - const encodedValue = ethers.utils.defaultAbiCoder.encode(['uint224'], [ethers.BigNumber.from(apiValue)]); + const block = await ethers.provider.getBlock('latest'); + const dataFeedTimestamp = (block!.timestamp + 1).toString(); + const encodedValue = ethers.AbiCoder.defaultAbiCoder().encode(['uint224'], [BigInt(apiValue)]); const signature = await signData(airnodeWallet, templateId, dataFeedTimestamp, encodedValue); await api3ServerV1 @@ -136,21 +135,21 @@ export const deployAndUpdate = async () => { const api3ServerV1Factory = new Api3ServerV1Factory(deployerAndManager as Signer); const api3ServerV1AdminRoleDescription = 'Api3ServerV1 admin'; const api3ServerV1 = await api3ServerV1Factory.deploy( - accessControlRegistry.address, + accessControlRegistry.getAddress(), api3ServerV1AdminRoleDescription, deployerAndManager!.address ); const airseekerRegistryFactory = new AirseekerRegistryFactory(deployerAndManager as Signer); const airseekerRegistry = await airseekerRegistryFactory.deploy( await (deployerAndManager as Signer).getAddress(), - api3ServerV1.address + api3ServerV1.getAddress() ); // Initialize special wallet for contract initialization const airseekerInitializationWallet = ethers.Wallet.createRandom().connect(ethers.provider); await walletFunder!.sendTransaction({ to: airseekerInitializationWallet.address, - value: ethers.utils.parseEther('1'), + value: ethers.parseEther('1'), }); // Create templates @@ -200,11 +199,11 @@ export const deployAndUpdate = async () => { .updateBeaconSetWithBeacons([binanceEthBeacon.beaconId, krakenEthBeacon.beaconId], { gasLimit: 500_000 }); // Derive beacon set IDs - const btcBeaconSetId = ethers.utils.keccak256( - ethers.utils.defaultAbiCoder.encode(['bytes32[]'], [[binanceBtcBeacon.beaconId, krakenBtcBeacon.beaconId]]) + const btcBeaconSetId = ethers.keccak256( + ethers.AbiCoder.defaultAbiCoder().encode(['bytes32[]'], [[binanceBtcBeacon.beaconId, krakenBtcBeacon.beaconId]]) ); - const ethBeaconSetId = ethers.utils.keccak256( - ethers.utils.defaultAbiCoder.encode(['bytes32[]'], [[binanceEthBeacon.beaconId, krakenEthBeacon.beaconId]]) + const ethBeaconSetId = ethers.keccak256( + ethers.AbiCoder.defaultAbiCoder().encode(['bytes32[]'], [[binanceEthBeacon.beaconId, krakenEthBeacon.beaconId]]) ); // Set active data feeds and initialize sponsor wallets @@ -215,7 +214,7 @@ export const deployAndUpdate = async () => { const airseekerWallet = ethers.Wallet.createRandom(); await Promise.all( apiTreeValues.map(async ([airnode, url]) => { - return airseekerRegistry.connect(deployerAndManager!).setSignedApiUrl(airnode, url); + return airseekerRegistry.connect(deployerAndManager).setSignedApiUrl(airnode, url); }) ); const dapiInfos = [ @@ -235,40 +234,40 @@ export const deployAndUpdate = async () => { for (const dapiInfo of dapiInfos) { const { airnodes, templateIds, dapiName, beaconSetId } = dapiInfo; - const encodedBeaconSetData = ethers.utils.defaultAbiCoder.encode( + const encodedBeaconSetData = ethers.AbiCoder.defaultAbiCoder().encode( ['address[]', 'bytes32[]'], [airnodes, templateIds] ); - await airseekerRegistry.connect(randomPerson!).registerDataFeed(encodedBeaconSetData); + await airseekerRegistry.connect(randomPerson).registerDataFeed(encodedBeaconSetData); const HUNDRED_PERCENT = 1e8; - const deviationThresholdInPercentage = ethers.BigNumber.from(HUNDRED_PERCENT / 50); // 2% - const deviationReference = ethers.constants.Zero; // Not used in Airseeker V1 - const heartbeatInterval = ethers.BigNumber.from(86_400); // 24 hrs + const deviationThresholdInPercentage = BigInt(HUNDRED_PERCENT / 50); // 2% + const deviationReference = 0n; // Not used in Airseeker V2 + const heartbeatInterval = BigInt(86_400); // 24 hrs await airseekerRegistry - .connect(deployerAndManager!) + .connect(deployerAndManager) .setDapiNameUpdateParameters( dapiName, - ethers.utils.defaultAbiCoder.encode( + ethers.AbiCoder.defaultAbiCoder().encode( ['uint256', 'uint256', 'uint256'], [deviationThresholdInPercentage, deviationReference, heartbeatInterval] ) ); - await api3ServerV1.connect(deployerAndManager!).setDapiName(dapiName, beaconSetId); - await airseekerRegistry.connect(deployerAndManager!).setDapiNameToBeActivated(dapiName); + await api3ServerV1.connect(deployerAndManager).setDapiName(dapiName, beaconSetId); + await airseekerRegistry.connect(deployerAndManager).setDapiNameToBeActivated(dapiName); // Initialize sponsor wallets - const sponsorWallet = deriveSponsorWallet(airseekerWallet.mnemonic.phrase, dapiName); + const sponsorWallet = deriveSponsorWallet(airseekerWallet.mnemonic!.phrase, dapiName); await walletFunder!.sendTransaction({ to: sponsorWallet.address, - value: ethers.utils.parseEther('1'), + value: ethers.parseEther('1'), }); } // Set up config const config = generateTestConfig(); - config.sponsorWalletMnemonic = airseekerWallet.mnemonic.phrase; - config.chains[31_337]!.contracts.Api3ServerV1 = api3ServerV1.address; - config.chains[31_337]!.contracts.AirseekerRegistry = airseekerRegistry.address; + config.sponsorWalletMnemonic = airseekerWallet.mnemonic!.phrase; + config.chains[31_337]!.contracts.Api3ServerV1 = await api3ServerV1.getAddress(); + config.chains[31_337]!.contracts.AirseekerRegistry = await airseekerRegistry.getAddress(); return { accessControlRegistry, @@ -288,5 +287,7 @@ export const deployAndUpdate = async () => { airseekerWallet, config, + + provider: ethers.provider as unknown as JsonRpcProvider, }; }; diff --git a/test/utils.ts b/test/utils.ts index 2a5b80e5..6f9e0950 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -1,15 +1,15 @@ -import { type Wallet, ethers } from 'ethers'; +import { randomBytes } from 'node:crypto'; + +import { ethers, type HDNodeWallet } from 'ethers'; import type { SignedData, Beacon } from '../src/types'; export const signData = async (signer: ethers.Signer, templateId: string, timestamp: string, data: string) => signer.signMessage( - ethers.utils.arrayify( - ethers.utils.solidityKeccak256(['bytes32', 'uint256', 'bytes'], [templateId, timestamp, data]) - ) + ethers.getBytes(ethers.solidityPackedKeccak256(['bytes32', 'uint256', 'bytes'], [templateId, timestamp, data])) ); -export const generateRandomBytes32 = () => ethers.utils.hexlify(ethers.utils.randomBytes(32)); +export const generateRandomBytes = (length: number) => `0x${randomBytes(length).toString('hex')}`; export type DeepPartial = T extends object ? { @@ -24,21 +24,21 @@ export type DeepPartial = T extends object export const allowPartial = (obj: DeepPartial): T => obj as T; export const encodeBeaconDetails = (dataFeed: Beacon) => - ethers.utils.defaultAbiCoder.encode(['address', 'bytes32'], [dataFeed.airnodeAddress, dataFeed.templateId]); + ethers.AbiCoder.defaultAbiCoder().encode(['address', 'bytes32'], [dataFeed.airnodeAddress, dataFeed.templateId]); export const encodeBeaconSetDetails = (dataFeed: Beacon[]) => - ethers.utils.defaultAbiCoder.encode( + ethers.AbiCoder.defaultAbiCoder().encode( ['address[]', 'bytes32[]'], [dataFeed.map((item) => item.airnodeAddress), dataFeed.map((item) => item.templateId)] ); export const generateSignedData = async ( - airnodeWallet: Wallet, + airnodeWallet: HDNodeWallet, templateId: string, dataFeedTimestamp: string, - apiValue = ethers.BigNumber.from(ethers.utils.randomBytes(Math.floor(Math.random() * 27) + 1)) // Fits into uint224. + apiValue = BigInt(generateRandomBytes(Math.floor(Math.random() * 27) + 1)) // Fits into uint224. ): Promise => { - const encodedValue = ethers.utils.defaultAbiCoder.encode(['uint224'], [ethers.BigNumber.from(apiValue)]); + const encodedValue = ethers.AbiCoder.defaultAbiCoder().encode(['uint224'], [BigInt(apiValue)]); const signature = await signData(airnodeWallet, templateId, dataFeedTimestamp, encodedValue); return { airnode: airnodeWallet.address, templateId, timestamp: dataFeedTimestamp, encodedValue, signature };