@solarity/hardhat-migrate
Version:
The simplest way to deploy smart contracts
155 lines (122 loc) • 4.41 kB
text/typescript
import { Transaction } from "ethers";
import { MigrateError } from "../../errors";
let trezorInitialized = false;
let cachedAddress: string | null = null;
let lastMnemonicIndexAsked: number | null = null;
export type TrezorTx = {
maxFeePerGas?: undefined;
maxPriorityFeePerGas?: undefined;
data?: string | undefined;
txType?: number | undefined;
to: string;
value: string;
gasPrice: string;
gasLimit: string;
nonce: string;
chainId: number;
};
export async function initTrezor(): Promise<void> {
if (trezorInitialized) return;
const TrezorConnect = await getTrezorConnect();
try {
await TrezorConnect.init({
manifest: {
email: "developer@example.com",
appUrl: "https://hardhat.org",
},
debug: false,
lazyLoad: true,
transports: ["BridgeTransport", "NodeUsbTransport"],
});
// eslint-disable-next-line no-console
console.log(`
Trezor Settings Configuration Notice
We're applying two important settings to your Trezor device:
1. Device Passphrase Entry
We've set your Trezor to always request passphrase entry directly on the device itself.
This ensures better security as it's currently not possible to enter passphrases through
the console interface.
2. Temporary Safety Check Override
We're temporarily modifying safety checks with the "PromptTemporarily" setting. This is
necessary because some derivation paths used by our application are incorrectly flagged
as "unknown" by the device firmware (reference: Trezor Suite issue #17203 -
https://github.com/trezor/trezor-suite/issues/17203).
The device will prompt you to confirm these changes.
`);
const result = await TrezorConnect.applySettings({
passphrase_always_on_device: true,
safety_checks: "PromptTemporarily",
});
if (!result.success) {
throw new MigrateError(`Failed to apply Trezor settings: ${result.payload.error}`);
}
trezorInitialized = true;
} catch (error: any) {
throw new MigrateError(`Failed to initialize Trezor connection: ${error.message}`);
}
}
export async function signWithTrezor(tx: Transaction, mnemonicIndex: number = 0): Promise<string> {
if (!trezorInitialized) {
await initTrezor();
}
const trezorTx: TrezorTx = {
to: tx.to!,
value: tx.value?.toString() || "0",
chainId: Number(tx.chainId)!,
gasLimit: tx.gasLimit?.toString() || "0",
gasPrice: tx.gasPrice?.toString()!,
nonce: tx.nonce?.toString() || "0",
data: tx.data || "0x",
};
const TrezorConnect = await getTrezorConnect();
const result = await TrezorConnect.ethereumSignTransaction({
transaction: trezorTx,
path: getKeyPath(mnemonicIndex),
useEmptyPassphrase: true,
});
if (!result.success) {
let errorMessage = `Failed to sign transaction with Trezor: ${result.payload.error}\n`;
if (result.payload.error.includes("Forbidden key path")) {
errorMessage +=
"\n See following GitHub issue on how to resolve the problem: https://github.com/trezor/trezor-suite/issues/17203 \n\n";
}
throw new MigrateError(errorMessage);
}
return result.payload.serializedTx;
}
export async function getTrezorAddress(mnemonicIndex: number = 0): Promise<string> {
if (!trezorInitialized) {
await initTrezor();
}
if (lastMnemonicIndexAsked === mnemonicIndex && cachedAddress) {
return cachedAddress;
}
const TrezorConnect = await getTrezorConnect();
try {
const result = await TrezorConnect.ethereumGetAddress({
path: getKeyPath(mnemonicIndex),
useEmptyPassphrase: true,
});
if (!result.success) {
throw new MigrateError(`Failed to get Trezor address: ${result.payload.error}`);
}
lastMnemonicIndexAsked = mnemonicIndex;
cachedAddress = result.payload.address;
return result.payload.address;
} catch (error: any) {
throw new MigrateError(`Error getting address from Trezor: ${error.message}`);
}
}
function getKeyPath(mnemonicIndex: number): string {
return `m/44'/60'/0'/0/${mnemonicIndex}`;
}
async function getTrezorConnect() {
try {
const mod = await import("@trezor/connect" as any);
return mod.default;
} catch (error: any) {
throw new MigrateError(
`An error occurred while importing @trezor/connect (if the module cannot be found, please install it): ${String(error.message || error)}.`,
);
}
}