diff --git a/packages/hardhat-plugin/src/index.ts b/packages/hardhat-plugin/src/index.ts index 83338842e..d4bcef25e 100644 --- a/packages/hardhat-plugin/src/index.ts +++ b/packages/hardhat-plugin/src/index.ts @@ -6,12 +6,12 @@ import { StatusResult, } from "@nomicfoundation/ignition-core"; import { + ensureDir, + pathExists, + readFile, readdirSync, rm, - pathExists, writeJSON, - ensureDir, - readFile, } from "fs-extra"; import { extendConfig, extendEnvironment, scope } from "hardhat/config"; import { NomicLabsHardhatPluginError } from "hardhat/plugins"; @@ -257,6 +257,37 @@ ignitionScope const strategyConfig = hre.config.ignition.strategyConfig?.[strategyName]; try { + try { + await hre.network.provider.send("hardhat_setLedgerOutputEnabled", [ + false, + ]); + + hre.network.provider.once( + "connection_start", + executionEventListener.ledgerConnectionStart + ); + hre.network.provider.once( + "connection_success", + executionEventListener.ledgerConnectionSuccess + ); + hre.network.provider.once( + "connection_failure", + executionEventListener.ledgerConnectionFailure + ); + hre.network.provider.on( + "confirmation_start", + executionEventListener.ledgerConfirmationStart + ); + hre.network.provider.on( + "confirmation_success", + executionEventListener.ledgerConfirmationSuccess + ); + hre.network.provider.on( + "confirmation_failure", + executionEventListener.ledgerConfirmationFailure + ); + } catch {} + const result = await deploy({ config: hre.config.ignition, provider: hre.network.provider, @@ -273,6 +304,37 @@ ignitionScope hre.config.networks[hre.network.name]?.ignition.maxFeePerGasLimit, }); + try { + await hre.network.provider.send("hardhat_setLedgerOutputEnabled", [ + true, + ]); + + hre.network.provider.off( + "connection_start", + executionEventListener.ledgerConnectionStart + ); + hre.network.provider.off( + "connection_success", + executionEventListener.ledgerConnectionSuccess + ); + hre.network.provider.off( + "connection_failure", + executionEventListener.ledgerConnectionFailure + ); + hre.network.provider.off( + "confirmation_start", + executionEventListener.ledgerConfirmationStart + ); + hre.network.provider.off( + "confirmation_success", + executionEventListener.ledgerConfirmationSuccess + ); + hre.network.provider.off( + "confirmation_failure", + executionEventListener.ledgerConfirmationFailure + ); + } catch {} + if (result.type === "SUCCESSFUL_DEPLOYMENT" && verify) { console.log(""); console.log(chalk.bold("Verifying deployed contracts")); diff --git a/packages/hardhat-plugin/src/ui/helpers/calculate-batch-display.ts b/packages/hardhat-plugin/src/ui/helpers/calculate-batch-display.ts index 9d4b5520c..f5fcb2d4c 100644 --- a/packages/hardhat-plugin/src/ui/helpers/calculate-batch-display.ts +++ b/packages/hardhat-plugin/src/ui/helpers/calculate-batch-display.ts @@ -5,7 +5,7 @@ export function calculateBatchDisplay(state: UiState): { height: number; } { const batch = state.batches[state.currentBatch - 1]; - const height = batch.length + 2; + const height = batch.length + (state.ledgerMessageIsDisplayed ? 4 : 2); let text = `Batch #${state.currentBatch}\n`; @@ -16,6 +16,10 @@ export function calculateBatchDisplay(state: UiState): { text += "\n"; + if (state.ledger) { + text += `\n Ledger: ${state.ledgerMessage}\n`; + } + return { text, height }; } diff --git a/packages/hardhat-plugin/src/ui/pretty-event-handler.ts b/packages/hardhat-plugin/src/ui/pretty-event-handler.ts index 84c4ecbda..63ff43ab5 100644 --- a/packages/hardhat-plugin/src/ui/pretty-event-handler.ts +++ b/packages/hardhat-plugin/src/ui/pretty-event-handler.ts @@ -67,9 +67,15 @@ export class PrettyEventHandler implements ExecutionEventListener { maxFeeBumps: 0, gasBumps: {}, strategy: null, + ledger: false, + ledgerMessage: "", + ledgerMessageIsDisplayed: false, }; - constructor(private _deploymentParams: DeploymentParameters = {}) {} + constructor( + private _deploymentParams: DeploymentParameters = {}, + private _disableOutput = false + ) {} public get state(): UiState { return this._uiState; @@ -309,6 +315,77 @@ export class PrettyEventHandler implements ExecutionEventListener { }; } + public ledgerConnectionStart(): void { + this.state = { + ...this.state, + ledger: true, + ledgerMessage: "Connecting wallet", + }; + + this._redisplayCurrentBatch(); + + this.state = { + ...this.state, + ledgerMessageIsDisplayed: true, + }; + } + + public ledgerConnectionSuccess(): void { + this.state = { + ...this.state, + ledgerMessage: "Wallet connected", + }; + + this._redisplayCurrentBatch(); + } + + public ledgerConnectionFailure(): void { + this.state = { + ...this.state, + ledgerMessage: "Wallet connection failed", + }; + + this._redisplayCurrentBatch(); + } + + public ledgerConfirmationStart(): void { + this.state = { + ...this.state, + ledger: true, + ledgerMessage: "Waiting for confirmation on device", + }; + + this._redisplayCurrentBatch(); + + this.state = { + ...this.state, + ledgerMessageIsDisplayed: true, + }; + } + + public ledgerConfirmationSuccess(): void { + this.state = { + ...this.state, + ledgerMessage: "Transaction approved by device", + }; + + this._redisplayCurrentBatch(); + + this.state = { + ...this.state, + ledger: false, + }; + } + + public ledgerConfirmationFailure(): void { + this.state = { + ...this.state, + ledgerMessage: "Transaction confirmation failed", + }; + + this._redisplayCurrentBatch(); + } + private _setFutureStatusInitializedAndRedisplayBatch({ futureId, }: { @@ -330,6 +407,11 @@ export class PrettyEventHandler implements ExecutionEventListener { futureId, this._getFutureStatusFromEventResult(result) ); + + this.state = { + ...this.state, + ledgerMessageIsDisplayed: false, + }; } private _setFutureStatusAndRedisplayBatch( @@ -454,11 +536,13 @@ export class PrettyEventHandler implements ExecutionEventListener { } private _redisplayCurrentBatch() { - const { height, text: batch } = calculateBatchDisplay(this.state); + if (!this._disableOutput) { + const { height, text: batch } = calculateBatchDisplay(this.state); - this._clearUpToHeight(height); + this._clearUpToHeight(height); - console.log(batch); + console.log(batch); + } } private _clearCurrentLine(): void { diff --git a/packages/hardhat-plugin/src/ui/types.ts b/packages/hardhat-plugin/src/ui/types.ts index 52eada52d..40e805e47 100644 --- a/packages/hardhat-plugin/src/ui/types.ts +++ b/packages/hardhat-plugin/src/ui/types.ts @@ -65,6 +65,9 @@ export interface UiState { maxFeeBumps: number; gasBumps: Record; strategy: string | null; + ledger: boolean; + ledgerMessage: string; + ledgerMessageIsDisplayed: boolean; } export interface AddressMap { diff --git a/packages/hardhat-plugin/test/ui/helpers/calculate-batch-display.ts b/packages/hardhat-plugin/test/ui/helpers/calculate-batch-display.ts index 6dbea878c..f77aa8a1f 100644 --- a/packages/hardhat-plugin/test/ui/helpers/calculate-batch-display.ts +++ b/packages/hardhat-plugin/test/ui/helpers/calculate-batch-display.ts @@ -23,6 +23,9 @@ const exampleState: UiState = { maxFeeBumps: 0, gasBumps: {}, strategy: null, + ledger: false, + ledgerMessage: "", + ledgerMessageIsDisplayed: false, }; describe("ui - calculate batch display", () => { @@ -179,16 +182,44 @@ describe("ui - calculate batch display", () => { expectedText ); }); + + it("should render a batch when using a ledger device", () => { + const expectedText = testFormat(` + Batch #1 + Executing ExampleModule#Token... + + Ledger: Waiting for confirmation on device + `); + + assertBatchText( + [ + { + status: { + type: UiFutureStatusType.UNSTARTED, + }, + futureId: "ExampleModule#Token", + }, + ], + 3, + expectedText, + { + ledger: true, + ledgerMessage: "Waiting for confirmation on device", + } + ); + }); }); function assertBatchText( batch: UiFuture[], expectedHeight: number, - expectedText: string + expectedText: string, + extraState?: Partial ) { const { text: actualText, height } = calculateBatchDisplay({ ...exampleState, batches: [batch], + ...extraState, }); assert.equal(height, expectedHeight); diff --git a/packages/hardhat-plugin/test/ui/helpers/calculate-deploying-module-panel-display.ts b/packages/hardhat-plugin/test/ui/helpers/calculate-deploying-module-panel-display.ts index 0615beb4e..4fe05ab54 100644 --- a/packages/hardhat-plugin/test/ui/helpers/calculate-deploying-module-panel-display.ts +++ b/packages/hardhat-plugin/test/ui/helpers/calculate-deploying-module-panel-display.ts @@ -21,6 +21,9 @@ describe("ui - calculate starting message display", () => { maxFeeBumps: 0, gasBumps: {}, strategy: "basic", + ledger: false, + ledgerMessage: "", + ledgerMessageIsDisplayed: false, }; it("should display the deploying module message", () => { diff --git a/packages/hardhat-plugin/test/ui/pretty-event-handler.ts b/packages/hardhat-plugin/test/ui/pretty-event-handler.ts new file mode 100644 index 000000000..b3274d1a2 --- /dev/null +++ b/packages/hardhat-plugin/test/ui/pretty-event-handler.ts @@ -0,0 +1,72 @@ +import { assert } from "chai"; + +import { PrettyEventHandler } from "../../src/ui/pretty-event-handler"; + +describe("ui - pretty event handler", () => { + describe("ledger", () => { + it("should set a message on connection start", () => { + const eventHandler = new PrettyEventHandler(undefined, true); + + eventHandler.ledgerConnectionStart(); + + assert.equal(eventHandler.state.ledgerMessage, "Connecting wallet"); + assert.isTrue(eventHandler.state.ledger); + assert.isTrue(eventHandler.state.ledgerMessageIsDisplayed); + }); + + it("should set a message on connection success", () => { + const eventHandler = new PrettyEventHandler(undefined, true); + + eventHandler.ledgerConnectionSuccess(); + + assert.equal(eventHandler.state.ledgerMessage, "Wallet connected"); + }); + + it("should set a message on connection failure", () => { + const eventHandler = new PrettyEventHandler(undefined, true); + + eventHandler.ledgerConnectionFailure(); + + assert.equal( + eventHandler.state.ledgerMessage, + "Wallet connection failed" + ); + }); + + it("should set a message on confirmation start", () => { + const eventHandler = new PrettyEventHandler(undefined, true); + + eventHandler.ledgerConfirmationStart(); + + assert.equal( + eventHandler.state.ledgerMessage, + "Waiting for confirmation on device" + ); + assert.isTrue(eventHandler.state.ledger); + assert.isTrue(eventHandler.state.ledgerMessageIsDisplayed); + }); + + it("should set a message on confirmation success", () => { + const eventHandler = new PrettyEventHandler(undefined, true); + + eventHandler.ledgerConfirmationSuccess(); + + assert.equal( + eventHandler.state.ledgerMessage, + "Transaction approved by device" + ); + assert.isFalse(eventHandler.state.ledger); + }); + + it("should set a message on confirmation failure", () => { + const eventHandler = new PrettyEventHandler(undefined, true); + + eventHandler.ledgerConfirmationFailure(); + + assert.equal( + eventHandler.state.ledgerMessage, + "Transaction confirmation failed" + ); + }); + }); +});