Skip to content

Adds hardhat-deploy scripts for proxy contracts #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
May 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ dist
gas_report

contracts/vendor

# Hardhat deploy artifacts
/deployments
4 changes: 3 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
module.exports = {
extends: ['plugin:@api3/eslint-plugin-commons/universal', 'plugin:@api3/eslint-plugin-commons/jest'],
parserOptions: {
project: ['./tsconfig.json'],
project: './tsconfig.json',
tsconfigRootDir: __dirname,
},
rules: {
camelcase: 'off',
Expand All @@ -26,4 +27,5 @@ module.exports = {
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/require-await': 'off',
},
ignorePatterns: ['typechain-types/*'],
};
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ dist
/coverage.json

gas_report

# Hardhat deploy artifacts
/deployments
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ dist
gas_report

contracts/vendor

# Hardhat deploy artifacts
/deployments
58 changes: 58 additions & 0 deletions deploy/001_deploy_InverseApi3ReaderProxyV1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { HardhatRuntimeEnvironment } from 'hardhat/types';

import { getDeploymentName } from '../src';

export const CONTRACT_NAME = 'InverseApi3ReaderProxyV1';

module.exports = async (hre: HardhatRuntimeEnvironment) => {
const { getUnnamedAccounts, deployments, ethers, network, run } = hre;
const { deploy, log } = deployments;

const [deployerAddress] = await getUnnamedAccounts();
if (!deployerAddress) {
throw new Error('No deployer address found.');
}
log(`Deployer address: ${deployerAddress}`);

const proxyAddress = process.env.PROXY;
if (!proxyAddress) {
throw new Error('PROXY environment variable not set. Please provide the address of the proxy contract.');
}
if (!ethers.isAddress(proxyAddress)) {
throw new Error(`Invalid address provided for PROXY: ${proxyAddress}`);
}
log(`Proxy address: ${proxyAddress}`);

const isLocalNetwork = network.name === 'hardhat' || network.name === 'localhost';

const confirmations = isLocalNetwork ? 1 : 5;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't 5 a bit too much?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On some chains 5 confirmations might take quite a long time but on most it won't. This number was suggested to me by the hardhat-deploy plugin when deploying against ethereum-sepolia-testnet and usually 5 confirmations is a common safety measure for non-local deployments, balancing speed with protection against blockchain reorganizations.

log(`Deployment confirmations: ${confirmations}`);

const constructorArgs = [proxyAddress];
const constructorArgTypes = ['address'];

const deploymentName = getDeploymentName(CONTRACT_NAME, constructorArgTypes, constructorArgs);
log(`Generated deterministic deployment name for this instance: ${deploymentName}`);

const deployment = await deploy(deploymentName, {
contract: CONTRACT_NAME,
from: deployerAddress,
args: constructorArgs,
log: true,
waitConfirmations: confirmations,
});

if (isLocalNetwork) {
log('Skipping verification on local network.');
return;
}

log(
`Attempting verification of ${deploymentName} (contract type ${CONTRACT_NAME}) at ${deployment.address} (already waited for confirmations)...`
);
await run('verify:verify', {
address: deployment.address,
constructorArguments: deployment.args,
});
};
module.exports.tags = [CONTRACT_NAME];
73 changes: 73 additions & 0 deletions deploy/002_deploy_NormalizedApi3ReaderProxyV1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import type { HardhatRuntimeEnvironment } from 'hardhat/types';
import type { DeploymentsExtension } from 'hardhat-deploy/types';

import { getDeploymentName } from '../src';

export const CONTRACT_NAME = 'NormalizedApi3ReaderProxyV1';

const deployTestFeed = async (deployments: DeploymentsExtension, deployerAddress: string) => {
const { address: scaledApi3FeedProxyV1Address } = await deployments.get('ScaledApi3FeedProxyV1').catch(async () => {
return deployments.deploy('ScaledApi3FeedProxyV1', {
from: deployerAddress,
args: ['0x5b0cf2b36a65a6BB085D501B971e4c102B9Cd473', 8],
log: true,
});
});
return scaledApi3FeedProxyV1Address;
};

module.exports = async (hre: HardhatRuntimeEnvironment) => {
const { getUnnamedAccounts, deployments, network, ethers, run } = hre;
const { deploy, log } = deployments;

const [deployerAddress] = await getUnnamedAccounts();
if (!deployerAddress) {
throw new Error('No deployer address found.');
}
log(`Deployer address: ${deployerAddress}`);

const feedAddress =
network.name === 'hardhat' ? await deployTestFeed(deployments, deployerAddress) : process.env.FEED;
if (!feedAddress) {
throw new Error(
'FEED environment variable not set. Please provide the address of the AggregatorV2V3Interface contract.'
);
}
if (!ethers.isAddress(feedAddress)) {
throw new Error(`Invalid address provided for FEED: ${feedAddress}`);
}
log(`Feed address: ${feedAddress}`);

const isLocalNetwork = network.name === 'hardhat' || network.name === 'localhost';

const confirmations = isLocalNetwork ? 1 : 5;
log(`Deployment confirmations: ${confirmations}`);

const constructorArgs = [feedAddress];
const constructorArgTypes = ['address'];

const deploymentName = getDeploymentName(CONTRACT_NAME, constructorArgTypes, constructorArgs);
log(`Generated deterministic deployment name for this instance: ${deploymentName}`);

const deployment = await deploy(deploymentName, {
contract: CONTRACT_NAME,
from: deployerAddress,
args: constructorArgs,
log: true,
waitConfirmations: confirmations,
});

if (isLocalNetwork) {
log('Skipping verification on local network.');
return;
}

log(
`Attempting verification of ${deploymentName} (contract type ${CONTRACT_NAME}) at ${deployment.address} (already waited for confirmations)...`
);
await run('verify:verify', {
address: deployment.address,
constructorArguments: deployment.args,
});
};
module.exports.tags = [CONTRACT_NAME];
67 changes: 67 additions & 0 deletions deploy/003_deploy_ProductApi3ReaderProxyV1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type { HardhatRuntimeEnvironment } from 'hardhat/types';

import { getDeploymentName } from '../src';

export const CONTRACT_NAME = 'ProductApi3ReaderProxyV1';

module.exports = async (hre: HardhatRuntimeEnvironment) => {
const { getUnnamedAccounts, deployments, ethers, network, run } = hre;
const { deploy, log } = deployments;

const [deployerAddress] = await getUnnamedAccounts();
if (!deployerAddress) {
throw new Error('No deployer address found.');
}
log(`Deployer address: ${deployerAddress}`);

const proxy1Address = process.env.PROXY1;
if (!proxy1Address) {
throw new Error('PROXY1 environment variable not set. Please provide the address of the first proxy contract.');
}
if (!ethers.isAddress(proxy1Address)) {
throw new Error(`Invalid address provided for PROXY1: ${proxy1Address}`);
}
log(`Proxy 1 address: ${proxy1Address}`);

const proxy2Address = process.env.PROXY2;
if (!proxy2Address) {
throw new Error('PROXY2 environment variable not set. Please provide the address of the second proxy contract.');
}
if (!ethers.isAddress(proxy2Address)) {
throw new Error(`Invalid address provided for PROXY2: ${proxy2Address}`);
}
log(`Proxy 2 address: ${proxy2Address}`);

const isLocalNetwork = network.name === 'hardhat' || network.name === 'localhost';

const confirmations = isLocalNetwork ? 1 : 5;
log(`Deployment confirmations: ${confirmations}`);

const constructorArgs = [proxy1Address, proxy2Address];
const constructorArgTypes = ['address', 'address'];

const deploymentName = getDeploymentName(CONTRACT_NAME, constructorArgTypes, constructorArgs);
log(`Generated deterministic deployment name for this instance: ${deploymentName}`);

const deployment = await deploy(deploymentName, {
contract: CONTRACT_NAME,
from: deployerAddress,
args: constructorArgs,
log: true,
waitConfirmations: confirmations,
});

if (isLocalNetwork) {
log('Skipping verification on local network.');
return;
}

log(
`Attempting verification of ${deploymentName} (contract type ${CONTRACT_NAME}) at ${deployment.address} (already waited for confirmations)...`
);
await run('verify:verify', {
address: deployment.address,
constructorArguments: deployment.args,
});
};
module.exports.tags = [CONTRACT_NAME];
79 changes: 79 additions & 0 deletions deploy/004_deploy_ScaledApi3FeedProxyV1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import type { HardhatRuntimeEnvironment } from 'hardhat/types';
import type { DeploymentsExtension } from 'hardhat-deploy/types';

import { getDeploymentName } from '../src';

export const CONTRACT_NAME = 'ScaledApi3FeedProxyV1';

const deployTestProxy = async (deployments: DeploymentsExtension, deployerAddress: string) => {
const { address: inverseApi3ReaderProxyV1Address } = await deployments
.get('InverseApi3ReaderProxyV1')
.catch(async () => {
return deployments.deploy('InverseApi3ReaderProxyV1', {
from: deployerAddress,
args: ['0x5b0cf2b36a65a6BB085D501B971e4c102B9Cd473'],
log: true,
});
});
return inverseApi3ReaderProxyV1Address;
};

module.exports = async (hre: HardhatRuntimeEnvironment) => {
const { getUnnamedAccounts, deployments, network, ethers, run } = hre;
const { deploy, log } = deployments;

const [deployerAddress] = await getUnnamedAccounts();
if (!deployerAddress) {
throw new Error('No deployer address found.');
}
log(`Deployer address: ${deployerAddress}`);

if (!process.env.DECIMALS) {
throw new Error('DECIMALS environment variable not set. Please provide the number of decimals to use.');
}
const decimals = Number.parseInt(process.env.DECIMALS, 10);
log(`Decimals: ${decimals}`);

const proxyAddress =
network.name === 'hardhat' ? await deployTestProxy(deployments, deployerAddress) : process.env.PROXY;
if (!proxyAddress) {
throw new Error('PROXY environment variable not set. Please provide the address of the Api3ReaderProxy contract.');
}
if (!ethers.isAddress(proxyAddress)) {
throw new Error(`Invalid address provided for PROXY: ${proxyAddress}`);
}
log(`Proxy address: ${proxyAddress}`);

const isLocalNetwork = network.name === 'hardhat' || network.name === 'localhost';

const confirmations = isLocalNetwork ? 1 : 5;
log(`Deployment confirmations: ${confirmations}`);

const constructorArgs = [proxyAddress, decimals];
const constructorArgTypes = ['address', 'uint8'];

const deploymentName = getDeploymentName(CONTRACT_NAME, constructorArgTypes, constructorArgs);
log(`Generated deterministic deployment name for this instance: ${deploymentName}`);

const deployment = await deploy(deploymentName, {
contract: CONTRACT_NAME,
from: deployerAddress,
args: constructorArgs,
log: true,
waitConfirmations: confirmations,
});

if (isLocalNetwork) {
log('Skipping verification on local network.');
return;
}

log(
`Attempting verification of ${deploymentName} (contract type ${CONTRACT_NAME}) at ${deployment.address} (already waited for confirmations)...`
);
await run('verify:verify', {
address: deployment.address,
constructorArguments: deployment.args,
});
};
module.exports.tags = [CONTRACT_NAME];
48 changes: 48 additions & 0 deletions example.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
MNEMONIC=
ETHERSCAN_API_KEY_APECHAIN=
ETHERSCAN_API_KEY_ARBITRUM_SEPOLIA_TESTNET=
ETHERSCAN_API_KEY_ARBITRUM=
ETHERSCAN_API_KEY_AVALANCHE_TESTNET=
ETHERSCAN_API_KEY_AVALANCHE=
ETHERSCAN_API_KEY_BASE_SEPOLIA_TESTNET=
ETHERSCAN_API_KEY_BASE=
ETHERSCAN_API_KEY_BERACHAIN=
ETHERSCAN_API_KEY_BLAST_SEPOLIA_TESTNET=
ETHERSCAN_API_KEY_BLAST=
ETHERSCAN_API_KEY_BSC_TESTNET=
ETHERSCAN_API_KEY_BSC=
ETHERSCAN_API_KEY_CORE_TESTNET=
ETHERSCAN_API_KEY_CORE=
ETHERSCAN_API_KEY_ETHEREUM_HOLESKY_TESTNET=
ETHERSCAN_API_KEY_ETHEREUM_SEPOLIA_TESTNET=
ETHERSCAN_API_KEY_ETHEREUM=
ETHERSCAN_API_KEY_FRAXTAL_HOLESKY_TESTNET=
ETHERSCAN_API_KEY_FRAXTAL=
ETHERSCAN_API_KEY_GNOSIS=
ETHERSCAN_API_KEY_KROMA_SEPOLIA_TESTNET=
ETHERSCAN_API_KEY_LINEA_SEPOLIA_TESTNET=
ETHERSCAN_API_KEY_LINEA=
ETHERSCAN_API_KEY_MANTLE_SEPOLIA_TESTNET=
ETHERSCAN_API_KEY_MANTLE=
ETHERSCAN_API_KEY_MOONBEAM_TESTNET=
ETHERSCAN_API_KEY_MOONBEAM=
ETHERSCAN_API_KEY_MOONRIVER=
ETHERSCAN_API_KEY_OPBNB_TESTNET=
ETHERSCAN_API_KEY_OPBNB=
ETHERSCAN_API_KEY_OPTIMISM_SEPOLIA_TESTNET=
ETHERSCAN_API_KEY_OPTIMISM=
ETHERSCAN_API_KEY_POLYGON_SEPOLIA_TESTNET=
ETHERSCAN_API_KEY_POLYGON_ZKEVM_SEPOLIA_TESTNET=
ETHERSCAN_API_KEY_POLYGON_ZKEVM=
ETHERSCAN_API_KEY_POLYGON=
ETHERSCAN_API_KEY_SCROLL_SEPOLIA_TESTNET=
ETHERSCAN_API_KEY_SCROLL=
ETHERSCAN_API_KEY_SONIC_TESTNET=
ETHERSCAN_API_KEY_SONIC=
ETHERSCAN_API_KEY_TAIKO_HOLESKY_TESTNET=
ETHERSCAN_API_KEY_TAIKO=
ETHERSCAN_API_KEY_UNICHAIN_SEPOLIA_TESTNET=
ETHERSCAN_API_KEY_UNICHAIN=
ETHERSCAN_API_KEY_WORLD=
ETHERSCAN_API_KEY_ZIRCUIT_SEPOLIA_TESTNET=
ETHERSCAN_API_KEY_ZIRCUIT=
3 changes: 3 additions & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { hardhatConfig } from '@api3/contracts';
import '@nomicfoundation/hardhat-toolbox';
import '@nomicfoundation/hardhat-verify';
import 'hardhat-deploy';
import 'dotenv/config';
import type { HardhatUserConfig } from 'hardhat/config';

const config: HardhatUserConfig = {
Expand Down
Loading