@openzeppelin/hardhat-upgrades
Version:
[](https://docs.openzeppelin.com/upgrades-plugins/hardhat-upgrades) [](https://www.npmjs.org/package/@openzeppelin/h
130 lines • 6.4 kB
JavaScript
import { getAddress, getContract } from 'viem';
import { resolveLinkedBytecode } from '@nomicfoundation/hardhat-utils/bytecode';
import { UpgradesError } from '@openzeppelin/upgrades-core';
import { makeViemBinding } from './viem-binding.js';
/**
* Asserts that the plugins required by the viem-based API are in use for the given connection:
* @nomicfoundation/hardhat-viem, which creates the contract instances that the API returns and
* signs and broadcasts the plugin's transactions.
*/
export function assertRequiredPlugins(connection) {
if (connection === undefined || connection === null) {
throw new UpgradesError('A network connection is required.', () => 'Create a connection with `await hre.network.create()` and pass it to this function.');
}
if (!('viem' in connection)) {
throw new UpgradesError('The viem-based API requires the @nomicfoundation/hardhat-viem plugin.', () => 'Install the @nomicfoundation/hardhat-viem and viem packages, and register @nomicfoundation/hardhat-viem in the `plugins` array of your Hardhat config.');
}
}
export function getContractAddress(addressOrInstance) {
if (typeof addressOrInstance === 'string') {
return asAddress(addressOrInstance);
}
else {
return asAddress(addressOrInstance.address);
}
}
export function isAddress(value) {
return /^0x[0-9a-fA-F]{40}$/.test(value);
}
/**
* Returns the given address as a checksummed viem address, so that the addresses returned by
* the viem-based API are consistently checksummed regardless of the caller's input case.
* Throws if the value is not an address or has an invalid checksum.
*/
export function asAddress(value) {
const checksummed = getAddress(value);
if (!isAddress(checksummed)) {
throw new Error(`Broken invariant: ${value} is not an address`);
}
return checksummed;
}
/**
* Resolves the wallet client whose account signs the transactions sent by the plugin: the given
* wallet client, or by default the first wallet client of the connection. Unlike the previous
* implementation, accounts that sign client-side (viem local accounts) are supported, since the
* plugin now signs through viem itself.
*/
export async function resolveWalletClient(connection, walletClient) {
const resolved = walletClient ?? (await connection.viem.getWalletClients())[0];
if (resolved === undefined) {
throw new UpgradesError('No wallet client is available.', () => 'Provide a wallet client with the `client` option, or configure accounts for the network connection.');
}
if (resolved.account === undefined || resolved.account === null) {
throw new UpgradesError('The wallet client must have an account.', () => 'Use a wallet client from `connection.viem.getWalletClients()` or `connection.viem.getWalletClient(address)`.');
}
return resolved;
}
/**
* Builds the engine binding for the viem-based API from the options' wallet client and transaction
* parameters.
*/
export async function makeBinding(hre, connection, opts = {}) {
const walletClient = await resolveWalletClient(connection, opts.client?.wallet);
return makeViemBinding(hre, connection, walletClient, execOptions(opts));
}
/**
* Builds the engine binding for read-only operations (validations, force-import) that never sign a
* transaction, so they work even when the connection has no accounts. Uses the options' wallet
* client if one is given, otherwise the connection's first wallet client if available.
*/
export async function makeReadBinding(hre, connection, opts = {}) {
const candidate = opts.client?.wallet ?? (await connection.viem.getWalletClients())[0];
const walletClient = candidate?.account ? candidate : undefined;
return makeViemBinding(hre, connection, walletClient, {});
}
/**
* Extracts the transaction parameters from the viem-based options.
*/
export function execOptions(opts) {
return {
value: opts.value,
gas: opts.gas,
gasPrice: opts.gasPrice,
maxFeePerGas: opts.maxFeePerGas,
maxPriorityFeePerGas: opts.maxPriorityFeePerGas,
timeout: opts.timeout,
pollingInterval: opts.pollingInterval,
};
}
/**
* Reads the contract's ABI and library-linked creation bytecode from the project artifacts,
* for use as the engine's client-neutral contract identity.
*/
export async function getContractInfo(hre, contractName, libraries = {}) {
const artifact = await hre.artifacts.readArtifact(contractName);
const bytecode = resolveLinkedBytecode(artifact, libraries);
return { abi: artifact.abi, bytecode };
}
/**
* Reads only a contract's ABI from the project artifacts, for functions that use the ABI to encode
* an initializer call but never deploy the contract's bytecode.
*/
export async function getAbi(hre, contractName) {
const artifact = await hre.artifacts.readArtifact(contractName);
return artifact.abi;
}
/**
* Gets a viem contract instance for the named contract at the given address, using
* `connection.viem.getContractAt` so that the result follows hardhat-viem conventions.
*/
export async function getViemContractAt(connection, contractName, address, client) {
return connection.viem.getContractAt(contractName, address, client !== undefined ? { client } : undefined);
}
/**
* Builds a viem contract instance at `address` for the given ABI, usable for reads even when the
* connection has no accounts. A wallet client (the provided one, or the connection's first account)
* enables writes; without one the instance is read-only — so attaching to an existing contract does
* not require an account, mirroring the ethers-based API. Used by `forceImport`, which records the
* deployment without needing an account and so must be able to return an instance without one.
*/
export async function attachViemContract(connection, abi, address, client) {
const [publicClient, walletClient] = await Promise.all([
client?.public ?? connection.viem.getPublicClient(),
client?.wallet ?? connection.viem.getWalletClients().then(clients => clients[0]),
]);
const contract = walletClient !== undefined
? getContract({ address, abi, client: { public: publicClient, wallet: walletClient } })
: getContract({ address, abi, client: { public: publicClient } });
return contract;
}
//# sourceMappingURL=utils.js.map