Merge pull request #1796 from gnosis/release/v2.18.0

Backmerge release v2.18.0 to development
This commit is contained in:
Daniel Sanchez 2021-01-19 21:52:30 +01:00 committed by GitHub
commit 87470956fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 242 additions and 187 deletions

View File

@ -1,55 +0,0 @@
name: Build/release
# this will help you specify where to run
on:
push:
branches:
# this will run on the specified branch
- feature/desktop-app
env:
REACT_APP_BLOCKNATIVE_KEY: ${{ secrets.REACT_APP_BLOCKNATIVE_KEY }}
REACT_APP_FORTMATIC_KEY: ${{ secrets.REACT_APP_FORTMATIC_KEY }}
REACT_APP_GOOGLE_ANALYTICS_ID_MAINNET: ${{ secrets.REACT_APP_GOOGLE_ANALYTICS_ID_MAINNET }}
REACT_APP_INFURA_TOKEN: ${{ secrets.REACT_APP_INFURA_TOKEN }}
REACT_APP_PORTIS_ID: ${{ secrets.REACT_APP_PORTIS_ID }}
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest]
steps:
- name: Check out Git repository
uses: actions/checkout@v1
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1
with:
node-version: 10.16
- name: Build/release Electron app
env:
# macOS notarization API key
APPLEID: ${{ secrets.APPLE_ID }}
APPLEIDPASS: ${{ secrets.APPLE_ID_PASS }}
uses: samuelmeuli/action-electron-builder@v1
with:
#Build scipt
build_script_name: build-desktop
# GitHub token, automatically provided to the action
# (No need to define this secret in the repo settings)
github_token: ${{ secrets.github_token }}
# macOS code signing certificate
mac_certs: ${{ secrets.MAC_CERTS }}
mac_certs_password: ${{ secrets.MAC_CERTS_PASSWORD }}
# If the commit is tagged with a version (e.g. "v1.0.0"),
# release the app after building
release: ${{ startsWith(github.ref, 'refs/tags/v') }}

View File

@ -5,7 +5,6 @@ on:
workflow_dispatch workflow_dispatch
env: env:
REACT_APP_BLOCKNATIVE_KEY: ${{ secrets.REACT_APP_BLOCKNATIVE_KEY }}
REACT_APP_FORTMATIC_KEY: ${{ secrets.REACT_APP_FORTMATIC_KEY }} REACT_APP_FORTMATIC_KEY: ${{ secrets.REACT_APP_FORTMATIC_KEY }}
REACT_APP_GOOGLE_ANALYTICS_ID_MAINNET: ${{ secrets.REACT_APP_GOOGLE_ANALYTICS_ID_MAINNET }} REACT_APP_GOOGLE_ANALYTICS_ID_MAINNET: ${{ secrets.REACT_APP_GOOGLE_ANALYTICS_ID_MAINNET }}
REACT_APP_INFURA_TOKEN: ${{ secrets.REACT_APP_INFURA_TOKEN }} REACT_APP_INFURA_TOKEN: ${{ secrets.REACT_APP_INFURA_TOKEN }}
@ -21,24 +20,24 @@ jobs:
fail-fast: false fail-fast: false
max-parallel: 15 max-parallel: 15
matrix: matrix:
os: [macos-latest, windows-latest, ubuntu-latest] os: [macos-latest, windows-latest, ubuntu-20.04]
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@v2 uses: actions/checkout@v2
# Add cache for yarn directory # Add cache for yarn directory
- name: Get yarn cache directory path # - name: Get yarn cache directory path
id: yarn-cache-dir-path # id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)" # run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2 # - uses: actions/cache@v2
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) # id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with: # with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }} # path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} # key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: | # restore-keys: |
${{ runner.os }}-yarn- # ${{ runner.os }}-yarn-
- name: Patch node gyp on windows to support Visual Studio 2019 - name: Patch node gyp on windows to support Visual Studio 2019
if: startsWith(matrix.os, 'windows') if: startsWith(matrix.os, 'windows')
@ -52,9 +51,9 @@ jobs:
yarn global add node-gyp yarn global add node-gyp
yarn config set node_gyp "$_\node_modules\node-gyp\bin\node-gyp.js" yarn config set node_gyp "$_\node_modules\node-gyp\bin\node-gyp.js"
- name: Install Node.js, NPM and Yarn - name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1 uses: actions/setup-node@v2
with: with:
node-version: 12 node-version: 14
- run: yarn install --frozen-lockfile --network-concurrency 1 - run: yarn install --frozen-lockfile --network-concurrency 1
- name: Build/Release Desktop App - name: Build/Release Desktop App
env: env:

View File

@ -161,7 +161,6 @@ export type GasPriceOracle = {
- **REACT_APP_GOOGLE_ANALYTICS**: Used for enabling google analytics - **REACT_APP_GOOGLE_ANALYTICS**: Used for enabling google analytics
- **REACT_APP_PORTIS_ID**: Portis ID for enabling it on given network - **REACT_APP_PORTIS_ID**: Portis ID for enabling it on given network
- **REACT_APP_FORTMATIC_KEY**: Formatic yey for given network - **REACT_APP_FORTMATIC_KEY**: Formatic yey for given network
- **REACT_APP_BLOCKNATIVE_KEY**: Blocknative key for given network
--- ---
## How to add a network ## How to add a network
@ -190,7 +189,6 @@ export enum ETHEREUM_NETWORK {
* REACT_APP_GOOGLE_ANALYTICS * REACT_APP_GOOGLE_ANALYTICS
* REACT_APP_PORTIS_ID * REACT_APP_PORTIS_ID
* REACT_APP_FORTMATIC_KEY * REACT_APP_FORTMATIC_KEY
* REACT_APP_BLOCKNATIVE_KEY
3) Add the **NetworkSettings** in [`src/config/networks`](/src/config/networks) as `<network_name>.ts`: 3) Add the **NetworkSettings** in [`src/config/networks`](/src/config/networks) as `<network_name>.ts`:

View File

@ -1,6 +1,6 @@
{ {
"name": "safe-react", "name": "safe-react",
"version": "2.17.0", "version": "2.18.0",
"description": "Allowing crypto users manage funds in a safer way", "description": "Allowing crypto users manage funds in a safer way",
"website": "https://github.com/gnosis/safe-react#readme", "website": "https://github.com/gnosis/safe-react#readme",
"bugs": { "bugs": {
@ -95,20 +95,22 @@
] ]
}, },
"files": [ "files": [
"build/", "build",
"patches",
"public", "public",
"scripts", "scripts",
"dev-app-update.yml",
"package.json" "package.json"
], ],
"directories": { "directories": {
"buildResources": "public/build" "buildResources": "public/resources"
}, },
"mac": { "mac": {
"category": "public.app-category.productivity", "category": "public.app-category.productivity",
"hardenedRuntime": true, "hardenedRuntime": true,
"entitlements": "public/build/entitlements.mac.plist", "entitlements": "public/resources/entitlements.mac.plist",
"gatekeeperAssess": false, "gatekeeperAssess": false,
"entitlementsInherit": "public/build/entitlements.mac.plist", "entitlementsInherit": "public/resources/entitlements.mac.plist",
"target": [ "target": [
"dmg", "dmg",
"zip" "zip"
@ -136,7 +138,7 @@
"target": [ "target": [
"nsis" "nsis"
], ],
"icon": "public/build/icon.ico" "icon": "public/resources/icon.ico"
} }
}, },
"resolutions": { "resolutions": {
@ -161,7 +163,7 @@
"@gnosis.pm/safe-contracts": "1.1.1-dev.2", "@gnosis.pm/safe-contracts": "1.1.1-dev.2",
"@gnosis.pm/safe-react-components": "https://github.com/gnosis/safe-react-components.git#bf3a84486b7353bd25447ddff39c406f6fafecc6", "@gnosis.pm/safe-react-components": "https://github.com/gnosis/safe-react-components.git#bf3a84486b7353bd25447ddff39c406f6fafecc6",
"@gnosis.pm/util-contracts": "2.0.6", "@gnosis.pm/util-contracts": "2.0.6",
"@ledgerhq/hw-transport-node-hid-singleton": "5.36.0", "@ledgerhq/hw-transport-node-hid-singleton": "5.38.0",
"@material-ui/core": "^4.11.0", "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.11.0", "@material-ui/icons": "^4.11.0",
"@material-ui/lab": "4.0.0-alpha.56", "@material-ui/lab": "4.0.0-alpha.56",
@ -249,7 +251,7 @@
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"dotenv-expand": "^5.1.0", "dotenv-expand": "^5.1.0",
"electron": "^9.3.5", "electron": "^9.4.0",
"electron-builder": "22.9.1", "electron-builder": "22.9.1",
"electron-notarize": "1.0.0", "electron-notarize": "1.0.0",
"eslint": "^7.11.0", "eslint": "^7.11.0",

View File

@ -90,9 +90,10 @@ function createWindow(port = DEFAULT_PORT) {
webPreferences: { webPreferences: {
preload: path.join(__dirname, '../scripts/preload.js'), preload: path.join(__dirname, '../scripts/preload.js'),
allowRunningInsecureContent: true, allowRunningInsecureContent: true,
enableRemoteModule: true,
nativeWindowOpen: true, // need to be set in order to display modal nativeWindowOpen: true, // need to be set in order to display modal
}, },
icon: electron.nativeImage.createFromPath(path.join(__dirname, '/public/build/safe.png')), icon: electron.nativeImage.createFromPath(path.join(__dirname, '../build/resources/safe.png')),
}) })
mainWindow.once('ready-to-show', () => { mainWindow.once('ready-to-show', () => {
@ -141,7 +142,7 @@ process.on('uncaughtException', function (error) {
}) })
app.userAgentFallback = app.userAgentFallback =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) old-airport-include/1.0.0 Chrome Electron/9.3.1 Safari/537.36' 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) old-airport-include/1.0.0 Chrome Electron/9.4.1 Safari/537.36'
// We have one non-context-aware module in node_modules/usb. This is used by @ledgerhq/hw-transport-node-hid // We have one non-context-aware module in node_modules/usb. This is used by @ledgerhq/hw-transport-node-hid
// This type of modules will be impossible to use after electron 10 // This type of modules will be impossible to use after electron 10

View File

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

View File

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

View File

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -3,6 +3,10 @@ import { EstimationStatus } from 'src/logic/hooks/useEstimateTransactionGas'
import Paragraph from 'src/components/layout/Paragraph' import Paragraph from 'src/components/layout/Paragraph'
import { getNetworkInfo } from 'src/config' import { getNetworkInfo } from 'src/config'
import { TransactionFailText } from 'src/components/TransactionFailText' import { TransactionFailText } from 'src/components/TransactionFailText'
import { useSelector } from 'react-redux'
import { providerNameSelector } from 'src/logic/wallets/store/selectors'
import { sameString } from 'src/utils/strings'
import { WALLETS } from 'src/config/networks/network.d'
type TransactionFailTextProps = { type TransactionFailTextProps = {
txEstimationExecutionStatus: EstimationStatus txEstimationExecutionStatus: EstimationStatus
@ -20,6 +24,8 @@ export const TransactionFees = ({
isOffChainSignature, isOffChainSignature,
txEstimationExecutionStatus, txEstimationExecutionStatus,
}: TransactionFailTextProps): React.ReactElement | null => { }: TransactionFailTextProps): React.ReactElement | null => {
const providerName = useSelector(providerNameSelector)
let transactionAction let transactionAction
if (isCreation) { if (isCreation) {
transactionAction = 'create' transactionAction = 'create'
@ -29,6 +35,11 @@ export const TransactionFees = ({
transactionAction = 'approve' transactionAction = 'approve'
} }
// FIXME this should be removed when estimating with WalletConnect correctly
if (!providerName || sameString(providerName, WALLETS.WALLET_CONNECT)) {
return null
}
return ( return (
<> <>
<Paragraph> <Paragraph>

View File

@ -130,7 +130,11 @@ export const estimateGasForDeployingSafe = async (
const proxyFactoryData = proxyFactoryMaster.methods const proxyFactoryData = proxyFactoryMaster.methods
.createProxyWithNonce(safeMaster.options.address, gnosisSafeData, safeCreationSalt) .createProxyWithNonce(safeMaster.options.address, gnosisSafeData, safeCreationSalt)
.encodeABI() .encodeABI()
const gas = await calculateGasOf(proxyFactoryData, userAccount, proxyFactoryMaster.options.address) const gas = await calculateGasOf({
data: proxyFactoryData,
from: userAccount,
to: proxyFactoryMaster.options.address,
})
const gasPrice = await calculateGasPrice() const gasPrice = await calculateGasPrice()
return gas * parseInt(gasPrice, 10) return gas * parseInt(gasPrice, 10)

View File

@ -21,6 +21,8 @@ import { List } from 'immutable'
import { Confirmation } from 'src/logic/safe/store/models/types/confirmation' import { Confirmation } from 'src/logic/safe/store/models/types/confirmation'
import { checkIfOffChainSignatureIsPossible } from 'src/logic/safe/safeTxSigner' import { checkIfOffChainSignatureIsPossible } from 'src/logic/safe/safeTxSigner'
import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses' import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses'
import { sameString } from 'src/utils/strings'
import { WALLETS } from 'src/config/networks/network.d'
export enum EstimationStatus { export enum EstimationStatus {
LOADING = 'LOADING', LOADING = 'LOADING',
@ -28,13 +30,19 @@ export enum EstimationStatus {
SUCCESS = 'SUCCESS', SUCCESS = 'SUCCESS',
} }
const checkIfTxIsExecution = (threshold: number, preApprovingOwner?: string, txConfirmations?: number): boolean => const checkIfTxIsExecution = (
txConfirmations === threshold || !!preApprovingOwner || threshold === 1 threshold: number,
preApprovingOwner?: string,
txConfirmations?: number,
txType?: string,
): boolean =>
txConfirmations === threshold || !!preApprovingOwner || threshold === 1 || sameString(txType, 'spendingLimit')
const checkIfTxIsApproveAndExecution = (threshold: number, txConfirmations: number): boolean => const checkIfTxIsApproveAndExecution = (threshold: number, txConfirmations: number, txType?: string): boolean =>
txConfirmations + 1 === threshold txConfirmations + 1 === threshold || sameString(txType, 'spendingLimit')
const checkIfTxIsCreation = (txConfirmations: number): boolean => txConfirmations === 0 const checkIfTxIsCreation = (txConfirmations: number, txType?: string): boolean =>
txConfirmations === 0 && !sameString(txType, 'spendingLimit')
type TransactionEstimationProps = { type TransactionEstimationProps = {
txData: string txData: string
@ -115,6 +123,7 @@ type UseEstimateTransactionGasProps = {
preApprovingOwner?: string preApprovingOwner?: string
operation?: number operation?: number
safeTxGas?: number safeTxGas?: number
txType?: string
} }
type TransactionGasEstimationResult = { type TransactionGasEstimationResult = {
@ -136,6 +145,7 @@ export const useEstimateTransactionGas = ({
preApprovingOwner, preApprovingOwner,
operation, operation,
safeTxGas, safeTxGas,
txType,
}: UseEstimateTransactionGasProps): TransactionGasEstimationResult => { }: UseEstimateTransactionGasProps): TransactionGasEstimationResult => {
const [gasEstimation, setGasEstimation] = useState<TransactionGasEstimationResult>({ const [gasEstimation, setGasEstimation] = useState<TransactionGasEstimationResult>({
txEstimationExecutionStatus: EstimationStatus.LOADING, txEstimationExecutionStatus: EstimationStatus.LOADING,
@ -151,17 +161,21 @@ export const useEstimateTransactionGas = ({
const safeAddress = useSelector(safeParamAddressFromStateSelector) const safeAddress = useSelector(safeParamAddressFromStateSelector)
const threshold = useSelector(safeThresholdSelector) const threshold = useSelector(safeThresholdSelector)
const safeVersion = useSelector(safeCurrentVersionSelector) const safeVersion = useSelector(safeCurrentVersionSelector)
const { account: from, smartContractWallet } = useSelector(providerSelector) const { account: from, smartContractWallet, name: providerName } = useSelector(providerSelector)
useEffect(() => { useEffect(() => {
const estimateGas = async () => { const estimateGas = async () => {
if (!txData.length) { if (!txData.length) {
return return
} }
// FIXME this should be removed when estimating with WalletConnect correctly
if (!providerName || sameString(providerName, WALLETS.WALLET_CONNECT)) {
return null
}
const isExecution = checkIfTxIsExecution(Number(threshold), preApprovingOwner, txConfirmations?.size) const isExecution = checkIfTxIsExecution(Number(threshold), preApprovingOwner, txConfirmations?.size, txType)
const isCreation = checkIfTxIsCreation(txConfirmations?.size || 0) const isCreation = checkIfTxIsCreation(txConfirmations?.size || 0, txType)
const approvalAndExecution = checkIfTxIsApproveAndExecution(Number(threshold), txConfirmations?.size || 0) const approvalAndExecution = checkIfTxIsApproveAndExecution(Number(threshold), txConfirmations?.size || 0, txType)
try { try {
const isOffChainSignature = checkIfOffChainSignatureIsPossible(isExecution, smartContractWallet, safeVersion) const isOffChainSignature = checkIfOffChainSignatureIsPossible(isExecution, smartContractWallet, safeVersion)
@ -235,6 +249,8 @@ export const useEstimateTransactionGas = ({
safeVersion, safeVersion,
smartContractWallet, smartContractWallet,
safeTxGas, safeTxGas,
txType,
providerName,
]) ])
return gasEstimation return gasEstimation

View File

@ -32,6 +32,7 @@ interface ProcessTransactionArgs {
safeAddress: string safeAddress: string
tx: Transaction tx: Transaction
userAddress: string userAddress: string
thresholdReached: boolean
} }
type ProcessTransactionAction = ThunkAction<Promise<void | string>, AppReduxState, DispatchReturn, AnyAction> type ProcessTransactionAction = ThunkAction<Promise<void | string>, AppReduxState, DispatchReturn, AnyAction>
@ -42,6 +43,7 @@ export const processTransaction = ({
safeAddress, safeAddress,
tx, tx,
userAddress, userAddress,
thresholdReached,
}: ProcessTransactionArgs): ProcessTransactionAction => async ( }: ProcessTransactionArgs): ProcessTransactionAction => async (
dispatch: Dispatch, dispatch: Dispatch,
getState: () => AppReduxState, getState: () => AppReduxState,
@ -56,7 +58,7 @@ export const processTransaction = ({
const isExecution = approveAndExecute || (await shouldExecuteTransaction(safeInstance, nonce, lastTx)) const isExecution = approveAndExecute || (await shouldExecuteTransaction(safeInstance, nonce, lastTx))
const safeVersion = await getCurrentSafeVersion(safeInstance) const safeVersion = await getCurrentSafeVersion(safeInstance)
const preApprovingOwner = approveAndExecute ? userAddress : undefined const preApprovingOwner = approveAndExecute && !thresholdReached ? userAddress : undefined
let sigs = generateSignaturesFromTxConfirmations(tx.confirmations, preApprovingOwner) let sigs = generateSignaturesFromTxConfirmations(tx.confirmations, preApprovingOwner)
if (!sigs) { if (!sigs) {

View File

@ -25,6 +25,21 @@ const parseRequiredTxGasResponse = (data: string): number => {
return data.match(/.{2}/g)?.reduce(reducer, 0) return data.match(/.{2}/g)?.reduce(reducer, 0)
} }
interface ErrorDataJson extends JSON {
originalError?: {
data?: string
}
data?: string
}
const getJSONOrNullFromString = (stringInput: string): ErrorDataJson | null => {
try {
return JSON.parse(stringInput)
} catch (error) {
return null
}
}
// Parses the result from the error message (GETH, OpenEthereum/Parity and Nethermind) and returns the data value // Parses the result from the error message (GETH, OpenEthereum/Parity and Nethermind) and returns the data value
export const getDataFromNodeErrorMessage = (errorMessage: string): string | undefined => { export const getDataFromNodeErrorMessage = (errorMessage: string): string | undefined => {
// Replace illegal characters that often comes within the error string (like <20> for example) // Replace illegal characters that often comes within the error string (like <20> for example)
@ -35,7 +50,13 @@ export const getDataFromNodeErrorMessage = (errorMessage: string): string | unde
const [, ...error] = normalizedErrorString.split('\n') const [, ...error] = normalizedErrorString.split('\n')
try { try {
const errorAsJSON = JSON.parse(error.join('')) const errorAsString = error.join('')
const errorAsJSON = getJSONOrNullFromString(errorAsString)
// Trezor wallet returns the error not as an JSON object but directly as string
if (!errorAsJSON) {
return errorAsString.length ? errorAsString : undefined
}
// For new GETH nodes they will return the data as error in the format: // For new GETH nodes they will return the data as error in the format:
// { // {
@ -251,5 +272,9 @@ export const estimateGasForTransactionApproval = async ({
from, from,
}) })
const approveTransactionTxData = await safeInstance.methods.approveHash(txHash).encodeABI() const approveTransactionTxData = await safeInstance.methods.approveHash(txHash).encodeABI()
return calculateGasOf(approveTransactionTxData, from, safeAddress) return calculateGasOf({
data: approveTransactionTxData,
from,
to: safeAddress,
})
} }

View File

@ -6,7 +6,6 @@ import {
SAFE_MASTER_COPY_ADDRESS, SAFE_MASTER_COPY_ADDRESS,
getGnosisSafeInstanceAt, getGnosisSafeInstanceAt,
} from 'src/logic/contracts/safeContracts' } from 'src/logic/contracts/safeContracts'
import { DELEGATE_CALL } from 'src/logic/safe/transactions'
import { getWeb3 } from 'src/logic/wallets/getWeb3' import { getWeb3 } from 'src/logic/wallets/getWeb3'
import { MultiSend } from 'src/types/contracts/MultiSend.d' import { MultiSend } from 'src/types/contracts/MultiSend.d'
@ -49,7 +48,7 @@ export const getEncodedMultiSendCallData = (txs: MultiSendTx[], web3: Web3): str
return encodedMultiSendCallData return encodedMultiSendCallData
} }
export const upgradeSafeToLatestVersion = async (safeAddress: string, createTransaction): Promise<void> => { export const getUpgradeSafeTransactionHash = async (safeAddress: string): Promise<string> => {
const safeInstance = await getGnosisSafeInstanceAt(safeAddress) const safeInstance = await getGnosisSafeInstanceAt(safeAddress)
const fallbackHandlerTxData = safeInstance.methods.setFallbackHandler(DEFAULT_FALLBACK_HANDLER_ADDRESS).encodeABI() const fallbackHandlerTxData = safeInstance.methods.setFallbackHandler(DEFAULT_FALLBACK_HANDLER_ADDRESS).encodeABI()
const updateSafeTxData = safeInstance.methods.changeMasterCopy(SAFE_MASTER_COPY_ADDRESS).encodeABI() const updateSafeTxData = safeInstance.methods.changeMasterCopy(SAFE_MASTER_COPY_ADDRESS).encodeABI()
@ -69,17 +68,5 @@ export const upgradeSafeToLatestVersion = async (safeAddress: string, createTran
] ]
const web3 = getWeb3() const web3 = getWeb3()
const encodeMultiSendCallData = getEncodedMultiSendCallData(txs, web3) return getEncodedMultiSendCallData(txs, web3)
createTransaction({
safeAddress,
to: MULTI_SEND_ADDRESS,
valueInWei: 0,
txData: encodeMultiSendCallData,
notifiedTransaction: 'STANDARD_TX',
enqueueSnackbar: () => {},
closeSnackbar: () => {},
operation: DELEGATE_CALL,
})
return
} }

View File

@ -50,10 +50,16 @@ export const calculateGasPrice = async (): Promise<string> => {
} }
} }
export const calculateGasOf = async (data: string, from: string, to: string): Promise<number> => { export const calculateGasOf = async (txConfig: {
to: string
from: string
data: string
gasPrice?: number
gas?: number
}): Promise<number> => {
const web3 = getWeb3() const web3 = getWeb3()
try { try {
const gas = await web3.eth.estimateGas({ data, from, to }) const gas = await web3.eth.estimateGas(txConfig)
return gas * 2 return gas * 2
} catch (err) { } catch (err) {

View File

@ -25,6 +25,8 @@ import GasEstimationInfo from './GasEstimationInfo'
import { getNetworkInfo } from 'src/config' import { getNetworkInfo } from 'src/config'
import { TransactionParams } from './AppFrame' import { TransactionParams } from './AppFrame'
import { EstimationStatus, useEstimateTransactionGas } from 'src/logic/hooks/useEstimateTransactionGas' import { EstimationStatus, useEstimateTransactionGas } from 'src/logic/hooks/useEstimateTransactionGas'
import Row from 'src/components/layout/Row'
import { TransactionFees } from 'src/components/TransactionsFees'
const isTxValid = (t: Transaction): boolean => { const isTxValid = (t: Transaction): boolean => {
if (!['string', 'number'].includes(typeof t.value)) { if (!['string', 'number'].includes(typeof t.value)) {
@ -67,6 +69,10 @@ const StyledTextBox = styled(TextBox)`
max-width: 444px; max-width: 444px;
` `
const Container = styled.div`
max-width: 480px;
`
type OwnProps = { type OwnProps = {
isOpen: boolean isOpen: boolean
app: SafeApp app: SafeApp
@ -96,7 +102,14 @@ export const ConfirmTransactionModal = ({
}: OwnProps): React.ReactElement | null => { }: OwnProps): React.ReactElement | null => {
const [estimatedSafeTxGas, setEstimatedSafeTxGas] = useState(0) const [estimatedSafeTxGas, setEstimatedSafeTxGas] = useState(0)
const { gasEstimation, txEstimationExecutionStatus } = useEstimateTransactionGas({ const {
gasEstimation,
isOffChainSignature,
isCreation,
isExecution,
gasCostFormatted,
txEstimationExecutionStatus,
} = useEstimateTransactionGas({
txData: encodeMultiSendCall(txs), txData: encodeMultiSendCall(txs),
txRecipient: MULTI_SEND_ADDRESS, txRecipient: MULTI_SEND_ADDRESS,
operation: DELEGATE_CALL, operation: DELEGATE_CALL,
@ -159,7 +172,7 @@ export const ConfirmTransactionModal = ({
</Text> </Text>
</> </>
) : ( ) : (
<> <Container>
<AddressInfo ethBalance={ethBalance} safeAddress={safeAddress} safeName={safeName} /> <AddressInfo ethBalance={ethBalance} safeAddress={safeAddress} safeName={safeName} />
<DividerLine withArrow /> <DividerLine withArrow />
{txs.map((tx, index) => ( {txs.map((tx, index) => (
@ -195,7 +208,16 @@ export const ConfirmTransactionModal = ({
/> />
</div> </div>
)} )}
</> <Row>
<TransactionFees
gasCostFormatted={gasCostFormatted}
isExecution={isExecution}
isCreation={isCreation}
isOffChainSignature={isOffChainSignature}
txEstimationExecutionStatus={txEstimationExecutionStatus}
/>
</Row>
</Container>
) )
return ( return (

View File

@ -64,7 +64,7 @@ const ReviewCollectible = ({ onClose, onPrev, tx }: Props): React.ReactElement =
isCreation, isCreation,
} = useEstimateTransactionGas({ } = useEstimateTransactionGas({
txData: data, txData: data,
txRecipient: tx.recipientAddress, txRecipient: tx.assetAddress,
}) })
useEffect(() => { useEffect(() => {

View File

@ -56,20 +56,6 @@ type ReviewTxProps = {
tx: ReviewTxProp tx: ReviewTxProp
} }
const useTxAmount = (tx: ReviewTxProp, isSendingNativeToken: boolean, txToken?: RecordOf<TokenProps>): string => {
const [txAmount, setTxAmount] = useState('0')
// txAmount should be 0 if we send tokens
// the real value is encoded in txData and will be used by the contract
// if txAmount > 0 it would send ETH from the Safe (and the data will be empty)
useEffect(() => {
const txAmount = isSendingNativeToken ? toTokenUnit(tx.amount, nativeCoin.decimals) : '0'
setTxAmount(txAmount)
}, [tx.amount, txToken, isSendingNativeToken])
return txAmount
}
const useTxData = ( const useTxData = (
isSendingNativeToken: boolean, isSendingNativeToken: boolean,
txAmount: string, txAmount: string,
@ -88,7 +74,8 @@ const useTxData = (
if (!isSendingNativeToken) { if (!isSendingNativeToken) {
const StandardToken = await getHumanFriendlyToken() const StandardToken = await getHumanFriendlyToken()
const tokenInstance = await StandardToken.at(txToken.address as string) const tokenInstance = await StandardToken.at(txToken.address as string)
txData = tokenInstance.contract.methods.transfer(recipientAddress, txAmount).encodeABI() const erc20TransferAmount = toTokenUnit(txAmount, txToken.decimals)
txData = tokenInstance.contract.methods.transfer(recipientAddress, erc20TransferAmount).encodeABI()
} }
setData(txData) setData(txData)
} }
@ -107,9 +94,8 @@ const ReviewTx = ({ onClose, onPrev, tx }: ReviewTxProps): React.ReactElement =>
const txToken = useMemo(() => tokens.find((token) => sameAddress(token.address, tx.token)), [tokens, tx.token]) const txToken = useMemo(() => tokens.find((token) => sameAddress(token.address, tx.token)), [tokens, tx.token])
const isSendingNativeToken = sameAddress(txToken?.address, nativeCoin.address) const isSendingNativeToken = sameAddress(txToken?.address, nativeCoin.address)
const txRecipient = isSendingNativeToken ? tx.recipientAddress : txToken?.address || '' const txRecipient = isSendingNativeToken ? tx.recipientAddress : txToken?.address || ''
const txValue = isSendingNativeToken ? toTokenUnit(tx.amount, nativeCoin.decimals) : '0'
const txAmount = useTxAmount(tx, isSendingNativeToken, txToken) const data = useTxData(isSendingNativeToken, tx.amount, tx.recipientAddress, txToken)
const data = useTxData(isSendingNativeToken, txAmount, tx.recipientAddress, txToken)
const { const {
gasCostFormatted, gasCostFormatted,
@ -120,6 +106,7 @@ const ReviewTx = ({ onClose, onPrev, tx }: ReviewTxProps): React.ReactElement =>
} = useEstimateTransactionGas({ } = useEstimateTransactionGas({
txData: data, txData: data,
txRecipient, txRecipient,
txType: tx.txType,
}) })
const submitTx = async () => { const submitTx = async () => {
@ -152,7 +139,7 @@ const ReviewTx = ({ onClose, onPrev, tx }: ReviewTxProps): React.ReactElement =>
createTransaction({ createTransaction({
safeAddress: safeAddress, safeAddress: safeAddress,
to: txRecipient as string, to: txRecipient as string,
valueInWei: txAmount, valueInWei: txValue,
txData: data, txData: data,
notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX, notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX,
}), }),

View File

@ -19,7 +19,7 @@ import enqueueSnackbar from 'src/logic/notifications/store/actions/enqueueSnackb
import { getNotificationsFromTxType, enhanceSnackbarForAction } from 'src/logic/notifications' import { getNotificationsFromTxType, enhanceSnackbarForAction } from 'src/logic/notifications'
import { sameAddress } from 'src/logic/wallets/ethAddresses' import { sameAddress } from 'src/logic/wallets/ethAddresses'
import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions' import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions'
import UpdateSafeModal from 'src/routes/safe/components/Settings/UpdateSafeModal' import { UpdateSafeModal } from 'src/routes/safe/components/Settings/UpdateSafeModal'
import { grantedSelector } from 'src/routes/safe/container/selector' import { grantedSelector } from 'src/routes/safe/container/selector'
import updateSafe from 'src/logic/safe/store/actions/updateSafe' import updateSafe from 'src/logic/safe/store/actions/updateSafe'
import Link from 'src/components/layout/Link' import Link from 'src/components/layout/Link'

View File

@ -1,9 +1,7 @@
import IconButton from '@material-ui/core/IconButton' import IconButton from '@material-ui/core/IconButton'
import Close from '@material-ui/icons/Close' import Close from '@material-ui/icons/Close'
import { withStyles } from '@material-ui/styles' import React, { useEffect, useState } from 'react'
import React from 'react'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
import { bindActionCreators } from 'redux'
import { styles } from './style' import { styles } from './style'
@ -13,17 +11,61 @@ import Button from 'src/components/layout/Button'
import Hairline from 'src/components/layout/Hairline' import Hairline from 'src/components/layout/Hairline'
import Paragraph from 'src/components/layout/Paragraph' import Paragraph from 'src/components/layout/Paragraph'
import Row from 'src/components/layout/Row' import Row from 'src/components/layout/Row'
import { upgradeSafeToLatestVersion } from 'src/logic/safe/utils/upgradeSafe' import { getUpgradeSafeTransactionHash } from 'src/logic/safe/utils/upgradeSafe'
import createTransaction from 'src/logic/safe/store/actions/createTransaction' import createTransaction from 'src/logic/safe/store/actions/createTransaction'
import { makeStyles } from '@material-ui/core'
import { TransactionFees } from 'src/components/TransactionsFees'
import { useEstimateTransactionGas } from 'src/logic/hooks/useEstimateTransactionGas'
import { MULTI_SEND_ADDRESS } from 'src/logic/contracts/safeContracts'
import { DELEGATE_CALL } from 'src/logic/safe/transactions'
import { EMPTY_DATA } from 'src/logic/wallets/ethTransactions'
const UpdateSafeModal = ({ classes, onClose, safeAddress }) => { const useStyles = makeStyles(styles)
type Props = {
onClose: () => void
safeAddress: string
}
export const UpdateSafeModal = ({ onClose, safeAddress }: Props): React.ReactElement => {
const classes = useStyles()
const dispatch = useDispatch() const dispatch = useDispatch()
const [multiSendCallData, setMultiSendCallData] = useState(EMPTY_DATA)
useEffect(() => {
const calculateUpgradeSafeModal = async () => {
const encodeMultiSendCallData = await getUpgradeSafeTransactionHash(safeAddress)
setMultiSendCallData(encodeMultiSendCallData)
}
calculateUpgradeSafeModal()
}, [safeAddress])
const handleSubmit = async () => { const handleSubmit = async () => {
// Call the update safe method // Call the update safe method
await upgradeSafeToLatestVersion(safeAddress, bindActionCreators(createTransaction, dispatch)) dispatch(
createTransaction({
safeAddress,
to: MULTI_SEND_ADDRESS,
valueInWei: '0',
txData: multiSendCallData,
notifiedTransaction: 'STANDARD_TX',
operation: DELEGATE_CALL,
}),
)
onClose() onClose()
} }
const {
gasCostFormatted,
txEstimationExecutionStatus,
isExecution,
isCreation,
isOffChainSignature,
} = useEstimateTransactionGas({
txData: multiSendCallData,
txRecipient: safeAddress,
})
return ( return (
<> <>
<Row align="center" className={classes.heading} grow> <Row align="center" className={classes.heading} grow>
@ -56,6 +98,15 @@ const UpdateSafeModal = ({ classes, onClose, safeAddress }) => {
have to confirm the update in case more than one confirmation is required for this Safe. have to confirm the update in case more than one confirmation is required for this Safe.
</Paragraph> </Paragraph>
</Row> </Row>
<Row>
<TransactionFees
gasCostFormatted={gasCostFormatted}
isExecution={isExecution}
isCreation={isCreation}
isOffChainSignature={isOffChainSignature}
txEstimationExecutionStatus={txEstimationExecutionStatus}
/>
</Row>
</Block> </Block>
<Hairline style={{ position: 'absolute', bottom: 85 }} /> <Hairline style={{ position: 'absolute', bottom: 85 }} />
<Row align="center" className={classes.buttonRow}> <Row align="center" className={classes.buttonRow}>
@ -72,5 +123,3 @@ const UpdateSafeModal = ({ classes, onClose, safeAddress }) => {
</> </>
) )
} }
export default withStyles(styles as any)(UpdateSafeModal)

View File

@ -1,6 +1,7 @@
import { lg, md, secondaryText, sm } from 'src/theme/variables' import { lg, md, secondaryText, sm } from 'src/theme/variables'
import { createStyles } from '@material-ui/core'
export const styles = () => ({ export const styles = createStyles({
heading: { heading: {
padding: `${sm} ${lg}`, padding: `${sm} ${lg}`,
justifyContent: 'space-between', justifyContent: 'space-between',

View File

@ -105,6 +105,7 @@ export const ApproveTxModal = ({
userAddress, userAddress,
notifiedTransaction: TX_NOTIFICATION_TYPES.CONFIRMATION_TX, notifiedTransaction: TX_NOTIFICATION_TYPES.CONFIRMATION_TX,
approveAndExecute: canExecute && approveAndExecute && isTheTxReadyToBeExecuted, approveAndExecute: canExecute && approveAndExecute && isTheTxReadyToBeExecuted,
thresholdReached,
}), }),
) )
onClose() onClose()

View File

@ -7,7 +7,6 @@ export const GOOGLE_ANALYTICS_ID = process.env.REACT_APP_GOOGLE_ANALYTICS || ''
export const SENTRY_DSN = process.env.REACT_APP_SENTRY_DSN || '' export const SENTRY_DSN = process.env.REACT_APP_SENTRY_DSN || ''
export const PORTIS_ID = process.env.REACT_APP_PORTIS_ID ?? '852b763d-f28b-4463-80cb-846d7ec5806b' export const PORTIS_ID = process.env.REACT_APP_PORTIS_ID ?? '852b763d-f28b-4463-80cb-846d7ec5806b'
export const FORTMATIC_KEY = process.env.REACT_APP_FORTMATIC_KEY ?? 'pk_test_CAD437AA29BE0A40' export const FORTMATIC_KEY = process.env.REACT_APP_FORTMATIC_KEY ?? 'pk_test_CAD437AA29BE0A40'
export const BLOCKNATIVE_KEY = process.env.REACT_APP_BLOCKNATIVE_KEY ?? '7fbb9cee-7e97-4436-8770-8b29a9a8814c'
/* /*
* Not being used * Not being used
export const SQUARELINK_ID = { export const SQUARELINK_ID = {

View File

@ -1782,19 +1782,19 @@
dependencies: dependencies:
invariant "2" invariant "2"
"@ledgerhq/devices@^5.36.0": "@ledgerhq/devices@^5.38.0":
version "5.36.0" version "5.38.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-5.36.0.tgz#f4493bea44390325fcc7a2a0d03bba69fc1604ec" resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-5.38.0.tgz#0b805020c4f3ac40d35f1b1af6d64d04256b1e0f"
integrity sha512-EwQwLZcz66a2V07Bad0J+Q67LR2afj2NJChJNcA6/gqvzXrtNtJ37u1co9eLU7S5GGll5JGi7KdBqAD9ZTHaaQ== integrity sha512-1RZ+Dh+oVUDMeaPSCeQ56qzgiMHHy481dbkRCDUMRJEzkGqOLI3k34x7XdkVKy1NQdt8G8sYyobP/yixDry5ow==
dependencies: dependencies:
"@ledgerhq/errors" "^5.36.0" "@ledgerhq/errors" "^5.38.0"
"@ledgerhq/logs" "^5.36.0" "@ledgerhq/logs" "^5.38.0"
rxjs "^6.6.3" rxjs "^6.6.3"
"@ledgerhq/errors@^5.34.0", "@ledgerhq/errors@^5.36.0": "@ledgerhq/errors@^5.34.0", "@ledgerhq/errors@^5.38.0":
version "5.36.0" version "5.38.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-5.36.0.tgz#9d178b54116ae81b1fbdfa80afcd33981453be4c" resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-5.38.0.tgz#1642b87de47cbabc7b75ca93005a690895920a72"
integrity sha512-VS6aFzn3IDUmSLaX2kAPg5sQOc5m7IwvswAGvoMc3FCi9/a1dXWwtiYn5rWd1QjDJlTKSfCwmSpvUMp8FKoY2Q== integrity sha512-d4gQzbOLNBoGIwDtEGFNSb0w0aYN10T5Y749e+vqiJoS3dWrB+5BCSQ/U/ALet0wi/UMIyFY/xmgd1gPaPB3Hw==
"@ledgerhq/hw-app-eth@^5.21.0": "@ledgerhq/hw-app-eth@^5.21.0":
version "5.37.0" version "5.37.0"
@ -1807,27 +1807,27 @@
bignumber.js "^9.0.1" bignumber.js "^9.0.1"
rlp "^2.2.6" rlp "^2.2.6"
"@ledgerhq/hw-transport-node-hid-noevents@^5.36.0": "@ledgerhq/hw-transport-node-hid-noevents@^5.38.0":
version "5.36.0" version "5.38.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid-noevents/-/hw-transport-node-hid-noevents-5.36.0.tgz#a25bdf0d10f9eac73148040e5887f40ca46f6a93" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid-noevents/-/hw-transport-node-hid-noevents-5.38.0.tgz#ffbfb0b997e585fc08b6b220997caaa59e094554"
integrity sha512-8zwokin0KFJaTzhy7oLVcy4QCNUjMuc49wPLnr3zxLqhO1innMp2crUxjIFNLRGJm/TfDLnlpxTPud6WZoo5zg== integrity sha512-zuxN3gWfCuN+pbK3BKc8z3VCulKI7zee1N3xhCWua9TiwL3leBTBs25Bvm22fmiLJYhzszccbF673/bpnnIQAA==
dependencies: dependencies:
"@ledgerhq/devices" "^5.36.0" "@ledgerhq/devices" "^5.38.0"
"@ledgerhq/errors" "^5.36.0" "@ledgerhq/errors" "^5.38.0"
"@ledgerhq/hw-transport" "^5.36.0" "@ledgerhq/hw-transport" "^5.38.0"
"@ledgerhq/logs" "^5.36.0" "@ledgerhq/logs" "^5.38.0"
node-hid "2.1.1" node-hid "2.1.1"
"@ledgerhq/hw-transport-node-hid-singleton@5.36.0": "@ledgerhq/hw-transport-node-hid-singleton@5.38.0":
version "5.36.0" version "5.38.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid-singleton/-/hw-transport-node-hid-singleton-5.36.0.tgz#99cedb773e571642d7f151c3134c5924147bc0d8" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid-singleton/-/hw-transport-node-hid-singleton-5.38.0.tgz#ea133ac3a7015aaafd5d629388da4d4a2086c761"
integrity sha512-eKkJGXW0m7PeJY7V/7FDfU4VFLIVSRjxi7PpfJKFYlUrRwx9QndlZT0LD0u3W9dLktgounHhsOuXvuG44uTHYA== integrity sha512-FPpr6R2Cs6YKCamprC9vzJa+P7RieyMLQC3cG6EjYg8tmvHPtIxpmWFclNi45jZ05Ve1YvNjutoHVTVn0cOnpg==
dependencies: dependencies:
"@ledgerhq/devices" "^5.36.0" "@ledgerhq/devices" "^5.38.0"
"@ledgerhq/errors" "^5.36.0" "@ledgerhq/errors" "^5.38.0"
"@ledgerhq/hw-transport" "^5.36.0" "@ledgerhq/hw-transport" "^5.38.0"
"@ledgerhq/hw-transport-node-hid-noevents" "^5.36.0" "@ledgerhq/hw-transport-node-hid-noevents" "^5.38.0"
"@ledgerhq/logs" "^5.36.0" "@ledgerhq/logs" "^5.38.0"
lodash "^4.17.20" lodash "^4.17.20"
node-hid "2.1.1" node-hid "2.1.1"
usb-detection "^4.10.0" usb-detection "^4.10.0"
@ -1842,19 +1842,19 @@
"@ledgerhq/logs" "^5.30.0" "@ledgerhq/logs" "^5.30.0"
u2f-api "0.2.7" u2f-api "0.2.7"
"@ledgerhq/hw-transport@^5.34.0", "@ledgerhq/hw-transport@^5.36.0": "@ledgerhq/hw-transport@^5.34.0", "@ledgerhq/hw-transport@^5.38.0":
version "5.36.0" version "5.38.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-5.36.0.tgz#b6e951c411182ece59c7b527f948240d1b0a8a51" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-5.38.0.tgz#b02bea73d77e729d13c367a967df1666f9ef940d"
integrity sha512-rwLTBUsdGCC3Ka4G99sqGbbyVhkVxXd4eWWeOb8gnuKhrTydZGkkP3JdZSHgWSrVRYTAUmkE1AnUmchbfh361w== integrity sha512-CAxvHukCcp+RqaEsSltmBw4Lb1yW42fiF/LTYN7JvCkZyLIQhvkndLDVCgp4hpMdtLK4bkf7RJRElqbN0vRoAQ==
dependencies: dependencies:
"@ledgerhq/devices" "^5.36.0" "@ledgerhq/devices" "^5.38.0"
"@ledgerhq/errors" "^5.36.0" "@ledgerhq/errors" "^5.38.0"
events "^3.2.0" events "^3.2.0"
"@ledgerhq/logs@^5.30.0", "@ledgerhq/logs@^5.36.0": "@ledgerhq/logs@^5.30.0", "@ledgerhq/logs@^5.38.0":
version "5.36.0" version "5.38.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-5.36.0.tgz#78721347162fb834d90effa1123b363dc46e3a52" resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-5.38.0.tgz#3c5dbd1f62c0bf5580477b218fb57c67b575643a"
integrity sha512-HnD/hByteUL1MsJu1lMinY9bNq8++5mWJ8qHCW9dLC9LbsvWIqwLwmZiGcW0D2tX9p0/C5ESuIpJ9B/d2dReuw== integrity sha512-i87Yn89Cq2D9Y0KmrEzCm62XHzI2edeOTBENKH6vAyzESGzyF+SBoqtZNwrjJcKup3/9dNn/zHjpicY7ev94Vw==
"@material-ui/core@^4.11.0": "@material-ui/core@^4.11.0":
version "4.11.2" version "4.11.2"
@ -8156,10 +8156,10 @@ electron-updater@4.3.5:
lodash.isequal "^4.5.0" lodash.isequal "^4.5.0"
semver "^7.3.2" semver "^7.3.2"
electron@^9.3.5: electron@^9.4.0:
version "9.3.5" version "9.4.1"
resolved "https://registry.yarnpkg.com/electron/-/electron-9.3.5.tgz#7967146b81e6d9b484773243fd4a4f671a50b884" resolved "https://registry.yarnpkg.com/electron/-/electron-9.4.1.tgz#62a2aae4cd93f1b56d794a47541505a71654177a"
integrity sha512-EPmDsp7sO0UPtw7nLD1ufse/nBskP+ifXzBgUg9psCUlapkzuwYi6pmLAzKLW/bVjwgyUKwh1OKWILWfOeLGcQ== integrity sha512-r4CxoVG9Ja7tBtkilWMnBsBGup8G8Z+v7icZmwysHa8/OSr0OrLjrcOF/30BAP7yPE5fz/XTxygnltzW4OTZdw==
dependencies: dependencies:
"@electron/get" "^1.0.1" "@electron/get" "^1.0.1"
"@types/node" "^12.0.12" "@types/node" "^12.0.12"