import { BigNumber } from '@ethersproject/bignumber' ; import { MetaTransactionData , OperationType , SafeSignature , SafeTransaction , SafeTransactionDataPartial } from '@gnosis.pm/safe-core-sdk-types' ; import EthAdapter from './ethereumLibs/EthAdapter' ; import ContractManager from './managers/contractManager' ; import ModuleManager from './managers/moduleManager' ; import OwnerManager from './managers/ownerManager' ; import { ContractNetworksConfig } from './types' ; import { isMetaTransactionArray , sameString } from './utils' ; import { generatePreValidatedSignature , generateSignature } from './utils/signatures' ; import { estimateGasForTransactionExecution } from './utils/transactions/gas' ; import EthSafeTransaction from './utils/transactions/SafeTransaction' ; import { SafeTransactionOptionalProps , TransactionOptions , TransactionResult } from './utils/transactions/types' ; import { encodeMultiSendData , standardizeMetaTransactionData , standardizeSafeTransactionData } from './utils/transactions/utils' ; export interface SafeConfig { /** ethAdapter - Ethereum adapter */ ethAdapter : EthAdapter ; /** safeAddress - The address of the Safe account to use */ safeAddress : string ; /** isL1SafeMasterCopy - Forces to use the Gnosis Safe L1 version of the contract instead of the L2 version */ isL1SafeMasterCopy ? : boolean ; /** contractNetworks - Contract network configuration */ contractNetworks ? : ContractNetworksConfig ; } export interface ConnectSafeConfig { /** ethAdapter - Ethereum adapter */ ethAdapter ? : EthAdapter ; /** safeAddress - The address of the Safe account to use */ safeAddress ? : string ; /** isL1SafeMasterCopy - Forces to use the Gnosis Safe L1 version of the contract instead of the L2 version */ isL1SafeMasterCopy ? : boolean ; /** contractNetworks - Contract network configuration */ contractNetworks ? : ContractNetworksConfig ; } export interface AddOwnerTxParams { /** ownerAddress - The address of the new owner */ ownerAddress : string ; /** threshold - The new threshold */ threshold ? : number ; } export interface RemoveOwnerTxParams { /** ownerAddress - The address of the owner that will be removed */ ownerAddress : string ; /** threshold - The new threshold */ threshold ? : number ; } export interface SwapOwnerTxParams { /** oldOwnerAddress - The old owner address */ oldOwnerAddress : string ; /** newOwnerAddress - The new owner address */ newOwnerAddress : string ; } class Safe { #ethAdapter ! : EthAdapter ; #contractManager ! : ContractManager ; #ownerManager ! : OwnerManager ; #moduleManager ! : ModuleManager ; param config returns throws throws static async create ( { ethAdapter , safeAddress , isL1SafeMasterCopy , contractNetworks } ) { const safeSdk = new Safe ( ) ; await safeSdk . init ( { ethAdapter , safeAddress , isL1SafeMasterCopy , contractNetworks } ) ; return safeSdk ; } param config throws throws throws private async init ( { ethAdapter , safeAddress , isL1SafeMasterCopy , contractNetworks } ) { this . #ethAdapter = ethAdapter ; this . #contractManager = await ContractManager . create ( { ethAdapter : this . #ethAdapter , safeAddress , isL1SafeMasterCopy , contractNetworks } ) ; this . #ownerManager = new OwnerManager ( this . #ethAdapter , this . #contractManager . safeContract ) ; this . #moduleManager = new ModuleManager ( this . #ethAdapter , this . #contractManager . safeContract ) ; } param config throws throws async connect ( { ethAdapter , safeAddress , isL1SafeMasterCopy , contractNetworks } ) { return await Safe . create ( { ethAdapter : ethAdapter || this . #ethAdapter , safeAddress : safeAddress || this . getAddress ( ) , isL1SafeMasterCopy : isL1SafeMasterCopy || this . #contractManager . isL1SafeMasterCopy , contractNetworks : contractNetworks || this . #contractManager . contractNetworks } ) ; } returns getAddress ( ) { return this . #contractManager . safeContract . getAddress ( ) ; } returns getContractManager ( ) { return this . #contractManager ; } returns getEthAdapter ( ) { return this . #ethAdapter ; } returns getMultiSendAddress ( ) { return this . #contractManager . multiSendContract . getAddress ( ) ; } returns async getContractVersion ( ) { return this . #contractManager . safeContract . getVersion ( ) ; } returns async getOwners ( ) { return this . #ownerManager . getOwners ( ) ; } returns async getNonce ( ) { return this . #contractManager . safeContract . getNonce ( ) ; } returns async getThreshold ( ) { return this . #ownerManager . getThreshold ( ) ; } returns async getChainId ( ) { return this . #ethAdapter . getChainId ( ) ; } returns async getBalance ( ) { return this . #ethAdapter . getBalance ( this . getAddress ( ) ) ; } returns async getModules ( ) { return this . #moduleManager . getModules ( ) ; } param moduleAddress returns async isModuleEnabled ( moduleAddress ) { return this . #moduleManager . isModuleEnabled ( moduleAddress ) ; } param ownerAddress returns async isOwner ( ownerAddress ) { return this . #ownerManager . isOwner ( ownerAddress ) ; } param safeTransactions returns throws async createTransaction ( safeTransactions ) ; async createTransaction ( safeTransactions , options ? ) ; async createTransaction ( safeTransactions , options ? ) { if ( isMetaTransactionArray ( safeTransactions ) && safeTransactions . length === 0 ) { throw new Error ( 'Invalid empty array of transactions' ) ; } let newTransaction ; if ( isMetaTransactionArray ( safeTransactions ) && safeTransactions . length > 1 ) { const multiSendData = encodeMultiSendData ( safeTransactions . map ( standardizeMetaTransactionData ) ) ; const multiSendTransaction = { ... options , to : this . #contractManager . multiSendContract . getAddress ( ) , value : '0' , data : this . #contractManager . multiSendContract . encode ( 'multiSend' , [ multiSendData ] ) , operation : OperationType . DelegateCall } ; newTransaction = multiSendTransaction ; } else { newTransaction = isMetaTransactionArray ( safeTransactions ) ? { ... options , ... safeTransactions [ 0 ] } : safeTransactions ; } const standardizedTransaction = await standardizeSafeTransactionData ( this . #contractManager . safeContract , this . #ethAdapter , newTransaction ) ; return new EthSafeTransaction ( standardizedTransaction ) ; } param nonce returns async createRejectionTransaction ( nonce ) { return this . createTransaction ( { to : this . getAddress ( ) , nonce , value : '0' , data : '0x' , safeTxGas : 0 } ) ; } param safeTransaction returns async getTransactionHash ( safeTransaction ) { const safeTransactionData = safeTransaction . data ; const txHash = await this . #contractManager . safeContract . getTransactionHash ( safeTransactionData ) ; return txHash ; } param hash returns throws async signTransactionHash ( hash ) { const owners = await this . getOwners ( ) ; const signerAddress = await this . #ethAdapter . getSignerAddress ( ) ; const addressIsOwner = owners . find ( ( owner ) => signerAddress && sameString ( owner , signerAddress ) ) ; if ( ! addressIsOwner ) { throw new Error ( 'Transactions can only be signed by Safe owners' ) ; } return generateSignature ( this . #ethAdapter , hash ) ; } param safeTransaction async signTransaction ( safeTransaction ) { const txHash = await this . getTransactionHash ( safeTransaction ) ; const signature = await this . signTransactionHash ( txHash ) ; safeTransaction . addSignature ( signature ) ; } param hash returns throws async approveTransactionHash ( hash ) { const owners = await this . getOwners ( ) ; const signerAddress = await this . #ethAdapter . getSignerAddress ( ) ; const addressIsOwner = owners . find ( ( owner ) => signerAddress && sameString ( owner , signerAddress ) ) ; if ( ! addressIsOwner ) { throw new Error ( 'Transaction hashes can only be approved by Safe owners' ) ; } return this . #contractManager . safeContract . approveHash ( hash , { from : signerAddress } ) ; } param txHash returns async getOwnersWhoApprovedTx ( txHash ) { const owners = await this . getOwners ( ) ; let ownersWhoApproved = [ ] ; for ( const owner of owners ) { const approved = await this . #contractManager . safeContract . approvedHashes ( owner , txHash ) ; if ( approved . gt ( 0 ) ) { ownersWhoApproved . push ( owner ) ; } } return ownersWhoApproved ; } param moduleAddress param options returns throws throws async getEnableModuleTx ( moduleAddress , options ? ) { const safeTransaction = await this . createTransaction ( { to : this . getAddress ( ) , value : '0' , data : await this . #moduleManager . encodeEnableModuleData ( moduleAddress ) , ... options } ) ; return safeTransaction ; } param moduleAddress param options returns throws throws async getDisableModuleTx ( moduleAddress , options ? ) { const safeTransaction = await this . createTransaction ( { to : this . getAddress ( ) , value : '0' , data : await this . #moduleManager . encodeDisableModuleData ( moduleAddress ) , ... options } ) ; return safeTransaction ; } param params param options returns throws throws throws throws async getAddOwnerTx ( { ownerAddress , threshold } , options ? ) { const safeTransaction = await this . createTransaction ( { to : this . getAddress ( ) , value : '0' , data : await this . #ownerManager . encodeAddOwnerWithThresholdData ( ownerAddress , threshold ) , ... options } ) ; return safeTransaction ; } param params param options returns throws throws throws throws async getRemoveOwnerTx ( { ownerAddress , threshold } , options ? ) { const safeTransaction = await this . createTransaction ( { to : this . getAddress ( ) , value : '0' , data : await this . #ownerManager . encodeRemoveOwnerData ( ownerAddress , threshold ) , ... options } ) ; return safeTransaction ; } param params param options returns throws throws throws throws async getSwapOwnerTx ( { oldOwnerAddress , newOwnerAddress } , options ? ) { const safeTransaction = await this . createTransaction ( { to : this . getAddress ( ) , value : '0' , data : await this . #ownerManager . encodeSwapOwnerData ( oldOwnerAddress , newOwnerAddress ) , ... options } ) ; return safeTransaction ; } param threshold param options returns throws throws async getChangeThresholdTx ( threshold , options ? ) { const safeTransaction = await this . createTransaction ( { to : this . getAddress ( ) , value : '0' , data : await this . #ownerManager . encodeChangeThresholdData ( threshold ) , ... options } ) ; return safeTransaction ; } param safeTransaction param options returns throws throws async executeTransaction ( safeTransaction , options ? ) { const txHash = await this . getTransactionHash ( safeTransaction ) ; const ownersWhoApprovedTx = await this . getOwnersWhoApprovedTx ( txHash ) ; for ( const owner of ownersWhoApprovedTx ) { safeTransaction . addSignature ( generatePreValidatedSignature ( owner ) ) ; } const owners = await this . getOwners ( ) ; const signerAddress = await this . #ethAdapter . getSignerAddress ( ) ; if ( owners . includes ( signerAddress ) ) { safeTransaction . addSignature ( generatePreValidatedSignature ( signerAddress ) ) ; } const threshold = await this . getThreshold ( ) ; if ( threshold > safeTransaction . signatures . size ) { const signaturesMissing = threshold - safeTransaction . signatures . size ; throw new Error ( `There ${ signaturesMissing > 1 ? 'are' : 'is' } ${ signaturesMissing } signature${ signaturesMissing > 1 ? 's' : '' } missing` ) ; } const value = BigNumber . from ( safeTransaction . data . value ) ; if ( ! value . isZero ( ) ) { const balance = await this . getBalance ( ) ; if ( value . gt ( BigNumber . from ( balance ) ) ) { throw new Error ( 'Not enough Ether funds' ) ; } } const gasLimit = options ?. gasLimit || ( await estimateGasForTransactionExecution ( this . #contractManager . safeContract , signerAddress , safeTransaction ) ) ; const executionOptions = { gasLimit , gasPrice : options ?. gasPrice , from : signerAddress } ; const txResponse = await this . #contractManager . safeContract . execTransaction ( safeTransaction , executionOptions ) ; return txResponse ; } } export default Safe ;