import { JsonRpcSigner , Web3Provider } from '@ethersproject/providers' ; import GNOSIS_SAFE_CONTRACT_ABI from 'abis/gnosis-safe-contract.json' ; import { keccak256 } from '@ethersproject/keccak256' ; import { toUtf8Bytes } from '@ethersproject/strings' ; import { CONTRACT_ACCOUNT , EXTERNALLY_OWNED_ACCOUNT } from 'types/Wallet' ; import { AbstractConnector } from '@web3-react/abstract-connector' ; import { WalletConnectConnector } from '@web3-react/walletconnect-connector' ; import walletlinkSigner from './walletlinkSigner' ; import { Contract } from '@ethersproject/contracts' ; import { WalletLinkConnector } from '@web3-react/walletlink-connector' ; import { Web3Error } from 'types/Error' ; const GNOSIS_VALID_SIGNATURE_MAGIC_VALUE = '0x1626ba7e' ; const GNOSIS_SAFE_SIGN_MESSAGE_EVENT_NAME = 'SignMsg' ; type EthereumAccountType = typeof EXTERNALLY_OWNED_ACCOUNT | typeof CONTRACT_ACCOUNT ; type Signature = string ; type ContractAccount = { name : string ; id : number ; } ; export const DEFAULT_WALLET_TYPE_ID = 0 ; export const GNOSIS_SAFE_WALLET_TYPE_ID = 1 ; const SUPPORTED_CONTRACT_ACCOUNTS = [ { name : 'Gnosis Safe' , id : GNOSIS_SAFE_WALLET_TYPE_ID } , ] ; /**
 * Checks what type of Ethereum account the wallet is and signs the message accordingly
 */ export async function signMessage ( address , nonce , connector , signer , library ? ) { const accountType = getEthereumAccountType ( connector ) ; if ( accountType === CONTRACT_ACCOUNT ) { return signMessageWithContractAccount ( address , nonce , connector , library ) ; } return signMessageWithEOA ( address , nonce , signer , connector ) ; } export async function signMessageWithEOA ( address , nonce , signer , connector ) { try { if ( connector instanceof WalletConnectConnector ) { return ( await connector . walletConnectProvider . connector . signPersonalMessage ( [ nonce , address , ] ) ) as Signature ; } if ( connector instanceof WalletLinkConnector ) { return await walletlinkSigner ( { connector , nonce , address } ) ; } return await signer . signMessage ( nonce ) ; } catch ( error ) { if ( error instanceof Error ) { throw { code : 'REJECTED_SIGNATURE' , ... error } as Web3Error ; } else if ( error instanceof Object && isRpcSignatureError ( error ) ) { throw { code : 'REJECTED_SIGNATURE' } as Web3Error ; } throw new Error ( 'Unknown error' ) ; } } export async function signMessageWithContractAccount ( address , nonce , connector , library ? ) { try { if ( ! ( connector instanceof WalletConnectConnector ) ) { throw new Error ( 'WalletConnectConnector is required for Contract Accounts' ) ; } if ( ! library ) { throw new Error ( 'Web3Provider is required for Contract Accounts' ) ; } return ( await connector . walletConnectProvider . connector . signPersonalMessage ( [ nonce , address , ] ) ) as Signature ; } catch ( error ) { console . error ( error ) ; if ( error instanceof Error ) { throw { code : 'REJECTED_SIGNATURE' , ... error } as Web3Error ; } else if ( error instanceof Object && isRpcSignatureError ( error ) ) { throw { code : 'REJECTED_SIGNATURE' } as Web3Error ; } throw new Error ( 'Unknown error' ) ; } } export async function listenForGnosisSignature ( address , nonce , library ? ) { const gnosisSafeContract = new Contract ( address , GNOSIS_SAFE_CONTRACT_ABI , library as any ) ; const messageHash = generateMessageHash ( nonce ) ; const listenToGnosisSafeContract = new Promise ( ( resolve ) => { gnosisSafeContract . on ( GNOSIS_SAFE_SIGN_MESSAGE_EVENT_NAME , async ( msgHash ) => { console . log ( 'Gnosis Safe Contract Event: ' , GNOSIS_SAFE_SIGN_MESSAGE_EVENT_NAME ) ; const messageWasSigned = await validateGnosisSignature ( gnosisSafeContract , messageHash ) ; if ( messageWasSigned ) { resolve ( msgHash ) ; } } ) ; } ) ; await listenToGnosisSafeContract ; gnosisSafeContract . removeAllListeners ( GNOSIS_SAFE_SIGN_MESSAGE_EVENT_NAME ) ; } export async function validateGnosisSignature ( gnosisSafeContract , messageHash ) { const magicValue = await gnosisSafeContract . isValidSignature ( messageHash , '0x' ) . catch ( ( error ) => { console . error ( error ) ; return false ; } ) ; return magicValue === GNOSIS_VALID_SIGNATURE_MAGIC_VALUE ; } export async function validateNonceSignedByGnosis ( address , nonce , library ? ) { console . log ( 'Querying Gnosis Safe' ) ; const gnosisSafeContract = new Contract ( address , GNOSIS_SAFE_CONTRACT_ABI , library as any ) ; const messageHash = generateMessageHash ( nonce ) ; return validateGnosisSignature ( gnosisSafeContract , messageHash ) ; } function getEthereumAccountType ( connector ) { if ( ! ( connector instanceof WalletConnectConnector ) ) { return EXTERNALLY_OWNED_ACCOUNT ; } const contractAccount = checkIfContractAccount ( connector ) ; if ( contractAccount ) { return CONTRACT_ACCOUNT ; } return EXTERNALLY_OWNED_ACCOUNT ; } function checkIfContractAccount ( connector ) { const walletName = connector . walletConnectProvider ?. wc ?. peerMeta ?. name ; if ( ! walletName ) { return null ; } const contractAccount = SUPPORTED_CONTRACT_ACCOUNTS . find ( ( contractAccount ) => walletName . startsWith ( contractAccount . name ) ) ; if ( ! contractAccount ) { return null ; } return contractAccount ; } export function getWalletTypeId ( connector ) { if ( ! ( connector instanceof WalletConnectConnector ) ) { return DEFAULT_WALLET_TYPE_ID ; } const contractAccount = checkIfContractAccount ( connector ) ; if ( ! contractAccount ) { return DEFAULT_WALLET_TYPE_ID ; } return contractAccount . id ; } function isRpcSignatureError ( error ) { return typeof error === 'object' && error !== null && 'code' in error && error . code === 4001 ; } function generateMessageHash ( nonce ) { const prependedNonce = `\x19Ethereum Signed Message:\n${ nonce . length }${ nonce }` ; return keccak256 ( toUtf8Bytes ( prependedNonce ) ) ; }