@nomicfoundation/hardhat-viem
Version:
Hardhat plugin for viem
262 lines (236 loc) • 7.11 kB
text/typescript
import type {
EthereumProvider,
HardhatRuntimeEnvironment,
} from "hardhat/types";
import type { Abi, Address, Hex } from "viem";
import type {
DeployContractConfig,
GetContractAtConfig,
GetContractReturnType,
GetTransactionReturnType,
PublicClient,
SendDeploymentTransactionConfig,
WalletClient,
} from "../types";
import { Libraries, resolveBytecodeWithLinkedLibraries } from "./bytecode";
import { getPublicClient, getWalletClients } from "./clients";
import {
DefaultWalletClientNotFoundError,
DeployContractError,
HardhatViemError,
InvalidConfirmationsError,
} from "./errors";
async function getContractAbiAndBytecode(
artifacts: HardhatRuntimeEnvironment["artifacts"],
contractName: string,
libraries: Libraries<Address>
) {
const artifact = await artifacts.readArtifact(contractName);
const bytecode = await resolveBytecodeWithLinkedLibraries(
artifact,
libraries
);
return {
abi: artifact.abi,
bytecode,
};
}
export async function deployContract(
{ artifacts, network }: HardhatRuntimeEnvironment,
contractName: string,
constructorArgs: any[] = [],
config: DeployContractConfig = {}
): Promise<GetContractReturnType> {
const {
client,
confirmations,
libraries = {},
...deployContractParameters
} = config;
const [publicClient, walletClient, { abi, bytecode }] = await Promise.all([
client?.public ?? getPublicClient(network.provider),
client?.wallet ?? getDefaultWalletClient(network.provider, network.name),
getContractAbiAndBytecode(artifacts, contractName, libraries),
]);
return innerDeployContract(
publicClient,
walletClient,
abi,
bytecode,
constructorArgs,
deployContractParameters,
confirmations
);
}
export async function innerDeployContract(
publicClient: PublicClient,
walletClient: WalletClient,
contractAbi: Abi,
contractBytecode: Hex,
constructorArgs: any[],
deployContractParameters: DeployContractConfig = {},
confirmations: number = 1
): Promise<GetContractReturnType> {
let deploymentTxHash: Hex;
// If gasPrice is defined, then maxFeePerGas and maxPriorityFeePerGas
// must be undefined because it's a legaxy tx.
if (deployContractParameters.gasPrice !== undefined) {
deploymentTxHash = await walletClient.deployContract({
abi: contractAbi,
bytecode: contractBytecode,
args: constructorArgs,
...deployContractParameters,
maxFeePerGas: undefined,
maxPriorityFeePerGas: undefined,
});
} else {
deploymentTxHash = await walletClient.deployContract({
abi: contractAbi,
bytecode: contractBytecode,
args: constructorArgs,
...deployContractParameters,
gasPrice: undefined,
});
}
if (confirmations < 0) {
throw new HardhatViemError("Confirmations must be greater than 0.");
}
if (confirmations === 0) {
throw new InvalidConfirmationsError();
}
const { contractAddress } = await publicClient.waitForTransactionReceipt({
hash: deploymentTxHash,
confirmations,
});
if (contractAddress === null || contractAddress === undefined) {
const transaction = await publicClient.getTransaction({
hash: deploymentTxHash,
});
throw new DeployContractError(deploymentTxHash, transaction.blockNumber);
}
const contract = await innerGetContractAt(
publicClient,
walletClient,
contractAbi,
contractAddress
);
return contract;
}
export async function sendDeploymentTransaction(
{ artifacts, network }: HardhatRuntimeEnvironment,
contractName: string,
constructorArgs: any[] = [],
config: SendDeploymentTransactionConfig = {}
): Promise<{
contract: GetContractReturnType;
deploymentTransaction: GetTransactionReturnType;
}> {
const { client, libraries = {}, ...deployContractParameters } = config;
const [publicClient, walletClient, { abi, bytecode }] = await Promise.all([
client?.public ?? getPublicClient(network.provider),
client?.wallet ?? getDefaultWalletClient(network.provider, network.name),
getContractAbiAndBytecode(artifacts, contractName, libraries),
]);
return innerSendDeploymentTransaction(
publicClient,
walletClient,
abi,
bytecode,
constructorArgs,
deployContractParameters
);
}
async function innerSendDeploymentTransaction(
publicClient: PublicClient,
walletClient: WalletClient,
contractAbi: Abi,
contractBytecode: Hex,
constructorArgs: any[],
deployContractParameters: SendDeploymentTransactionConfig = {}
): Promise<{
contract: GetContractReturnType;
deploymentTransaction: GetTransactionReturnType;
}> {
let deploymentTxHash: Hex;
// If gasPrice is defined, then maxFeePerGas and maxPriorityFeePerGas
// must be undefined because it's a legaxy tx.
if (deployContractParameters.gasPrice !== undefined) {
deploymentTxHash = await walletClient.deployContract({
abi: contractAbi,
bytecode: contractBytecode,
args: constructorArgs,
...deployContractParameters,
maxFeePerGas: undefined,
maxPriorityFeePerGas: undefined,
});
} else {
deploymentTxHash = await walletClient.deployContract({
abi: contractAbi,
bytecode: contractBytecode,
args: constructorArgs,
...deployContractParameters,
gasPrice: undefined,
});
}
const deploymentTx = await publicClient.getTransaction({
hash: deploymentTxHash,
});
const { getContractAddress } = await import("viem");
const contractAddress = getContractAddress({
from: walletClient.account.address,
nonce: BigInt(deploymentTx.nonce),
});
const contract = await innerGetContractAt(
publicClient,
walletClient,
contractAbi,
contractAddress
);
return { contract, deploymentTransaction: deploymentTx };
}
export async function getContractAt(
{ artifacts, network }: HardhatRuntimeEnvironment,
contractName: string,
address: Address,
config: GetContractAtConfig = {}
): Promise<GetContractReturnType> {
const [publicClient, walletClient, contractArtifact] = await Promise.all([
config.client?.public ?? getPublicClient(network.provider),
config.client?.wallet ??
getDefaultWalletClient(network.provider, network.name),
artifacts.readArtifact(contractName),
]);
return innerGetContractAt(
publicClient,
walletClient,
contractArtifact.abi,
address
);
}
async function innerGetContractAt(
publicClient: PublicClient,
walletClient: WalletClient,
contractAbi: Abi,
address: Address
): Promise<GetContractReturnType> {
const viem = await import("viem");
const contract = viem.getContract({
address,
client: {
public: publicClient,
wallet: walletClient,
},
abi: contractAbi,
});
return contract;
}
async function getDefaultWalletClient(
provider: EthereumProvider,
networkName: string
): Promise<WalletClient> {
const [defaultWalletClient] = await getWalletClients(provider);
if (defaultWalletClient === undefined) {
throw new DefaultWalletClientNotFoundError(networkName);
}
return defaultWalletClient;
}