Please note: There is currently no easy UI-way of doing this. Hence this guide requires some technical knowledge.
We use a 0.01 DAI (v1) transfer from a Safe to another account as example. The Safe has 2 owners (1 Metamask, 1 Hardware wallet, i.e. Ledger, Trezor or Keystone) and uses a threshold of 2, i.e. both Metamask and the hardware wallet have to sign.
Off-chain confirmations with Ledger
1. Initiate the transaction via the web interface. In this example the transaction is initiated via Metamask and the second signer is the Ledger. However it should work similar vice versa.
2. Metamask pops up asking us to sign. The message to be signed (EIP712) can be inspected. The fields in the message correspond to the parameters of the execTransaction
method of the core Safe contract.
to
is the token contract.value
is set to0
since no ETH is sent.data
contains the transaction data. The first 4bytes (0xa9059cbb
) determine that this is thetransfer
function. It is not part of this guide to verify that this data is correct, rather that what you are signing with the Ledger is actually what you are seeing on the Safe interface.operation
is set to0
since that specifies that it is a simple EVMCALL
according to the Safe contracts.safeTxGas
is a parameter that determines how much gas forwarded to the inner transaction.baseGas
,gasPrice
,gasToken
andrefundReceiver
are all set to zero since they belong to the Safe's ability to pay a refund for relayed transactions. Please refer to the contract documentation for an explanation of those.
3. You now see a transaction in your transaction list waiting for the other owner (the Ledger) to be signed. Connect your Ledger to the interface and click "Confirm".
4. This part is about off-chain confirmations, so we remove the checkmark that says "Execute transaction". For executions, please refer to the next section.
5. You will now see a message like 5dafc3c8178f6f56e55e8d44bf55201b8e4fe3ab5a700372f837f8e8a5fff943
on your Ledger - How can you know that this is actually the transaction you wanted to make?
6. Open the transaction on our backend by replacing the Safe address and the nonce with the respective value in the following URL: https://safe-transaction-mainnet.safe.global/api/v1/safes/0xA063Cb7CFd8E57c30c788A0572CBbf2129ae56B6/multisig-transactions/?nonce=52. Note that the transaction parameters from above are the same here.
7. Open your Safe on Etherscan: https://etherscan.io/address/0xA063Cb7CFd8E57c30c788A0572CBbf2129ae56B6#readProxyContract. You need it to "Read as proxy". In case that does not exist yet, you can mark it as such via Contract -> More options -> Is this a Proxy?
8. Find the getTransactionHash
method and fill in all the data from Safe transaction service URL from above. Hit "Query" and compare the result with safeTxHash
from the transaction service. They need to be equal. In our case: 0x4ab6e70bdedf5288dc28c03ac3d61603e1fffaf26a1d9c9e1c11a0c2b0274a64
. Learn more about what safeTxHash
in a separate article.
Note: In case you are using the Safe Mobile app, you will see this safeTxHash
in the "advanced" section of the transaction details. On web, this will be added in the future.
9. This safeTxHash
is hashed again with sha256
and the result is displayed on the Ledger.
10. Head to an online hashing tool or any other one of your choice, enter the safeTxHash
(i.e. 4ab6e70bdedf5288dc28c03ac3d61603e1fffaf26a1d9c9e1c11a0c2b0274a64
, Note: WITHOUT the leading 0x
). Make sure that the input type is hex
or bytes
10. The resulting hash is the exact same value that is displayed on your Ledger: 5dafc3c8178f6f56e55e8d44bf55201b8e4fe3ab5a700372f837f8e8a5fff943
Now you have verified that what you are signing actually corresponds to the transaction parameters that have been put in.
The next section describes how to verify Safe transaction executions with a Ledger.
On-chain executions with Ledger
0. On your Ledger, go to the Settings of your Ethereum app and switch "Debug data" to "Displayed" in order to see the full details.
For 1 - 3, please refer to the previous section.
4. Instead of taking out that checkmark to "Execute transaction", we now leave it in.
5. Verify selector
: That's the method being called. For a Safe this is the execTransaction
method. It will always be 6A761202
for current Safe versions.
6. Field 1
is the to
param, i.e. 89d24a6b4ccb1b6faa2625fe562bdd9a23260359
in the example above.
7. Field 2
is the value
, i.e. 0
in our example above. In case it is >0, please note that it is displayed as hexadecimal value, not as decimal value on the Ledger. Consider using a tool like this one to convert between both systems.
8. Field 3
is the offset to the data
(Field 12+
below)
9. Field 4
is operation
, i.e. 0
is most cases.
10. Field 5
is safeTxGas
, i.e. a455
(hex) or 42069
(decimal)
11. Field 6-9
is baseGas
, gasPrice
, gasToken
and refundReceiver
, i.e. 0
in our case.
12: Field 10
is the offset to the signatures
(Field 17+
below)
13. Field 11
is the length of the following data
field.
14. Field 12-16
is the data
, i.e. a9059cbb0000000000000000000000008ed44000983b5789798b43af9d8d9c1e1e2093d0000000000000000000000000000000000000000000000000002386f26fc10000
15. Starting with Field 17
onwards, all required signatures are submitted.
16. Afterwards you review the fees and actually send the transaction.
Note: The actual field numbers will differ depending on the size of the actual transaction and the number of signatures.
Off-chain confirmations with Trezor
Follow the steps 1 - 8 from the section "Off-chain confirmations with Ledger" above.
There is no need for the sha256
(Steps 9 and 10). The Trezor displays the safeTxHash
directly.
On-chain execution with Trezor
Trezor does not allow you to verify the full transaction data. Hence you can only verify the target address (i.e. you Safe) and the transaction fee.
Off-chain confirmations with Keystone
Please follow the Decoding Multi-Signature Transactions article at the Keystone documentation website.