Skip to content
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ dist
/typechain
/typechain-types

# hardhat-deploy typed artifacts
/generated

# solidity-coverage files
/coverage
/coverage.json
Expand Down
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ dist
/typechain
/typechain-types

# hardhat-deploy typed artifacts
/generated

# solidity-coverage files
/coverage
/coverage.json
Expand Down
3 changes: 0 additions & 3 deletions .solcover.js

This file was deleted.

26 changes: 9 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,23 +72,13 @@ You'll need to set up a `.env` file in the root of the project to store a mnemon
# Example for Arbitrum Mainnet (replace with your actual provider URL if needed):
# HARDHAT_HTTP_RPC_URL_ARBITRUM_MAINNET=https://arb1.arbitrum.io/rpc

# API keys for block explorers. The @api3/contracts package configures Hardhat
# to use these for contract verification on various networks (including those using
# explorers like Polygonscan, Arbiscan, etc., not just Etherscan.io).
# The environment variable name must follow the pattern:
# ETHERSCAN_API_KEY_<NETWORK_NAME_UPPERCASE_WITH_UNDERSCORES>
# where <NETWORK_NAME_UPPERCASE_WITH_UNDERSCORES> corresponds to a network
# name defined in @api3/contracts (e.g., ETHEREUM_SEPOLIA_TESTNET, POLYGON_MAINNET).

# Example for Sepolia testnet:
ETHERSCAN_API_KEY_ETHEREUM_SEPOLIA_TESTNET=your_etherscan_api_key_for_sepolia
# Example for Polygon Mainnet:
# ETHERSCAN_API_KEY_POLYGON_MAINNET=your_polygonscan_api_key
# Example for Arbitrum Mainnet:
# ETHERSCAN_API_KEY_ARBITRUM_MAINNET=your_arbiscan_api_key
# Etherscan API key for contract verification. The @api3/contracts package uses
# this one key for every chain that Etherscan serves. Chains that use Blockscout
# need no key.
ETHERSCAN_API_KEY=your_etherscan_api_key
```

_Note: This repository uses Hardhat. The `hardhat.config.ts` file relies on these environment variables for proper configuration. Specifically, the `networks` and `etherscan` properties are automatically configured using helper functions from the [`@api3/contracts`](https://github.com/api3dao/contracts/blob/main/src/hardhat-config.ts) package._
_Note: This repository uses Hardhat. The `hardhat.config.ts` file relies on these environment variables for proper configuration. Specifically, the `networks`, `chainDescriptors` and `verify` properties are automatically configured using helper functions from the [`@api3/contracts`](https://github.com/api3dao/contracts/blob/main/src/hardhat-config-v3.ts) package._

### 3. Deploying Contracts

Expand All @@ -98,6 +88,8 @@ To deploy a specific contract, you need to set the `NETWORK` environment variabl

The `NETWORK` variable should be set to a chain name as defined by `@api3/contracts` (e.g., `ethereum-sepolia-testnet`, `polygon-mainnet`, `base-mainnet`). You can find a list of available chain names [here](https://github.com/api3dao/contracts/blob/main/src/generated/chains.ts).

Before it sends transactions to a live network, `hardhat-deploy` shows the current gas price and asks you to confirm. Add `--skip-prompts` to the command to deploy without the confirmation.

**Required Environment Variables per Contract:**

- **`InverseApi3ReaderProxyV1`**:
Expand Down Expand Up @@ -165,10 +157,10 @@ _Note: The specific `pnpm deploy:<ContractName>` scripts for each combinator are

**Deployment Artifacts**: After deployment, contract artifacts (including ABI and deployed address) are saved in the `deployments/<network_name>/` directory.
For example, deploying `InverseApi3ReaderProxyV1` to the `ethereum-sepolia-testnet` network would create an artifact file like `deployments/ethereum-sepolia-testnet/InverseApi3ReaderProxyV1_SomeHash.json`. The `_SomeHash` part is derived from the constructor arguments, allowing multiple instances of the same contract with different configurations to be deployed and tracked.
These artifact files contain the deployed contract address, ABI, and the constructor arguments used for deployment (look for the `"args"` array in the JSON file). This information is crucial for integration and manual verification if needed.
These artifact files contain the deployed contract address, ABI, and the ABI-encoded constructor arguments used for deployment (look for the `"argsData"` field in the JSON file). This information is crucial for integration and manual verification if needed.

**Contract Verification**:
The deployment scripts automatically attempt to verify the contract on the appropriate block explorer (e.g., Etherscan for Ethereum networks, Polygonscan for Polygon) after a successful deployment. This process uses the API keys (e.g., `ETHERSCAN_API_KEY_ETHEREUM_SEPOLIA_TESTNET`, `ETHERSCAN_API_KEY_POLYGON_MAINNET`) configured in your `.env` file, as described in "### 2. Environment Setup".
The deployment scripts automatically attempt to verify the contract on the block explorer that `@api3/contracts` specifies for the network (Etherscan or Blockscout) after a successful deployment. On Etherscan, this process uses the `ETHERSCAN_API_KEY` configured in your `.env` file, as described in "### 2. Environment Setup".
If verification fails during the deployment (e.g., due to network latency or an explorer API issue), you can simply re-run the exact same deployment command. `hardhat-deploy` is idempotent; it will detect that the contract is already deployed with the same arguments and bytecode, skip the deployment step, and only re-attempt the verification.

### 4. Combining Contracts (Advanced Usage)
Expand Down
102 changes: 40 additions & 62 deletions deploy/001_deploy_InverseApi3ReaderProxyV1.ts
Original file line number Diff line number Diff line change
@@ -1,73 +1,51 @@
import type { HardhatRuntimeEnvironment } from 'hardhat/types';
import type { DeploymentsExtension } from 'hardhat-deploy/types';
import { isAddress } from 'ethers';

import { getDeploymentName } from '../src';
import type { Environment } from '../rocketh/config.js';
import { artifacts, deployScript } from '../rocketh/deploy.js';
import { isLocalNetwork, verifyDeployment } from '../rocketh/utils.js';
import { getDeploymentName } from '../src/index.js';

export const CONTRACT_NAME = 'InverseApi3ReaderProxyV1';

const deployMockApi3ReaderProxyV1 = async (deployments: DeploymentsExtension, deployerAddress: string) => {
const { address } = await deployments.deploy('MockApi3ReaderProxyV1', {
from: deployerAddress,
const deployMockApi3ReaderProxyV1 = async (env: Environment) => {
const { address } = await env.deploy('MockApi3ReaderProxyV1', {
account: env.namedAccounts.deployer,
artifact: artifacts.MockApi3ReaderProxyV1,
args: [
'2000000000000000000000', // A mock value (2000e18)
2000n * 10n ** 18n, // A mock value (2000e18)
Math.floor(Date.now() / 1000), // A mock timestamp
],
log: true,
});
return address;
};

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 isLocalNetwork = network.name === 'hardhat' || network.name === 'localhost';

const proxyAddress = isLocalNetwork
? await deployMockApi3ReaderProxyV1(deployments, deployerAddress)
: 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 confirmations = isLocalNetwork ? 1 : 5;
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];
export default deployScript(
async (env) => {
const { deployer } = env.namedAccounts;
env.showMessage(`Deployer address: ${deployer}`);

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

const constructorArgs: [`0x${string}`] = [proxyAddress as `0x${string}`];
const constructorArgTypes = ['address'];

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

const deployment = await env.deploy(deploymentName, {
account: deployer,
artifact: artifacts.InverseApi3ReaderProxyV1,
args: constructorArgs,
});

await verifyDeployment(env, `${deploymentName} (contract type ${CONTRACT_NAME})`, deployment, constructorArgs);
},
{ tags: [CONTRACT_NAME] }
);
106 changes: 43 additions & 63 deletions deploy/002_deploy_NormalizedApi3ReaderProxyV1.ts
Original file line number Diff line number Diff line change
@@ -1,74 +1,54 @@
import type { HardhatRuntimeEnvironment } from 'hardhat/types';
import type { DeploymentsExtension } from 'hardhat-deploy/types';
import { isAddress } from 'ethers';

import { getDeploymentName } from '../src';
import type { Environment } from '../rocketh/config.js';
import { artifacts, deployScript } from '../rocketh/deploy.js';
import { isLocalNetwork, verifyDeployment } from '../rocketh/utils.js';
import { getDeploymentName } from '../src/index.js';

export const CONTRACT_NAME = 'NormalizedApi3ReaderProxyV1';

const deployMockAggregatorV2V3 = async (deployments: DeploymentsExtension, deployerAddress: string) => {
const { address } = await deployments.deploy('MockAggregatorV2V3', {
from: deployerAddress,
const deployMockAggregatorV2V3 = async (env: Environment) => {
const { address } = await env.deploy('MockAggregatorV2V3', {
account: env.namedAccounts.deployer,
artifact: artifacts.MockAggregatorV2V3,
args: [
8, // A mock decimals
'25000000', // A mock value (0.25e8)
Math.floor(Date.now() / 1000), // A mock timestamp
25_000_000n, // A mock value (0.25e8)
BigInt(Math.floor(Date.now() / 1000)), // A mock timestamp
],
log: true,
});
return address;
};

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 isLocalNetwork = network.name === 'hardhat' || network.name === 'localhost';

const feedAddress = isLocalNetwork ? await deployMockAggregatorV2V3(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 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];
export default deployScript(
async (env) => {
const { deployer } = env.namedAccounts;
env.showMessage(`Deployer address: ${deployer}`);

const feedAddress = isLocalNetwork(env) ? await deployMockAggregatorV2V3(env) : process.env.FEED;
if (!feedAddress) {
throw new Error(
'FEED environment variable not set. Please provide the address of the AggregatorV2V3Interface contract.'
);
}
if (!isAddress(feedAddress)) {
throw new Error(`Invalid address provided for FEED: ${feedAddress}`);
}
env.showMessage(`Feed address: ${feedAddress}`);

const constructorArgs: [`0x${string}`] = [feedAddress as `0x${string}`];
const constructorArgTypes = ['address'];

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

const deployment = await env.deploy(deploymentName, {
account: deployer,
artifact: artifacts.NormalizedApi3ReaderProxyV1,
args: constructorArgs,
});

await verifyDeployment(env, `${deploymentName} (contract type ${CONTRACT_NAME})`, deployment, constructorArgs);
},
{ tags: [CONTRACT_NAME] }
);
Loading
Loading