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.
tois the token contract.
valueis set to
0since no ETH is sent.
datacontains the transaction data. The first 4bytes (
0xa9059cbb) determine that this is the
transferfunction. 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.
operationis set to
0since that specifies that it is a simple EVM
CALLaccording to the Safe contracts.
safeTxGasis a parameter that determines how much gas forwarded to the inner transaction.
refundReceiverare 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.
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
4ab6e70bdedf5288dc28c03ac3d61603e1fffaf26a1d9c9e1c11a0c2b0274a64, Note: WITHOUT the leading
0x). Make sure that the input type is
10. The resulting hash is the exact same value that is displayed on your Ledger:
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.
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.
Field 1 is the
to param, i.e.
89d24a6b4ccb1b6faa2625fe562bdd9a23260359 in the example above.
Field 2 is the
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.
Field 3 is the offset to the
Field 12+ below)
Field 4 is
0 is most cases.
Field 5 is
a455 (hex) or
Field 6-9 is
0 in our case.
Field 10 is the offset to the
Field 17+ below)
Field 11 is the length of the following
Field 12-16 is the
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
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.