import { Contract , Wallet , utils , BigNumber , BigNumberish , Signer , PopulatedTransaction } from "ethers" ; import { TypedDataSigner } from "@ethersproject/abstract-signer" ; import { AddressZero } from "@ethersproject/constants" ; export const EIP_DOMAIN = { EIP712Domain : [ { type : "uint256" , name : "chainId" } , { type : "address" , name : "verifyingContract" } ] } ; export const EIP712_SAFE_TX_TYPE = { SafeTx : [ { type : "address" , name : "to" } , { type : "uint256" , name : "value" } , { type : "bytes" , name : "data" } , { type : "uint8" , name : "operation" } , { type : "uint256" , name : "safeTxGas" } , { type : "uint256" , name : "baseGas" } , { type : "uint256" , name : "gasPrice" } , { type : "address" , name : "gasToken" } , { type : "address" , name : "refundReceiver" } , { type : "uint256" , name : "nonce" } , ] } ; export const EIP712_SAFE_MESSAGE_TYPE = { SafeMessage : [ { type : "bytes" , name : "message" } , ] } ; export interface MetaTransaction { to : string ; value : string | number | BigNumber ; data : string ; operation : number ; } export interface SafeTransaction extends MetaTransaction { safeTxGas : string | number ; baseGas : string | number ; gasPrice : string | number ; gasToken : string ; refundReceiver : string ; nonce : string | number ; } export interface SafeSignature { signer : string ; data : string ; } export const calculateSafeDomainSeparator = ( safe , chainId ) : string => { return utils . _TypedDataEncoder . hashDomain ( { verifyingContract : safe . address , chainId } ) ; } ; export const preimageSafeTransactionHash = ( safe , safeTx , chainId ) : string => { return utils . _TypedDataEncoder . encode ( { verifyingContract : safe . address , chainId } , EIP712_SAFE_TX_TYPE , safeTx ) ; } ; export const calculateSafeTransactionHash = ( safe , safeTx , chainId ) : string => { return utils . _TypedDataEncoder . hash ( { verifyingContract : safe . address , chainId } , EIP712_SAFE_TX_TYPE , safeTx ) ; } ; export const calculateSafeMessageHash = ( safe , message , chainId ) : string => { return utils . _TypedDataEncoder . hash ( { verifyingContract : safe . address , chainId } , EIP712_SAFE_MESSAGE_TYPE , { message } ) ; } ; export const safeApproveHash = async ( signer , safe , safeTx , skipOnChainApproval ? ) : Promise < SafeSignature > => { if ( ! skipOnChainApproval ) { if ( ! signer . provider ) throw Error ( "Provider required for on-chain approval" ) ; const chainId = ( await signer . provider . getNetwork ( ) ) . chainId ; const typedDataHash = utils . arrayify ( calculateSafeTransactionHash ( safe , safeTx , chainId ) ) ; const signerSafe = safe . connect ( signer ) ; await signerSafe . approveHash ( typedDataHash ) ; } const signerAddress = await signer . getAddress ( ) ; return { signer : signerAddress , data : "0x000000000000000000000000" + signerAddress . slice ( 2 ) + "0000000000000000000000000000000000000000000000000000000000000000" + "01" } ; } ; export const safeSignTypedData = async ( signer , safe , safeTx , chainId ? ) : Promise < SafeSignature > => { if ( ! chainId && ! signer . provider ) throw Error ( "Provider required to retrieve chainId" ) ; const cid = chainId || ( await signer . provider ! ! . getNetwork ( ) ) . chainId ; const signerAddress = await signer . getAddress ( ) ; return { signer : signerAddress , data : await signer . _signTypedData ( { verifyingContract : safe . address , chainId : cid } , EIP712_SAFE_TX_TYPE , safeTx ) } ; } ; export const signHash = async ( signer , hash ) : Promise < SafeSignature > => { const typedDataHash = utils . arrayify ( hash ) ; const signerAddress = await signer . getAddress ( ) ; return { signer : signerAddress , data : ( await signer . signMessage ( typedDataHash ) ) . replace ( /1b$/ , "1f" ) . replace ( /1c$/ , "20" ) } ; } ; export const safeSignMessage = async ( signer , safe , safeTx , chainId ? ) : Promise < SafeSignature > => { const cid = chainId || ( await signer . provider ! ! . getNetwork ( ) ) . chainId ; return signHash ( signer , calculateSafeTransactionHash ( safe , safeTx , cid ) ) ; } ; export const buildSignatureBytes = ( signatures ) : string => { signatures . sort ( ( left , right ) => left . signer . toLowerCase ( ) . localeCompare ( right . signer . toLowerCase ( ) ) ) ; let signatureBytes = "0x" ; for ( const sig of signatures ) { signatureBytes += sig . data . slice ( 2 ) ; } return signatureBytes ; } ; export const logGas = async ( message , tx , skip ? ) : Promise < any > => { return tx . then ( async ( result ) => { const receipt = await result . wait ( ) ; if ( ! skip ) console . log ( "           Used" , receipt . gasUsed . toNumber ( ) , `gas for >${ message }<` ) ; return result ; } ) ; } ; export const executeTx = async ( safe , safeTx , signatures , overrides ? ) : Promise < any > => { const signatureBytes = buildSignatureBytes ( signatures ) ; return safe . execTransaction ( safeTx . to , safeTx . value , safeTx . data , safeTx . operation , safeTx . safeTxGas , safeTx . baseGas , safeTx . gasPrice , safeTx . gasToken , safeTx . refundReceiver , signatureBytes , overrides || { } ) ; } ; export const populateExecuteTx = async ( safe , safeTx , signatures , overrides ? ) : Promise < PopulatedTransaction > => { const signatureBytes = buildSignatureBytes ( signatures ) ; return safe . populateTransaction . execTransaction ( safeTx . to , safeTx . value , safeTx . data , safeTx . operation , safeTx . safeTxGas , safeTx . baseGas , safeTx . gasPrice , safeTx . gasToken , safeTx . refundReceiver , signatureBytes , overrides || { } ) ; } ; export const buildContractCall = ( contract , method , params , nonce , delegateCall ? , overrides ? ) : SafeTransaction => { const data = contract . interface . encodeFunctionData ( method , params ) ; return buildSafeTransaction ( Object . assign ( { to : contract . address , data , operation : delegateCall ? 1 : 0 , nonce } , overrides ) ) ; } ; export const executeTxWithSigners = async ( safe , tx , signers , overrides ? ) => { const sigs = await Promise . all ( signers . map ( ( signer ) => safeSignTypedData ( signer , safe , tx ) ) ) ; return executeTx ( safe , tx , sigs , overrides ) ; } ; export const executeContractCallWithSigners = async ( safe , contract , method , params , signers , delegateCall ? , overrides ? ) => { const tx = buildContractCall ( contract , method , params , await safe . nonce ( ) , delegateCall , overrides ) ; return executeTxWithSigners ( safe , tx , signers ) ; } ; export const buildSafeTransaction = ( template ) : SafeTransaction => { return { to : template . to , value : template . value || 0 , data : template . data || "0x" , operation : template . operation || 0 , safeTxGas : template . safeTxGas || 0 , baseGas : template . baseGas || 0 , gasPrice : template . gasPrice || 0 , gasToken : template . gasToken || AddressZero , refundReceiver : template . refundReceiver || AddressZero , nonce : template . nonce } ; } ;