UNPKG

@nomicfoundation/hardhat-viem

Version:
270 lines (246 loc) 7.84 kB
import type { ContractReturnType, DeployContractConfig, GetContractAtConfig, GetTransactionReturnType, Libraries, PublicClient, SendDeploymentTransactionConfig, WalletClient, } from "../types.js"; import type { PrefixedHexString } from "@nomicfoundation/hardhat-utils/hex"; import type { ArtifactManager, StringWithArtifactContractNamesAutocompletion, } from "hardhat/types/artifacts"; import type { ChainDescriptorsConfig } from "hardhat/types/config"; import type { EthereumProvider } from "hardhat/types/providers"; import type { Abi as ViemAbi, Address as ViemAddress } from "viem"; import { HardhatError } from "@nomicfoundation/hardhat-errors"; import { toBigInt } from "@nomicfoundation/hardhat-utils/bigint"; import { resolveLinkedBytecode } from "@nomicfoundation/hardhat-utils/bytecode"; import { ensureError } from "@nomicfoundation/hardhat-utils/error"; import { getContractAddress, getContract } from "viem"; import { getDefaultWalletClient, getPublicClient } from "./clients.js"; export async function deployContract< ContractName extends StringWithArtifactContractNamesAutocompletion, >( provider: EthereumProvider, artifactManager: ArtifactManager, chainDescriptors: ChainDescriptorsConfig, networkName: string, contractName: ContractName, constructorArgs: readonly unknown[] = [], deployContractConfig: DeployContractConfig = {}, ): Promise<ContractReturnType<ContractName>> { const { client, confirmations = 1, libraries = {}, ...deployContractParameters } = deployContractConfig; if (confirmations < 0) { throw new HardhatError( HardhatError.ERRORS.HARDHAT_VIEM.GENERAL.INVALID_CONFIRMATIONS, { error: "Confirmations must be greater than 0.", }, ); } if (confirmations === 0) { throw new HardhatError( HardhatError.ERRORS.HARDHAT_VIEM.GENERAL.INVALID_CONFIRMATIONS, { error: "deployContract does not support 0 confirmations. Use sendDeploymentTransaction if you want to handle the deployment transaction yourself.", }, ); } const [publicClient, walletClient, { abi, bytecode }] = await Promise.all([ client?.public ?? getPublicClient(provider, "l1", chainDescriptors, networkName), client?.wallet ?? getDefaultWalletClient(provider, "l1", chainDescriptors, networkName), getContractAbiAndBytecode(artifactManager, contractName, libraries), ]); let deploymentTxHash: PrefixedHexString; // If gasPrice is defined, then maxFeePerGas and maxPriorityFeePerGas // must be undefined because it's a legacy tx. if (deployContractParameters.gasPrice !== undefined) { deploymentTxHash = await walletClient.deployContract({ abi, bytecode, args: constructorArgs, ...deployContractParameters, maxFeePerGas: undefined, maxPriorityFeePerGas: undefined, }); } else { deploymentTxHash = await walletClient.deployContract({ abi, bytecode, args: constructorArgs, ...deployContractParameters, gasPrice: undefined, }); } const { contractAddress } = await publicClient.waitForTransactionReceipt({ hash: deploymentTxHash, confirmations, }); if (contractAddress === null || contractAddress === undefined) { const transaction = await publicClient.getTransaction({ hash: deploymentTxHash, }); throw new HardhatError( HardhatError.ERRORS.HARDHAT_VIEM.GENERAL.DEPLOY_CONTRACT_ERROR, { txHash: deploymentTxHash, blockNumber: transaction.blockNumber, }, ); } const contract = createContractInstance( contractName, publicClient, walletClient, abi, contractAddress, ); return contract; } export async function sendDeploymentTransaction< ContractName extends StringWithArtifactContractNamesAutocompletion, >( provider: EthereumProvider, artifactManager: ArtifactManager, chainDescriptors: ChainDescriptorsConfig, networkName: string, contractName: ContractName, constructorArgs: readonly unknown[] = [], sendDeploymentTransactionConfig: SendDeploymentTransactionConfig = {}, ): Promise<{ contract: ContractReturnType<ContractName>; deploymentTransaction: GetTransactionReturnType; }> { const { client, libraries = {}, ...deployContractParameters } = sendDeploymentTransactionConfig; const [publicClient, walletClient, { abi, bytecode }] = await Promise.all([ client?.public ?? getPublicClient(provider, "l1", chainDescriptors, networkName), client?.wallet ?? getDefaultWalletClient(provider, "l1", chainDescriptors, networkName), getContractAbiAndBytecode(artifactManager, contractName, libraries), ]); let deploymentTxHash: PrefixedHexString; // If gasPrice is defined, then maxFeePerGas and maxPriorityFeePerGas // must be undefined because it's a legacy tx. if (deployContractParameters.gasPrice !== undefined) { deploymentTxHash = await walletClient.deployContract({ abi, bytecode, args: constructorArgs, ...deployContractParameters, maxFeePerGas: undefined, maxPriorityFeePerGas: undefined, }); } else { deploymentTxHash = await walletClient.deployContract({ abi, bytecode, args: constructorArgs, ...deployContractParameters, gasPrice: undefined, }); } const deploymentTx = await publicClient.getTransaction({ hash: deploymentTxHash, }); const contractAddress = getContractAddress({ from: walletClient.account.address, nonce: toBigInt(deploymentTx.nonce), }); const contract = createContractInstance( contractName, publicClient, walletClient, abi, contractAddress, ); return { contract, deploymentTransaction: deploymentTx }; } export async function getContractAt< ContractName extends StringWithArtifactContractNamesAutocompletion, >( provider: EthereumProvider, artifactManager: ArtifactManager, chainDescriptors: ChainDescriptorsConfig, networkName: string, contractName: ContractName, address: ViemAddress, getContractAtConfig: GetContractAtConfig = {}, ): Promise<ContractReturnType<ContractName>> { const [publicClient, walletClient, artifact] = await Promise.all([ getContractAtConfig.client?.public ?? getPublicClient(provider, "l1", chainDescriptors, networkName), getContractAtConfig.client?.wallet ?? getDefaultWalletClient(provider, "l1", chainDescriptors, networkName), artifactManager.readArtifact(contractName), ]); return createContractInstance( contractName, publicClient, walletClient, artifact.abi, address, ); } function createContractInstance< ContractName extends StringWithArtifactContractNamesAutocompletion, >( _contractName: ContractName, publicClient: PublicClient, walletClient: WalletClient, abi: ViemAbi, address: ViemAddress, ): ContractReturnType<ContractName> { const contract = getContract({ address, client: { public: publicClient, wallet: walletClient, }, abi, }); /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Cast it as TS can't infer the type of the contract */ return contract as ContractReturnType<ContractName>; } async function getContractAbiAndBytecode( artifacts: ArtifactManager, contractName: string, libraries: Libraries, ) { const artifact = await artifacts.readArtifact(contractName); let bytecode; try { bytecode = resolveLinkedBytecode(artifact, libraries); } catch (error) { ensureError(error); throw new HardhatError( HardhatError.ERRORS.HARDHAT_VIEM.GENERAL.LINKING_CONTRACT_ERROR, { contractName, error: error.message, }, error, ); } return { abi: artifact.abi, bytecode, }; }