Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,27 @@ 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:

```solidity
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:

Expand Down
22 changes: 22 additions & 0 deletions src/Safe.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Loading