diff --git a/packages/protocol-kit/scripts/generateTypechainFiles.ts b/packages/protocol-kit/scripts/generateTypechainFiles.ts index 8de7c6f20..52e8ba40b 100644 --- a/packages/protocol-kit/scripts/generateTypechainFiles.ts +++ b/packages/protocol-kit/scripts/generateTypechainFiles.ts @@ -26,7 +26,7 @@ const safeContracts_V1_4_1 = [ `${safeContractsPath}/v1.4.1/simulate_tx_accessor.json` ].join(' ') const safeContracts_V1_3_0 = [ - `${safeContractsPath}/v1.3.0/gnosis_safe.json`, + // `${safeContractsPath}/v1.3.0/gnosis_safe.json`, // Remove contract 1.3.0 from typechain as it's migrated to Abitype `${safeContractsPath}/v1.3.0/proxy_factory.json`, `${safeContractsPath}/v1.3.0/multi_send.json`, `${safeContractsPath}/v1.3.0/multi_send_call_only.json`, @@ -103,15 +103,15 @@ function generateTypes(typechainTarget: string) { generateTypechainFiles( typechainTarget, `${outDirSrc}${typechainTarget}/v1.4.1`, - safeContracts_V1_4_1 + // Remove Safe Contract v1.4.1 for web3-v1 + typechainTarget === 'web3-v1' + ? safeContracts_V1_4_1.replace(`${safeContractsPath}/v1.4.1/safe.json `, '') + : safeContracts_V1_4_1 ) generateTypechainFiles( typechainTarget, `${outDirSrc}${typechainTarget}/v1.3.0`, - // removed Safe Contract v1.3.0 for ethers-v6 - typechainTarget === 'ethers-v6' - ? safeContracts_V1_3_0.replace(`${safeContractsPath}/v1.3.0/gnosis_safe.json `, '') - : safeContracts_V1_3_0 + safeContracts_V1_3_0 ) generateTypechainFiles( typechainTarget, diff --git a/packages/protocol-kit/src/adapters/ethers/contracts/Safe/v1.3.0/SafeContract_v1_3_0_Ethers.ts b/packages/protocol-kit/src/adapters/ethers/contracts/Safe/v1.3.0/SafeContract_v1_3_0_Ethers.ts index 723527873..52f436fd3 100644 --- a/packages/protocol-kit/src/adapters/ethers/contracts/Safe/v1.3.0/SafeContract_v1_3_0_Ethers.ts +++ b/packages/protocol-kit/src/adapters/ethers/contracts/Safe/v1.3.0/SafeContract_v1_3_0_Ethers.ts @@ -67,7 +67,7 @@ class SafeContract_v1_3_0_Ethers return this.contract.interface.encodeFunctionData(functionToEncode, args) } - estimateGas: EstimateGasSafeFunction = ( + estimateGas: EstimateGasSafeFunction = ( functionToEstimate, args, options = {} diff --git a/packages/protocol-kit/src/adapters/web3/Web3Adapter.ts b/packages/protocol-kit/src/adapters/web3/Web3Adapter.ts index c95c4cf32..a07227ef4 100644 --- a/packages/protocol-kit/src/adapters/web3/Web3Adapter.ts +++ b/packages/protocol-kit/src/adapters/web3/Web3Adapter.ts @@ -87,7 +87,8 @@ class Web3Adapter implements EthAdapter { safeVersion, singletonDeployment, customContractAddress, - customContractAbi + customContractAbi, + isL1SafeSingleton }: GetContractProps): Promise { const chainId = await this.getChainId() const contractAddress = @@ -95,11 +96,18 @@ class Web3Adapter implements EthAdapter { if (!contractAddress) { throw new Error('Invalid SafeProxy contract address') } - const safeContract = this.getContract( + const safeSingletonContract = this.getContract( contractAddress, customContractAbi ?? (singletonDeployment?.abi as AbiItem[]) ) - return getSafeContractInstance(safeVersion, safeContract) + return getSafeContractInstance( + safeVersion, + safeSingletonContract, + contractAddress, + this, + customContractAbi, + isL1SafeSingleton + ) } async getSafeProxyFactoryContract({ diff --git a/packages/protocol-kit/src/adapters/web3/contracts/Safe/SafeBaseContractWeb3.ts b/packages/protocol-kit/src/adapters/web3/contracts/Safe/SafeBaseContractWeb3.ts new file mode 100644 index 000000000..87cff2168 --- /dev/null +++ b/packages/protocol-kit/src/adapters/web3/contracts/Safe/SafeBaseContractWeb3.ts @@ -0,0 +1,67 @@ +import Contract from 'web3-eth-contract' +import { AbiItem } from 'web3-utils' + +import Web3Adapter from '@safe-global/protocol-kit/adapters/web3/Web3Adapter' +import { SafeVersion } from 'packages/safe-core-sdk-types' +import SafeBaseContract from '@safe-global/protocol-kit/adapters/SafeBaseContract' + +/** + * Abstract class SafeBaseContractWeb3 extends SafeBaseContract to specifically integrate with the Web3.js library. + * It is designed to be instantiated for different versions of the Safe contract. + * + * This abstract class sets up the Web3.js Contract object that interacts with a Safe contract version. + * + * Subclasses of SafeBaseContractWeb3 are expected to represent specific versions of the Safe contract. + * + * @template SafeContractAbiType - The ABI type specific to the version of the Safe contract, extending AbiItem. + * @extends SafeBaseContract - Extends the generic SafeBaseContract with Web3-specific implementation. + * + * Example subclasses: + * - SafeContract_v1_4_1_Web3 extends SafeBaseContractWeb3 + * - SafeContract_v1_3_0_Web3 extends SafeBaseContractWeb3 + * - SafeContract_v1_2_0_Web3 extends SafeBaseContractWeb3 + * - SafeContract_v1_1_1_Web3 extends SafeBaseContractWeb3 + * - SafeContract_v1_0_0_Web3 extends SafeBaseContractWeb3 + */ +abstract class SafeBaseContractWeb3< + SafeContractAbiType extends AbiItem[] +> extends SafeBaseContract { + contract: Contract + adapter: Web3Adapter + + /** + * @constructor + * Constructs an instance of SafeBaseContractWeb3. + * + * @param chainId - The chain ID of the contract. + * @param web3Adapter - An instance of Web3Adapter. + * @param defaultAbi - The default ABI for the Safe contract. It should be compatible with the specific version of the Safe contract. + * @param safeVersion - The version of the Safe contract. + * @param isL1SafeSingleton - A flag indicating if the contract is a L1 Safe Singleton. + * @param customContractAddress - Optional custom address for the contract. If not provided, the address is derived from the Safe deployments based on the chainId and safeVersion. + * @param customContractAbi - Optional custom ABI for the contract. If not provided, the ABI is derived from the Safe deployments or the defaultAbi is used. + */ + constructor( + chainId: bigint, + web3Adapter: Web3Adapter, + defaultAbi: SafeContractAbiType, + safeVersion: SafeVersion, + isL1SafeSingleton = false, + customContractAddress?: string, + customContractAbi?: SafeContractAbiType + ) { + super( + chainId, + defaultAbi, + safeVersion, + isL1SafeSingleton, + customContractAddress, + customContractAbi + ) + + this.adapter = web3Adapter + this.contract = web3Adapter.getContract(this.contractAddress, this.contractAbi) + } +} + +export default SafeBaseContractWeb3 diff --git a/packages/protocol-kit/src/adapters/web3/contracts/Safe/SafeContractWeb3.ts b/packages/protocol-kit/src/adapters/web3/contracts/Safe/SafeContractWeb3.ts index 4e1dc31fc..b2008069c 100644 --- a/packages/protocol-kit/src/adapters/web3/contracts/Safe/SafeContractWeb3.ts +++ b/packages/protocol-kit/src/adapters/web3/contracts/Safe/SafeContractWeb3.ts @@ -6,8 +6,6 @@ import { toTxResult } from '@safe-global/protocol-kit/adapters/web3/utils' import { Gnosis_safe as Safe_V1_0_0 } from '@safe-global/protocol-kit/typechain/src/web3-v1/v1.0.0/Gnosis_safe' import { Gnosis_safe as Safe_V1_1_1 } from '@safe-global/protocol-kit/typechain/src/web3-v1/v1.1.1/Gnosis_safe' import { Gnosis_safe as Safe_V1_2_0 } from '@safe-global/protocol-kit/typechain/src/web3-v1/v1.2.0/Gnosis_safe' -import { Gnosis_safe as Safe_V1_3_0 } from '@safe-global/protocol-kit/typechain/src/web3-v1/v1.3.0/Gnosis_safe' -import { Safe as Safe_V1_4_1 } from '@safe-global/protocol-kit/typechain/src/web3-v1/v1.4.1/Safe' import { SafeContract, SafeSetupConfig, @@ -17,9 +15,7 @@ import { } from '@safe-global/safe-core-sdk-types' abstract class SafeContractWeb3 implements SafeContract { - constructor( - public contract: Safe_V1_4_1 | Safe_V1_3_0 | Safe_V1_2_0 | Safe_V1_1_1 | Safe_V1_0_0 - ) {} + constructor(public contract: Safe_V1_2_0 | Safe_V1_1_1 | Safe_V1_0_0) {} abstract setup( setupConfig: SafeSetupConfig, diff --git a/packages/protocol-kit/src/adapters/web3/contracts/Safe/v1.3.0/SafeContract_V1_3_0_Web3.ts b/packages/protocol-kit/src/adapters/web3/contracts/Safe/v1.3.0/SafeContract_V1_3_0_Web3.ts deleted file mode 100644 index cc82db076..000000000 --- a/packages/protocol-kit/src/adapters/web3/contracts/Safe/v1.3.0/SafeContract_V1_3_0_Web3.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { - Web3TransactionOptions, - Web3TransactionResult -} from '@safe-global/protocol-kit/adapters/web3/types' -import { toTxResult } from '@safe-global/protocol-kit/adapters/web3/utils' -import { - EMPTY_DATA, - SENTINEL_ADDRESS, - ZERO_ADDRESS -} from '@safe-global/protocol-kit/adapters/web3/utils/constants' -import { Gnosis_safe as Safe } from '@safe-global/protocol-kit/typechain/src/web3-v1/v1.3.0/Gnosis_safe' -import { SafeSetupConfig } from '@safe-global/safe-core-sdk-types' -import SafeContractWeb3 from '../SafeContractWeb3' - -class SafeContract_V1_3_0_Web3 extends SafeContractWeb3 { - constructor(public contract: Safe) { - super(contract) - } - - async setup( - setupConfig: SafeSetupConfig, - options?: Web3TransactionOptions - ): Promise { - const { - owners, - threshold, - to = ZERO_ADDRESS, - data = EMPTY_DATA, - fallbackHandler = ZERO_ADDRESS, - paymentToken = ZERO_ADDRESS, - payment = 0, - paymentReceiver = ZERO_ADDRESS - } = setupConfig - - if (options && !options.gas) { - options.gas = await this.estimateGas( - 'setup', - [owners, threshold, to, data, fallbackHandler, paymentToken, payment, paymentReceiver], - { - ...options - } - ) - } - const txResponse = this.contract.methods - .setup(owners, threshold, to, data, fallbackHandler, paymentToken, payment, paymentReceiver) - .send(options) - - return toTxResult(txResponse, options) - } - - async getModules(): Promise { - const { array } = await this.contract.methods.getModulesPaginated(SENTINEL_ADDRESS, 10).call() - return array - } - - async isModuleEnabled(moduleAddress: string): Promise { - return this.contract.methods.isModuleEnabled(moduleAddress).call() - } -} - -export default SafeContract_V1_3_0_Web3 diff --git a/packages/protocol-kit/src/adapters/web3/contracts/Safe/v1.3.0/SafeContract_v1_3_0_Web3.ts b/packages/protocol-kit/src/adapters/web3/contracts/Safe/v1.3.0/SafeContract_v1_3_0_Web3.ts new file mode 100644 index 000000000..4efb84110 --- /dev/null +++ b/packages/protocol-kit/src/adapters/web3/contracts/Safe/v1.3.0/SafeContract_v1_3_0_Web3.ts @@ -0,0 +1,356 @@ +import SafeBaseContractWeb3 from '@safe-global/protocol-kit/adapters/web3/contracts/Safe/SafeBaseContractWeb3' +import { + DeepWriteable, + Web3TransactionOptions, + Web3TransactionResult +} from '@safe-global/protocol-kit/adapters/web3/types' +import { toTxResult } from '@safe-global/protocol-kit/adapters/web3/utils' +import { SENTINEL_ADDRESS } from '@safe-global/protocol-kit/adapters/web3/utils/constants' +import Web3Adapter from '@safe-global/protocol-kit/adapters/web3/Web3Adapter' +import safe_1_3_0_ContractArtifacts from '@safe-global/protocol-kit/contracts/AbiType/assets/Safe/v1.3.0/gnosis_safe_l2' +import { + EncodeSafeFunction, + EstimateGasSafeFunction +} from '@safe-global/protocol-kit/contracts/AbiType/Safe/SafeBaseContract' +import SafeContract_v1_3_0_Contract, { + SafeContract_v1_3_0_Abi as SafeContract_v1_3_0_Abi_Readonly +} from '@safe-global/protocol-kit/contracts/AbiType/Safe/v1.3.0/SafeContract_v1_3_0' +import { SafeTransaction, SafeTransactionData, SafeVersion } from '@safe-global/safe-core-sdk-types' + +// Remove all nested `readonly` modifiers from the ABI type +type SafeContract_v1_3_0_Abi = DeepWriteable + +/** + * SafeContract_v1_3_0_Web3 is the implementation specific to the Safe contract version 1.3.0. + * + * This class specializes in handling interactions with the Safe contract version 1.3.0 using Web3.js. + * + * @extends SafeBaseContractWeb3 - Inherits from SafeBaseContractWeb3 with ABI specific to Safe contract version 1.3.0. + * @implements SafeContract_v1_3_0_Contract - Implements the interface specific to Safe contract version 1.3.0. + */ +class SafeContract_v1_3_0_Web3 + extends SafeBaseContractWeb3> + implements SafeContract_v1_3_0_Contract +{ + safeVersion: SafeVersion + + /** + * Constructs an instance of SafeContract_v1_3_0_Web3 + * + * @param chainId - The chain ID where the contract resides. + * @param web3Adapter - An instance of Web3Adapter. + * @param isL1SafeSingleton - A flag indicating if the contract is a L1 Safe Singleton. + * @param customContractAddress - Optional custom address for the contract. If not provided, the address is derived from the Safe deployments based on the chainId and safeVersion. + * @param customContractAbi - Optional custom ABI for the contract. If not provided, the default ABI for version 1.3.0 is used. + */ + constructor( + chainId: bigint, + web3Adapter: Web3Adapter, + isL1SafeSingleton = false, + customContractAddress?: string, + customContractAbi?: SafeContract_v1_3_0_Abi_Readonly + ) { + const safeVersion = '1.3.0' + const defaultAbi = safe_1_3_0_ContractArtifacts.abi as DeepWriteable + + super( + chainId, + web3Adapter, + defaultAbi, + safeVersion, + isL1SafeSingleton, + customContractAddress, + customContractAbi as DeepWriteable + ) + + this.safeVersion = safeVersion + } + + async VERSION(): Promise<[SafeVersion]> { + return [await this.contract.methods.VERSION().call()] + } + + async approvedHashes(args: readonly [owner: string, txHash: string]): Promise<[bigint]> { + return [await this.contract.methods.approvedHashes(...args).call()] + } + + async checkNSignatures( + args: readonly [dataHash: string, data: string, signatures: string, requiredSignatures: bigint] + ): Promise<[]> { + // this method just checks whether the signature provided is valid for the provided data and hash. Reverts otherwise. + if (this.contract.methods.checkNSignatures) { + await this.contract.methods.checkNSignatures(...args).call() + } + return [] + } + + async checkSignatures( + args: readonly [dataHash: string, data: string, signatures: string] + ): Promise<[]> { + await this.contract.methods.checkSignatures(...args).call() + return [] + } + + async domainSeparator(): Promise<[string]> { + return [await this.contract.methods.domainSeparator().call()] + } + + async encodeTransactionData( + args: readonly [ + to: string, + value: bigint, + data: string, + operation: number, + safeTxGas: bigint, + baseGas: bigint, + gasPrice: bigint, + gasToken: string, + refundReceiver: string, + _nonce: bigint + ] + ): Promise<[string]> { + return [await this.contract.methods.encodeTransactionData(...args).call()] + } + + async getChainId(): Promise<[bigint]> { + return [await this.contract.methods.getChainId().call()] + } + + getModulesPaginated( + args: readonly [start: string, pageSize: bigint] + ): Promise<[modules: string[], next: string]> { + return this.contract.methods.getModulesPaginated(...args).call() + } + + async getOwners(): Promise { + return [await this.contract.methods.getOwners().call()] + } + + async getStorageAt(args: readonly [offset: bigint, length: bigint]): Promise<[string]> { + return [await this.contract.methods.getStorageAt(...args).call()] + } + + async getThreshold(): Promise<[bigint]> { + return [await this.contract.methods.getThreshold().call()] + } + + async getTransactionHash( + args: readonly [ + to: string, + value: bigint, + data: string, + operation: number, + safeTxGas: bigint, + baseGas: bigint, + gasPrice: bigint, + gasToken: string, + refundReceiver: string, + _nonce: bigint + ] + ): Promise<[string]> { + return [await this.contract.methods.getTransactionHash(...args).call()] + } + + async isModuleEnabled(args: readonly [moduleAddress: string]): Promise<[boolean]> { + return [await this.contract.methods.isModuleEnabled(...args).call()] + } + + async isOwner(args: readonly [address: string]): Promise<[boolean]> { + return [await this.contract.methods.isOwner(...args).call()] + } + + async nonce(): Promise<[bigint]> { + return [await this.contract.methods.nonce().call()] + } + + async signedMessages(args: readonly [messageHash: string]): Promise<[bigint]> { + return [await this.contract.methods.signedMessages(...args).call()] + } + + encode: EncodeSafeFunction = (functionToEncode, args) => { + return this.contract.methods[functionToEncode](...args).encodeABI() + } + + estimateGas: EstimateGasSafeFunction = ( + functionToEstimate, + args, + options = {} + ) => { + return this.contract.methods[functionToEstimate](...args) + .estimateGas(options) + .then(BigInt) + } + + // Custom method (not defined in the Safe Contract) + // TODO: review this custom method + async getModules(): Promise { + const { array } = await this.contract.methods.getModulesPaginated(SENTINEL_ADDRESS, 10).call() + return array + } + + // Custom method (not defined in the Safe Contract) + getAddress(): Promise { + return Promise.resolve(this.contract.options.address) + } + + // Custom method (not defined in the Safe Contract) + async execTransaction( + safeTransaction: SafeTransaction, + options?: Web3TransactionOptions + ): Promise { + if (options && !options.gas) { + options.gas = ( + await this.estimateGas( + 'execTransaction', + [ + safeTransaction.data.to, + BigInt(safeTransaction.data.value), + safeTransaction.data.data, + safeTransaction.data.operation, + BigInt(safeTransaction.data.safeTxGas), + BigInt(safeTransaction.data.baseGas), + BigInt(safeTransaction.data.gasPrice), + safeTransaction.data.gasToken, + safeTransaction.data.refundReceiver, + safeTransaction.encodedSignatures() + ], + options + ) + ).toString() + } + const txResponse = this.contract.methods + .execTransaction( + safeTransaction.data.to, + BigInt(safeTransaction.data.value), + safeTransaction.data.data, + safeTransaction.data.operation, + BigInt(safeTransaction.data.safeTxGas), + BigInt(safeTransaction.data.baseGas), + BigInt(safeTransaction.data.gasPrice), + safeTransaction.data.gasToken, + safeTransaction.data.refundReceiver, + safeTransaction.encodedSignatures() + ) + .send(options) + + return toTxResult(txResponse, options) + } + + // Custom method (not defined in the Safe Contract) + async isValidTransaction( + safeTransaction: SafeTransaction, + options?: Web3TransactionOptions + ): Promise { + let isTxValid = false + try { + if (options && !options.gas) { + options.gas = ( + await this.estimateGas( + 'execTransaction', + [ + safeTransaction.data.to, + BigInt(safeTransaction.data.value), + safeTransaction.data.data, + safeTransaction.data.operation, + BigInt(safeTransaction.data.safeTxGas), + BigInt(safeTransaction.data.baseGas), + BigInt(safeTransaction.data.gasPrice), + safeTransaction.data.gasToken, + safeTransaction.data.refundReceiver, + safeTransaction.encodedSignatures() + ], + options + ) + ).toString() + } + isTxValid = await this.contract.methods + .execTransaction( + safeTransaction.data.to, + BigInt(safeTransaction.data.value), + safeTransaction.data.data, + safeTransaction.data.operation, + BigInt(safeTransaction.data.safeTxGas), + BigInt(safeTransaction.data.baseGas), + BigInt(safeTransaction.data.gasPrice), + safeTransaction.data.gasToken, + safeTransaction.data.refundReceiver, + safeTransaction.encodedSignatures() + ) + .call(options) + } catch {} + return isTxValid + } + + // Custom method (not defined in the Safe Contract) + async approveHash( + hash: string, + options?: Web3TransactionOptions + ): Promise { + if (options && !options.gas) { + options.gas = (await this.estimateGas('approveHash', [hash], { ...options })).toString() + } + const txResponse = this.contract.methods.approveHash(hash).send(options) + return toTxResult(txResponse, options) + } + + // TODO: Remove this mapper after remove Typechain + mapToTypechainContract(): any { + return { + contract: this.contract as any, + + setup: (): any => { + // setup function is labelled as `external` on the contract code, but not present on type SafeContract_v1_3_0_Contract + return + }, + + approveHash: this.approveHash, + + isValidTransaction: this.isValidTransaction, + + execTransaction: this.execTransaction, + + getAddress: this.getAddress, + + getModules: this.getModules, + + isModuleEnabled: async (moduleAddress: string) => + (await this.isModuleEnabled([moduleAddress]))[0], + + getVersion: async () => (await this.VERSION())[0] as SafeVersion, + + getNonce: async () => Number((await this.nonce())[0]), + + getThreshold: async () => Number((await this.getThreshold())[0]), + + getOwners: async () => (await this.getOwners())[0], + + isOwner: async (address: string) => (await this.isOwner([address]))[0], + + getTransactionHash: async (safeTransactionData: SafeTransactionData) => { + return ( + await this.getTransactionHash([ + safeTransactionData.to, + BigInt(safeTransactionData.value), + safeTransactionData.data, + safeTransactionData.operation, + BigInt(safeTransactionData.safeTxGas), + BigInt(safeTransactionData.baseGas), + BigInt(safeTransactionData.gasPrice), + safeTransactionData.gasToken, + safeTransactionData.refundReceiver, + BigInt(safeTransactionData.nonce) + ]) + )[0] + }, + + approvedHashes: async (ownerAddress: string, hash: string) => + (await this.approvedHashes([ownerAddress, hash]))[0], + + encode: this.encode as any, + + estimateGas: this.estimateGas as any + } + } +} + +export default SafeContract_v1_3_0_Web3 diff --git a/packages/protocol-kit/src/adapters/web3/contracts/Safe/v1.4.1/SafeContract_V1_4_1_Web3.ts b/packages/protocol-kit/src/adapters/web3/contracts/Safe/v1.4.1/SafeContract_V1_4_1_Web3.ts deleted file mode 100644 index 1d1aaf561..000000000 --- a/packages/protocol-kit/src/adapters/web3/contracts/Safe/v1.4.1/SafeContract_V1_4_1_Web3.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { - Web3TransactionOptions, - Web3TransactionResult -} from '@safe-global/protocol-kit/adapters/web3/types' -import { toTxResult } from '@safe-global/protocol-kit/adapters/web3/utils' -import { - EMPTY_DATA, - SENTINEL_ADDRESS, - ZERO_ADDRESS -} from '@safe-global/protocol-kit/adapters/web3/utils/constants' -import { Safe } from '@safe-global/protocol-kit/typechain/src/web3-v1/v1.4.1/Safe' -import { SafeSetupConfig } from '@safe-global/safe-core-sdk-types' -import SafeContractWeb3 from '../SafeContractWeb3' - -class SafeContract_V1_4_1_Web3 extends SafeContractWeb3 { - constructor(public contract: Safe) { - super(contract) - } - - async setup( - setupConfig: SafeSetupConfig, - options?: Web3TransactionOptions - ): Promise { - const { - owners, - threshold, - to = ZERO_ADDRESS, - data = EMPTY_DATA, - fallbackHandler = ZERO_ADDRESS, - paymentToken = ZERO_ADDRESS, - payment = 0, - paymentReceiver = ZERO_ADDRESS - } = setupConfig - - if (options && !options.gas) { - options.gas = await this.estimateGas( - 'setup', - [owners, threshold, to, data, fallbackHandler, paymentToken, payment, paymentReceiver], - { - ...options - } - ) - } - const txResponse = this.contract.methods - .setup(owners, threshold, to, data, fallbackHandler, paymentToken, payment, paymentReceiver) - .send(options) - - return toTxResult(txResponse, options) - } - - async getModules(): Promise { - const { array } = await this.contract.methods.getModulesPaginated(SENTINEL_ADDRESS, 10).call() - return array - } - - async isModuleEnabled(moduleAddress: string): Promise { - return this.contract.methods.isModuleEnabled(moduleAddress).call() - } -} - -export default SafeContract_V1_4_1_Web3 diff --git a/packages/protocol-kit/src/adapters/web3/contracts/Safe/v1.4.1/SafeContract_v1_4_1_Web3.ts b/packages/protocol-kit/src/adapters/web3/contracts/Safe/v1.4.1/SafeContract_v1_4_1_Web3.ts new file mode 100644 index 000000000..092c71588 --- /dev/null +++ b/packages/protocol-kit/src/adapters/web3/contracts/Safe/v1.4.1/SafeContract_v1_4_1_Web3.ts @@ -0,0 +1,356 @@ +import SafeBaseContractWeb3 from '@safe-global/protocol-kit/adapters/web3/contracts/Safe/SafeBaseContractWeb3' +import { + DeepWriteable, + Web3TransactionOptions, + Web3TransactionResult +} from '@safe-global/protocol-kit/adapters/web3/types' +import { toTxResult } from '@safe-global/protocol-kit/adapters/web3/utils' +import { SENTINEL_ADDRESS } from '@safe-global/protocol-kit/adapters/web3/utils/constants' +import Web3Adapter from '@safe-global/protocol-kit/adapters/web3/Web3Adapter' +import safe_1_4_1_ContractArtifacts from '@safe-global/protocol-kit/contracts/AbiType/assets/Safe/v1.4.1/safe_l2' +import { + EncodeSafeFunction, + EstimateGasSafeFunction +} from '@safe-global/protocol-kit/contracts/AbiType/Safe/SafeBaseContract' +import SafeContract_v1_4_1_Contract, { + SafeContract_v1_4_1_Abi as SafeContract_v1_4_1_Abi_Readonly +} from '@safe-global/protocol-kit/contracts/AbiType/Safe/v1.4.1/SafeContract_v1_4_1' +import { SafeTransaction, SafeTransactionData, SafeVersion } from '@safe-global/safe-core-sdk-types' + +// Remove all nested `readonly` modifiers from the ABI type +type SafeContract_v1_4_1_Abi = DeepWriteable + +/** + * SafeContract_v1_4_1_Web3 is the implementation specific to the Safe contract version 1.4.1. + * + * This class specializes in handling interactions with the Safe contract version 1.4.1 using Web3.js. + * + * @extends SafeBaseContractWeb3 - Inherits from SafeBaseContractWeb3 with ABI specific to Safe contract version 1.4.1. + * @implements SafeContract_v1_4_1_Contract - Implements the interface specific to Safe contract version 1.4.1. + */ +class SafeContract_v1_4_1_Web3 + extends SafeBaseContractWeb3 + implements SafeContract_v1_4_1_Contract +{ + safeVersion: SafeVersion + + /** + * Constructs an instance of SafeContract_v1_4_1_Web3 + * + * @param chainId - The chain ID where the contract resides. + * @param web3Adapter - An instance of Web3Adapter. + * @param isL1SafeSingleton - A flag indicating if the contract is a L1 Safe Singleton. + * @param customContractAddress - Optional custom address for the contract. If not provided, the address is derived from the Safe deployments based on the chainId and safeVersion. + * @param customContractAbi - Optional custom ABI for the contract. If not provided, the default ABI for version 1.4.1 is used. + */ + constructor( + chainId: bigint, + web3Adapter: Web3Adapter, + isL1SafeSingleton = false, + customContractAddress?: string, + customContractAbi?: SafeContract_v1_4_1_Abi_Readonly + ) { + const safeVersion = '1.4.1' + const defaultAbi = safe_1_4_1_ContractArtifacts.abi as SafeContract_v1_4_1_Abi + + super( + chainId, + web3Adapter, + defaultAbi, + safeVersion, + isL1SafeSingleton, + customContractAddress, + customContractAbi as SafeContract_v1_4_1_Abi + ) + + this.safeVersion = safeVersion + } + + async VERSION(): Promise<[SafeVersion]> { + return [await this.contract.methods.VERSION().call()] + } + + async approvedHashes(args: readonly [owner: string, txHash: string]): Promise<[bigint]> { + return [await this.contract.methods.approvedHashes(...args).call()] + } + + async checkNSignatures( + args: readonly [dataHash: string, data: string, signatures: string, requiredSignatures: bigint] + ): Promise<[]> { + // this method just checks whether the signature provided is valid for the provided data and hash. Reverts otherwise. + if (this.contract.methods.checkNSignatures) { + await this.contract.methods.checkNSignatures(...args).call() + } + return [] + } + + async checkSignatures( + args: readonly [dataHash: string, data: string, signatures: string] + ): Promise<[]> { + await this.contract.methods.checkSignatures(...args).call() + return [] + } + + async domainSeparator(): Promise<[string]> { + return [await this.contract.methods.domainSeparator().call()] + } + + async encodeTransactionData( + args: readonly [ + to: string, + value: bigint, + data: string, + operation: number, + safeTxGas: bigint, + baseGas: bigint, + gasPrice: bigint, + gasToken: string, + refundReceiver: string, + _nonce: bigint + ] + ): Promise<[string]> { + return [await this.contract.methods.encodeTransactionData(...args).call()] + } + + async getChainId(): Promise<[bigint]> { + return [await this.contract.methods.getChainId().call()] + } + + getModulesPaginated( + args: readonly [start: string, pageSize: bigint] + ): Promise<[modules: string[], next: string]> { + return this.contract.methods.getModulesPaginated(...args).call() + } + + async getOwners(): Promise { + return [await this.contract.methods.getOwners().call()] + } + + async getStorageAt(args: readonly [offset: bigint, length: bigint]): Promise<[string]> { + return [await this.contract.methods.getStorageAt(...args).call()] + } + + async getThreshold(): Promise<[bigint]> { + return [await this.contract.methods.getThreshold().call()] + } + + async getTransactionHash( + args: readonly [ + to: string, + value: bigint, + data: string, + operation: number, + safeTxGas: bigint, + baseGas: bigint, + gasPrice: bigint, + gasToken: string, + refundReceiver: string, + _nonce: bigint + ] + ): Promise<[string]> { + return [await this.contract.methods.getTransactionHash(...args).call()] + } + + async isModuleEnabled(args: readonly [moduleAddress: string]): Promise<[boolean]> { + return [await this.contract.methods.isModuleEnabled(...args).call()] + } + + async isOwner(args: readonly [address: string]): Promise<[boolean]> { + return [await this.contract.methods.isOwner(...args).call()] + } + + async nonce(): Promise<[bigint]> { + return [await this.contract.methods.nonce().call()] + } + + async signedMessages(args: readonly [messageHash: string]): Promise<[bigint]> { + return [await this.contract.methods.signedMessages(...args).call()] + } + + encode: EncodeSafeFunction = (functionToEncode, args) => { + return this.contract.methods[functionToEncode](...args).encodeABI() + } + + estimateGas: EstimateGasSafeFunction = ( + functionToEstimate, + args, + options = {} + ) => { + return this.contract.methods[functionToEstimate](...args) + .estimateGas(options) + .then(BigInt) + } + + // Custom method (not defined in the Safe Contract) + // TODO: review this custom method + async getModules(): Promise { + const { array } = await this.contract.methods.getModulesPaginated(SENTINEL_ADDRESS, 10).call() + return array + } + + // Custom method (not defined in the Safe Contract) + getAddress(): Promise { + return Promise.resolve(this.contract.options.address) + } + + // Custom method (not defined in the Safe Contract) + async execTransaction( + safeTransaction: SafeTransaction, + options?: Web3TransactionOptions + ): Promise { + if (options && !options.gas) { + options.gas = ( + await this.estimateGas( + 'execTransaction', + [ + safeTransaction.data.to, + BigInt(safeTransaction.data.value), + safeTransaction.data.data, + safeTransaction.data.operation, + BigInt(safeTransaction.data.safeTxGas), + BigInt(safeTransaction.data.baseGas), + BigInt(safeTransaction.data.gasPrice), + safeTransaction.data.gasToken, + safeTransaction.data.refundReceiver, + safeTransaction.encodedSignatures() + ], + options + ) + ).toString() + } + const txResponse = this.contract.methods + .execTransaction( + safeTransaction.data.to, + BigInt(safeTransaction.data.value), + safeTransaction.data.data, + safeTransaction.data.operation, + BigInt(safeTransaction.data.safeTxGas), + BigInt(safeTransaction.data.baseGas), + BigInt(safeTransaction.data.gasPrice), + safeTransaction.data.gasToken, + safeTransaction.data.refundReceiver, + safeTransaction.encodedSignatures() + ) + .send(options) + + return toTxResult(txResponse, options) + } + + // Custom method (not defined in the Safe Contract) + async isValidTransaction( + safeTransaction: SafeTransaction, + options?: Web3TransactionOptions + ): Promise { + let isTxValid = false + try { + if (options && !options.gas) { + options.gas = ( + await this.estimateGas( + 'execTransaction', + [ + safeTransaction.data.to, + BigInt(safeTransaction.data.value), + safeTransaction.data.data, + safeTransaction.data.operation, + BigInt(safeTransaction.data.safeTxGas), + BigInt(safeTransaction.data.baseGas), + BigInt(safeTransaction.data.gasPrice), + safeTransaction.data.gasToken, + safeTransaction.data.refundReceiver, + safeTransaction.encodedSignatures() + ], + options + ) + ).toString() + } + isTxValid = await this.contract.methods + .execTransaction( + safeTransaction.data.to, + BigInt(safeTransaction.data.value), + safeTransaction.data.data, + safeTransaction.data.operation, + BigInt(safeTransaction.data.safeTxGas), + BigInt(safeTransaction.data.baseGas), + BigInt(safeTransaction.data.gasPrice), + safeTransaction.data.gasToken, + safeTransaction.data.refundReceiver, + safeTransaction.encodedSignatures() + ) + .call(options) + } catch {} + return isTxValid + } + + // Custom method (not defined in the Safe Contract) + async approveHash( + hash: string, + options?: Web3TransactionOptions + ): Promise { + if (options && !options.gas) { + options.gas = (await this.estimateGas('approveHash', [hash], { ...options })).toString() + } + const txResponse = this.contract.methods.approveHash(hash).send(options) + return toTxResult(txResponse, options) + } + + // TODO: Remove this mapper after remove Typechain + mapToTypechainContract(): any { + return { + contract: this.contract as any, + + setup: (): any => { + // setup function is labelled as `external` on the contract code, but not present on type SafeContract_v1_4_1_Contract + return + }, + + approveHash: this.approveHash, + + isValidTransaction: this.isValidTransaction, + + execTransaction: this.execTransaction, + + getAddress: this.getAddress, + + getModules: this.getModules, + + isModuleEnabled: async (moduleAddress: string) => + (await this.isModuleEnabled([moduleAddress]))[0], + + getVersion: async () => (await this.VERSION())[0] as SafeVersion, + + getNonce: async () => Number((await this.nonce())[0]), + + getThreshold: async () => Number((await this.getThreshold())[0]), + + getOwners: async () => (await this.getOwners())[0], + + isOwner: async (address: string) => (await this.isOwner([address]))[0], + + getTransactionHash: async (safeTransactionData: SafeTransactionData) => { + return ( + await this.getTransactionHash([ + safeTransactionData.to, + BigInt(safeTransactionData.value), + safeTransactionData.data, + safeTransactionData.operation, + BigInt(safeTransactionData.safeTxGas), + BigInt(safeTransactionData.baseGas), + BigInt(safeTransactionData.gasPrice), + safeTransactionData.gasToken, + safeTransactionData.refundReceiver, + BigInt(safeTransactionData.nonce) + ]) + )[0] + }, + + approvedHashes: async (ownerAddress: string, hash: string) => + (await this.approvedHashes([ownerAddress, hash]))[0], + + encode: this.encode as any, + + estimateGas: this.estimateGas as any + } + } +} + +export default SafeContract_v1_4_1_Web3 diff --git a/packages/protocol-kit/src/adapters/web3/contracts/contractInstancesWeb3.ts b/packages/protocol-kit/src/adapters/web3/contracts/contractInstancesWeb3.ts index 6378370f2..d2ade6b40 100644 --- a/packages/protocol-kit/src/adapters/web3/contracts/contractInstancesWeb3.ts +++ b/packages/protocol-kit/src/adapters/web3/contracts/contractInstancesWeb3.ts @@ -1,3 +1,9 @@ +import { AbiItem } from 'web3-utils' +import SafeContract_v1_3_0_Web3 from '@safe-global/protocol-kit/adapters/web3/contracts/Safe/v1.3.0/SafeContract_v1_3_0_Web3' +import SafeContract_v1_4_1_Web3 from '@safe-global/protocol-kit/adapters/web3/contracts/Safe/v1.4.1/SafeContract_v1_4_1_Web3' +import Web3Adapter from '@safe-global/protocol-kit/adapters/web3/Web3Adapter' +import { SafeContract_v1_3_0_Abi } from '@safe-global/protocol-kit/contracts/AbiType/Safe/v1.3.0/SafeContract_v1_3_0' +import { SafeContract_v1_4_1_Abi } from '@safe-global/protocol-kit/contracts/AbiType/Safe/v1.4.1/SafeContract_v1_4_1' import { Gnosis_safe as SafeSingleton_V1_0_0 } from '@safe-global/protocol-kit/typechain/src/web3-v1/v1.0.0/Gnosis_safe' import { Proxy_factory as SafeProxyFactory_V1_0_0 } from '@safe-global/protocol-kit/typechain/src/web3-v1/v1.0.0/Proxy_factory' import { Gnosis_safe as SafeSingleton_V1_1_1 } from '@safe-global/protocol-kit/typechain/src/web3-v1/v1.1.1/Gnosis_safe' @@ -6,7 +12,6 @@ import { Proxy_factory as SafeProxyFactory_V1_1_1 } from '@safe-global/protocol- import { Gnosis_safe as SafeSingleton_V1_2_0 } from '@safe-global/protocol-kit/typechain/src/web3-v1/v1.2.0/Gnosis_safe' import { Compatibility_fallback_handler as CompatibilityFallbackHandler_V1_3_0 } from '@safe-global/protocol-kit/typechain/src/web3-v1/v1.3.0/Compatibility_fallback_handler' import { Create_call as CreateCall_V1_3_0 } from '@safe-global/protocol-kit/typechain/src/web3-v1/v1.3.0/Create_call' -import { Gnosis_safe as SafeSingleton_V1_3_0 } from '@safe-global/protocol-kit/typechain/src/web3-v1/v1.3.0/Gnosis_safe' import { Multi_send as MultiSend_V1_3_0 } from '@safe-global/protocol-kit/typechain/src/web3-v1/v1.3.0/Multi_send' import { Multi_send_call_only as MultiSendCallOnly_V1_3_0 } from '@safe-global/protocol-kit/typechain/src/web3-v1/v1.3.0/Multi_send_call_only' import { Proxy_factory as SafeProxyFactory_V1_3_0 } from '@safe-global/protocol-kit/typechain/src/web3-v1/v1.3.0/Proxy_factory' @@ -16,7 +21,6 @@ import { Compatibility_fallback_handler as CompatibilityFallbackHandler_V1_4_1 } import { Create_call as CreateCall_V1_4_1 } from '@safe-global/protocol-kit/typechain/src/web3-v1/v1.4.1/Create_call' import { Multi_send as MultiSend_V1_4_1 } from '@safe-global/protocol-kit/typechain/src/web3-v1/v1.4.1/Multi_send' import { Multi_send_call_only as MultiSendCallOnly_V1_4_1 } from '@safe-global/protocol-kit/typechain/src/web3-v1/v1.4.1/Multi_send_call_only' -import { Safe as SafeSingleton_V1_4_1 } from '@safe-global/protocol-kit/typechain/src/web3-v1/v1.4.1/Safe' import { Safe_proxy_factory as SafeProxyFactory_V1_4_1 } from '@safe-global/protocol-kit/typechain/src/web3-v1/v1.4.1/Safe_proxy_factory' import { Sign_message_lib as SignMessageLib_V1_4_1 } from '@safe-global/protocol-kit/typechain/src/web3-v1/v1.4.1/Sign_message_lib' import { Simulate_tx_accessor as SimulateTxAccessor_V1_4_1 } from '@safe-global/protocol-kit/typechain/src/web3-v1/v1.4.1/Simulate_tx_accessor' @@ -33,8 +37,6 @@ import MultiSendCallOnlyContract_V1_4_1_Web3 from './MultiSendCallOnly/v1.4.1/Mu import SafeContract_V1_0_0_Web3 from './Safe/v1.0.0/SafeContract_V1_0_0_Web3' import SafeContract_V1_1_1_Web3 from './Safe/v1.1.1/SafeContract_V1_1_1_Web3' import SafeContract_V1_2_0_Web3 from './Safe/v1.2.0/SafeContract_V1_2_0_Web3' -import SafeContract_V1_3_0_Web3 from './Safe/v1.3.0/SafeContract_V1_3_0_Web3' -import SafeContract_V1_4_1_Web3 from './Safe/v1.4.1/SafeContract_V1_4_1_Web3' import SafeProxyFactoryContract_V1_0_0_Web3 from './SafeProxyFactory/v1.0.0/SafeProxyFactoryContract_V1_0_0_Web3' import SafeProxyFactoryContract_V1_1_1_Web3 from './SafeProxyFactory/v1.1.1/SafeProxyFactoryContract_V1_1_1_Web3' import SafeProxyFactoryContract_V1_3_0_Web3 from './SafeProxyFactory/v1.3.0/SafeProxyFactoryContract_V1_3_0_Web3' @@ -44,31 +46,45 @@ import SignMessageLibContract_V1_4_1_Web3 from './SignMessageLib/v1.4.1/SignMess import SimulateTxAccessorContract_V1_3_0_Web3 from './SimulateTxAccessor/v1.3.0/SimulateTxAccessorContract_V1_3_0_Web3' import SimulateTxAccessorContract_V1_4_1_Web3 from './SimulateTxAccessor/v1.4.1/SimulateTxAccessorContract_V1_4_1_Web3' -export function getSafeContractInstance( +export async function getSafeContractInstance( safeVersion: SafeVersion, - safeContract: - | SafeSingleton_V1_4_1 - | SafeSingleton_V1_3_0 - | SafeSingleton_V1_2_0 - | SafeSingleton_V1_1_1 - | SafeSingleton_V1_0_0 -): - | SafeContract_V1_4_1_Web3 - | SafeContract_V1_3_0_Web3 - | SafeContract_V1_2_0_Web3 - | SafeContract_V1_1_1_Web3 - | SafeContract_V1_0_0_Web3 { + safeSingletonContract: SafeSingleton_V1_2_0 | SafeSingleton_V1_1_1 | SafeSingleton_V1_0_0, + contractAddress: string, + web3Adapter: Web3Adapter, + customContractAbi?: AbiItem | AbiItem[] | undefined, + isL1SafeSingleton?: boolean +): Promise { + const chainId = await web3Adapter.getChainId() + let safeContract switch (safeVersion) { case '1.4.1': - return new SafeContract_V1_4_1_Web3(safeContract as SafeSingleton_V1_4_1) + safeContract = new SafeContract_v1_4_1_Web3( + chainId, + web3Adapter, + isL1SafeSingleton, + contractAddress, + // TODO: Remove this unknown after remove Typechain + customContractAbi as unknown as SafeContract_v1_4_1_Abi + ) + // TODO: Remove this mapper after remove typechain + return safeContract.mapToTypechainContract() case '1.3.0': - return new SafeContract_V1_3_0_Web3(safeContract as SafeSingleton_V1_3_0) + safeContract = new SafeContract_v1_3_0_Web3( + chainId, + web3Adapter, + isL1SafeSingleton, + contractAddress, + // TODO: Remove this unknown after remove Typechain + customContractAbi as unknown as SafeContract_v1_3_0_Abi + ) + // TODO: Remove this mapper after remove typechain + return safeContract.mapToTypechainContract() case '1.2.0': - return new SafeContract_V1_2_0_Web3(safeContract as SafeSingleton_V1_2_0) + return new SafeContract_V1_2_0_Web3(safeSingletonContract as SafeSingleton_V1_2_0) case '1.1.1': - return new SafeContract_V1_1_1_Web3(safeContract as SafeSingleton_V1_1_1) + return new SafeContract_V1_1_1_Web3(safeSingletonContract as SafeSingleton_V1_1_1) case '1.0.0': - return new SafeContract_V1_0_0_Web3(safeContract as SafeSingleton_V1_0_0) + return new SafeContract_V1_0_0_Web3(safeSingletonContract as SafeSingleton_V1_0_0) default: throw new Error('Invalid Safe version') } diff --git a/packages/protocol-kit/src/adapters/web3/types.ts b/packages/protocol-kit/src/adapters/web3/types.ts index 6e60f9337..af01ceb97 100644 --- a/packages/protocol-kit/src/adapters/web3/types.ts +++ b/packages/protocol-kit/src/adapters/web3/types.ts @@ -14,3 +14,15 @@ export interface Web3TransactionResult extends BaseTransactionResult { promiEvent: PromiEvent options?: Web3TransactionOptions } + +/** + * Removes `readonly` modifier from all properties in T recursively. + * + * @template T - The type to make writable. + */ +export type DeepWriteable = T extends object & NotFunction + ? { -readonly [K in keyof T]: DeepWriteable } + : T + +type Not = T extends U ? never : T +type NotFunction = Not any> diff --git a/packages/protocol-kit/src/contracts/AbiType/Safe/SafeBaseContract.ts b/packages/protocol-kit/src/contracts/AbiType/Safe/SafeBaseContract.ts index 8f3373294..0fca0bfcf 100644 --- a/packages/protocol-kit/src/contracts/AbiType/Safe/SafeBaseContract.ts +++ b/packages/protocol-kit/src/contracts/AbiType/Safe/SafeBaseContract.ts @@ -1,4 +1,5 @@ import { EthersTransactionOptions } from '@safe-global/protocol-kit/adapters/ethers' +import { Web3TransactionOptions } from '@safe-global/protocol-kit/adapters/web3' import { Abi, AbiParametersToPrimitiveTypes, @@ -70,6 +71,7 @@ export type EncodeSafeFunction< */ export type EstimateGasSafeFunction< SafeContractAbi extends Abi, // Abi of the Safe Contract, + TransactionOptions extends EthersTransactionOptions | Web3TransactionOptions, SafeFunction extends ExtractAbiFunctionNames = ExtractAbiFunctionNames > = ( functionToEncode: SafeFunction, @@ -77,7 +79,7 @@ export type EstimateGasSafeFunction< ExtractAbiFunction['inputs'], 'inputs' >, - options?: EthersTransactionOptions + options?: TransactionOptions ) => Promise /** @@ -104,7 +106,10 @@ type SafeBaseContract = { } & { safeVersion: SafeVersion encode: EncodeSafeFunction - estimateGas: EstimateGasSafeFunction + estimateGas: EstimateGasSafeFunction< + SafeContractAbi, + EthersTransactionOptions | Web3TransactionOptions + > } export default SafeBaseContract diff --git a/packages/protocol-kit/src/contracts/AbiType/Safe/v1.4.1/SafeContract_v1_4_1.ts b/packages/protocol-kit/src/contracts/AbiType/Safe/v1.4.1/SafeContract_v1_4_1.ts new file mode 100644 index 000000000..ed6a990d8 --- /dev/null +++ b/packages/protocol-kit/src/contracts/AbiType/Safe/v1.4.1/SafeContract_v1_4_1.ts @@ -0,0 +1,39 @@ +import { narrow } from 'abitype' +import safe_1_4_1_ContractArtifacts from '@safe-global/protocol-kit/contracts/AbiType/assets/Safe/v1.4.1/safe_l2' +import SafeBaseContract, { + SafeContractReadFunctions, + SafeContractWriteFunctions +} from '../SafeBaseContract' + +const safeContract_v1_4_1_AbiTypes = narrow(safe_1_4_1_ContractArtifacts.abi) + +/** + * Represents the ABI of the Safe contract version 1.4.1. + * + * @type {SafeContract_v1_4_1_Abi} + */ +export type SafeContract_v1_4_1_Abi = typeof safeContract_v1_4_1_AbiTypes + +/** + * Extracts the names of read-only functions (view or pure) specific to the Safe contract version 1.4.1. + * + * @type {Safe_v1_4_1_Read_Functions} + */ +export type Safe_v1_4_1_Read_Functions = SafeContractReadFunctions + +/** + * Extracts the names of write functions (nonpayable or payable) specific to the Safe contract version 1.4.1. + * + * @type {Safe_v1_4_1_Write_Functions} + */ +export type Safe_v1_4_1_Write_Functions = SafeContractWriteFunctions + +/** + * Represents the contract type for a Safe contract version 1.4.1, defining read and write methods. + * Utilizes the generic SafeBaseContract with the ABI specific to version 1.4.1. + * + * @type {SafeContract_v1_4_1_Contract} + */ +type SafeContract_v1_4_1_Contract = SafeBaseContract + +export default SafeContract_v1_4_1_Contract diff --git a/packages/protocol-kit/src/contracts/AbiType/assets/Safe/v1.4.1/safe_l2.ts b/packages/protocol-kit/src/contracts/AbiType/assets/Safe/v1.4.1/safe_l2.ts new file mode 100644 index 000000000..b600a0708 --- /dev/null +++ b/packages/protocol-kit/src/contracts/AbiType/assets/Safe/v1.4.1/safe_l2.ts @@ -0,0 +1,1124 @@ +// Source: https://github.com/safe-global/safe-deployments/blob/main/src/assets/v1.4.1/safe_l2.json +export default { + defaultAddress: '0x29fcB43b46531BcA003ddC8FCB67FFE91900C762', + released: true, + contractName: 'SafeL2', + version: '1.4.1', + networkAddresses: { + '1': '0x29fcB43b46531BcA003ddC8FCB67FFE91900C762', + '5': '0x29fcB43b46531BcA003ddC8FCB67FFE91900C762', + '56': '0x29fcB43b46531BcA003ddC8FCB67FFE91900C762', + '100': '0x29fcB43b46531BcA003ddC8FCB67FFE91900C762', + '137': '0x29fcB43b46531BcA003ddC8FCB67FFE91900C762', + '1442': '0x29fcB43b46531BcA003ddC8FCB67FFE91900C762', + '8194': '0x29fcB43b46531BcA003ddC8FCB67FFE91900C762', + '17000': '0x29fcB43b46531BcA003ddC8FCB67FFE91900C762', + '80001': '0x29fcB43b46531BcA003ddC8FCB67FFE91900C762', + '84531': '0x29fcB43b46531BcA003ddC8FCB67FFE91900C762', + '11155111': '0x29fcB43b46531BcA003ddC8FCB67FFE91900C762' + }, + abi: [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address' + } + ], + name: 'AddedOwner', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'bytes32', + name: 'approvedHash', + type: 'bytes32' + }, + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address' + } + ], + name: 'ApproveHash', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'handler', + type: 'address' + } + ], + name: 'ChangedFallbackHandler', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'guard', + type: 'address' + } + ], + name: 'ChangedGuard', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'threshold', + type: 'uint256' + } + ], + name: 'ChangedThreshold', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'module', + type: 'address' + } + ], + name: 'DisabledModule', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'module', + type: 'address' + } + ], + name: 'EnabledModule', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'bytes32', + name: 'txHash', + type: 'bytes32' + }, + { + indexed: false, + internalType: 'uint256', + name: 'payment', + type: 'uint256' + } + ], + name: 'ExecutionFailure', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'module', + type: 'address' + } + ], + name: 'ExecutionFromModuleFailure', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'module', + type: 'address' + } + ], + name: 'ExecutionFromModuleSuccess', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'bytes32', + name: 'txHash', + type: 'bytes32' + }, + { + indexed: false, + internalType: 'uint256', + name: 'payment', + type: 'uint256' + } + ], + name: 'ExecutionSuccess', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address' + } + ], + name: 'RemovedOwner', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'module', + type: 'address' + }, + { + indexed: false, + internalType: 'address', + name: 'to', + type: 'address' + }, + { + indexed: false, + internalType: 'uint256', + name: 'value', + type: 'uint256' + }, + { + indexed: false, + internalType: 'bytes', + name: 'data', + type: 'bytes' + }, + { + indexed: false, + internalType: 'enum Enum.Operation', + name: 'operation', + type: 'uint8' + } + ], + name: 'SafeModuleTransaction', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'to', + type: 'address' + }, + { + indexed: false, + internalType: 'uint256', + name: 'value', + type: 'uint256' + }, + { + indexed: false, + internalType: 'bytes', + name: 'data', + type: 'bytes' + }, + { + indexed: false, + internalType: 'enum Enum.Operation', + name: 'operation', + type: 'uint8' + }, + { + indexed: false, + internalType: 'uint256', + name: 'safeTxGas', + type: 'uint256' + }, + { + indexed: false, + internalType: 'uint256', + name: 'baseGas', + type: 'uint256' + }, + { + indexed: false, + internalType: 'uint256', + name: 'gasPrice', + type: 'uint256' + }, + { + indexed: false, + internalType: 'address', + name: 'gasToken', + type: 'address' + }, + { + indexed: false, + internalType: 'address payable', + name: 'refundReceiver', + type: 'address' + }, + { + indexed: false, + internalType: 'bytes', + name: 'signatures', + type: 'bytes' + }, + { + indexed: false, + internalType: 'bytes', + name: 'additionalInfo', + type: 'bytes' + } + ], + name: 'SafeMultiSigTransaction', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address' + }, + { + indexed: false, + internalType: 'uint256', + name: 'value', + type: 'uint256' + } + ], + name: 'SafeReceived', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'initiator', + type: 'address' + }, + { + indexed: false, + internalType: 'address[]', + name: 'owners', + type: 'address[]' + }, + { + indexed: false, + internalType: 'uint256', + name: 'threshold', + type: 'uint256' + }, + { + indexed: false, + internalType: 'address', + name: 'initializer', + type: 'address' + }, + { + indexed: false, + internalType: 'address', + name: 'fallbackHandler', + type: 'address' + } + ], + name: 'SafeSetup', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'bytes32', + name: 'msgHash', + type: 'bytes32' + } + ], + name: 'SignMsg', + type: 'event' + }, + { + stateMutability: 'nonpayable', + type: 'fallback' + }, + { + inputs: [], + name: 'VERSION', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address' + }, + { + internalType: 'uint256', + name: '_threshold', + type: 'uint256' + } + ], + name: 'addOwnerWithThreshold', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'hashToApprove', + type: 'bytes32' + } + ], + name: 'approveHash', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address' + }, + { + internalType: 'bytes32', + name: '', + type: 'bytes32' + } + ], + name: 'approvedHashes', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_threshold', + type: 'uint256' + } + ], + name: 'changeThreshold', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'dataHash', + type: 'bytes32' + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes' + }, + { + internalType: 'bytes', + name: 'signatures', + type: 'bytes' + }, + { + internalType: 'uint256', + name: 'requiredSignatures', + type: 'uint256' + } + ], + name: 'checkNSignatures', + outputs: [], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'dataHash', + type: 'bytes32' + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes' + }, + { + internalType: 'bytes', + name: 'signatures', + type: 'bytes' + } + ], + name: 'checkSignatures', + outputs: [], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: 'prevModule', + type: 'address' + }, + { + internalType: 'address', + name: 'module', + type: 'address' + } + ], + name: 'disableModule', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [], + name: 'domainSeparator', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: 'module', + type: 'address' + } + ], + name: 'enableModule', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: 'to', + type: 'address' + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256' + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes' + }, + { + internalType: 'enum Enum.Operation', + name: 'operation', + type: 'uint8' + }, + { + internalType: 'uint256', + name: 'safeTxGas', + type: 'uint256' + }, + { + internalType: 'uint256', + name: 'baseGas', + type: 'uint256' + }, + { + internalType: 'uint256', + name: 'gasPrice', + type: 'uint256' + }, + { + internalType: 'address', + name: 'gasToken', + type: 'address' + }, + { + internalType: 'address', + name: 'refundReceiver', + type: 'address' + }, + { + internalType: 'uint256', + name: '_nonce', + type: 'uint256' + } + ], + name: 'encodeTransactionData', + outputs: [ + { + internalType: 'bytes', + name: '', + type: 'bytes' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: 'to', + type: 'address' + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256' + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes' + }, + { + internalType: 'enum Enum.Operation', + name: 'operation', + type: 'uint8' + }, + { + internalType: 'uint256', + name: 'safeTxGas', + type: 'uint256' + }, + { + internalType: 'uint256', + name: 'baseGas', + type: 'uint256' + }, + { + internalType: 'uint256', + name: 'gasPrice', + type: 'uint256' + }, + { + internalType: 'address', + name: 'gasToken', + type: 'address' + }, + { + internalType: 'address payable', + name: 'refundReceiver', + type: 'address' + }, + { + internalType: 'bytes', + name: 'signatures', + type: 'bytes' + } + ], + name: 'execTransaction', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool' + } + ], + stateMutability: 'payable', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: 'to', + type: 'address' + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256' + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes' + }, + { + internalType: 'enum Enum.Operation', + name: 'operation', + type: 'uint8' + } + ], + name: 'execTransactionFromModule', + outputs: [ + { + internalType: 'bool', + name: 'success', + type: 'bool' + } + ], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: 'to', + type: 'address' + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256' + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes' + }, + { + internalType: 'enum Enum.Operation', + name: 'operation', + type: 'uint8' + } + ], + name: 'execTransactionFromModuleReturnData', + outputs: [ + { + internalType: 'bool', + name: 'success', + type: 'bool' + }, + { + internalType: 'bytes', + name: 'returnData', + type: 'bytes' + } + ], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [], + name: 'getChainId', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: 'start', + type: 'address' + }, + { + internalType: 'uint256', + name: 'pageSize', + type: 'uint256' + } + ], + name: 'getModulesPaginated', + outputs: [ + { + internalType: 'address[]', + name: 'array', + type: 'address[]' + }, + { + internalType: 'address', + name: 'next', + type: 'address' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'getOwners', + outputs: [ + { + internalType: 'address[]', + name: '', + type: 'address[]' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'offset', + type: 'uint256' + }, + { + internalType: 'uint256', + name: 'length', + type: 'uint256' + } + ], + name: 'getStorageAt', + outputs: [ + { + internalType: 'bytes', + name: '', + type: 'bytes' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'getThreshold', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: 'to', + type: 'address' + }, + { + internalType: 'uint256', + name: 'value', + type: 'uint256' + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes' + }, + { + internalType: 'enum Enum.Operation', + name: 'operation', + type: 'uint8' + }, + { + internalType: 'uint256', + name: 'safeTxGas', + type: 'uint256' + }, + { + internalType: 'uint256', + name: 'baseGas', + type: 'uint256' + }, + { + internalType: 'uint256', + name: 'gasPrice', + type: 'uint256' + }, + { + internalType: 'address', + name: 'gasToken', + type: 'address' + }, + { + internalType: 'address', + name: 'refundReceiver', + type: 'address' + }, + { + internalType: 'uint256', + name: '_nonce', + type: 'uint256' + } + ], + name: 'getTransactionHash', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: 'module', + type: 'address' + } + ], + name: 'isModuleEnabled', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address' + } + ], + name: 'isOwner', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'nonce', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: 'prevOwner', + type: 'address' + }, + { + internalType: 'address', + name: 'owner', + type: 'address' + }, + { + internalType: 'uint256', + name: '_threshold', + type: 'uint256' + } + ], + name: 'removeOwner', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: 'handler', + type: 'address' + } + ], + name: 'setFallbackHandler', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: 'guard', + type: 'address' + } + ], + name: 'setGuard', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address[]', + name: '_owners', + type: 'address[]' + }, + { + internalType: 'uint256', + name: '_threshold', + type: 'uint256' + }, + { + internalType: 'address', + name: 'to', + type: 'address' + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes' + }, + { + internalType: 'address', + name: 'fallbackHandler', + type: 'address' + }, + { + internalType: 'address', + name: 'paymentToken', + type: 'address' + }, + { + internalType: 'uint256', + name: 'payment', + type: 'uint256' + }, + { + internalType: 'address payable', + name: 'paymentReceiver', + type: 'address' + } + ], + name: 'setup', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32' + } + ], + name: 'signedMessages', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: 'targetContract', + type: 'address' + }, + { + internalType: 'bytes', + name: 'calldataPayload', + type: 'bytes' + } + ], + name: 'simulateAndRevert', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: 'prevOwner', + type: 'address' + }, + { + internalType: 'address', + name: 'oldOwner', + type: 'address' + }, + { + internalType: 'address', + name: 'newOwner', + type: 'address' + } + ], + name: 'swapOwner', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + stateMutability: 'payable', + type: 'receive' + } + ] +} as const diff --git a/packages/safe-core-sdk-types/src/ethereumLibs/EthAdapter.ts b/packages/safe-core-sdk-types/src/ethereumLibs/EthAdapter.ts index 6127894a4..9cd517677 100644 --- a/packages/safe-core-sdk-types/src/ethereumLibs/EthAdapter.ts +++ b/packages/safe-core-sdk-types/src/ethereumLibs/EthAdapter.ts @@ -44,7 +44,8 @@ export interface EthAdapter { safeVersion, singletonDeployment, customContractAddress, - customContractAbi + customContractAbi, + isL1SafeSingleton }: GetContractProps): Promise getMultiSendContract({ safeVersion,