diff --git a/.prettierignore b/.prettierignore index 0b56bd824e..5660707c45 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,7 +1,7 @@ !.eslintrc.js build -config -contracts +/config +/contracts flow-typed flow-typed/npm migrations @@ -9,5 +9,5 @@ node_modules public scripts src/assets -src/config +src/types/contracts test \ No newline at end of file diff --git a/src/config/__tests__/config.test.ts b/src/config/__tests__/config.test.ts index 021a4a20e7..e9a6ef40b2 100644 --- a/src/config/__tests__/config.test.ts +++ b/src/config/__tests__/config.test.ts @@ -80,7 +80,7 @@ describe('Config Services', () => { jest.mock('src/utils/constants', () => ({ NODE_ENV: 'production', NETWORK: 'MAINNET', - APP_ENV: 'production' + APP_ENV: 'production', })) const { getTxServiceUrl, getGnosisSafeAppsUrl } = require('src/config') const TX_SERVICE_URL = mainnet.environment.production.txServiceUrl @@ -100,7 +100,7 @@ describe('Config Services', () => { jest.mock('src/utils/constants', () => ({ NODE_ENV: 'production', NETWORK: 'XDAI', - APP_ENV: 'production' + APP_ENV: 'production', })) const { getTxServiceUrl, getGnosisSafeAppsUrl } = require('src/config') const TX_SERVICE_URL = xdai.environment.production.txServiceUrl diff --git a/src/config/index.ts b/src/config/index.ts index 5dd3dcd6e2..d86606030e 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -32,9 +32,9 @@ const getCurrentEnvironment = (): string => { } type NetworkSpecificConfiguration = EnvironmentSettings & { - network: NetworkSettings, - disabledFeatures?: SafeFeatures, - disabledWallets?: Wallets, + network: NetworkSettings + disabledFeatures?: SafeFeatures + disabledWallets?: Wallets } const configuration = (): NetworkSpecificConfiguration => { @@ -60,7 +60,7 @@ const configuration = (): NetworkSpecificConfiguration => { ...networkBaseConfig, network: configFile.network, disabledFeatures: configFile.disabledFeatures, - disabledWallets: configFile.disabledWallets + disabledWallets: configFile.disabledWallets, } } @@ -137,10 +137,10 @@ const fetchContractABI = memoize( (url, contractAddress) => `${url}_${contractAddress}`, ) -const getNetworkExplorerApiKey = (networkExplorerName: string): string | undefined=> { +const getNetworkExplorerApiKey = (networkExplorerName: string): string | undefined => { switch (networkExplorerName.toLowerCase()) { case 'etherscan': { - return ETHERSCAN_API_KEY + return ETHERSCAN_API_KEY } default: { return undefined @@ -148,7 +148,7 @@ const getNetworkExplorerApiKey = (networkExplorerName: string): string | undefin } } -export const getContractABI = async (contractAddress: string) =>{ +export const getContractABI = async (contractAddress: string) => { const { apiUrl, name } = getNetworkExplorerInfo() const apiKey = getNetworkExplorerApiKey(name) @@ -181,7 +181,7 @@ export const getExplorerInfo = (hash: string): BlockScanInfo => { const type = hash.length > 42 ? 'tx' : 'address' return () => ({ url: `${url}/${type}/${hash}`, - alt: name || '', + alt: name || '', }) } } diff --git a/src/config/networks/__tests__/networks.test.ts b/src/config/networks/__tests__/networks.test.ts index 00a79dfdf0..6cdc086131 100644 --- a/src/config/networks/__tests__/networks.test.ts +++ b/src/config/networks/__tests__/networks.test.ts @@ -41,11 +41,10 @@ describe('Networks config files test', () => { return } - const environmentConfigKeys = Object - .keys(networkConfigElement) - .filter((environmentConfigKey) => - environmentConfigKey.endsWith('Uri') && !!networkConfigElement[environmentConfigKey] - ) + const environmentConfigKeys = Object.keys(networkConfigElement).filter( + (environmentConfigKey) => + environmentConfigKey.endsWith('Uri') && !!networkConfigElement[environmentConfigKey], + ) // Then environmentConfigKeys.forEach((environmentConfigKey) => { @@ -53,7 +52,10 @@ describe('Networks config files test', () => { const isValid = isValidURL(networkConfigElementUri) if (!isValid) { - console.log(`Invalid URI in "${networkFileName}" at ${environment}.${environmentConfigKey}:`, networkConfigElementUri) + console.log( + `Invalid URI in "${networkFileName}" at ${environment}.${environmentConfigKey}:`, + networkConfigElementUri, + ) } expect(isValid).toBeTruthy() diff --git a/src/config/networks/energy_web_chain.ts b/src/config/networks/energy_web_chain.ts index 7253be5bc3..df13c013ae 100644 --- a/src/config/networks/energy_web_chain.ts +++ b/src/config/networks/energy_web_chain.ts @@ -60,7 +60,7 @@ const mainnet: NetworkConfig = { WALLETS.WALLET_LINK, WALLETS.AUTHEREUM, WALLETS.LATTICE, - ] + ], } export default mainnet diff --git a/src/config/networks/index.ts b/src/config/networks/index.ts index 682b33d3ed..03ef405b11 100644 --- a/src/config/networks/index.ts +++ b/src/config/networks/index.ts @@ -11,5 +11,5 @@ export default { rinkeby, xdai, energy_web_chain, - volta + volta, } diff --git a/src/config/networks/mainnet.ts b/src/config/networks/mainnet.ts index 0a7ca3a44d..5aea9a6171 100644 --- a/src/config/networks/mainnet.ts +++ b/src/config/networks/mainnet.ts @@ -42,7 +42,7 @@ const mainnet: NetworkConfig = { decimals: 18, logoUri: EtherLogo, }, - } + }, } export default mainnet diff --git a/src/config/networks/network.d.ts b/src/config/networks/network.d.ts index fed876bb18..2cd2f1a740 100644 --- a/src/config/networks/network.d.ts +++ b/src/config/networks/network.d.ts @@ -51,12 +51,12 @@ export enum ETHEREUM_NETWORK { export type NetworkSettings = { // TODO: id now seems to be unnecessary - id: ETHEREUM_NETWORK, - backgroundColor: string, - textColor: string, - label: string, - isTestNet: boolean, - nativeCoin: Token, + id: ETHEREUM_NETWORK + backgroundColor: string + textColor: string + label: string + isTestNet: boolean + nativeCoin: Token } // something around this to display or not some critical sections in the app, depending on the network support @@ -73,14 +73,16 @@ export type GasPriceOracle = { gasParameter: string } -type GasPrice = { - gasPrice: number - gasPriceOracle?: GasPriceOracle -} | { - gasPrice?: number - // for infura there's a REST API Token required stored in: `REACT_APP_INFURA_TOKEN` - gasPriceOracle: GasPriceOracle -} +type GasPrice = + | { + gasPrice: number + gasPriceOracle?: GasPriceOracle + } + | { + gasPrice?: number + // for infura there's a REST API Token required stored in: `REACT_APP_INFURA_TOKEN` + gasPriceOracle: GasPriceOracle + } export type EnvironmentSettings = GasPrice & { txServiceUrl: string diff --git a/src/config/networks/xdai.ts b/src/config/networks/xdai.ts index b6aa7b2496..743f85fe43 100644 --- a/src/config/networks/xdai.ts +++ b/src/config/networks/xdai.ts @@ -14,12 +14,11 @@ const baseConfig: EnvironmentSettings = { const xDai: NetworkConfig = { environment: { staging: { - ...baseConfig + ...baseConfig, }, production: { ...baseConfig, safeAppsUrl: 'https://apps-xdai.gnosis-safe.io', - }, }, network: { @@ -52,9 +51,7 @@ const xDai: NetworkConfig = { WALLETS.AUTHEREUM, WALLETS.LATTICE, ], - disabledFeatures: [ - FEATURES.ENS_LOOKUP, - ], + disabledFeatures: [FEATURES.ENS_LOOKUP], } export default xDai diff --git a/src/logic/contracts/api/masterCopies.ts b/src/logic/contracts/api/masterCopies.ts new file mode 100644 index 0000000000..0890911e9e --- /dev/null +++ b/src/logic/contracts/api/masterCopies.ts @@ -0,0 +1,47 @@ +import axios from 'axios' +import { getTxServiceUrl } from 'src/config' +import memoize from 'lodash.memoize' + +export enum MasterCopyDeployer { + GNOSIS = 'Gnosis', + CIRCLES = 'Circles', +} + +type MasterCopyFetch = { + address: string + version: string +} + +export type MasterCopy = { + address: string + version: string + deployer: MasterCopyDeployer + deployerRepoUrl: string +} + +const extractMasterCopyInfo = (mc: MasterCopyFetch): MasterCopy => { + const isCircles = mc.version.toLowerCase().includes(MasterCopyDeployer.CIRCLES.toLowerCase()) + const dashIndex = mc.version.indexOf('-') + + const masterCopy = { + address: mc.address, + version: !isCircles ? mc.version : mc.version.substring(0, dashIndex), + deployer: !isCircles ? MasterCopyDeployer.GNOSIS : MasterCopyDeployer.CIRCLES, + deployerRepoUrl: !isCircles + ? 'https://github.com/gnosis/safe-contracts/releases' + : 'https://github.com/CirclesUBI/safe-contracts/releases', + } + return masterCopy +} + +export const fetchMasterCopies = memoize( + async (): Promise => { + const url = `${getTxServiceUrl()}/about/master-copies/` + try { + const res = await axios.get<{ address: string; version: string }[]>(url) + return res.data.map(extractMasterCopyInfo) + } catch (error) { + console.error('Fetching data from master-copies errored', error) + } + }, +) diff --git a/src/logic/contracts/generateBatchRequests.ts b/src/logic/contracts/generateBatchRequests.ts index adf327b84f..6a724f98c6 100644 --- a/src/logic/contracts/generateBatchRequests.ts +++ b/src/logic/contracts/generateBatchRequests.ts @@ -14,25 +14,33 @@ import { AbiItem } from 'web3-utils' */ type MethodsArgsType = Array - interface Props { +interface Props { abi: AbiItem[] address: string batch?: BatchRequest context?: unknown - methods: Array - } + methods: Array +} -const generateBatchRequests = ({ abi, address, batch, context, methods }: Props): Promise => { +const generateBatchRequests = ({ + abi, + address, + batch, + context, + methods, +}: Props): Promise => { const contractInstance = new web3.eth.Contract(abi, address) const localBatch = new web3.BatchRequest() const values = methods.map((methodObject) => { - let method, type, args: MethodsArgsType = [] + let method, + type, + args: MethodsArgsType = [] if (typeof methodObject === 'string') { method = methodObject } else { - ({ method, type, args } = methodObject) + ;({ method, type, args } = methodObject) } return new Promise((resolve) => { diff --git a/src/logic/contracts/historicProxyCode.ts b/src/logic/contracts/historicProxyCode.ts index 90e08fdc61..b2ebeee791 100644 --- a/src/logic/contracts/historicProxyCode.ts +++ b/src/logic/contracts/historicProxyCode.ts @@ -1,4 +1,4 @@ -// +// // Code of the safe v1.0.0 const proxyCodeV10 = diff --git a/src/logic/contracts/safeContracts.ts b/src/logic/contracts/safeContracts.ts index f392a17a18..2555095838 100644 --- a/src/logic/contracts/safeContracts.ts +++ b/src/logic/contracts/safeContracts.ts @@ -1,17 +1,16 @@ import { AbiItem } from 'web3-utils' import GnosisSafeSol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafe.json' -import memoize from 'lodash.memoize' import ProxyFactorySol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafeProxyFactory.json' -import SafeProxy from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafeProxy.json' import Web3 from 'web3' import { ETHEREUM_NETWORK } from 'src/config/networks/network.d' -import { isProxyCode } from 'src/logic/contracts/historicProxyCode' import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses' import { calculateGasOf, calculateGasPrice } from 'src/logic/wallets/ethTransactions' import { getWeb3, getNetworkIdFrom } from 'src/logic/wallets/getWeb3' import { GnosisSafe } from 'src/types/contracts/GnosisSafe.d' import { GnosisSafeProxyFactory } from 'src/types/contracts/GnosisSafeProxyFactory.d' +import { AllowanceModule } from 'src/types/contracts/AllowanceModule.d' +import { getSafeInfo, SafeInfo } from 'src/logic/safe/utils/safeInformation' import { SPENDING_LIMIT_MODULE_ADDRESS } from 'src/utils/constants' import SpendingLimitModule from './artifacts/AllowanceModule.json' @@ -22,7 +21,6 @@ export const SAFE_MASTER_COPY_ADDRESS = '0x34CfAC646f301356fAa8B21e94227e3583Fe3 export const DEFAULT_FALLBACK_HANDLER_ADDRESS = '0xd5D82B6aDDc9027B22dCA772Aa68D5d74cdBdF44' export const SAFE_MASTER_COPY_ADDRESS_V10 = '0xb6029EA3B2c51D09a50B53CA8012FeEB05bDa35A' - let proxyFactoryMaster: GnosisSafeProxyFactory let safeMaster: GnosisSafe @@ -31,13 +29,13 @@ let safeMaster: GnosisSafe * @param {Web3} web3 * @param {ETHEREUM_NETWORK} networkId */ -const createGnosisSafeContract = (web3: Web3, networkId: ETHEREUM_NETWORK) => { +export const getGnosisSafeContract = (web3: Web3, networkId: ETHEREUM_NETWORK) => { const networks = GnosisSafeSol.networks // TODO: this may not be the most scalable approach, // but up until v1.2.0 the address is the same for all the networks. // So, if we can't find the network in the Contract artifact, we fallback to MAINNET. const contractAddress = networks[networkId]?.address ?? networks[ETHEREUM_NETWORK.MAINNET].address - return new web3.eth.Contract(GnosisSafeSol.abi as AbiItem[], contractAddress) as unknown as GnosisSafe + return (new web3.eth.Contract(GnosisSafeSol.abi as AbiItem[], contractAddress) as unknown) as GnosisSafe } /** @@ -45,48 +43,69 @@ const createGnosisSafeContract = (web3: Web3, networkId: ETHEREUM_NETWORK) => { * @param {Web3} web3 * @param {ETHEREUM_NETWORK} networkId */ -const createProxyFactoryContract = (web3: Web3, networkId: ETHEREUM_NETWORK): GnosisSafeProxyFactory => { +const getProxyFactoryContract = (web3: Web3, networkId: ETHEREUM_NETWORK): GnosisSafeProxyFactory => { const networks = ProxyFactorySol.networks // TODO: this may not be the most scalable approach, // but up until v1.2.0 the address is the same for all the networks. // So, if we can't find the network in the Contract artifact, we fallback to MAINNET. const contractAddress = networks[networkId]?.address ?? networks[ETHEREUM_NETWORK.MAINNET].address - return new web3.eth.Contract(ProxyFactorySol.abi as AbiItem[], contractAddress) as unknown as GnosisSafeProxyFactory + return (new web3.eth.Contract(ProxyFactorySol.abi as AbiItem[], contractAddress) as unknown) as GnosisSafeProxyFactory } -const createSpendingLimitContract = () => { +/** + * Creates a Contract instance of the GnosisSafeProxyFactory contract + */ +export const getSpendingLimitContract = () => { const web3 = getWeb3() - return new web3.eth.Contract(SpendingLimitModule.abi as AbiItem[], SPENDING_LIMIT_MODULE_ADDRESS) + return (new web3.eth.Contract( + SpendingLimitModule.abi as AbiItem[], + SPENDING_LIMIT_MODULE_ADDRESS, + ) as unknown) as AllowanceModule } -export const getGnosisSafeContract = memoize(createGnosisSafeContract) -export const getSpendingLimitContract = memoize(createSpendingLimitContract) -const getCreateProxyFactoryContract = memoize(createProxyFactoryContract) +export const getMasterCopyAddressFromProxyAddress = async (proxyAddress: string): Promise => { + const res = await getSafeInfo(proxyAddress) + const masterCopyAddress = (res as SafeInfo)?.masterCopy + if (!masterCopyAddress) { + console.error(`There was not possible to get masterCopy address from proxy ${proxyAddress}.`) + return + } + return masterCopyAddress +} -const instantiateMasterCopies = async () => { +export const instantiateSafeContracts = async () => { const web3 = getWeb3() const networkId = await getNetworkIdFrom(web3) // Create ProxyFactory Master Copy - proxyFactoryMaster = getCreateProxyFactoryContract(web3, networkId) + proxyFactoryMaster = getProxyFactoryContract(web3, networkId) // Create Safe Master copy safeMaster = getGnosisSafeContract(web3, networkId) } -export const initContracts = instantiateMasterCopies - export const getSafeMasterContract = async () => { - await initContracts() - + await instantiateSafeContracts() return safeMaster } -export const getSafeDeploymentTransaction = (safeAccounts: string[], numConfirmations: number, safeCreationSalt: number) => { +export const getSafeDeploymentTransaction = ( + safeAccounts: string[], + numConfirmations: number, + safeCreationSalt: number, +) => { const gnosisSafeData = safeMaster.methods - .setup(safeAccounts, numConfirmations, ZERO_ADDRESS, '0x', DEFAULT_FALLBACK_HANDLER_ADDRESS, ZERO_ADDRESS, 0, ZERO_ADDRESS) + .setup( + safeAccounts, + numConfirmations, + ZERO_ADDRESS, + '0x', + DEFAULT_FALLBACK_HANDLER_ADDRESS, + ZERO_ADDRESS, + 0, + ZERO_ADDRESS, + ) .encodeABI() - return proxyFactoryMaster.methods.createProxyWithNonce(safeMaster.options.address, gnosisSafeData, safeCreationSalt) } @@ -94,10 +113,19 @@ export const estimateGasForDeployingSafe = async ( safeAccounts: string[], numConfirmations: number, userAccount: string, - safeCreationSalt: number + safeCreationSalt: number, ) => { const gnosisSafeData = await safeMaster.methods - .setup(safeAccounts, numConfirmations, ZERO_ADDRESS, '0x', DEFAULT_FALLBACK_HANDLER_ADDRESS, ZERO_ADDRESS, 0, ZERO_ADDRESS) + .setup( + safeAccounts, + numConfirmations, + ZERO_ADDRESS, + '0x', + DEFAULT_FALLBACK_HANDLER_ADDRESS, + ZERO_ADDRESS, + 0, + ZERO_ADDRESS, + ) .encodeABI() const proxyFactoryData = proxyFactoryMaster.methods .createProxyWithNonce(safeMaster.options.address, gnosisSafeData, safeCreationSalt) @@ -110,29 +138,5 @@ export const estimateGasForDeployingSafe = async ( export const getGnosisSafeInstanceAt = (safeAddress: string): GnosisSafe => { const web3 = getWeb3() - return new web3.eth.Contract(GnosisSafeSol.abi as AbiItem[], safeAddress) as unknown as GnosisSafe -} - -const cleanByteCodeMetadata = (bytecode: string): string => { - const metaData = 'a165' - return bytecode.substring(0, bytecode.lastIndexOf(metaData)) -} - -export const validateProxy = async (safeAddress: string): Promise => { - // https://solidity.readthedocs.io/en/latest/metadata.html#usage-for-source-code-verification - const web3 = getWeb3() - const code = await web3.eth.getCode(safeAddress) - const codeWithoutMetadata = cleanByteCodeMetadata(code) - const supportedProxies = [SafeProxy] - for (let i = 0; i < supportedProxies.length; i += 1) { - const proxy = supportedProxies[i] - const proxyCode = proxy.deployedBytecode - const proxyCodeWithoutMetadata = cleanByteCodeMetadata(proxyCode) - if (codeWithoutMetadata === proxyCodeWithoutMetadata) { - return true - } - } - - - return isProxyCode(codeWithoutMetadata) + return (new web3.eth.Contract(GnosisSafeSol.abi as AbiItem[], safeAddress) as unknown) as GnosisSafe } diff --git a/src/routes/load/components/DetailsForm/index.tsx b/src/routes/load/components/DetailsForm/index.tsx index 1524f9e122..799d1c95d8 100644 --- a/src/routes/load/components/DetailsForm/index.tsx +++ b/src/routes/load/components/DetailsForm/index.tsx @@ -20,10 +20,9 @@ import { import Block from 'src/components/layout/Block' import Col from 'src/components/layout/Col' import Paragraph from 'src/components/layout/Paragraph' -import { SAFE_MASTER_COPY_ADDRESS_V10, getSafeMasterContract, validateProxy } from 'src/logic/contracts/safeContracts' -import { getWeb3 } from 'src/logic/wallets/getWeb3' import { FIELD_LOAD_ADDRESS, FIELD_LOAD_NAME } from 'src/routes/load/components/fields' import { secondary } from 'src/theme/variables' +import { getSafeInfo } from 'src/logic/safe/utils/safeInformation' const useStyles = makeStyles({ root: { @@ -42,42 +41,23 @@ const useStyles = makeStyles({ }, }) -export const SAFE_INSTANCE_ERROR = 'Address given is not a Safe instance' -export const SAFE_MASTERCOPY_ERROR = 'Address is not a Safe or mastercopy is not supported' +export const SAFE_ADDRESS_NOT_VALID = 'Address given is not a valid Safe address' // In case of an error here, it will be swallowed by final-form // So if you're experiencing any strang behaviours like freeze or hanging // Don't mind to check if everything is OK inside this function :) export const safeFieldsValidation = async (values): Promise> => { const errors = {} - const web3 = getWeb3() - const safeAddress = values[FIELD_LOAD_ADDRESS] + const address = values[FIELD_LOAD_ADDRESS] - if (!safeAddress || mustBeEthereumAddress(safeAddress) !== undefined) { + if (!address || mustBeEthereumAddress(address) !== undefined) { return errors } - const isValidProxy = await validateProxy(safeAddress) - if (!isValidProxy) { - errors[FIELD_LOAD_ADDRESS] = SAFE_INSTANCE_ERROR - return errors - } - - // check mastercopy - const proxyAddressFromStorage = await web3.eth.getStorageAt(safeAddress, 0) - // https://www.reddit.com/r/ethereum/comments/6l3da1/how_long_are_ethereum_addresses/ - // ganache returns plain address - // rinkeby returns 0x0000000000000+{40 address charachers} - // address comes last so we just get last 40 charachers (1byte = 2hex chars) - const checksummedProxyAddress = web3.utils.toChecksumAddress( - `0x${proxyAddressFromStorage.substr(proxyAddressFromStorage.length - 40)}`, - ) - const safeMaster = await getSafeMasterContract() - const masterCopy = safeMaster.options.address - const sameMasterCopy = - checksummedProxyAddress === masterCopy || checksummedProxyAddress === SAFE_MASTER_COPY_ADDRESS_V10 - if (!sameMasterCopy) { - errors[FIELD_LOAD_ADDRESS] = SAFE_MASTERCOPY_ERROR + // if getSafeInfo does not provide data, it's not a valid safe. + const safeInfo = await getSafeInfo(address) + if (!safeInfo) { + errors[FIELD_LOAD_ADDRESS] = SAFE_ADDRESS_NOT_VALID } return errors diff --git a/src/routes/open/components/Layout.tsx b/src/routes/open/components/Layout.tsx index 728276ad4f..78e5a43fea 100644 --- a/src/routes/open/components/Layout.tsx +++ b/src/routes/open/components/Layout.tsx @@ -6,7 +6,7 @@ import Stepper, { StepperPage } from 'src/components/Stepper' import Block from 'src/components/layout/Block' import Heading from 'src/components/layout/Heading' import Row from 'src/components/layout/Row' -import { initContracts } from 'src/logic/contracts/safeContracts' +import { instantiateSafeContracts } from 'src/logic/contracts/safeContracts' import { Review } from 'src/routes/open/components/ReviewInformation' import SafeNameField from 'src/routes/open/components/SafeNameForm' import { SafeOwnersPage } from 'src/routes/open/components/SafeOwnersConfirmationsForm' @@ -105,7 +105,7 @@ export const Layout = (props: LayoutProps): React.ReactElement => { useEffect(() => { if (provider) { - initContracts() + instantiateSafeContracts() } }, [provider]) diff --git a/src/routes/open/container/Open.tsx b/src/routes/open/container/Open.tsx index 7161c4e787..1af1761397 100644 --- a/src/routes/open/container/Open.tsx +++ b/src/routes/open/container/Open.tsx @@ -62,7 +62,6 @@ export const createSafe = (values, userAccount) => { const safeCreationSalt = getSafeCreationSaltFrom(values) const deploymentTx = getSafeDeploymentTransaction(ownerAddresses, confirmations, safeCreationSalt) - const promiEvent = deploymentTx.send({ from: userAccount }) promiEvent diff --git a/src/routes/opening/index.tsx b/src/routes/opening/index.tsx index d976284625..28fd8f817e 100644 --- a/src/routes/opening/index.tsx +++ b/src/routes/opening/index.tsx @@ -9,7 +9,7 @@ import Button from 'src/components/layout/Button' import Heading from 'src/components/layout/Heading' import Img from 'src/components/layout/Img' import Paragraph from 'src/components/layout/Paragraph' -import { initContracts } from 'src/logic/contracts/safeContracts' +import { instantiateSafeContracts } from 'src/logic/contracts/safeContracts' import { EMPTY_DATA } from 'src/logic/wallets/ethTransactions' import { getWeb3 } from 'src/logic/wallets/getWeb3' import { background, connected } from 'src/theme/variables' @@ -152,7 +152,7 @@ const SafeDeployment = ({ creationTxHash, onCancel, onRetry, onSuccess, submitte useEffect(() => { const loadContracts = async () => { - await initContracts() + await instantiateSafeContracts() setLoading(false) } diff --git a/src/routes/safe/components/Settings/SafeDetails/index.tsx b/src/routes/safe/components/Settings/SafeDetails/index.tsx index c04ed03a6b..d071c122ab 100644 --- a/src/routes/safe/components/Settings/SafeDetails/index.tsx +++ b/src/routes/safe/components/Settings/SafeDetails/index.tsx @@ -1,5 +1,5 @@ import { makeStyles } from '@material-ui/core/styles' -import React, { useEffect } from 'react' +import React, { useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { styles } from './style' @@ -17,6 +17,7 @@ import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' import enqueueSnackbar from 'src/logic/notifications/store/actions/enqueueSnackbar' import { getNotificationsFromTxType, enhanceSnackbarForAction } from 'src/logic/notifications' +import { sameAddress } from 'src/logic/wallets/ethAddresses' import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions' import UpdateSafeModal from 'src/routes/safe/components/Settings/UpdateSafeModal' import { grantedSelector } from 'src/routes/safe/container/selector' @@ -30,6 +31,8 @@ import { safeParamAddressFromStateSelector, } from 'src/logic/safe/store/selectors' import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics' +import { fetchMasterCopies, MasterCopy, MasterCopyDeployer } from 'src/logic/contracts/api/masterCopies' +import { getMasterCopyAddressFromProxyAddress } from 'src/logic/contracts/safeContracts' export const SAFE_NAME_INPUT_TEST_ID = 'safe-name-input' export const SAFE_NAME_SUBMIT_BTN_TEST_ID = 'change-safe-name-btn' @@ -49,6 +52,7 @@ const SafeDetails = (): React.ReactElement => { const [isModalOpen, setModalOpen] = React.useState(false) const safeAddress = useSelector(safeParamAddressFromStateSelector) + const [safeInfo, setSafeInfo] = useState() const toggleModal = () => { setModalOpen((prevOpen) => !prevOpen) @@ -65,87 +69,111 @@ const SafeDetails = (): React.ReactElement => { setModalOpen(true) } + const getSafeVersion = () => { + if (!safeInfo) { + return '' + } + return safeInfo.deployer === MasterCopyDeployer.GNOSIS + ? safeCurrentVersion + : `${safeCurrentVersion}-${safeInfo.deployer}` + } + + const getSafeVersionUpdate = () => { + if (!safeInfo) { + return '' + } + return safeInfo.deployer === MasterCopyDeployer.GNOSIS && safeNeedsUpdate + ? ` (there's a newer version: ${latestMasterContractVersion})` + : '' + } + useEffect(() => { trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'Settings', label: 'Details' }) }, [trackEvent]) + useEffect(() => { + const getMasterCopyInfo = async () => { + const masterCopies = await fetchMasterCopies() + const masterCopyAddress = await getMasterCopyAddressFromProxyAddress(safeAddress) + const masterCopy = masterCopies?.find((mc) => sameAddress(mc.address, masterCopyAddress)) + setSafeInfo(masterCopy) + } + + if (safeAddress) { + getMasterCopyInfo() + } + }, [safeAddress]) + return ( - <> - - {() => ( - <> - - Safe Version + + {() => ( + <> + + Safe Version + + + + {getSafeVersion()} + {getSafeVersionUpdate()} + + + + {safeNeedsUpdate && isUserOwner ? ( - - + - {safeNeedsUpdate && isUserOwner ? ( - - - - - - ) : null} + ) : null} + + + Modify Safe name + + You can change the name of this Safe. This name is only stored locally and never shared with Gnosis or any + third parties. + + + - - Modify Safe name - - You can change the name of this Safe. This name is only stored locally and never shared with Gnosis or - any third parties. - - - - - - - - - - - - - - - )} - - + + + + + + + + + + + )} + ) }