@solarity/hardhat-migrate
Version:
The simplest way to deploy smart contracts
171 lines (131 loc) • 5.21 kB
text/typescript
import {
AddressLike,
JsonRpcProvider,
Transaction,
TransactionLike,
TransactionRequest,
TransactionResponse,
VoidSigner,
} from "ethers";
import { HardhatEthersProvider, HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/types";
import { MigrateError } from "../../utils/index.js";
import { getTrezorAddress, initTrezor, signWithTrezor } from "../integrations/trezor-integration.js";
import {
CastSignOptions,
getCastVersion,
getCastWalletAddress,
getSignedTxViaCast,
} from "../integrations/cast-integration.js";
import { networkManager } from "./NetworkManager.js";
import { connection, migratorConfig } from "./EthersProvider.js";
export class ExtendedHardhatEthersSigner {
public readonly provider: JsonRpcProvider | HardhatEthersProvider;
public readonly ethersSigner: VoidSigner | HardhatEthersSigner;
public readonly signerIdentifier: AddressLike;
private _initialized: boolean = false;
constructor(ethersSigner: VoidSigner | HardhatEthersSigner, signerName?: AddressLike) {
this.ethersSigner = ethersSigner;
this.provider = connection!.ethers.provider;
this.signerIdentifier = this._determineSignerIdentifier(signerName);
}
static async fromSignerName(signerName?: AddressLike): Promise<ExtendedHardhatEthersSigner> {
let ethersSigner: VoidSigner | HardhatEthersSigner;
try {
ethersSigner = await networkManager?.getEthersSigner(signerName as any)!;
} catch {
const ethers = await import("ethers");
const address = ethers.isAddress(signerName) ? signerName : ethers.ZeroAddress;
ethersSigner = new ethers.VoidSigner(address, connection!.ethers.provider);
}
return new ExtendedHardhatEthersSigner(ethersSigner, signerName);
}
public async getAddress(): Promise<string> {
if (this._isCastEnabled()) {
return getCastWalletAddress(this._getCastOptions());
}
if (migratorConfig!.trezorWallet.enabled) {
return getTrezorAddress(migratorConfig!.trezorWallet.mnemonicIndex || 0);
}
if ("getAddress" in this.ethersSigner) {
return this.ethersSigner.getAddress();
}
throw new MigrateError("No valid signer configuration found to determine address");
}
public async sendTransaction(tx: TransactionRequest): Promise<TransactionResponse> {
await this._ensureInitialized();
if (!this._isCastEnabled() && !migratorConfig!.trezorWallet.enabled) {
return this.ethersSigner.sendTransaction(tx);
}
const voidSigner = new connection!.ethers.VoidSigner(await this.getAddress(), connection!.ethers.provider);
let preparedTx = await voidSigner.populateTransaction(tx);
if (connection!.networkConfig.gasPrice) {
try {
preparedTx.gasPrice = connection?.ethers.parseUnits(connection!.networkConfig.gasPrice.toString(), "gwei");
} catch {
/* empty */
}
}
if (connection!.networkConfig.gasMultiplier) {
preparedTx.gasLimit = String(
(BigInt(preparedTx.gasLimit!) * BigInt(connection!.networkConfig.gasMultiplier * 100)) / 100n,
);
}
delete preparedTx.from;
if (migratorConfig!.trezorWallet.enabled) {
preparedTx = await this._prepareTrezorTransaction(preparedTx);
}
const signedTx = await this._signTransaction(connection!.ethers.Transaction.from(preparedTx));
return connection!.ethers.provider.broadcastTransaction(signedTx);
}
private _determineSignerIdentifier(signerName?: AddressLike): AddressLike {
if (signerName) {
return signerName;
}
if (this._isCastEnabled()) {
return (migratorConfig!.castWallet.account || migratorConfig!.castWallet.keystore)!;
}
if (migratorConfig!.trezorWallet.enabled) {
return "trezor";
}
return this.ethersSigner.getAddress();
}
private async _ensureInitialized(): Promise<void> {
if (this._initialized) return;
if (migratorConfig!.trezorWallet.enabled) {
await initTrezor();
}
if (this._isCastEnabled()) {
await getCastVersion();
}
this._initialized = true;
}
private async _prepareTrezorTransaction(tx: TransactionLike): Promise<TransactionLike> {
delete tx.maxFeePerBlobGas;
delete tx.maxFeePerGas;
delete tx.maxPriorityFeePerGas;
tx.type = 1;
tx.gasPrice = await connection!.ethers.provider.send("eth_gasPrice", []);
return tx;
}
private async _signTransaction(tx: Transaction): Promise<string> {
if (this._isCastEnabled()) {
return getSignedTxViaCast(tx, this._getCastOptions());
}
if (migratorConfig!.trezorWallet.enabled) {
const mnemonicIndex = migratorConfig!.trezorWallet.mnemonicIndex || 0;
return signWithTrezor(tx, mnemonicIndex);
}
throw new MigrateError("No valid signer configuration found to sign transaction");
}
private _getCastOptions(): CastSignOptions {
const config = migratorConfig!.castWallet;
return {
keystore: config.keystore,
passwordFile: config.passwordFile,
account: config.account,
};
}
private _isCastEnabled(): boolean {
return migratorConfig!.castWallet.account !== undefined || migratorConfig!.castWallet.keystore !== undefined;
}
}