Example DApp that supports on-/off-chain signing via the Safe.
Please note that all linked repositories and deployments are subject to change.
- Clone this repository.
- Install dependencies by running
yarn install
. - Start the local server by running
yarn start
. - Connect to the custom Safe deployment via the custom WalletConnect Safe App.
To connect a Safe to this DApp, a custom WalletConnect Safe App needs to be added to the Safe interface.
- Run the test DApp by running
yarn start
and click "Connect" to open the WalletConnect modal. - Open your test Safe via this test deployment, connect your wallet and navigate to "Apps" in the sidebar.
- Add
https://pr608--safereactapps.review-react-hr.5afe.dev/wallet-connect/
as a custom App and open it in the UI * - Copy the WalletConnect QR code and enter it into the custom Safe App.
* If you encounter the "The Safe App is taking too long to load, consider refreshing." error, you may need to add %2F
to the end of the URL, e.g.
https://app.safe.global/<SAFE_ADDRESS>/apps?appUrl=https%3A%2F%2Fpr608--safereactapps.review-react-hr.5afe.dev%2Fwallet-connect%2F
After connecting a Safe via WalletConnect, you should see the address of the connected Safe address in the test DApp. To toggle off-chain signing, click "Enable off-chain signing".
By entering a message and clicking "Sign message" or "Sign message in example typed data", the Safe will sign the message off-chain. It is possible to see a list of messages signed off chain in the Safe UI in the "Transactions" section under the "Messages" tab (example here).
The message hash will be automatically populated and can be verified by clicking "Verify signature". A SafeMessage
is only valid once the Safe threshold has been met.
Off-chain signing leverages the Safe transaction service in a similar fashion to transactions.
- A
SafeMessage
is created, signed and proposed to the service. - Other Safe owners sign the message until the threshold is met.
- The message is then valid and its hash can be verified against the contract.
The Safe needs to know whether a DApp supports off-chain signing. In order to turn it on, a custom safe_setSettings
RPC method needs to be called, e.g. using WalletConnect:
type SafeSettings = {
offChainSigning?: boolean
}
const settings: SafeSettings = {
offChainSigning: true,
}
const enableOffChainSigning = async () => {
await connector.sendCustomRequest({
method: 'safe_setSettings',
params: [settings],
})
}
After enabling off-chain signing, all eth_sign
or eth_signTypedData
calls will sign off-chain. When calling either, the following is returned upon successful sign.
{
messageHash: string // `SafeMessage` hash
}
Note: each App "session" needs to enable off-chain signing. If the Safe is closed or the "Apps" section navigated away from, it will default to on-chain signing again.
- Get the message hash by calling
getMessageHash
from the contract or from a successfuleth_sign
oreth_signTypedData
. - Fetch the
SafeMessage
and its signatures from the transaction service from<TRANSACTION_SERVICE>/api/v1/messages/<MESSAGE_HASH>
with the message hash. - Check the validity of the signature by calling
isValidSignature
on the contract.
When a DApp wants to sign a message, e.g. "Hello world", eth_sign
is called with the message as a parameter. When signing off-chain, this original message is hashed and added to a SafeMessage
. It is the SafeMessage
that is signed by the user and proposed to the Safe services.
A SafeMessage
is an EIP-712 structured message, containing the chainId
, verifyingContract
(current Safe address) and hash of the original message. By signing this, the signature is guaranteed to be unique to the current Safe for the original message.
const safeAddress = '0x65c57CC1317a1f728E921Cbf7bC08b3894196D29'
const hashedMessagee = '0x8144a6fa26be252b86456491fbcd43c1de7e022241845ffea1c3df066f7cfede' // Hello world
const SafeMessage = {
domain: {
chainId,
verifyingContract: safeAddress,
},
types: {
SafeMessage: [{ name: 'message', type: 'bytes' }],
},
message: {
message: hashedMessagee,
},
}
Off-chain signing currently only supports Externally Owned Accounts (EOAs). Smart contract wallet support is under development. The progress of which can be followed here.