From 7a9ee58342dd067c8341f46a2f9387f5555cfae6 Mon Sep 17 00:00:00 2001 From: chasebrownn Date: Thu, 23 Apr 2026 20:37:39 -0700 Subject: [PATCH] Adding HARDWARE_WALLET conditional to support trezor --- README.md | 12 +++++++++--- src/Safe.sol | 22 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6c3e47b..8e9b85b 100644 --- a/README.md +++ b/README.md @@ -36,13 +36,19 @@ function setUp() public { safe.proposeTransaction(weth, abi.encodeCall(IWETH.withdraw, (0)), sender); ``` -If you are using ledger, make sure to pass the derivation path as the last argument: +If you are using a hardware wallet, make sure to pass the derivation path as the last argument: ```solidity safe.proposeTransaction(weth, abi.encodeCall(IWETH.withdraw, (0)), sender, "m/44'/60'/0'/0/0"); ``` -Proposing a transaction/transactions using a Ledger will also require pre-computing the signature, due to a (current) limitation with forge. +Ledger is the default. To sign with a Trezor instead, set the `HARDWARE_WALLET` environment variable: + +```bash +HARDWARE_WALLET=trezor forge script ... --ffi +``` + +Proposing a transaction/transactions using a hardware wallet will also require pre-computing the signature, due to a (current) limitation with forge. The first step is to pre-compute the signature: @@ -50,7 +56,7 @@ The first step is to pre-compute the signature: bytes memory signature = safe.sign(weth, abi.encodeCall(IWETH.withdraw, (0)), Enum.Operation.Call, sender, "m/44'/60'/0'/0/0"); ``` -Note that this call will fail if `forge script` is called with the `--ledger` flag, as that would block this library's contracts from utilising the same device. Instead, pass the Ledger derivation path as an argument to the script. +Note that this call will fail if `forge script` is called with the `--ledger` or `--trezor` flag, as that would block this library's contracts from utilising the same device. Instead, pass the derivation path as an argument to the script. The second step is to take the value for the returned `bytes` and provide them when proposing the transaction: diff --git a/src/Safe.sol b/src/Safe.sol index 53d843a..3d0dd64 100644 --- a/src/Safe.sol +++ b/src/Safe.sol @@ -449,6 +449,28 @@ library Safe { string memory derivationPath ) internal returns (bytes memory) { if (bytes(derivationPath).length > 0) { + // Hardware wallet selection via HARDWARE_WALLET env var (defaults to ledger) + string memory hardwareWallet = vm.envOr("HARDWARE_WALLET", string("ledger")); + if (keccak256(bytes(hardwareWallet)) == keccak256(bytes("trezor"))) { + // Trezor signs the raw safeTxHash: `cast wallet sign` does not support + // EIP-712 --data with --trezor, so we must pass the hash as the message. + bytes32 safeTxHash = getSafeTxHash(self, to, 0, data, operation, nonce); + string[] memory trezorInputs = new string[](7); + trezorInputs[0] = "cast"; + trezorInputs[1] = "wallet"; + trezorInputs[2] = "sign"; + trezorInputs[3] = "--trezor"; + trezorInputs[4] = "--mnemonic-derivation-path"; + trezorInputs[5] = derivationPath; + trezorInputs[6] = vm.toString(safeTxHash); + /// forge-lint: disable-next-line(unsafe-cheatcode) + bytes memory trezorOutput = vm.ffi(trezorInputs); + // Trezor uses eth_sign, which prepends "\x19Ethereum Signed Message:\n32" + // to the hash. Safe's checkNSignatures detects this via v >= 31, so add 4. + trezorOutput[64] = bytes1(uint8(trezorOutput[64]) + 4); + return trezorOutput; + } + string[] memory inputs = new string[](8); inputs[0] = "cast"; inputs[1] = "wallet";