@nomicfoundation/hardhat-ignition-viem
Version:
The Viem extension to Hardhat Ignition. Hardhat Ignition is a declarative system for deploying smart contracts on Ethereum. It enables you to define smart contract instances you want to deploy, and any operation you want to run on them. By taking over the
415 lines (370 loc) • 12 kB
text/typescript
import type {
ViemIgnitionHelper,
IgnitionModuleResultsToViemContracts,
} from "../types.js";
import type { GetContractReturnType } from "@nomicfoundation/hardhat-viem/types";
import type {
ContractAtFuture,
ContractDeploymentFuture,
ContractFuture,
DeployConfig,
DeploymentParameters,
EIP1193Provider,
Future,
IgnitionModule,
IgnitionModuleResult,
LibraryDeploymentFuture,
NamedArtifactContractAtFuture,
NamedArtifactContractDeploymentFuture,
NamedArtifactLibraryDeploymentFuture,
StrategyConfig,
SuccessfulDeploymentResult,
} from "@nomicfoundation/ignition-core";
import type { ArtifactManager } from "hardhat/types/artifacts";
import type { HardhatConfig } from "hardhat/types/config";
import type { HookManager, UserInterruptionHooks } from "hardhat/types/hooks";
import type { NetworkConnection, ChainType } from "hardhat/types/network";
import type { UserInterruptionManager } from "hardhat/types/user-interruptions";
import path from "node:path";
import {
assertHardhatInvariant,
HardhatError,
} from "@nomicfoundation/hardhat-errors";
import {
HardhatArtifactResolver,
PrettyEventHandler,
errorDeploymentResultToExceptionMessage,
getUserInterruptionsHandlers,
readDeploymentParameters,
resolveDeploymentId,
} from "@nomicfoundation/hardhat-ignition/helpers";
import {
DeploymentResultType,
FutureType,
deploy,
isContractFuture,
} from "@nomicfoundation/ignition-core";
import { getContract } from "viem";
export class ViemIgnitionHelperImpl<ChainTypeT extends ChainType | string>
implements ViemIgnitionHelper
{
public type: "viem" = "viem";
readonly
readonly
readonly
readonly
readonly
readonly
readonly
constructor(
hardhatConfig: HardhatConfig,
artifactsManager: ArtifactManager,
connection: NetworkConnection<ChainTypeT>,
userInterruptions: UserInterruptionManager,
hooks: HookManager,
config?: Partial<DeployConfig> | undefined,
provider?: EIP1193Provider,
) {
this.
this.
this.
this.
this.
this.
this.
}
/**
* Deploys the given Ignition module and returns the results of the module
* as Viem contract instances.
*
* @param ignitionModule - The Ignition module to deploy.
* @param options - The options to use for the deployment.
* @returns Viem contract instances for each contract returned by the module.
*/
public async deploy<
ModuleIdT extends string,
ContractNameT extends string,
IgnitionModuleResultsT extends IgnitionModuleResult<ContractNameT>,
StrategyT extends keyof StrategyConfig = "basic",
>(
ignitionModule: IgnitionModule<
ModuleIdT,
ContractNameT,
IgnitionModuleResultsT
>,
{
parameters = {},
config: perDeployConfig = {},
defaultSender = undefined,
strategy,
strategyConfig,
deploymentId: givenDeploymentId = undefined,
displayUi = false,
}: {
parameters?: DeploymentParameters | string;
config?: Partial<DeployConfig>;
defaultSender?: string;
strategy?: StrategyT;
strategyConfig?: StrategyConfig[StrategyT];
deploymentId?: string;
displayUi?: boolean;
} = {
parameters: {},
config: {},
defaultSender: undefined,
strategy: undefined,
strategyConfig: undefined,
deploymentId: undefined,
displayUi: undefined,
},
): Promise<
IgnitionModuleResultsToViemContracts<ContractNameT, IgnitionModuleResultsT>
> {
if (this.
throw new HardhatError(
HardhatError.ERRORS.IGNITION.DEPLOY.ALREADY_IN_PROGRESS,
);
}
this.
let userInterruptionsHandlers: UserInterruptionHooks | undefined;
try {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- eth_accounts returns a string array
const accounts: string[] = (await this.
method: "eth_accounts",
})) as string[];
const artifactResolver = new HardhatArtifactResolver(
this.
);
const resolvedConfig: Partial<DeployConfig> =
this.getResolvedConfig(perDeployConfig);
const resolvedStrategyConfig =
ViemIgnitionHelperImpl.
this.
strategy,
strategyConfig,
);
const chainId = Number(
await this.
method: "eth_chainId",
}),
);
const deploymentId = resolveDeploymentId(givenDeploymentId, chainId);
const deploymentDir =
this.
? undefined
: path.join(
this.
"deployments",
deploymentId,
);
const executionEventListener = displayUi
? new PrettyEventHandler(this.
: undefined;
if (executionEventListener !== undefined) {
userInterruptionsHandlers = getUserInterruptionsHandlers();
this.
"userInterruptions",
userInterruptionsHandlers,
);
}
let deploymentParameters: DeploymentParameters;
if (typeof parameters === "string") {
deploymentParameters = await readDeploymentParameters(parameters);
} else {
deploymentParameters = parameters;
}
if (
resolvedConfig.maxRetries === undefined &&
this.
) {
resolvedConfig.maxRetries =
this.
}
if (
resolvedConfig.retryInterval === undefined &&
this.
) {
resolvedConfig.retryInterval =
this.
}
const result = await deploy({
config: resolvedConfig,
provider: this.
deploymentDir,
executionEventListener,
artifactResolver,
ignitionModule,
deploymentParameters,
accounts,
defaultSender,
strategy,
strategyConfig: resolvedStrategyConfig,
maxFeePerGasLimit:
this.
maxPriorityFeePerGas:
this.
});
if (result.type !== DeploymentResultType.SUCCESSFUL_DEPLOYMENT) {
const message = errorDeploymentResultToExceptionMessage(result);
throw new HardhatError(
HardhatError.ERRORS.IGNITION.INTERNAL.DEPLOYMENT_ERROR,
{
message,
},
);
}
return await this.
this.
ignitionModule,
result,
);
} finally {
if (userInterruptionsHandlers !== undefined) {
this.
"userInterruptions",
userInterruptionsHandlers,
);
}
this.
}
}
public getResolvedConfig(
perDeployConfig: Partial<DeployConfig>,
): Partial<DeployConfig> {
return {
...this.
...perDeployConfig,
};
}
async
ModuleIdT extends string,
ContractNameT extends string,
IgnitionModuleResultsT extends IgnitionModuleResult<ContractNameT>,
>(
connection: NetworkConnection<ChainTypeT>,
ignitionModule: IgnitionModule<
ModuleIdT,
ContractNameT,
IgnitionModuleResultsT
>,
result: SuccessfulDeploymentResult,
): Promise<
IgnitionModuleResultsToViemContracts<ContractNameT, IgnitionModuleResultsT>
> {
return Object.fromEntries(
await Promise.all(
Object.entries(ignitionModule.results).map(
async ([name, contractFuture]) => [
name,
await this.
connection,
contractFuture,
result.contracts[contractFuture.id],
),
],
),
),
);
}
async
connection: NetworkConnection<ChainTypeT>,
future: Future,
deployedContract: { address: string },
): Promise<GetContractReturnType> {
assertHardhatInvariant(
isContractFuture(future),
`Expected contract future but got ${future.id} with type ${future.type} instead`,
);
return await this.
connection,
future,
deployedContract,
);
}
async
connection: NetworkConnection<ChainTypeT>,
future: ContractFuture<string>,
deployedContract: { address: string },
) {
switch (future.type) {
case FutureType.NAMED_ARTIFACT_CONTRACT_DEPLOYMENT:
case FutureType.NAMED_ARTIFACT_LIBRARY_DEPLOYMENT:
case FutureType.NAMED_ARTIFACT_CONTRACT_AT:
return await this.
connection,
future,
deployedContract,
);
case FutureType.CONTRACT_DEPLOYMENT:
case FutureType.LIBRARY_DEPLOYMENT:
case FutureType.CONTRACT_AT:
return await this.
connection,
future,
deployedContract,
);
}
}
connection: NetworkConnection<ChainTypeT>,
future:
| NamedArtifactContractDeploymentFuture<string>
| NamedArtifactLibraryDeploymentFuture<string>
| NamedArtifactContractAtFuture<string>,
deployedContract: { address: string },
): Promise<GetContractReturnType> {
return connection.viem.getContractAt(
future.contractName,
ViemIgnitionHelperImpl.
);
}
async
connection: NetworkConnection<ChainTypeT>,
future:
| ContractDeploymentFuture
| LibraryDeploymentFuture
| ContractAtFuture,
deployedContract: { address: string },
): Promise<GetContractReturnType> {
const publicClient = await connection.viem.getPublicClient();
const [walletClient] = await connection.viem.getWalletClients();
if (walletClient === undefined) {
throw new HardhatError(
HardhatError.ERRORS.IGNITION.INTERNAL.NO_DEFAULT_VIEM_WALLET_CLIENT,
);
}
const contract = getContract({
address: ViemIgnitionHelperImpl.
deployedContract.address,
),
abi: future.artifact.abi,
client: {
public: publicClient,
wallet: walletClient,
},
});
return contract;
}
static
if (!address.startsWith("0x")) {
return `0x${address}`;
}
return `0x${address.slice(2)}`;
}
static
hardhatConfig: HardhatConfig,
strategyName: StrategyT | undefined,
strategyConfig: StrategyConfig[StrategyT] | undefined,
): StrategyConfig[StrategyT] | undefined {
if (strategyName === undefined) {
return undefined;
}
if (strategyConfig === undefined) {
const fromHardhatConfig =
hardhatConfig.ignition?.strategyConfig?.[strategyName];
return fromHardhatConfig;
}
return strategyConfig;
}
}