import { getAddress, isAddress, ZeroAddress } from "ethers"; import type { Signer } from "ethers"; import { createSigner, resolveWalletCredential } from "gate-evm-tools/custody"; import { ethers, network } from "hardhat"; import { assertExecutionSignerAddress } from "./signerIdentity"; import type { WorkflowMode } from "./simulation"; const SIMULATION_SIGNER_BALANCE = "0x3635C9ADC5DEA00000"; export interface WorkflowSignerOptions { explicitAddress?: string; taskAddress?: string; useLock?: boolean; } const normalizedSimulationAddress = (value: string | undefined): string | undefined => { if (value === undefined) return undefined; const candidate = value.trim(); if (!isAddress(candidate) || getAddress(candidate) === ZeroAddress) { throw new Error("simulation signer must be a valid non-zero EVM address"); } return getAddress(candidate); }; export const resolveSimulationSignerAddress = (options: WorkflowSignerOptions): string => { const explicitAddress = normalizedSimulationAddress(options.explicitAddress); const taskAddress = options.useLock ? undefined : normalizedSimulationAddress(options.taskAddress); if ( explicitAddress && taskAddress && explicitAddress.toLowerCase() !== taskAddress.toLowerCase() ) { throw new Error("simulation signer sources do not match"); } const selectedAddress = explicitAddress || taskAddress; if (!selectedAddress) { throw new Error("Simulation requires an explicit signer address"); } return selectedAddress; }; export const impersonateWorkflowSimulationSigner = async ( signerAddress: string, ): Promise => { if (network.name !== "hardhat") { throw new Error("Simulation signer impersonation requires --network hardhat"); } await network.provider.request({ method: "hardhat_impersonateAccount", params: [signerAddress], }); await network.provider.request({ method: "hardhat_setBalance", params: [signerAddress, SIMULATION_SIGNER_BALANCE], }); return ethers.getSigner(signerAddress); }; export const resolveWorkflowSigner = async ( mode: WorkflowMode, options: WorkflowSignerOptions = {}, ): Promise => { if (mode === "simulate") { return impersonateWorkflowSimulationSigner(resolveSimulationSignerAddress(options)); } const executionCredential = mode === "execute" ? resolveWalletCredential(process.env) : undefined; if (executionCredential) { const baseUrl = process.env.CUSTODY_API_BASE_URL?.trim() || undefined; if (executionCredential.kind === "custody" && !baseUrl) { throw new Error("Custody wallet mode requires CUSTODY_API_BASE_URL"); } const credential = executionCredential.kind === "private-key" ? executionCredential.privateKey : `custody:${executionCredential.secret}:${executionCredential.address}`; const signer = createSigner({ ethers, credential, provider: ethers.provider, baseUrl, }) as Signer; assertExecutionSignerAddress(await signer.getAddress(), options.taskAddress); return signer; } const [signer] = await ethers.getSigners(); return signer; };