mirror of
https://github.com/status-im/safe-react.git
synced 2026-08-31 13:41:06 +00:00
Merge branch 'development' into upgrade-web3
This commit is contained in:
@@ -79,12 +79,17 @@ jobs:
|
||||
REACT_APP_INTERCOM_ID: ${{ secrets.REACT_APP_INTERCOM_ID }}
|
||||
REACT_APP_IPFS_GATEWAY: ${{ secrets.REACT_APP_IPFS_GATEWAY }}
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ secrets.AWS_DEFAULT_REGION }}
|
||||
|
||||
# Script to deploy Pull Requests
|
||||
- run: bash ./scripts/github/deploy_pull_request.sh
|
||||
if: success() && github.event.number
|
||||
env:
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
PR_NUMBER: ${{ github.event.number }}
|
||||
REVIEW_BUCKET_NAME: ${{ secrets.AWS_REVIEW_BUCKET_NAME }}
|
||||
REACT_APP_NETWORK: ${{ env.REACT_APP_NETWORK }}
|
||||
@@ -101,13 +106,6 @@ jobs:
|
||||
env:
|
||||
REVIEW_FEATURE_URL: https://pr${{ github.event.number }}--${{ env.REPO_NAME_ALPHANUMERIC }}.review.gnosisdev.com
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ secrets.AWS_DEFAULT_REGION }}
|
||||
|
||||
# Script to deploy to development environment
|
||||
# EWC build is never created in development branch
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Remove broken apt repos [Ubuntu]
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
if: ${{ matrix.os }} == 'ubuntu-latest'
|
||||
run: |
|
||||
for apt_file in `grep -lr microsoft /etc/apt/sources.list.d/`; do sudo rm $apt_file; done
|
||||
- uses: actions/checkout@v2
|
||||
@@ -91,8 +91,6 @@ jobs:
|
||||
- run: bash ./scripts/github/deploy_pull_request.sh
|
||||
if: success() && github.event.number
|
||||
env:
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
PR_NUMBER: ${{ github.event.number }}
|
||||
REVIEW_BUCKET_NAME: ${{ secrets.AWS_REVIEW_BUCKET_NAME }}
|
||||
REACT_APP_NETWORK: ${{ env.REACT_APP_NETWORK }}
|
||||
|
||||
@@ -79,12 +79,17 @@ jobs:
|
||||
REACT_APP_INTERCOM_ID: ${{ secrets.REACT_APP_INTERCOM_ID }}
|
||||
REACT_APP_IPFS_GATEWAY: ${{ secrets.REACT_APP_IPFS_GATEWAY }}
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ secrets.AWS_DEFAULT_REGION }}
|
||||
|
||||
# Script to deploy Pull Requests
|
||||
- run: bash ./scripts/github/deploy_pull_request.sh
|
||||
if: success() && github.event.number
|
||||
env:
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
PR_NUMBER: ${{ github.event.number }}
|
||||
REVIEW_BUCKET_NAME: ${{ secrets.AWS_REVIEW_BUCKET_NAME }}
|
||||
REACT_APP_NETWORK: ${{ env.REACT_APP_NETWORK }}
|
||||
@@ -101,14 +106,6 @@ jobs:
|
||||
env:
|
||||
REVIEW_FEATURE_URL: https://pr${{ github.event.number }}--${{ env.REPO_NAME_ALPHANUMERIC }}.review.gnosisdev.com
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ secrets.AWS_DEFAULT_REGION }}
|
||||
|
||||
|
||||
# Script to deploy to development environment
|
||||
# Volta build is never created in development branch
|
||||
|
||||
|
||||
@@ -79,12 +79,17 @@ jobs:
|
||||
REACT_APP_INTERCOM_ID: ${{ secrets.REACT_APP_INTERCOM_ID }}
|
||||
REACT_APP_IPFS_GATEWAY: ${{ secrets.REACT_APP_IPFS_GATEWAY }}
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ secrets.AWS_DEFAULT_REGION }}
|
||||
|
||||
# Script to deploy Pull Requests
|
||||
- run: bash ./scripts/github/deploy_pull_request.sh
|
||||
if: success() && github.event.number
|
||||
env:
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
PR_NUMBER: ${{ github.event.number }}
|
||||
REVIEW_BUCKET_NAME: ${{ secrets.AWS_REVIEW_BUCKET_NAME }}
|
||||
REACT_APP_NETWORK: ${{ env.REACT_APP_NETWORK }}
|
||||
@@ -101,13 +106,6 @@ jobs:
|
||||
env:
|
||||
REVIEW_FEATURE_URL: https://pr${{ github.event.number }}--${{ env.REPO_NAME_ALPHANUMERIC }}.review.gnosisdev.com
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ secrets.AWS_DEFAULT_REGION }}
|
||||
|
||||
# Script to deploy to development environment
|
||||
# xDai build is never created in development branch
|
||||
|
||||
|
||||
@@ -19,17 +19,18 @@ jobs:
|
||||
fail-fast: false
|
||||
max-parallel: 15
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest, ubuntu-20.04]
|
||||
os: [macos-latest, windows-latest, ubuntu-18.04]
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
# Linux build patches
|
||||
- name: Install linux dependencies
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
run: sudo apt-get update && sudo apt-get install libudev-dev libusb-1.0-0-dev
|
||||
|
||||
# Windows build patches
|
||||
- name: Patch node gyp on windows to support Visual Studio 2019
|
||||
if: startsWith(matrix.os, 'windows')
|
||||
shell: powershell
|
||||
@@ -41,18 +42,20 @@ jobs:
|
||||
run: |
|
||||
yarn global add node-gyp
|
||||
yarn config set node_gyp "$_\node_modules\node-gyp\bin\node-gyp.js"
|
||||
|
||||
# Load node_modules cache action
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
|
||||
- name: Install Node.js, NPM and Yarn
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 14
|
||||
- run: |
|
||||
# Due to some dependencies yarn may randomly throw an error about invalid cache
|
||||
# This approach is taken from https://github.com/yarnpkg/yarn/issues/7212#issuecomment-506155894 to fix the issue
|
||||
# Another approach is to install with flag --network-concurrency 1, but this will make the installation pretty slow (default value is 8)
|
||||
mkdir .yarncache
|
||||
yarn install --cache-folder ./.yarncache --frozen-lockfile
|
||||
rm -rf .yarncache
|
||||
yarn cache clean
|
||||
|
||||
- name: Run yarn install
|
||||
run: yarn install --frozen-lockfile --network-concurrency 1
|
||||
|
||||
- name: Build/Release Desktop App
|
||||
env:
|
||||
|
||||
+2
-2
@@ -286,7 +286,7 @@ const xDai: NetworkConfig = {
|
||||
label: 'xDai',
|
||||
isTestNet: false,
|
||||
nativeCoin: {
|
||||
address: '0x000',
|
||||
address: '0x0000000000000000000000000000000000000000',
|
||||
name: 'xDai',
|
||||
symbol: 'xDai',
|
||||
decimals: 18,
|
||||
@@ -343,7 +343,7 @@ const mainnet: NetworkConfig = {
|
||||
label: 'Mainnet',
|
||||
isTestNet: false,
|
||||
nativeCoin: {
|
||||
address: '0x000',
|
||||
address: '0x0000000000000000000000000000000000000000',
|
||||
name: 'Ether',
|
||||
symbol: 'ETH',
|
||||
decimals: 18,
|
||||
|
||||
+1
-1
@@ -161,7 +161,7 @@
|
||||
"@gnosis.pm/safe-apps-sdk": "1.0.3",
|
||||
"@gnosis.pm/safe-apps-sdk-v1": "npm:@gnosis.pm/safe-apps-sdk@0.4.2",
|
||||
"@gnosis.pm/safe-contracts": "1.1.1-dev.2",
|
||||
"@gnosis.pm/safe-react-components": "https://github.com/gnosis/safe-react-components.git#f610327",
|
||||
"@gnosis.pm/safe-react-components": "https://github.com/gnosis/safe-react-components.git#80f5db6",
|
||||
"@gnosis.pm/util-contracts": "2.0.6",
|
||||
"@ledgerhq/hw-transport-node-hid-singleton": "5.45.0",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
|
||||
+4
-2
@@ -89,7 +89,9 @@ function createWindow(port = DEFAULT_PORT) {
|
||||
height: 768,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, '../scripts/preload.js'),
|
||||
allowRunningInsecureContent: true,
|
||||
experimentalFeatures: true,
|
||||
nodeIntegration: true,
|
||||
// allowRunningInsecureContent: true,
|
||||
enableRemoteModule: true,
|
||||
nativeWindowOpen: true, // need to be set in order to display modal
|
||||
},
|
||||
@@ -142,7 +144,7 @@ process.on('uncaughtException', function (error) {
|
||||
})
|
||||
|
||||
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.4.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/11.3.0 Safari/537.36'
|
||||
|
||||
// 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
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.0 KiB |
@@ -38,7 +38,7 @@ const styles = () => ({
|
||||
zIndex: 1301,
|
||||
},
|
||||
logo: {
|
||||
flexBasis: '114px',
|
||||
flexBasis: '140px',
|
||||
flexShrink: '0',
|
||||
flexGrow: '0',
|
||||
maxWidth: '55px',
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { Transaction } from '@gnosis.pm/safe-apps-sdk-v1'
|
||||
import { Text, EthHashInfo, CopyToClipboardBtn, IconText, FixedIcon } from '@gnosis.pm/safe-react-components'
|
||||
import get from 'lodash.get'
|
||||
|
||||
import { web3ReadOnly as web3 } from 'src/logic/wallets/getWeb3'
|
||||
import { getExplorerInfo } from 'src/config'
|
||||
import { DecodedData, DecodedDataBasicParameter, DecodedDataParameterValue } from 'src/types/transactions/decode.d'
|
||||
import { DecodedTxDetail } from 'src/routes/safe/components/Apps/components/ConfirmTxModal'
|
||||
|
||||
const FlexWrapper = styled.div<{ margin: number }>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> :nth-child(2) {
|
||||
margin-left: ${({ margin }) => margin}px;
|
||||
}
|
||||
`
|
||||
|
||||
const BasicTxInfoWrapper = styled.div`
|
||||
margin-bottom: 15px;
|
||||
|
||||
> :nth-child(2) {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
`
|
||||
|
||||
const TxList = styled.div`
|
||||
width: 100%;
|
||||
max-height: 260px;
|
||||
overflow-y: auto;
|
||||
border-top: 2px solid ${({ theme }) => theme.colors.separator};
|
||||
`
|
||||
|
||||
const TxListItem = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
padding: 0 24px;
|
||||
height: 50px;
|
||||
border-bottom: 2px solid ${({ theme }) => theme.colors.separator};
|
||||
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
const ElementWrapper = styled.div`
|
||||
margin-bottom: 15px;
|
||||
`
|
||||
|
||||
export const BasicTxInfo = ({
|
||||
txRecipient,
|
||||
txData,
|
||||
txValue,
|
||||
}: {
|
||||
txRecipient: string
|
||||
txData: string
|
||||
txValue: string
|
||||
}): ReactElement => {
|
||||
return (
|
||||
<BasicTxInfoWrapper>
|
||||
{/* TO */}
|
||||
<>
|
||||
<Text size="lg" strong>
|
||||
{`Send ${txValue} ETH to:`}
|
||||
</Text>
|
||||
<EthHashInfo
|
||||
hash={txRecipient}
|
||||
showIdenticon
|
||||
textSize="lg"
|
||||
showCopyBtn
|
||||
explorerUrl={getExplorerInfo(txRecipient)}
|
||||
/>
|
||||
</>
|
||||
<>
|
||||
{/* Data */}
|
||||
<Text size="lg" strong>
|
||||
Data (hex encoded):
|
||||
</Text>
|
||||
<FlexWrapper margin={5}>
|
||||
<Text size="lg">{web3.utils.hexToBytes(txData).length} bytes</Text>
|
||||
<CopyToClipboardBtn textToCopy={txData} />
|
||||
</FlexWrapper>
|
||||
</>
|
||||
</BasicTxInfoWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export const getParameterElement = (parameter: DecodedDataBasicParameter, index: number): ReactElement => {
|
||||
let valueElement
|
||||
|
||||
if (parameter.type === 'address') {
|
||||
valueElement = (
|
||||
<EthHashInfo
|
||||
hash={parameter.value}
|
||||
showIdenticon
|
||||
textSize="lg"
|
||||
showCopyBtn
|
||||
explorerUrl={getExplorerInfo(parameter.value)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (parameter.type.startsWith('bytes')) {
|
||||
valueElement = (
|
||||
<FlexWrapper margin={5}>
|
||||
<Text size="lg">{web3.utils.hexToBytes(parameter.value).length} bytes</Text>
|
||||
<CopyToClipboardBtn textToCopy={parameter.value} />
|
||||
</FlexWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
if (!valueElement) {
|
||||
let value = parameter.value
|
||||
if (parameter.type.endsWith('[]')) {
|
||||
try {
|
||||
value = JSON.stringify(parameter.value)
|
||||
} catch (e) {}
|
||||
}
|
||||
valueElement = <Text size="lg">{value}</Text>
|
||||
}
|
||||
|
||||
return (
|
||||
<ElementWrapper key={index}>
|
||||
<Text size="lg" strong>
|
||||
{parameter.name} ({parameter.type})
|
||||
</Text>
|
||||
{valueElement}
|
||||
</ElementWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
const SingleTx = ({
|
||||
decodedData,
|
||||
onTxItemClick,
|
||||
}: {
|
||||
decodedData: DecodedData | null
|
||||
onTxItemClick: (decodedTxDetails: DecodedData) => void
|
||||
}): ReactElement | null => {
|
||||
if (!decodedData) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<TxList>
|
||||
<TxListItem onClick={() => onTxItemClick(decodedData)}>
|
||||
<IconText iconSize="sm" iconType="code" text="Contract interaction" textSize="xl" />
|
||||
|
||||
<FlexWrapper margin={20}>
|
||||
<Text size="xl">{decodedData.method}</Text>
|
||||
<FixedIcon type="chevronRight" />
|
||||
</FlexWrapper>
|
||||
</TxListItem>
|
||||
</TxList>
|
||||
)
|
||||
}
|
||||
|
||||
const MultiSendTx = ({
|
||||
decodedData,
|
||||
onTxItemClick,
|
||||
}: {
|
||||
decodedData: DecodedData | null
|
||||
onTxItemClick: (decodedTxDetails: DecodedDataParameterValue) => void
|
||||
}): ReactElement | null => {
|
||||
const txs: DecodedDataParameterValue[] | undefined = get(decodedData, 'parameters[0].valueDecoded')
|
||||
|
||||
if (!txs) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<TxList>
|
||||
{txs.map((tx, index) => (
|
||||
<TxListItem key={index} onClick={() => onTxItemClick(tx)}>
|
||||
<IconText iconSize="sm" iconType="code" text="Contract interaction" textSize="xl" />
|
||||
|
||||
<FlexWrapper margin={20}>
|
||||
{tx.dataDecoded && <Text size="xl">{tx.dataDecoded.method}</Text>}
|
||||
<FixedIcon type="chevronRight" />
|
||||
</FlexWrapper>
|
||||
</TxListItem>
|
||||
))}
|
||||
</TxList>
|
||||
)
|
||||
}
|
||||
|
||||
type Props = {
|
||||
txs: Transaction[]
|
||||
decodedData: DecodedData | null
|
||||
onTxItemClick: (decodedTxDetails: DecodedTxDetail) => void
|
||||
}
|
||||
|
||||
export const DecodeTxs = ({ txs, decodedData, onTxItemClick }: Props): ReactElement => {
|
||||
return txs.length > 1 ? (
|
||||
<MultiSendTx decodedData={decodedData} onTxItemClick={onTxItemClick} />
|
||||
) : (
|
||||
<SingleTx decodedData={decodedData} onTxItemClick={onTxItemClick} />
|
||||
)
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import IconButton from '@material-ui/core/IconButton'
|
||||
import Close from '@material-ui/icons/Close'
|
||||
import { Icon } from '@gnosis.pm/safe-react-components'
|
||||
|
||||
import Paragraph from 'src/components/layout/Paragraph'
|
||||
import { md, lg } from 'src/theme/variables'
|
||||
@@ -33,18 +34,28 @@ const StyledClose = styled(Close)`
|
||||
width: 35px;
|
||||
`
|
||||
|
||||
const ModalTitle = ({
|
||||
iconUrl,
|
||||
title,
|
||||
onClose,
|
||||
}: {
|
||||
const GoBackWrapper = styled.div`
|
||||
margin-right: 15px;
|
||||
`
|
||||
|
||||
type Props = {
|
||||
title: string
|
||||
iconUrl: string
|
||||
goBack?: () => void
|
||||
iconUrl?: string
|
||||
onClose?: () => void
|
||||
}): React.ReactElement => {
|
||||
}
|
||||
|
||||
const ModalTitle = ({ goBack, iconUrl, title, onClose }: Props): React.ReactElement => {
|
||||
return (
|
||||
<StyledRow align="center" grow>
|
||||
<TitleWrapper>
|
||||
{goBack && (
|
||||
<GoBackWrapper>
|
||||
<IconButton onClick={goBack}>
|
||||
<Icon type="arrowLeft" size="md" />
|
||||
</IconButton>
|
||||
</GoBackWrapper>
|
||||
)}
|
||||
{iconUrl && <IconImg alt={title} src={iconUrl} />}
|
||||
<StyledParagraph noMargin weight="bolder">
|
||||
{title}
|
||||
|
||||
@@ -80,9 +80,7 @@ export const mustBeEthereumContractAddress = memoize(
|
||||
async (address: string): Promise<ValidatorReturnType> => {
|
||||
const contractCode = await getWeb3().eth.getCode(address)
|
||||
|
||||
const errorMessage = `Input must be a valid Ethereum contract address${
|
||||
isFeatureEnabled(FEATURES.DOMAIN_LOOKUP) ? ', ENS or Unstoppable domain' : ''
|
||||
}`
|
||||
const errorMessage = `Must resolve to a valid smart contract address.`
|
||||
|
||||
return !contractCode || contractCode.replace('0x', '').replace(/0/g, '') === '' ? errorMessage : undefined
|
||||
},
|
||||
|
||||
@@ -38,7 +38,7 @@ const mainnet: NetworkConfig = {
|
||||
label: 'EWC',
|
||||
isTestNet: false,
|
||||
nativeCoin: {
|
||||
address: '0x000',
|
||||
address: '0x0000000000000000000000000000000000000000',
|
||||
name: 'Energy web token',
|
||||
symbol: 'EWT',
|
||||
decimals: 18,
|
||||
|
||||
@@ -29,7 +29,7 @@ const local: NetworkConfig = {
|
||||
label: 'LocalRPC',
|
||||
isTestNet: true,
|
||||
nativeCoin: {
|
||||
address: '0x000',
|
||||
address: '0x0000000000000000000000000000000000000000',
|
||||
name: 'Ether',
|
||||
symbol: 'ETH',
|
||||
decimals: 18,
|
||||
|
||||
@@ -38,7 +38,7 @@ const mainnet: NetworkConfig = {
|
||||
label: 'Mainnet',
|
||||
isTestNet: false,
|
||||
nativeCoin: {
|
||||
address: '0x000',
|
||||
address: '0x0000000000000000000000000000000000000000',
|
||||
name: 'Ether',
|
||||
symbol: 'ETH',
|
||||
decimals: 18,
|
||||
|
||||
@@ -38,7 +38,7 @@ const rinkeby: NetworkConfig = {
|
||||
label: 'Rinkeby',
|
||||
isTestNet: true,
|
||||
nativeCoin: {
|
||||
address: '0x000',
|
||||
address: '0x0000000000000000000000000000000000000000',
|
||||
name: 'Ether',
|
||||
symbol: 'ETH',
|
||||
decimals: 18,
|
||||
|
||||
@@ -35,7 +35,7 @@ const mainnet: NetworkConfig = {
|
||||
label: 'Volta',
|
||||
isTestNet: true,
|
||||
nativeCoin: {
|
||||
address: '0x000',
|
||||
address: '0x0000000000000000000000000000000000000000',
|
||||
name: 'Energy web token',
|
||||
symbol: 'EWT',
|
||||
decimals: 18,
|
||||
|
||||
@@ -29,7 +29,7 @@ const xDai: NetworkConfig = {
|
||||
label: 'xDai',
|
||||
isTestNet: false,
|
||||
nativeCoin: {
|
||||
address: '0x000',
|
||||
address: '0x0000000000000000000000000000000000000000',
|
||||
name: 'xDai',
|
||||
symbol: 'xDai',
|
||||
decimals: 18,
|
||||
|
||||
@@ -218,10 +218,9 @@ export const useEstimateTransactionGas = ({
|
||||
)
|
||||
|
||||
const fixedGasCosts = getFixedGasCosts(Number(threshold))
|
||||
const isOffChainSignature = checkIfOffChainSignatureIsPossible(isExecution, smartContractWallet, safeVersion)
|
||||
|
||||
try {
|
||||
const isOffChainSignature = checkIfOffChainSignatureIsPossible(isExecution, smartContractWallet, safeVersion)
|
||||
|
||||
const gasEstimation = await estimateTransactionGas({
|
||||
safeAddress,
|
||||
txRecipient,
|
||||
@@ -279,7 +278,7 @@ export const useEstimateTransactionGas = ({
|
||||
gasLimit: '0',
|
||||
isExecution,
|
||||
isCreation,
|
||||
isOffChainSignature: false,
|
||||
isOffChainSignature,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
import { getNetworkInfo } from 'src/config'
|
||||
import { Token } from 'src/logic/tokens/store/model/token'
|
||||
import { sameAddress, ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses'
|
||||
import { sameAddress } from 'src/logic/wallets/ethAddresses'
|
||||
import { safeKnownCoins } from 'src/routes/safe/container/selector'
|
||||
|
||||
const { nativeCoin } = getNetworkInfo()
|
||||
|
||||
const useTokenInfo = (address: string): Token | undefined => {
|
||||
const tokens = useSelector(safeKnownCoins)
|
||||
|
||||
if (tokens) {
|
||||
const tokenAddress = sameAddress(address, ZERO_ADDRESS) ? nativeCoin.address : address
|
||||
return tokens.find((token) => sameAddress(token.address, tokenAddress)) ?? undefined
|
||||
return tokens.find((token) => sameAddress(token.address, address))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,6 @@ export const buildSafe = async (
|
||||
featuresEnabled,
|
||||
balances: localSafe?.balances || Map(),
|
||||
latestIncomingTxBlock: 0,
|
||||
activeAssets: Set(),
|
||||
activeTokens: Set(),
|
||||
modules,
|
||||
spendingLimits,
|
||||
|
||||
@@ -34,7 +34,6 @@ export type SafeRecordProps = {
|
||||
modules?: ModulePair[] | null
|
||||
spendingLimits?: SpendingLimit[] | null
|
||||
activeTokens: Set<string>
|
||||
activeAssets: Set<string>
|
||||
balances: Map<string, BalanceRecord>
|
||||
nonce: number
|
||||
latestIncomingTxBlock: number
|
||||
@@ -54,7 +53,6 @@ const makeSafe = Record<SafeRecordProps>({
|
||||
modules: [],
|
||||
spendingLimits: [],
|
||||
activeTokens: Set(),
|
||||
activeAssets: Set(),
|
||||
balances: Map(),
|
||||
nonce: 0,
|
||||
latestIncomingTxBlock: 0,
|
||||
|
||||
@@ -25,7 +25,6 @@ export const buildSafe = (storedSafe: SafeRecordProps): SafeRecordProps => {
|
||||
const addresses = storedSafe.owners.map((owner) => checksumAddress(owner.address))
|
||||
const owners = buildOwnersFrom(Array.from(names), Array.from(addresses))
|
||||
const activeTokens = Set(storedSafe.activeTokens)
|
||||
const activeAssets = Set(storedSafe.activeAssets)
|
||||
const balances = Map(storedSafe.balances)
|
||||
|
||||
return {
|
||||
@@ -33,7 +32,6 @@ export const buildSafe = (storedSafe: SafeRecordProps): SafeRecordProps => {
|
||||
owners,
|
||||
balances,
|
||||
activeTokens,
|
||||
activeAssets,
|
||||
latestIncomingTxBlock: 0,
|
||||
modules: null,
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ const getMockedOldSafe = ({
|
||||
needsUpdate,
|
||||
balances,
|
||||
recurringUser,
|
||||
assets,
|
||||
activeTokens,
|
||||
owners,
|
||||
featuresEnabled,
|
||||
@@ -30,8 +29,6 @@ const getMockedOldSafe = ({
|
||||
}
|
||||
const mockedActiveTokenAddress1 = '0x36591cd3DA96b21Ac9ca54cFaf80fe45107294F1'
|
||||
const mockedActiveTokenAddress2 = '0x92aF97cbF10742dD2527ffaBA70e34C03CFFC2c1'
|
||||
const mockedActiveAssetsAddress1 = '0x503ab2a6A70c6C6ec8b25a4C87C784e1c8f8e8CD'
|
||||
const mockedActiveAssetsAddress2 = '0xfdd4E685361CB7E89a4D27e03DCd0001448d731F'
|
||||
|
||||
return {
|
||||
name: name || 'MockedSafe',
|
||||
@@ -42,7 +39,6 @@ const getMockedOldSafe = ({
|
||||
modules: modules || [],
|
||||
spendingLimits: spendingLimits || [],
|
||||
activeTokens: activeTokens || Set([mockedActiveTokenAddress1, mockedActiveTokenAddress2]),
|
||||
assets: assets || Set([mockedActiveAssetsAddress1, mockedActiveAssetsAddress2]),
|
||||
balances:
|
||||
balances ||
|
||||
Map({
|
||||
@@ -198,23 +194,6 @@ describe('shouldSafeStoreBeUpdated', () => {
|
||||
// Then
|
||||
expect(expectedResult).toEqual(true)
|
||||
})
|
||||
it(`Given an old activeAssets list and a new activeAssets list for the safe, should return true`, () => {
|
||||
// given
|
||||
const mockedActiveTokenAddress1 = '0x36591cd3DA96b21Ac9ca54cFaf80fe45107294F1'
|
||||
const mockedActiveTokenAddress2 = '0x92aF97cbF10742dD2527ffaBA70e34C03CFFC2c1'
|
||||
const oldActiveAssets = Set([mockedActiveTokenAddress1, mockedActiveTokenAddress2])
|
||||
const newActiveAssets = Set([mockedActiveTokenAddress1])
|
||||
const oldSafe = getMockedOldSafe({ assets: oldActiveAssets })
|
||||
const newSafeProps: Partial<SafeRecordProps> = {
|
||||
assets: newActiveAssets,
|
||||
}
|
||||
|
||||
// When
|
||||
const expectedResult = shouldSafeStoreBeUpdated(newSafeProps, oldSafe)
|
||||
|
||||
// Then
|
||||
expect(expectedResult).toEqual(true)
|
||||
})
|
||||
it(`Given an old balances list and a new balances list for the safe, should return true`, () => {
|
||||
// given
|
||||
const mockedActiveTokenAddress1 = '0x36591cd3DA96b21Ac9ca54cFaf80fe45107294F1'
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { BigNumber } from 'bignumber.js'
|
||||
import { getNetworkInfo } from 'src/config'
|
||||
import { AbiItem } from 'web3-utils'
|
||||
|
||||
import { CreateTransactionArgs } from 'src/logic/safe/store/actions/createTransaction'
|
||||
@@ -9,7 +8,7 @@ import SpendingLimitModule from 'src/logic/contracts/artifacts/AllowanceModule.j
|
||||
import generateBatchRequests from 'src/logic/contracts/generateBatchRequests'
|
||||
import { getSpendingLimitContract, MULTI_SEND_ADDRESS } from 'src/logic/contracts/safeContracts'
|
||||
import { SpendingLimit } from 'src/logic/safe/store/models/safe'
|
||||
import { sameAddress, ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses'
|
||||
import { sameAddress } from 'src/logic/wallets/ethAddresses'
|
||||
import { getWeb3, web3ReadOnly } from 'src/logic/wallets/getWeb3'
|
||||
import { SPENDING_LIMIT_MODULE_ADDRESS } from 'src/utils/constants'
|
||||
import { getEncodedMultiSendCallData, MultiSendTx } from './upgradeSafe'
|
||||
@@ -138,16 +137,13 @@ type DeleteAllowanceParams = {
|
||||
}
|
||||
|
||||
export const getDeleteAllowanceTxData = ({ beneficiary, tokenAddress }: DeleteAllowanceParams): string => {
|
||||
const { nativeCoin } = getNetworkInfo()
|
||||
const token = sameAddress(tokenAddress, nativeCoin.address) ? ZERO_ADDRESS : tokenAddress
|
||||
|
||||
const web3 = getWeb3()
|
||||
const spendingLimitContract = new web3.eth.Contract(
|
||||
SpendingLimitModule.abi as AbiItem[],
|
||||
SPENDING_LIMIT_MODULE_ADDRESS,
|
||||
)
|
||||
|
||||
return spendingLimitContract.methods.deleteAllowance(beneficiary, token).encodeABI()
|
||||
return spendingLimitContract.methods.deleteAllowance(beneficiary, tokenAddress).encodeABI()
|
||||
}
|
||||
|
||||
export const enableSpendingLimitModuleMultiSendTx = (safeAddress: string): MultiSendTx => {
|
||||
@@ -188,20 +184,13 @@ export const setSpendingLimitTx = ({
|
||||
safeAddress,
|
||||
}: SpendingLimitTxParams): CreateTransactionArgs => {
|
||||
const spendingLimitContract = getSpendingLimitContract()
|
||||
const { nativeCoin } = getNetworkInfo()
|
||||
|
||||
const txArgs: CreateTransactionArgs = {
|
||||
safeAddress,
|
||||
to: SPENDING_LIMIT_MODULE_ADDRESS,
|
||||
valueInWei: ZERO_VALUE,
|
||||
txData: spendingLimitContract.methods
|
||||
.setAllowance(
|
||||
beneficiary,
|
||||
token === nativeCoin.address ? ZERO_ADDRESS : token,
|
||||
spendingLimitInWei,
|
||||
resetTimeMin,
|
||||
resetBaseMin,
|
||||
)
|
||||
.setAllowance(beneficiary, token, spendingLimitInWei, resetTimeMin, resetBaseMin)
|
||||
.encodeABI(),
|
||||
operation: CALL,
|
||||
notifiedTransaction: TX_NOTIFICATION_TYPES.NEW_SPENDING_LIMIT_TX,
|
||||
@@ -285,12 +274,5 @@ export const getSpendingLimitByTokenAddress = ({
|
||||
return
|
||||
}
|
||||
|
||||
const { nativeCoin } = getNetworkInfo()
|
||||
|
||||
return spendingLimits.find(({ token: spendingLimitTokenAddress }) => {
|
||||
spendingLimitTokenAddress = sameAddress(spendingLimitTokenAddress, ZERO_ADDRESS)
|
||||
? nativeCoin.address
|
||||
: spendingLimitTokenAddress
|
||||
return sameAddress(spendingLimitTokenAddress, tokenAddress)
|
||||
})
|
||||
return spendingLimits.find(({ token }) => sameAddress(token, tokenAddress))
|
||||
}
|
||||
|
||||
@@ -11,8 +11,6 @@ import { AppReduxState } from 'src/store'
|
||||
import { humanReadableValue } from 'src/logic/tokens/utils/humanReadableValue'
|
||||
import { safeActiveTokensSelector, safeSelector } from 'src/logic/safe/store/selectors'
|
||||
import { tokensSelector } from 'src/logic/tokens/store/selectors'
|
||||
import { sameAddress, ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses'
|
||||
import { getNetworkInfo } from 'src/config'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { currentCurrencySelector } from 'src/logic/currencyValues/store/selectors'
|
||||
|
||||
@@ -27,24 +25,20 @@ interface ExtractedData {
|
||||
tokens: List<Token>
|
||||
}
|
||||
|
||||
const { nativeCoin } = getNetworkInfo()
|
||||
|
||||
const extractDataFromResult = (currentTokens: TokenState) => (
|
||||
acc: ExtractedData,
|
||||
{ balance, fiatBalance, tokenInfo }: TokenBalance,
|
||||
): ExtractedData => {
|
||||
const { address: tokenAddress, decimals } = tokenInfo
|
||||
if (sameAddress(tokenAddress, ZERO_ADDRESS) || sameAddress(tokenAddress, nativeCoin.address)) {
|
||||
acc.ethBalance = humanReadableValue(balance, 18)
|
||||
}
|
||||
const { address, decimals } = tokenInfo
|
||||
|
||||
acc.balances = acc.balances.merge({
|
||||
[tokenAddress]: {
|
||||
[address]: {
|
||||
fiatBalance,
|
||||
tokenBalance: humanReadableValue(balance, Number(decimals)),
|
||||
},
|
||||
})
|
||||
|
||||
if (currentTokens && !currentTokens.get(tokenAddress)) {
|
||||
if (currentTokens && !currentTokens.get(address)) {
|
||||
acc.tokens = acc.tokens.push(makeToken({ ...tokenInfo }))
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ describe('getERC20DecimalsAndSymbol', () => {
|
||||
symbol,
|
||||
decimals,
|
||||
logoUri: 'https://gnosis-safe-token-logos.s3.amazonaws.com/0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa.png',
|
||||
balance: 0,
|
||||
balance: { tokenBalance: '0', fiatBalance: '0' },
|
||||
})
|
||||
const expectedResult = {
|
||||
decimals,
|
||||
|
||||
@@ -14,7 +14,7 @@ export const styles = createStyles({
|
||||
},
|
||||
hide: {
|
||||
'&:hover': {
|
||||
backgroundColor: '#fff3e2',
|
||||
backgroundColor: '#f7f5f5',
|
||||
},
|
||||
'&:hover $actions': {
|
||||
visibility: 'initial',
|
||||
|
||||
@@ -15,12 +15,12 @@ type MessageHandler = (
|
||||
) => void | MethodToResponse[Methods] | ErrorResponse | Promise<MethodToResponse[Methods] | ErrorResponse | void>
|
||||
|
||||
class AppCommunicator {
|
||||
private iframe: HTMLIFrameElement
|
||||
private iframeRef: MutableRefObject<HTMLIFrameElement | null>
|
||||
private handlers = new Map<Methods, MessageHandler>()
|
||||
private app: SafeApp
|
||||
|
||||
constructor(iframeRef: MutableRefObject<HTMLIFrameElement>, app: SafeApp) {
|
||||
this.iframe = iframeRef.current
|
||||
constructor(iframeRef: MutableRefObject<HTMLIFrameElement | null>, app: SafeApp) {
|
||||
this.iframeRef = iframeRef
|
||||
this.app = app
|
||||
|
||||
window.addEventListener('message', this.handleIncomingMessage)
|
||||
@@ -49,7 +49,7 @@ class AppCommunicator {
|
||||
? MessageFormatter.makeErrorResponse(requestId, data, sdkVersion)
|
||||
: MessageFormatter.makeResponse(requestId, data, sdkVersion)
|
||||
|
||||
this.iframe.contentWindow?.postMessage(msg, this.app.url)
|
||||
this.iframeRef.current?.contentWindow?.postMessage(msg, this.app.url)
|
||||
}
|
||||
|
||||
handleIncomingMessage = async (msg: SDKMessageEvent): Promise<void> => {
|
||||
@@ -83,7 +83,6 @@ const useAppCommunicator = (
|
||||
app?: SafeApp,
|
||||
): AppCommunicator | undefined => {
|
||||
const [communicator, setCommunicator] = useState<AppCommunicator | undefined>(undefined)
|
||||
|
||||
useEffect(() => {
|
||||
let communicatorInstance
|
||||
const initCommunicator = (iframeRef: MutableRefObject<HTMLIFrameElement>, app: SafeApp) => {
|
||||
@@ -91,7 +90,7 @@ const useAppCommunicator = (
|
||||
setCommunicator(communicatorInstance)
|
||||
}
|
||||
|
||||
if (app && iframeRef.current !== null) {
|
||||
if (app) {
|
||||
initCommunicator(iframeRef as MutableRefObject<HTMLIFrameElement>, app)
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ import { LoadingContainer } from 'src/components/LoaderContainer/index'
|
||||
import { TIMEOUT } from 'src/utils/constants'
|
||||
import { web3ReadOnly } from 'src/logic/wallets/getWeb3'
|
||||
|
||||
import { ConfirmTransactionModal } from '../components/ConfirmTransactionModal'
|
||||
import { ConfirmTxModal } from '../components/ConfirmTxModal'
|
||||
import { useIframeMessageHandler } from '../hooks/useIframeMessageHandler'
|
||||
import { useLegalConsent } from '../hooks/useLegalConsent'
|
||||
import LegalDisclaimer from './LegalDisclaimer'
|
||||
@@ -56,6 +56,7 @@ const AppWrapper = styled.div`
|
||||
|
||||
const StyledCard = styled(Card)`
|
||||
flex-grow: 1;
|
||||
padding: 0;
|
||||
`
|
||||
|
||||
const StyledIframe = styled.iframe`
|
||||
@@ -354,7 +355,7 @@ const AppFrame = ({ appUrl }: Props): React.ReactElement => {
|
||||
/>
|
||||
)}
|
||||
|
||||
<ConfirmTransactionModal
|
||||
<ConfirmTxModal
|
||||
isOpen={confirmTransactionModal.isOpen}
|
||||
app={safeApp as SafeApp}
|
||||
safeAddress={safeAddress}
|
||||
|
||||
@@ -1,328 +0,0 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { Icon, ModalFooterConfirmation, Text, Title } from '@gnosis.pm/safe-react-components'
|
||||
import { Transaction } from '@gnosis.pm/safe-apps-sdk-v1'
|
||||
import styled from 'styled-components'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
import AddressInfo from 'src/components/AddressInfo'
|
||||
import DividerLine from 'src/components/DividerLine'
|
||||
import Collapse from 'src/components/Collapse'
|
||||
import TextBox from 'src/components/TextBox'
|
||||
import ModalTitle from 'src/components/ModalTitle'
|
||||
import { mustBeEthereumAddress } from 'src/components/forms/validator'
|
||||
import Bold from 'src/components/layout/Bold'
|
||||
import Heading from 'src/components/layout/Heading'
|
||||
import Img from 'src/components/layout/Img'
|
||||
import { getEthAsToken } from 'src/logic/tokens/utils/tokenHelpers'
|
||||
import { SafeApp } from 'src/routes/safe/components/Apps/types.d'
|
||||
import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue'
|
||||
import { createTransaction } from 'src/logic/safe/store/actions/createTransaction'
|
||||
import { MULTI_SEND_ADDRESS } from 'src/logic/contracts/safeContracts'
|
||||
import { DELEGATE_CALL, TX_NOTIFICATION_TYPES, CALL } from 'src/logic/safe/transactions'
|
||||
import { encodeMultiSendCall } from 'src/logic/safe/transactions/multisend'
|
||||
import { web3ReadOnly } from 'src/logic/wallets/getWeb3'
|
||||
|
||||
import GasEstimationInfo from './GasEstimationInfo'
|
||||
import { getNetworkInfo } from 'src/config'
|
||||
import { TransactionParams } from './AppFrame'
|
||||
import { EstimationStatus, useEstimateTransactionGas } from 'src/logic/hooks/useEstimateTransactionGas'
|
||||
import { safeThresholdSelector } from 'src/logic/safe/store/selectors'
|
||||
import Modal from 'src/components/Modal'
|
||||
import Row from 'src/components/layout/Row'
|
||||
import Hairline from 'src/components/layout/Hairline'
|
||||
import { TransactionFees } from 'src/components/TransactionsFees'
|
||||
import { EditableTxParameters } from 'src/routes/safe/components/Transactions/helpers/EditableTxParameters'
|
||||
import { TxParametersDetail } from 'src/routes/safe/components/Transactions/helpers/TxParametersDetail'
|
||||
import { md, lg, sm } from 'src/theme/variables'
|
||||
import { TxParameters } from 'src/routes/safe/container/hooks/useTransactionParameters'
|
||||
|
||||
const isTxValid = (t: Transaction): boolean => {
|
||||
if (!['string', 'number'].includes(typeof t.value)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (typeof t.value === 'string' && !/^(0x)?[0-9a-f]+$/i.test(t.value)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const isAddressValid = mustBeEthereumAddress(t.to) === undefined
|
||||
return isAddressValid && !!t.data && typeof t.data === 'string'
|
||||
}
|
||||
|
||||
const Wrapper = styled.div`
|
||||
margin-bottom: 15px;
|
||||
`
|
||||
const CollapseContent = styled.div`
|
||||
padding: 15px 0;
|
||||
|
||||
.section {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.value-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
`
|
||||
|
||||
const IconText = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
margin-right: 4px;
|
||||
}
|
||||
`
|
||||
const StyledTextBox = styled(TextBox)`
|
||||
max-width: 444px;
|
||||
`
|
||||
|
||||
const Container = styled.div`
|
||||
max-width: 480px;
|
||||
padding: ${md} ${lg};
|
||||
`
|
||||
|
||||
const ModalFooter = styled(Row)`
|
||||
padding: ${md} ${lg};
|
||||
justify-content: center;
|
||||
`
|
||||
const TransactionFeesWrapper = styled.div`
|
||||
background-color: ${({ theme }) => theme.colors.background};
|
||||
padding: ${sm} ${lg};
|
||||
`
|
||||
|
||||
type OwnProps = {
|
||||
isOpen: boolean
|
||||
app: SafeApp
|
||||
txs: Transaction[]
|
||||
params?: TransactionParams
|
||||
safeAddress: string
|
||||
safeName: string
|
||||
ethBalance: string
|
||||
onUserConfirm: (safeTxHash: string) => void
|
||||
onTxReject: () => void
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const { nativeCoin } = getNetworkInfo()
|
||||
|
||||
const parseTxValue = (value: string | number): string => {
|
||||
return web3ReadOnly.utils.toBN(value).toString()
|
||||
}
|
||||
|
||||
export const ConfirmTransactionModal = ({
|
||||
isOpen,
|
||||
app,
|
||||
txs,
|
||||
safeAddress,
|
||||
ethBalance,
|
||||
safeName,
|
||||
params,
|
||||
onUserConfirm,
|
||||
onClose,
|
||||
onTxReject,
|
||||
}: OwnProps): React.ReactElement | null => {
|
||||
const [estimatedSafeTxGas, setEstimatedSafeTxGas] = useState(0)
|
||||
const threshold = useSelector(safeThresholdSelector) || 1
|
||||
|
||||
const txRecipient: string | undefined = useMemo(() => (txs.length > 1 ? MULTI_SEND_ADDRESS : txs[0]?.to), [txs])
|
||||
const txData: string | undefined = useMemo(() => (txs.length > 1 ? encodeMultiSendCall(txs) : txs[0]?.data), [txs])
|
||||
const txValue: string | undefined = useMemo(
|
||||
() => (txs.length > 1 ? '0' : txs[0]?.value && parseTxValue(txs[0]?.value)),
|
||||
[txs],
|
||||
)
|
||||
const operation = useMemo(() => (txs.length > 1 ? DELEGATE_CALL : CALL), [txs])
|
||||
const [manualSafeTxGas, setManualSafeTxGas] = useState(0)
|
||||
const [manualGasPrice, setManualGasPrice] = useState<string | undefined>()
|
||||
|
||||
const {
|
||||
gasLimit,
|
||||
gasPriceFormatted,
|
||||
gasEstimation,
|
||||
isOffChainSignature,
|
||||
isCreation,
|
||||
isExecution,
|
||||
gasCostFormatted,
|
||||
txEstimationExecutionStatus,
|
||||
} = useEstimateTransactionGas({
|
||||
txData: txData || '',
|
||||
txRecipient,
|
||||
operation,
|
||||
txAmount: txValue,
|
||||
safeTxGas: manualSafeTxGas,
|
||||
manualGasPrice,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (params?.safeTxGas) {
|
||||
setEstimatedSafeTxGas(gasEstimation)
|
||||
}
|
||||
}, [params, gasEstimation])
|
||||
|
||||
const dispatch = useDispatch()
|
||||
if (!isOpen) {
|
||||
return null
|
||||
}
|
||||
|
||||
const handleTxRejection = () => {
|
||||
onTxReject()
|
||||
onClose()
|
||||
}
|
||||
|
||||
const handleUserConfirmation = (safeTxHash: string): void => {
|
||||
onUserConfirm(safeTxHash)
|
||||
onClose()
|
||||
}
|
||||
|
||||
const getParametersStatus = () => (threshold > 1 ? 'ETH_DISABLED' : 'ENABLED')
|
||||
|
||||
const confirmTransactions = async (txParameters: TxParameters) => {
|
||||
await dispatch(
|
||||
createTransaction(
|
||||
{
|
||||
safeAddress,
|
||||
to: txRecipient,
|
||||
valueInWei: txValue,
|
||||
txData,
|
||||
operation,
|
||||
origin: app.id,
|
||||
navigateToTransactionsTab: false,
|
||||
txNonce: txParameters.safeNonce,
|
||||
safeTxGas: txParameters.safeTxGas
|
||||
? Number(txParameters.safeTxGas)
|
||||
: Math.max(params?.safeTxGas || 0, estimatedSafeTxGas),
|
||||
ethParameters: txParameters,
|
||||
notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX,
|
||||
},
|
||||
handleUserConfirmation,
|
||||
handleTxRejection,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
const closeEditModalCallback = (txParameters: TxParameters) => {
|
||||
const oldGasPrice = Number(gasPriceFormatted)
|
||||
const newGasPrice = Number(txParameters.ethGasPrice)
|
||||
const oldSafeTxGas = Number(gasEstimation)
|
||||
const newSafeTxGas = Number(txParameters.safeTxGas)
|
||||
|
||||
if (newGasPrice && oldGasPrice !== newGasPrice) {
|
||||
setManualGasPrice(txParameters.ethGasPrice)
|
||||
}
|
||||
|
||||
if (newSafeTxGas && oldSafeTxGas !== newSafeTxGas) {
|
||||
setManualSafeTxGas(newSafeTxGas)
|
||||
}
|
||||
}
|
||||
|
||||
const areTxsMalformed = txs.some((t) => !isTxValid(t))
|
||||
|
||||
const body = areTxsMalformed
|
||||
? () => (
|
||||
<>
|
||||
<IconText>
|
||||
<Icon color="error" size="md" type="info" />
|
||||
<Title size="xs">Transaction error</Title>
|
||||
</IconText>
|
||||
<Text size="lg">
|
||||
This Safe App initiated a transaction which cannot be processed. Please get in touch with the developer of
|
||||
this Safe App for more information.
|
||||
</Text>
|
||||
</>
|
||||
)
|
||||
: (txParameters, toggleEditMode) => {
|
||||
return (
|
||||
<>
|
||||
<Container>
|
||||
<AddressInfo ethBalance={ethBalance} safeAddress={safeAddress} safeName={safeName} />
|
||||
<DividerLine withArrow />
|
||||
{txs.map((tx, index) => (
|
||||
<Wrapper key={index}>
|
||||
<Collapse description={<AddressInfo safeAddress={tx.to} />} title={`Transaction ${index + 1}`}>
|
||||
<CollapseContent>
|
||||
<div className="section">
|
||||
<Heading tag="h3">Value</Heading>
|
||||
<div className="value-section">
|
||||
<Img alt="Ether" height={40} src={getEthAsToken('0').logoUri} />
|
||||
<Bold>
|
||||
{fromTokenUnit(tx.value, nativeCoin.decimals)} {nativeCoin.name}
|
||||
</Bold>
|
||||
</div>
|
||||
</div>
|
||||
<div className="section">
|
||||
<Heading tag="h3">Data (hex encoded)*</Heading>
|
||||
<StyledTextBox>{tx.data}</StyledTextBox>
|
||||
</div>
|
||||
</CollapseContent>
|
||||
</Collapse>
|
||||
</Wrapper>
|
||||
))}
|
||||
<DividerLine withArrow={false} />
|
||||
{params?.safeTxGas && (
|
||||
<div className="section">
|
||||
<Heading tag="h3">SafeTxGas</Heading>
|
||||
<StyledTextBox>{params?.safeTxGas}</StyledTextBox>
|
||||
<GasEstimationInfo
|
||||
appEstimation={params.safeTxGas}
|
||||
internalEstimation={estimatedSafeTxGas}
|
||||
loading={txEstimationExecutionStatus === EstimationStatus.LOADING}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tx Parameters */}
|
||||
<TxParametersDetail
|
||||
txParameters={txParameters}
|
||||
onEdit={toggleEditMode}
|
||||
parametersStatus={getParametersStatus()}
|
||||
isTransactionCreation={isCreation}
|
||||
isTransactionExecution={isExecution}
|
||||
/>
|
||||
</Container>
|
||||
{txEstimationExecutionStatus === EstimationStatus.LOADING ? null : (
|
||||
<TransactionFeesWrapper>
|
||||
<TransactionFees
|
||||
gasCostFormatted={gasCostFormatted}
|
||||
isExecution={isExecution}
|
||||
isCreation={isCreation}
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
txEstimationExecutionStatus={txEstimationExecutionStatus}
|
||||
/>
|
||||
</TransactionFeesWrapper>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal description="Safe App transaction" title="Safe App transaction" open>
|
||||
<EditableTxParameters
|
||||
ethGasLimit={gasLimit}
|
||||
ethGasPrice={gasPriceFormatted}
|
||||
safeTxGas={gasEstimation.toString()}
|
||||
parametersStatus={getParametersStatus()}
|
||||
closeEditModalCallback={closeEditModalCallback}
|
||||
>
|
||||
{(txParameters, toggleEditMode) => (
|
||||
<>
|
||||
<ModalTitle title={app.name} iconUrl={app.iconUrl} onClose={handleTxRejection} />
|
||||
|
||||
<Hairline />
|
||||
|
||||
{body(txParameters, toggleEditMode)}
|
||||
|
||||
<ModalFooter align="center" grow>
|
||||
<ModalFooterConfirmation
|
||||
cancelText="Cancel"
|
||||
handleCancel={handleTxRejection}
|
||||
handleOk={() => confirmTransactions(txParameters)}
|
||||
okDisabled={areTxsMalformed}
|
||||
okText="Submit"
|
||||
/>
|
||||
</ModalFooter>
|
||||
</>
|
||||
)}
|
||||
</EditableTxParameters>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { getNetworkInfo } from 'src/config'
|
||||
import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue'
|
||||
import { md, lg } from 'src/theme/variables'
|
||||
import ModalTitle from 'src/components/ModalTitle'
|
||||
import Hairline from 'src/components/layout/Hairline'
|
||||
import { DecodedDataParameterValue, DecodedData } from 'src/types/transactions/decode.d'
|
||||
import { BasicTxInfo, getParameterElement } from 'src/components/DecodeTxs'
|
||||
|
||||
const { nativeCoin } = getNetworkInfo()
|
||||
|
||||
const Container = styled.div`
|
||||
max-width: 480px;
|
||||
padding: ${md} ${lg};
|
||||
word-break: break-word;
|
||||
`
|
||||
|
||||
function isDataDecodedParameterValue(arg: any): arg is DecodedDataParameterValue {
|
||||
return arg.operation !== undefined
|
||||
}
|
||||
|
||||
type Props = {
|
||||
hideDecodedTxData: () => void
|
||||
onClose: () => void
|
||||
decodedTxData: DecodedDataParameterValue | DecodedData
|
||||
}
|
||||
|
||||
export const DecodedTxDetail = ({ hideDecodedTxData, onClose, decodedTxData: tx }: Props): ReactElement => {
|
||||
let body
|
||||
// If we are dealing with a multiSend
|
||||
// decodedTxData is of type DataDecodedParameter
|
||||
if (isDataDecodedParameterValue(tx)) {
|
||||
const txValue = fromTokenUnit(tx.value, nativeCoin.decimals)
|
||||
|
||||
body = (
|
||||
<>
|
||||
<BasicTxInfo txRecipient={tx.to} txData={tx.data} txValue={txValue} />
|
||||
{tx.dataDecoded?.parameters.map((p, index) => getParameterElement(p, index))}
|
||||
</>
|
||||
)
|
||||
} else {
|
||||
// If we are dealing with a single tx
|
||||
// decodedTxData is of type DecodedData
|
||||
body = <>{tx.parameters.map((p, index) => getParameterElement(p, index))}</>
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ModalTitle
|
||||
title={(tx as DecodedDataParameterValue).dataDecoded?.method || (tx as DecodedData).method}
|
||||
onClose={onClose}
|
||||
goBack={hideDecodedTxData}
|
||||
/>
|
||||
|
||||
<Hairline />
|
||||
|
||||
<Container>{body}</Container>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { ModalFooterConfirmation } from '@gnosis.pm/safe-react-components'
|
||||
import styled from 'styled-components'
|
||||
import { useDispatch } from 'react-redux'
|
||||
|
||||
import DividerLine from 'src/components/DividerLine'
|
||||
import TextBox from 'src/components/TextBox'
|
||||
import ModalTitle from 'src/components/ModalTitle'
|
||||
import Hairline from 'src/components/layout/Hairline'
|
||||
import Heading from 'src/components/layout/Heading'
|
||||
import { createTransaction } from 'src/logic/safe/store/actions/createTransaction'
|
||||
import { MULTI_SEND_ADDRESS } from 'src/logic/contracts/safeContracts'
|
||||
import { DELEGATE_CALL, TX_NOTIFICATION_TYPES, CALL } from 'src/logic/safe/transactions'
|
||||
import { encodeMultiSendCall } from 'src/logic/safe/transactions/multisend'
|
||||
import { getNetworkInfo } from 'src/config'
|
||||
import { EstimationStatus, useEstimateTransactionGas } from 'src/logic/hooks/useEstimateTransactionGas'
|
||||
import { TransactionFees } from 'src/components/TransactionsFees'
|
||||
import { EditableTxParameters } from 'src/routes/safe/components/Transactions/helpers/EditableTxParameters'
|
||||
import { TxParametersDetail } from 'src/routes/safe/components/Transactions/helpers/TxParametersDetail'
|
||||
import { md, lg, sm } from 'src/theme/variables'
|
||||
import { TxParameters } from 'src/routes/safe/container/hooks/useTransactionParameters'
|
||||
import AddressInfo from 'src/components/AddressInfo'
|
||||
import { DecodeTxs, BasicTxInfo } from 'src/components/DecodeTxs'
|
||||
import { fetchTxDecoder } from 'src/utils/decodeTx'
|
||||
import { DecodedData } from 'src/types/transactions/decode.d'
|
||||
import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue'
|
||||
|
||||
import GasEstimationInfo from '../GasEstimationInfo'
|
||||
import { ConfirmTxModalProps, DecodedTxDetail } from '.'
|
||||
|
||||
const { nativeCoin } = getNetworkInfo()
|
||||
|
||||
const StyledTextBox = styled(TextBox)`
|
||||
max-width: 444px;
|
||||
`
|
||||
|
||||
const Container = styled.div`
|
||||
max-width: 480px;
|
||||
padding: ${md} ${lg} 0;
|
||||
`
|
||||
const TransactionFeesWrapper = styled.div`
|
||||
background-color: ${({ theme }) => theme.colors.background};
|
||||
padding: ${sm} ${lg};
|
||||
margin-bottom: 15px;
|
||||
`
|
||||
|
||||
const FooterWrapper = styled.div`
|
||||
margin-bottom: 15px;
|
||||
`
|
||||
|
||||
const DecodeTxsWrapper = styled.div`
|
||||
margin: 24px -24px;
|
||||
`
|
||||
|
||||
type Props = ConfirmTxModalProps & {
|
||||
areTxsMalformed: boolean
|
||||
showDecodedTxData: (decodedTxDetails: DecodedTxDetail) => void
|
||||
hidden: boolean // used to prevent re-rendering the modal each time a tx is inspected
|
||||
}
|
||||
|
||||
export const ReviewConfirm = ({
|
||||
app,
|
||||
txs,
|
||||
safeAddress,
|
||||
ethBalance,
|
||||
safeName,
|
||||
params,
|
||||
hidden,
|
||||
onUserConfirm,
|
||||
onClose,
|
||||
onTxReject,
|
||||
areTxsMalformed,
|
||||
showDecodedTxData,
|
||||
}: Props): React.ReactElement => {
|
||||
const [estimatedSafeTxGas, setEstimatedSafeTxGas] = useState(0)
|
||||
const isMultiSend = txs.length > 1
|
||||
const [decodedData, setDecodedData] = useState<DecodedData | null>(null)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const txRecipient: string | undefined = useMemo(() => (isMultiSend ? MULTI_SEND_ADDRESS : txs[0]?.to), [
|
||||
txs,
|
||||
isMultiSend,
|
||||
])
|
||||
const txData: string | undefined = useMemo(() => (isMultiSend ? encodeMultiSendCall(txs) : txs[0]?.data), [
|
||||
txs,
|
||||
isMultiSend,
|
||||
])
|
||||
const txValue: string | undefined = useMemo(
|
||||
() => (isMultiSend ? '0' : txs[0]?.value && fromTokenUnit(txs[0]?.value, nativeCoin.decimals)),
|
||||
[txs, isMultiSend],
|
||||
)
|
||||
|
||||
const operation = useMemo(() => (isMultiSend ? DELEGATE_CALL : CALL), [isMultiSend])
|
||||
const [manualSafeTxGas, setManualSafeTxGas] = useState(0)
|
||||
const [manualGasPrice, setManualGasPrice] = useState<string | undefined>()
|
||||
|
||||
const {
|
||||
gasLimit,
|
||||
gasPriceFormatted,
|
||||
gasEstimation,
|
||||
isOffChainSignature,
|
||||
isCreation,
|
||||
isExecution,
|
||||
gasCostFormatted,
|
||||
txEstimationExecutionStatus,
|
||||
} = useEstimateTransactionGas({
|
||||
txData: txData || '',
|
||||
txRecipient,
|
||||
operation,
|
||||
txAmount: txValue,
|
||||
safeTxGas: manualSafeTxGas,
|
||||
manualGasPrice,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (params?.safeTxGas) {
|
||||
setEstimatedSafeTxGas(gasEstimation)
|
||||
}
|
||||
}, [params, gasEstimation])
|
||||
|
||||
// Decode tx data.
|
||||
useEffect(() => {
|
||||
const decodeTxData = async () => {
|
||||
const res = await fetchTxDecoder(txData)
|
||||
setDecodedData(res)
|
||||
}
|
||||
|
||||
decodeTxData()
|
||||
}, [txData])
|
||||
|
||||
const handleTxRejection = () => {
|
||||
onTxReject()
|
||||
onClose()
|
||||
}
|
||||
|
||||
const handleUserConfirmation = (safeTxHash: string): void => {
|
||||
onUserConfirm(safeTxHash)
|
||||
onClose()
|
||||
}
|
||||
|
||||
const confirmTransactions = async (txParameters: TxParameters) => {
|
||||
await dispatch(
|
||||
createTransaction(
|
||||
{
|
||||
safeAddress,
|
||||
to: txRecipient,
|
||||
valueInWei: txValue,
|
||||
txData,
|
||||
operation,
|
||||
origin: app.id,
|
||||
navigateToTransactionsTab: false,
|
||||
txNonce: txParameters.safeNonce,
|
||||
safeTxGas: txParameters.safeTxGas
|
||||
? Number(txParameters.safeTxGas)
|
||||
: Math.max(params?.safeTxGas || 0, estimatedSafeTxGas),
|
||||
ethParameters: txParameters,
|
||||
notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX,
|
||||
},
|
||||
handleUserConfirmation,
|
||||
handleTxRejection,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
const closeEditModalCallback = (txParameters: TxParameters) => {
|
||||
const oldGasPrice = Number(gasPriceFormatted)
|
||||
const newGasPrice = Number(txParameters.ethGasPrice)
|
||||
const oldSafeTxGas = Number(gasEstimation)
|
||||
const newSafeTxGas = Number(txParameters.safeTxGas)
|
||||
|
||||
if (newGasPrice && oldGasPrice !== newGasPrice) {
|
||||
setManualGasPrice(txParameters.ethGasPrice)
|
||||
}
|
||||
|
||||
if (newSafeTxGas && oldSafeTxGas !== newSafeTxGas) {
|
||||
setManualSafeTxGas(newSafeTxGas)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<EditableTxParameters
|
||||
ethGasLimit={gasLimit}
|
||||
ethGasPrice={gasPriceFormatted}
|
||||
safeTxGas={gasEstimation.toString()}
|
||||
closeEditModalCallback={closeEditModalCallback}
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
isExecution={isExecution}
|
||||
>
|
||||
{(txParameters, toggleEditMode) => (
|
||||
<div hidden={hidden}>
|
||||
<ModalTitle title={app.name} iconUrl={app.iconUrl} onClose={handleTxRejection} />
|
||||
|
||||
<Hairline />
|
||||
|
||||
<Container>
|
||||
{/* Safe */}
|
||||
<AddressInfo ethBalance={ethBalance} safeAddress={safeAddress} safeName={safeName} />
|
||||
|
||||
<DividerLine withArrow />
|
||||
|
||||
{/* Txs decoded */}
|
||||
<BasicTxInfo txRecipient={txRecipient} txData={txData} txValue={txValue} />
|
||||
|
||||
<DecodeTxsWrapper>
|
||||
<DecodeTxs txs={txs} decodedData={decodedData} onTxItemClick={showDecodedTxData} />
|
||||
</DecodeTxsWrapper>
|
||||
|
||||
{!isMultiSend && <DividerLine withArrow={false} />}
|
||||
|
||||
{/* Warning gas estimation */}
|
||||
{params?.safeTxGas && (
|
||||
<div className="section">
|
||||
<Heading tag="h3">SafeTxGas</Heading>
|
||||
<StyledTextBox>{params?.safeTxGas}</StyledTextBox>
|
||||
<GasEstimationInfo
|
||||
appEstimation={params.safeTxGas}
|
||||
internalEstimation={estimatedSafeTxGas}
|
||||
loading={txEstimationExecutionStatus === EstimationStatus.LOADING}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tx Parameters */}
|
||||
<TxParametersDetail
|
||||
txParameters={txParameters}
|
||||
onEdit={toggleEditMode}
|
||||
isTransactionCreation={isCreation}
|
||||
isTransactionExecution={isExecution}
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
/>
|
||||
</Container>
|
||||
|
||||
{/* Gas info */}
|
||||
{txEstimationExecutionStatus === EstimationStatus.LOADING ? null : (
|
||||
<TransactionFeesWrapper>
|
||||
<TransactionFees
|
||||
gasCostFormatted={gasCostFormatted}
|
||||
isExecution={isExecution}
|
||||
isCreation={isCreation}
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
txEstimationExecutionStatus={txEstimationExecutionStatus}
|
||||
/>
|
||||
</TransactionFeesWrapper>
|
||||
)}
|
||||
|
||||
{/* Buttons */}
|
||||
<FooterWrapper>
|
||||
<ModalFooterConfirmation
|
||||
cancelText="Cancel"
|
||||
handleCancel={handleTxRejection}
|
||||
handleOk={() => confirmTransactions(txParameters)}
|
||||
okDisabled={areTxsMalformed}
|
||||
okText="Submit"
|
||||
/>
|
||||
</FooterWrapper>
|
||||
</div>
|
||||
)}
|
||||
</EditableTxParameters>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import { Icon, Text, Title, ModalFooterConfirmation } from '@gnosis.pm/safe-react-components'
|
||||
import styled from 'styled-components'
|
||||
import { ConfirmTxModalProps } from '.'
|
||||
|
||||
const IconText = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
margin-right: 4px;
|
||||
}
|
||||
`
|
||||
|
||||
const FooterWrapper = styled.div`
|
||||
margin-top: 15px;
|
||||
`
|
||||
|
||||
export const SafeAppLoadError = ({ onTxReject, onClose }: ConfirmTxModalProps): ReactElement => {
|
||||
const handleTxRejection = () => {
|
||||
onTxReject()
|
||||
onClose()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<IconText>
|
||||
<Icon color="error" size="md" type="info" />
|
||||
<Title size="xs">Transaction error</Title>
|
||||
</IconText>
|
||||
<Text size="lg">
|
||||
This Safe App initiated a transaction which cannot be processed. Please get in touch with the developer of this
|
||||
Safe App for more information.
|
||||
</Text>
|
||||
|
||||
<FooterWrapper>
|
||||
<ModalFooterConfirmation
|
||||
cancelText="Cancel"
|
||||
handleCancel={() => handleTxRejection()}
|
||||
handleOk={() => {}}
|
||||
okDisabled={true}
|
||||
okText="Submit"
|
||||
/>
|
||||
</FooterWrapper>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import React, { ReactElement, useState } from 'react'
|
||||
import { Transaction } from '@gnosis.pm/safe-apps-sdk-v1'
|
||||
|
||||
import Modal from 'src/components/Modal'
|
||||
import { SafeApp } from 'src/routes/safe/components/Apps/types.d'
|
||||
import { TransactionParams } from 'src/routes/safe/components/Apps/components/AppFrame'
|
||||
import { mustBeEthereumAddress } from 'src/components/forms/validator'
|
||||
import { SafeAppLoadError } from './SafeAppLoadError'
|
||||
import { ReviewConfirm } from './ReviewConfirm'
|
||||
import { DecodedDataParameterValue, DecodedData } from 'src/types/transactions/decode'
|
||||
import { DecodedTxDetail } from './DecodedTxDetail'
|
||||
|
||||
export type ConfirmTxModalProps = {
|
||||
isOpen: boolean
|
||||
app: SafeApp
|
||||
txs: Transaction[]
|
||||
params?: TransactionParams
|
||||
safeAddress: string
|
||||
safeName: string
|
||||
ethBalance: string
|
||||
onUserConfirm: (safeTxHash: string) => void
|
||||
onTxReject: () => void
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const isTxValid = (t: Transaction): boolean => {
|
||||
if (!['string', 'number'].includes(typeof t.value)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (typeof t.value === 'string' && !/^(0x)?[0-9a-f]+$/i.test(t.value)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const isAddressValid = mustBeEthereumAddress(t.to) === undefined
|
||||
return isAddressValid && !!t.data && typeof t.data === 'string'
|
||||
}
|
||||
|
||||
export type DecodedTxDetail = DecodedDataParameterValue | DecodedData | undefined
|
||||
|
||||
export const ConfirmTxModal = (props: ConfirmTxModalProps): ReactElement | null => {
|
||||
const [decodedTxDetails, setDecodedTxDetails] = useState<DecodedTxDetail>()
|
||||
const areTxsMalformed = props.txs.some((t) => !isTxValid(t))
|
||||
|
||||
const showDecodedTxData = setDecodedTxDetails
|
||||
const hideDecodedTxData = () => setDecodedTxDetails(undefined)
|
||||
|
||||
const closeDecodedTxDetail = () => {
|
||||
hideDecodedTxData()
|
||||
props.onClose()
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal description="Safe App transaction" title="Safe App transaction" open={props.isOpen}>
|
||||
{areTxsMalformed && <SafeAppLoadError {...props} />}
|
||||
{decodedTxDetails && (
|
||||
<DecodedTxDetail
|
||||
onClose={closeDecodedTxDetail}
|
||||
hideDecodedTxData={hideDecodedTxData}
|
||||
decodedTxData={decodedTxDetails}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ReviewConfirm
|
||||
{...props}
|
||||
areTxsMalformed={areTxsMalformed}
|
||||
showDecodedTxData={showDecodedTxData}
|
||||
hidden={areTxsMalformed || !!decodedTxDetails}
|
||||
/>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@@ -3,7 +3,6 @@ import memoize from 'lodash.memoize'
|
||||
|
||||
import { SafeApp, SAFE_APP_FETCH_STATUS } from './types.d'
|
||||
|
||||
import { getGnosisSafeAppsUrl } from 'src/config'
|
||||
import { getContentFromENS } from 'src/logic/wallets/getWeb3'
|
||||
import appsIconSvg from 'src/assets/icons/apps.svg'
|
||||
import { ETHEREUM_NETWORK } from 'src/config/networks/network.d'
|
||||
@@ -17,7 +16,6 @@ const removeLastTrailingSlash = (url) => {
|
||||
return url
|
||||
}
|
||||
|
||||
const gnosisAppsUrl = removeLastTrailingSlash(getGnosisSafeAppsUrl())
|
||||
export type StaticAppInfo = {
|
||||
url: string
|
||||
disabled: boolean
|
||||
@@ -26,7 +24,7 @@ export type StaticAppInfo = {
|
||||
export const staticAppsList: Array<StaticAppInfo> = [
|
||||
// 1inch
|
||||
{
|
||||
url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmRWtuktjfU6WMAEJFgzBC4cUfqp3FF5uN9QoWb55SdGG5`,
|
||||
url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmUXF1yVGdqUfMbhNyfM3jpP6Bw66cYnKPoWq6iHkhd3Aw`,
|
||||
disabled: false,
|
||||
networks: [ETHEREUM_NETWORK.MAINNET],
|
||||
},
|
||||
@@ -56,7 +54,11 @@ export const staticAppsList: Array<StaticAppInfo> = [
|
||||
networks: [ETHEREUM_NETWORK.RINKEBY, ETHEREUM_NETWORK.XDAI],
|
||||
},
|
||||
// Compound
|
||||
{ url: `${gnosisAppsUrl}/compound`, disabled: false, networks: [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY] },
|
||||
{
|
||||
url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmX31xCdhFDmJzoVG33Y6kJtJ5Ujw8r5EJJBrsp8Fbjm7k`,
|
||||
disabled: false,
|
||||
networks: [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY],
|
||||
},
|
||||
// dHedge
|
||||
{
|
||||
url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmaiemnumMaaK9wE1pbMfm3YSBUpcFNgDh3Bf6VZCZq57Q`,
|
||||
@@ -65,7 +67,7 @@ export const staticAppsList: Array<StaticAppInfo> = [
|
||||
},
|
||||
// Idle
|
||||
{
|
||||
url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmVkGHm6gfQumJhnRfFCh7m2oSYwLXb51EKHzChpcV9J3N`,
|
||||
url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmTvrLwJtyjG8QFHgvqdPhcV5DBMQ7oZceSU4uvPw9vQaj`,
|
||||
disabled: false,
|
||||
networks: [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY],
|
||||
},
|
||||
@@ -131,7 +133,7 @@ export const staticAppsList: Array<StaticAppInfo> = [
|
||||
},
|
||||
// Wallet-Connect
|
||||
{
|
||||
url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmRMGTA5ARMwfhYbdmK83zzMd13NnEUKFJSZEgEjKa8YQm`,
|
||||
url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmU1pT35yPXxpnABcH3pZ1MxFeyYVtftT5RKhWopQmZHQV`,
|
||||
disabled: false,
|
||||
networks: [
|
||||
ETHEREUM_NETWORK.MAINNET,
|
||||
|
||||
@@ -12,7 +12,7 @@ export const styles = createStyles({
|
||||
},
|
||||
hide: {
|
||||
'&:hover': {
|
||||
backgroundColor: '#fff3e2',
|
||||
backgroundColor: '#f7f5f5',
|
||||
},
|
||||
'&:hover $actions': {
|
||||
visibility: 'initial',
|
||||
|
||||
@@ -133,6 +133,7 @@ const SendModal = ({
|
||||
|
||||
{activeScreen === 'sendFunds' && (
|
||||
<SendFunds
|
||||
initialValues={tx as ReviewTxProp}
|
||||
onClose={onClose}
|
||||
onReview={handleTxCreation}
|
||||
recipientAddress={recipientAddress}
|
||||
|
||||
+3
@@ -126,6 +126,8 @@ const ContractInteractionReview = ({ onClose, onPrev, tx }: Props): React.ReactE
|
||||
|
||||
return (
|
||||
<EditableTxParameters
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
isExecution={isExecution}
|
||||
ethGasLimit={gasLimit}
|
||||
ethGasPrice={gasPriceFormatted}
|
||||
safeTxGas={gasEstimation.toString()}
|
||||
@@ -210,6 +212,7 @@ const ContractInteractionReview = ({ onClose, onPrev, tx }: Props): React.ReactE
|
||||
onEdit={toggleEditMode}
|
||||
isTransactionCreation={isCreation}
|
||||
isTransactionExecution={isExecution}
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
/>
|
||||
</Block>
|
||||
<div className={classes.gasCostsContainer}>
|
||||
|
||||
+8
-1
@@ -94,7 +94,13 @@ const ReviewCustomTx = ({ onClose, onPrev, tx }: Props): React.ReactElement => {
|
||||
}
|
||||
|
||||
return (
|
||||
<EditableTxParameters ethGasLimit={gasLimit} ethGasPrice={gasPriceFormatted} safeTxGas={gasEstimation.toString()}>
|
||||
<EditableTxParameters
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
isExecution={isExecution}
|
||||
ethGasLimit={gasLimit}
|
||||
ethGasPrice={gasPriceFormatted}
|
||||
safeTxGas={gasEstimation.toString()}
|
||||
>
|
||||
{(txParameters, toggleEditMode) => (
|
||||
<>
|
||||
<Row align="center" className={classes.heading} grow>
|
||||
@@ -168,6 +174,7 @@ const ReviewCustomTx = ({ onClose, onPrev, tx }: Props): React.ReactElement => {
|
||||
onEdit={toggleEditMode}
|
||||
isTransactionCreation={isCreation}
|
||||
isTransactionExecution={isExecution}
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
/>
|
||||
</Block>
|
||||
{txEstimationExecutionStatus === EstimationStatus.LOADING ? null : (
|
||||
|
||||
@@ -140,6 +140,8 @@ const ReviewCollectible = ({ onClose, onPrev, tx }: Props): React.ReactElement =
|
||||
|
||||
return (
|
||||
<EditableTxParameters
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
isExecution={isExecution}
|
||||
ethGasLimit={gasLimit}
|
||||
ethGasPrice={gasPriceFormatted}
|
||||
safeTxGas={gasEstimation.toString()}
|
||||
@@ -206,6 +208,7 @@ const ReviewCollectible = ({ onClose, onPrev, tx }: Props): React.ReactElement =
|
||||
onEdit={toggleEditMode}
|
||||
isTransactionCreation={isCreation}
|
||||
isTransactionExecution={isExecution}
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
/>
|
||||
</Block>
|
||||
<div className={classes.gasCostsContainer}>
|
||||
|
||||
@@ -178,6 +178,8 @@ const ReviewSendFundsTx = ({ onClose, onPrev, tx }: ReviewTxProps): React.ReactE
|
||||
|
||||
return (
|
||||
<EditableTxParameters
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
isExecution={isExecution}
|
||||
ethGasLimit={gasLimit}
|
||||
ethGasPrice={gasPriceFormatted}
|
||||
safeTxGas={gasEstimation.toString()}
|
||||
@@ -260,10 +262,11 @@ const ReviewSendFundsTx = ({ onClose, onPrev, tx }: ReviewTxProps): React.ReactE
|
||||
onEdit={toggleEditMode}
|
||||
isTransactionCreation={isCreation}
|
||||
isTransactionExecution={isExecution}
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
/>
|
||||
|
||||
{/* Disclaimer */}
|
||||
</Block>
|
||||
|
||||
{/* Disclaimer */}
|
||||
{txEstimationExecutionStatus !== EstimationStatus.LOADING && (
|
||||
<div className={classes.gasCostsContainer}>
|
||||
<TransactionFees
|
||||
|
||||
+2
-2
@@ -34,7 +34,7 @@ const SelectedToken = ({ tokenAddress, tokens }: SelectTokenProps): ReactElement
|
||||
<ListItemText
|
||||
className={classes.tokenData}
|
||||
primary={token.name}
|
||||
secondary={`${formatAmount(token.balance?.toString() ?? '0')} ${token.symbol}`}
|
||||
secondary={`${formatAmount(token.balance?.tokenBalance.toString() ?? '0')} ${token.symbol}`}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
@@ -73,7 +73,7 @@ const TokenSelectField = ({ initialValue, isValid = true, tokens }: TokenSelectF
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={token.name}
|
||||
secondary={`${formatAmount(token.balance?.toString() ?? '0')} ${token.symbol}`}
|
||||
secondary={`${formatAmount(token.balance?.tokenBalance.toString() ?? '0')} ${token.symbol}`}
|
||||
data-testid={`select-token-${token.name}`}
|
||||
/>
|
||||
</MenuItem>
|
||||
|
||||
@@ -68,6 +68,7 @@ export type SendFundsTx = {
|
||||
}
|
||||
|
||||
type SendFundsProps = {
|
||||
initialValues: SendFundsTx
|
||||
onClose: () => void
|
||||
onReview: (txInfo: unknown) => void
|
||||
recipientAddress?: string
|
||||
@@ -80,6 +81,7 @@ const InputAdornmentChildSymbol = ({ symbol }: { symbol?: string }): ReactElemen
|
||||
}
|
||||
|
||||
const SendFunds = ({
|
||||
initialValues,
|
||||
onClose,
|
||||
onReview,
|
||||
recipientAddress,
|
||||
@@ -93,12 +95,14 @@ const SendFunds = ({
|
||||
const defaultEntry = { address: recipientAddress || '', name: '' }
|
||||
|
||||
// if there's nothing to lookup for, we return the default entry
|
||||
if (!recipientAddress) {
|
||||
if (!initialValues?.recipientAddress && !recipientAddress) {
|
||||
return defaultEntry
|
||||
}
|
||||
|
||||
// if there's something to lookup for, `initialValues` has precedence over `recipientAddress`
|
||||
const predefinedAddress = initialValues?.recipientAddress ?? recipientAddress
|
||||
const addressBookEntry = addressBook.find(({ address }) => {
|
||||
return sameAddress(recipientAddress, address)
|
||||
return sameAddress(predefinedAddress, address)
|
||||
})
|
||||
|
||||
// if found in the Address Book, then we return the entry
|
||||
@@ -170,7 +174,11 @@ const SendFunds = ({
|
||||
<Hairline />
|
||||
<GnoForm
|
||||
formMutators={formMutators}
|
||||
initialValues={{ amount, recipientAddress, token: selectedToken }}
|
||||
initialValues={{
|
||||
amount: initialValues?.amount || amount,
|
||||
recipientAddress: initialValues.recipientAddress || recipientAddress,
|
||||
token: initialValues?.token || selectedToken,
|
||||
}}
|
||||
onSubmit={handleSubmit}
|
||||
validation={sendFundsValidation}
|
||||
>
|
||||
|
||||
@@ -127,6 +127,8 @@ export const RemoveModuleModal = ({ onClose, selectedModulePair }: RemoveModuleM
|
||||
open
|
||||
>
|
||||
<EditableTxParameters
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
isExecution={isExecution}
|
||||
ethGasLimit={gasLimit}
|
||||
ethGasPrice={gasPriceFormatted}
|
||||
safeTxGas={gasEstimation.toString()}
|
||||
@@ -181,6 +183,7 @@ export const RemoveModuleModal = ({ onClose, selectedModulePair }: RemoveModuleM
|
||||
onEdit={toggleEditMode}
|
||||
isTransactionCreation={isCreation}
|
||||
isTransactionExecution={isExecution}
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
/>
|
||||
</Block>
|
||||
<Row className={cn(classes.modalDescription, classes.gasCostsContainer)}>
|
||||
|
||||
@@ -8,7 +8,7 @@ export const styles = createStyles({
|
||||
},
|
||||
hide: {
|
||||
'&:hover': {
|
||||
backgroundColor: '#fff3e2',
|
||||
backgroundColor: '#f7f5f5',
|
||||
},
|
||||
'&:hover $actions': {
|
||||
visibility: 'initial',
|
||||
|
||||
+3
@@ -101,6 +101,8 @@ export const ReviewAddOwner = ({ onClickBack, onClose, onSubmit, values }: Revie
|
||||
|
||||
return (
|
||||
<EditableTxParameters
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
isExecution={isExecution}
|
||||
ethGasLimit={gasLimit}
|
||||
ethGasPrice={gasPriceFormatted}
|
||||
safeTxGas={gasEstimation.toString()}
|
||||
@@ -214,6 +216,7 @@ export const ReviewAddOwner = ({ onClickBack, onClose, onSubmit, values }: Revie
|
||||
compact={false}
|
||||
isTransactionCreation={isCreation}
|
||||
isTransactionExecution={isExecution}
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
/>
|
||||
|
||||
<Block className={classes.gasCostsContainer}>
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ export const styles = createStyles({
|
||||
selectedOwner: {
|
||||
padding: sm,
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#fff3e2',
|
||||
backgroundColor: '#f7f5f5',
|
||||
},
|
||||
user: {
|
||||
justifyContent: 'left',
|
||||
|
||||
+3
@@ -123,6 +123,8 @@ export const ReviewRemoveOwnerModal = ({
|
||||
|
||||
return (
|
||||
<EditableTxParameters
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
isExecution={isExecution}
|
||||
ethGasLimit={gasLimit}
|
||||
ethGasPrice={gasPriceFormatted}
|
||||
safeTxGas={gasEstimation.toString()}
|
||||
@@ -241,6 +243,7 @@ export const ReviewRemoveOwnerModal = ({
|
||||
compact={false}
|
||||
isTransactionCreation={isCreation}
|
||||
isTransactionExecution={isExecution}
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
/>
|
||||
|
||||
{txEstimationExecutionStatus === EstimationStatus.LOADING ? null : (
|
||||
|
||||
+3
@@ -120,6 +120,8 @@ export const ReviewReplaceOwnerModal = ({
|
||||
|
||||
return (
|
||||
<EditableTxParameters
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
isExecution={isExecution}
|
||||
ethGasLimit={gasLimit}
|
||||
ethGasPrice={gasPriceFormatted}
|
||||
safeTxGas={gasEstimation.toString()}
|
||||
@@ -261,6 +263,7 @@ export const ReviewReplaceOwnerModal = ({
|
||||
compact={false}
|
||||
isTransactionCreation={isCreation}
|
||||
isTransactionExecution={isExecution}
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
/>
|
||||
|
||||
<Block className={classes.gasCostsContainer}>
|
||||
|
||||
+1
-1
@@ -69,7 +69,7 @@ export const styles = createStyles({
|
||||
selectedOwnerAdded: {
|
||||
padding: sm,
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#fff3e2',
|
||||
backgroundColor: '#f7f5f5',
|
||||
},
|
||||
user: {
|
||||
justifyContent: 'left',
|
||||
|
||||
@@ -14,7 +14,7 @@ export const styles = createStyles({
|
||||
},
|
||||
hide: {
|
||||
'&:hover': {
|
||||
backgroundColor: '#fff3e2',
|
||||
backgroundColor: '#f7f5f5',
|
||||
},
|
||||
'&:hover $actions': {
|
||||
visibility: 'initial',
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useDispatch, useSelector } from 'react-redux'
|
||||
import Block from 'src/components/layout/Block'
|
||||
import Col from 'src/components/layout/Col'
|
||||
import Row from 'src/components/layout/Row'
|
||||
import { getNetworkInfo } from 'src/config'
|
||||
import { createTransaction, CreateTransactionArgs } from 'src/logic/safe/store/actions/createTransaction'
|
||||
import { SafeRecordProps, SpendingLimit } from 'src/logic/safe/store/models/safe'
|
||||
import {
|
||||
@@ -20,7 +19,7 @@ import {
|
||||
import { MultiSendTx } from 'src/logic/safe/utils/upgradeSafe'
|
||||
import { makeToken, Token } from 'src/logic/tokens/store/model/token'
|
||||
import { fromTokenUnit, toTokenUnit } from 'src/logic/tokens/utils/humanReadableValue'
|
||||
import { sameAddress, ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses'
|
||||
import { sameAddress } from 'src/logic/wallets/ethAddresses'
|
||||
import { RESET_TIME_OPTIONS } from 'src/routes/safe/components/Settings/SpendingLimit/FormFields/ResetTime'
|
||||
import { AddressInfo, ResetTimeInfo, TokenInfo } from 'src/routes/safe/components/Settings/SpendingLimit/InfoDisplay'
|
||||
import Modal from 'src/routes/safe/components/Settings/SpendingLimit/Modal'
|
||||
@@ -34,8 +33,6 @@ import { EditableTxParameters } from 'src/routes/safe/components/Transactions/he
|
||||
import { TransactionFees } from 'src/components/TransactionsFees'
|
||||
import { EstimationStatus, useEstimateTransactionGas } from 'src/logic/hooks/useEstimateTransactionGas'
|
||||
|
||||
const { nativeCoin } = getNetworkInfo()
|
||||
|
||||
const useExistentSpendingLimit = ({
|
||||
spendingLimits,
|
||||
txToken,
|
||||
@@ -51,9 +48,7 @@ const useExistentSpendingLimit = ({
|
||||
return useMemo<SpendingLimit | null>(() => {
|
||||
// if `delegate` already exist, check what tokens were delegated to the _beneficiary_ `getTokens(safe, delegate)`
|
||||
const currentDelegate = spendingLimits?.find(
|
||||
({ delegate, token }) =>
|
||||
sameAddress(delegate, values.beneficiary) &&
|
||||
sameAddress(token, sameAddress(values.token, nativeCoin.address) ? ZERO_ADDRESS : values.token),
|
||||
({ delegate, token }) => sameAddress(delegate, values.beneficiary) && sameAddress(token, values.token),
|
||||
)
|
||||
|
||||
// let the user know that is about to replace an existent allowance
|
||||
@@ -238,6 +233,8 @@ export const ReviewSpendingLimits = ({ onBack, onClose, txToken, values }: Revie
|
||||
|
||||
return (
|
||||
<EditableTxParameters
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
isExecution={isExecution}
|
||||
ethGasLimit={gasLimit}
|
||||
ethGasPrice={gasPriceFormatted}
|
||||
safeTxGas={gasEstimation.toString()}
|
||||
@@ -287,6 +284,7 @@ export const ReviewSpendingLimits = ({ onBack, onClose, txToken, values }: Revie
|
||||
onEdit={toggleEditMode}
|
||||
isTransactionCreation={isCreation}
|
||||
isTransactionExecution={isExecution}
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
/>
|
||||
</Block>
|
||||
<div className={classes.gasCostsContainer}>
|
||||
|
||||
@@ -116,6 +116,8 @@ export const RemoveLimitModal = ({ onClose, spendingLimit, open }: RemoveSpendin
|
||||
description="Remove the selected Spending Limit"
|
||||
>
|
||||
<EditableTxParameters
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
isExecution={isExecution}
|
||||
ethGasLimit={gasLimit}
|
||||
ethGasPrice={gasPriceFormatted}
|
||||
safeTxGas={gasEstimation.toString()}
|
||||
@@ -148,6 +150,7 @@ export const RemoveLimitModal = ({ onClose, spendingLimit, open }: RemoveSpendin
|
||||
onEdit={toggleEditMode}
|
||||
isTransactionCreation={isCreation}
|
||||
isTransactionExecution={isExecution}
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
/>
|
||||
</Block>
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ export const useStyles = makeStyles(
|
||||
},
|
||||
hide: {
|
||||
'&:hover': {
|
||||
backgroundColor: '#fff3e2',
|
||||
backgroundColor: '#f7f5f5',
|
||||
},
|
||||
'&:hover $actions': {
|
||||
visibility: 'initial',
|
||||
|
||||
@@ -84,8 +84,6 @@ export const ChangeThresholdModal = ({
|
||||
}
|
||||
}, [safeAddress, editedThreshold])
|
||||
|
||||
const getParametersStatus = () => (threshold > 1 ? 'ETH_DISABLED' : 'ENABLED')
|
||||
|
||||
const handleSubmit = async ({ txParameters }) => {
|
||||
await dispatch(
|
||||
createTransaction({
|
||||
@@ -120,6 +118,8 @@ export const ChangeThresholdModal = ({
|
||||
|
||||
return (
|
||||
<EditableTxParameters
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
isExecution={isExecution}
|
||||
ethGasLimit={gasLimit}
|
||||
ethGasPrice={gasPriceFormatted}
|
||||
safeTxGas={gasEstimation.toString()}
|
||||
@@ -181,9 +181,9 @@ export const ChangeThresholdModal = ({
|
||||
<TxParametersDetail
|
||||
txParameters={txParameters}
|
||||
onEdit={toggleEditMode}
|
||||
parametersStatus={getParametersStatus()}
|
||||
isTransactionCreation={isCreation}
|
||||
isTransactionExecution={isExecution}
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
/>
|
||||
</Block>
|
||||
{txEstimationExecutionStatus !== EstimationStatus.LOADING && (
|
||||
|
||||
@@ -76,7 +76,13 @@ export const UpdateSafeModal = ({ onClose, safeAddress }: Props): React.ReactEle
|
||||
})
|
||||
|
||||
return (
|
||||
<EditableTxParameters ethGasLimit={gasLimit} ethGasPrice={gasPriceFormatted} safeTxGas={gasEstimation.toString()}>
|
||||
<EditableTxParameters
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
isExecution={isExecution}
|
||||
ethGasLimit={gasLimit}
|
||||
ethGasPrice={gasPriceFormatted}
|
||||
safeTxGas={gasEstimation.toString()}
|
||||
>
|
||||
{(txParameters, toggleEditMode) => (
|
||||
<>
|
||||
<Row align="center" className={classes.heading} grow>
|
||||
@@ -116,6 +122,7 @@ export const UpdateSafeModal = ({ onClose, safeAddress }: Props): React.ReactEle
|
||||
compact={false}
|
||||
isTransactionCreation={isCreation}
|
||||
isTransactionExecution={isExecution}
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
/>
|
||||
</Block>
|
||||
{txEstimationExecutionStatus === EstimationStatus.LOADING ? null : (
|
||||
|
||||
@@ -317,6 +317,8 @@ export const ApproveTxModal = ({
|
||||
return (
|
||||
<Modal description={description} handleClose={onClose} open={isOpen} title={title}>
|
||||
<EditableTxParameters
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
isExecution={isExecution}
|
||||
parametersStatus={getParametersStatus()}
|
||||
ethGasLimit={gasLimit}
|
||||
ethGasPrice={gasPriceFormatted}
|
||||
@@ -370,13 +372,14 @@ export const ApproveTxModal = ({
|
||||
)}
|
||||
|
||||
{/* Tx Parameters */}
|
||||
{approveAndExecute && (
|
||||
{(approveAndExecute || !isOffChainSignature) && (
|
||||
<TxParametersDetail
|
||||
txParameters={txParameters}
|
||||
onEdit={toggleEditMode}
|
||||
parametersStatus={getParametersStatus()}
|
||||
isTransactionCreation={isCreation}
|
||||
isTransactionExecution={isExecution}
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
/>
|
||||
)}
|
||||
</Row>
|
||||
|
||||
@@ -82,6 +82,8 @@ export const RejectTxModal = ({ isOpen, onClose, gwTransaction }: Props): React.
|
||||
return (
|
||||
<Modal description="Reject Transaction" handleClose={onClose} open={isOpen} title="Reject Transaction">
|
||||
<EditableTxParameters
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
isExecution={isExecution}
|
||||
ethGasLimit={gasLimit}
|
||||
ethGasPrice={gasPriceFormatted}
|
||||
safeTxGas={'0'}
|
||||
@@ -119,6 +121,7 @@ export const RejectTxModal = ({ isOpen, onClose, gwTransaction }: Props): React.
|
||||
parametersStatus={getParametersStatus()}
|
||||
isTransactionCreation={isCreation}
|
||||
isTransactionExecution={isExecution}
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
/>
|
||||
</Block>
|
||||
|
||||
|
||||
@@ -15,11 +15,11 @@ import GnoForm from 'src/components/forms/GnoForm'
|
||||
import { TxParameters } from 'src/routes/safe/container/hooks/useTransactionParameters'
|
||||
import { composeValidators, minValue } from 'src/components/forms/validator'
|
||||
|
||||
import { ParametersStatus, areSafeParamsEnabled, areEthereumParamsEnabled } from '../utils'
|
||||
import { ParametersStatus, areSafeParamsEnabled, areEthereumParamsVisible, ethereumTxParametersTitle } from '../utils'
|
||||
import { getNetworkInfo } from 'src/config'
|
||||
|
||||
const StyledDivider = styled(Divider)`
|
||||
margin: 0px;
|
||||
margin: 16px 0;
|
||||
`
|
||||
|
||||
const SafeOptions = styled.div`
|
||||
@@ -39,7 +39,7 @@ const EthereumOptions = styled.div`
|
||||
}
|
||||
`
|
||||
const StyledLink = styled(Link)`
|
||||
margin: 16px 0;
|
||||
margin: 16px 0 0 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -65,6 +65,7 @@ interface Props {
|
||||
txParameters: TxParameters
|
||||
onClose: (txParameters?: TxParameters) => void
|
||||
parametersStatus: ParametersStatus
|
||||
isExecution: boolean
|
||||
}
|
||||
|
||||
const formValidation = (values) => {
|
||||
@@ -101,6 +102,7 @@ export const EditTxParametersForm = ({
|
||||
onClose,
|
||||
txParameters,
|
||||
parametersStatus = 'ENABLED',
|
||||
isExecution,
|
||||
}: Props): React.ReactElement => {
|
||||
const classes = useStyles()
|
||||
const { safeNonce, safeTxGas, ethNonce, ethGasLimit, ethGasPrice } = txParameters
|
||||
@@ -142,7 +144,7 @@ export const EditTxParametersForm = ({
|
||||
{() => (
|
||||
<>
|
||||
<StyledText size="xl" strong>
|
||||
Safe transactions parameters
|
||||
Safe transaction
|
||||
</StyledText>
|
||||
|
||||
<SafeOptions>
|
||||
@@ -168,49 +170,53 @@ export const EditTxParametersForm = ({
|
||||
/>
|
||||
</SafeOptions>
|
||||
|
||||
<StyledTextMt size="xl" strong>
|
||||
Ethereum transactions parameters
|
||||
</StyledTextMt>
|
||||
{areEthereumParamsVisible(parametersStatus) && (
|
||||
<>
|
||||
<StyledTextMt size="xl" strong>
|
||||
{ethereumTxParametersTitle(isExecution)}
|
||||
</StyledTextMt>
|
||||
|
||||
<EthereumOptions>
|
||||
<Field
|
||||
name="ethNonce"
|
||||
defaultValue={ethNonce}
|
||||
placeholder="Ethereum nonce"
|
||||
text="Ethereum nonce"
|
||||
type="number"
|
||||
component={TextField}
|
||||
disabled={!areEthereumParamsEnabled(parametersStatus)}
|
||||
/>
|
||||
<Field
|
||||
name="ethGasLimit"
|
||||
defaultValue={ethGasLimit}
|
||||
placeholder="Ethereum gas limit"
|
||||
text="Ethereum gas limit"
|
||||
type="number"
|
||||
component={TextField}
|
||||
disabled={parametersStatus === 'CANCEL_TRANSACTION'}
|
||||
/>
|
||||
<Field
|
||||
name="ethGasPrice"
|
||||
defaultValue={ethGasPrice}
|
||||
type="number"
|
||||
placeholder="Ethereum gas price (GWEI)"
|
||||
text="Ethereum gas price (GWEI)"
|
||||
component={TextField}
|
||||
disabled={!areEthereumParamsEnabled(parametersStatus)}
|
||||
/>
|
||||
</EthereumOptions>
|
||||
<EthereumOptions>
|
||||
<Field
|
||||
name="ethNonce"
|
||||
defaultValue={ethNonce}
|
||||
placeholder="Nonce"
|
||||
text="Nonce"
|
||||
type="number"
|
||||
component={TextField}
|
||||
disabled={!areEthereumParamsVisible(parametersStatus)}
|
||||
/>
|
||||
<Field
|
||||
name="ethGasLimit"
|
||||
defaultValue={ethGasLimit}
|
||||
placeholder="Gas limit"
|
||||
text="Gas limit"
|
||||
type="number"
|
||||
component={TextField}
|
||||
disabled={parametersStatus === 'CANCEL_TRANSACTION'}
|
||||
/>
|
||||
<Field
|
||||
name="ethGasPrice"
|
||||
defaultValue={ethGasPrice}
|
||||
type="number"
|
||||
placeholder="Gas price (GWEI)"
|
||||
text="Gas price (GWEI)"
|
||||
component={TextField}
|
||||
disabled={!areEthereumParamsVisible(parametersStatus)}
|
||||
/>
|
||||
</EthereumOptions>
|
||||
|
||||
<StyledLink
|
||||
href="https://help.gnosis-safe.io/en/articles/4738445-configure-advanced-transaction-parameters-manually"
|
||||
target="_blank"
|
||||
>
|
||||
<Text size="xl" color="primary">
|
||||
How can I configure the gas price manually?
|
||||
</Text>
|
||||
<Icon size="sm" type="externalLink" color="primary" />
|
||||
</StyledLink>
|
||||
<StyledLink
|
||||
href="https://help.gnosis-safe.io/en/articles/4738445-configure-advanced-transaction-parameters-manually"
|
||||
target="_blank"
|
||||
>
|
||||
<Text size="xl" color="primary">
|
||||
How can I configure these parameters manually?
|
||||
</Text>
|
||||
<Icon size="sm" type="externalLink" color="primary" />
|
||||
</StyledLink>
|
||||
</>
|
||||
)}
|
||||
|
||||
<StyledDivider />
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import { safeThresholdSelector } from 'src/logic/safe/store/selectors'
|
||||
|
||||
type Props = {
|
||||
children: (txParameters: TxParameters, toggleStatus: (txParameters?: TxParameters) => void) => any
|
||||
isOffChainSignature: boolean
|
||||
isExecution: boolean
|
||||
parametersStatus?: ParametersStatus
|
||||
ethGasLimit?: TxParameters['ethGasLimit']
|
||||
ethGasPrice?: TxParameters['ethGasPrice']
|
||||
@@ -17,6 +19,8 @@ type Props = {
|
||||
|
||||
export const EditableTxParameters = ({
|
||||
children,
|
||||
isOffChainSignature,
|
||||
isExecution,
|
||||
parametersStatus,
|
||||
ethGasLimit,
|
||||
ethGasPrice,
|
||||
@@ -27,7 +31,7 @@ export const EditableTxParameters = ({
|
||||
const [isEditMode, toggleEditMode] = useState(false)
|
||||
const [useManualValues, setUseManualValues] = useState(false)
|
||||
const threshold = useSelector(safeThresholdSelector) || 1
|
||||
const defaultParameterStatus = threshold > 1 ? 'ETH_DISABLED' : 'ENABLED'
|
||||
const defaultParameterStatus = isOffChainSignature && threshold > 1 ? 'ETH_HIDDEN' : 'ENABLED'
|
||||
const txParameters = useTransactionParameters({
|
||||
parameterStatus: parametersStatus || defaultParameterStatus,
|
||||
initialEthGasLimit: ethGasLimit,
|
||||
@@ -65,6 +69,7 @@ export const EditableTxParameters = ({
|
||||
|
||||
return isEditMode ? (
|
||||
<EditTxParametersForm
|
||||
isExecution={isExecution}
|
||||
txParameters={txParameters}
|
||||
onClose={closeEditFormHandler}
|
||||
parametersStatus={parametersStatus ? parametersStatus : defaultParameterStatus}
|
||||
|
||||
@@ -3,7 +3,7 @@ import styled from 'styled-components'
|
||||
import { Text, ButtonLink, Accordion, AccordionSummary, AccordionDetails } from '@gnosis.pm/safe-react-components'
|
||||
|
||||
import { TxParameters } from 'src/routes/safe/container/hooks/useTransactionParameters'
|
||||
import { ParametersStatus, areEthereumParamsEnabled, areSafeParamsEnabled } from '../utils'
|
||||
import { ParametersStatus, areEthereumParamsVisible, areSafeParamsEnabled, ethereumTxParametersTitle } from '../utils'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { safeThresholdSelector } from 'src/logic/safe/store/selectors'
|
||||
|
||||
@@ -35,8 +35,9 @@ type Props = {
|
||||
onEdit: () => void
|
||||
compact?: boolean
|
||||
parametersStatus?: ParametersStatus
|
||||
isTransactionExecution: boolean
|
||||
isTransactionCreation: boolean
|
||||
isTransactionExecution: boolean
|
||||
isOffChainSignature: boolean
|
||||
}
|
||||
|
||||
export const TxParametersDetail = ({
|
||||
@@ -46,11 +47,12 @@ export const TxParametersDetail = ({
|
||||
parametersStatus,
|
||||
isTransactionCreation,
|
||||
isTransactionExecution,
|
||||
isOffChainSignature,
|
||||
}: Props): ReactElement | null => {
|
||||
const threshold = useSelector(safeThresholdSelector) || 1
|
||||
const defaultParameterStatus = threshold > 1 ? 'ETH_DISABLED' : 'ENABLED'
|
||||
const defaultParameterStatus = isOffChainSignature && threshold > 1 ? 'ETH_HIDDEN' : 'ENABLED'
|
||||
|
||||
if (!isTransactionExecution && !isTransactionCreation) {
|
||||
if (!isTransactionExecution && !isTransactionCreation && isOffChainSignature) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -62,7 +64,7 @@ export const TxParametersDetail = ({
|
||||
<AccordionDetails>
|
||||
<AccordionDetailsWrapper>
|
||||
<StyledText size="md" color="placeHolder">
|
||||
Safe transactions parameters
|
||||
Safe transaction
|
||||
</StyledText>
|
||||
|
||||
<TxParameterWrapper>
|
||||
@@ -95,57 +97,30 @@ export const TxParametersDetail = ({
|
||||
</Text>
|
||||
</TxParameterWrapper>
|
||||
|
||||
<TxParameterWrapper>
|
||||
<StyledText size="md" color="placeHolder">
|
||||
Ethereum transaction parameters
|
||||
</StyledText>
|
||||
</TxParameterWrapper>
|
||||
{areEthereumParamsVisible(parametersStatus || defaultParameterStatus) && (
|
||||
<>
|
||||
<TxParameterWrapper>
|
||||
<StyledText size="md" color="placeHolder">
|
||||
{ethereumTxParametersTitle(isTransactionExecution)}
|
||||
</StyledText>
|
||||
</TxParameterWrapper>
|
||||
|
||||
<TxParameterWrapper>
|
||||
<Text
|
||||
size="lg"
|
||||
color={areEthereumParamsEnabled(parametersStatus || defaultParameterStatus) ? 'text' : 'secondaryLight'}
|
||||
>
|
||||
Ethereum nonce
|
||||
</Text>
|
||||
<Text
|
||||
size="lg"
|
||||
color={areEthereumParamsEnabled(parametersStatus || defaultParameterStatus) ? 'text' : 'secondaryLight'}
|
||||
>
|
||||
{txParameters.ethNonce}
|
||||
</Text>
|
||||
</TxParameterWrapper>
|
||||
<TxParameterWrapper>
|
||||
<Text size="lg">Nonce</Text>
|
||||
<Text size="lg">{txParameters.ethNonce}</Text>
|
||||
</TxParameterWrapper>
|
||||
|
||||
<TxParameterWrapper>
|
||||
<Text
|
||||
size="lg"
|
||||
color={areEthereumParamsEnabled(parametersStatus || defaultParameterStatus) ? 'text' : 'secondaryLight'}
|
||||
>
|
||||
Ethereum gas limit
|
||||
</Text>
|
||||
<Text
|
||||
size="lg"
|
||||
color={areEthereumParamsEnabled(parametersStatus || defaultParameterStatus) ? 'text' : 'secondaryLight'}
|
||||
>
|
||||
{txParameters.ethGasLimit}
|
||||
</Text>
|
||||
</TxParameterWrapper>
|
||||
|
||||
<TxParameterWrapper>
|
||||
<Text
|
||||
size="lg"
|
||||
color={areEthereumParamsEnabled(parametersStatus || defaultParameterStatus) ? 'text' : 'secondaryLight'}
|
||||
>
|
||||
Ethereum gas price
|
||||
</Text>
|
||||
<Text
|
||||
size="lg"
|
||||
color={areEthereumParamsEnabled(parametersStatus || defaultParameterStatus) ? 'text' : 'secondaryLight'}
|
||||
>
|
||||
{txParameters.ethGasPrice}
|
||||
</Text>
|
||||
</TxParameterWrapper>
|
||||
<TxParameterWrapper>
|
||||
<Text size="lg">Gas limit</Text>
|
||||
<Text size="lg">{txParameters.ethGasLimit}</Text>
|
||||
</TxParameterWrapper>
|
||||
|
||||
<TxParameterWrapper>
|
||||
<Text size="lg">Gas price</Text>
|
||||
<Text size="lg">{txParameters.ethGasPrice}</Text>
|
||||
</TxParameterWrapper>
|
||||
</>
|
||||
)}
|
||||
<StyledButtonLink color="primary" textSize="xl" onClick={onEdit}>
|
||||
Edit
|
||||
</StyledButtonLink>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export type ParametersStatus = 'ENABLED' | 'DISABLED' | 'SAFE_DISABLED' | 'ETH_DISABLED' | 'CANCEL_TRANSACTION'
|
||||
export type ParametersStatus = 'ENABLED' | 'DISABLED' | 'SAFE_DISABLED' | 'ETH_HIDDEN' | 'CANCEL_TRANSACTION'
|
||||
|
||||
export const areEthereumParamsEnabled = (parametersStatus: ParametersStatus): boolean => {
|
||||
export const areEthereumParamsVisible = (parametersStatus: ParametersStatus): boolean => {
|
||||
return (
|
||||
parametersStatus === 'ENABLED' || (parametersStatus !== 'ETH_DISABLED' && parametersStatus !== 'CANCEL_TRANSACTION')
|
||||
parametersStatus === 'ENABLED' || (parametersStatus !== 'ETH_HIDDEN' && parametersStatus !== 'CANCEL_TRANSACTION')
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,3 +12,7 @@ export const areSafeParamsEnabled = (parametersStatus: ParametersStatus): boolea
|
||||
(parametersStatus !== 'SAFE_DISABLED' && parametersStatus !== 'CANCEL_TRANSACTION')
|
||||
)
|
||||
}
|
||||
|
||||
export const ethereumTxParametersTitle = (isExecution: boolean): string => {
|
||||
return `Owner transaction ${isExecution ? '(Execution)' : '(On-chain approval)'}`
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { createSelector } from 'reselect'
|
||||
import { Token } from 'src/logic/tokens/store/model/token'
|
||||
import { tokensSelector } from 'src/logic/tokens/store/selectors'
|
||||
import { getEthAsToken } from 'src/logic/tokens/utils/tokenHelpers'
|
||||
import { isUserAnOwner, sameAddress, ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses'
|
||||
import { isUserAnOwner, sameAddress } from 'src/logic/wallets/ethAddresses'
|
||||
import { userAccountSelector } from 'src/logic/wallets/store/selectors'
|
||||
|
||||
import { safeActiveTokensSelector, safeBalancesSelector, safeSelector } from 'src/logic/safe/store/selectors'
|
||||
@@ -37,7 +37,7 @@ export const extendedSafeTokensSelector = createSelector(
|
||||
|
||||
if (baseToken) {
|
||||
const updatedBaseToken = baseToken.set('balance', tokenBalance || { tokenBalance: '0', fiatBalance: '0' })
|
||||
if (sameAddress(tokenAddress, ZERO_ADDRESS) || sameAddress(tokenAddress, ethAsToken?.address)) {
|
||||
if (sameAddress(tokenAddress, ethAsToken?.address)) {
|
||||
map.set(tokenAddress, updatedBaseToken.set('logoUri', ethAsToken?.logoUri || baseToken.logoUri))
|
||||
} else {
|
||||
map.set(tokenAddress, updatedBaseToken)
|
||||
|
||||
+1
-1
@@ -455,7 +455,7 @@ export const DropdownListTheme = {
|
||||
},
|
||||
button: {
|
||||
'&:hover': {
|
||||
backgroundColor: '#fff3e2',
|
||||
backgroundColor: '#f7f5f5',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Vendored
+24
@@ -0,0 +1,24 @@
|
||||
export type DecodedDataBasicParameter = {
|
||||
name: string
|
||||
type: string
|
||||
value: string
|
||||
}
|
||||
export type DecodedDataParameterValue = {
|
||||
operation: 0 | 1
|
||||
to: string
|
||||
value: string
|
||||
data: string
|
||||
dataDecoded: {
|
||||
method: string
|
||||
parameters: DecodedDataBasicParameter[]
|
||||
} | null
|
||||
}
|
||||
|
||||
export type DecodedDataParameter = {
|
||||
valueDecoded?: DecodedDataParameterValue[]
|
||||
} & DecodedDataBasicParameter
|
||||
|
||||
export type DecodedData = {
|
||||
method: string
|
||||
parameters: DecodedDataParameter[]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import axios from 'axios'
|
||||
|
||||
import { getTxServiceUrl } from 'src/config'
|
||||
import { DecodedData } from 'src/types/transactions/decode.d'
|
||||
|
||||
export const fetchTxDecoder = async (txData: string): Promise<DecodedData | null> => {
|
||||
if (!txData?.length || txData === '0x') {
|
||||
return null
|
||||
}
|
||||
|
||||
const url = `${getTxServiceUrl()}/data-decoder/`
|
||||
try {
|
||||
const res = await axios.post<DecodedData>(url, { data: txData })
|
||||
return res.data
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -1596,9 +1596,9 @@
|
||||
solc "0.5.14"
|
||||
truffle "^5.1.21"
|
||||
|
||||
"@gnosis.pm/safe-react-components@https://github.com/gnosis/safe-react-components.git#f610327":
|
||||
"@gnosis.pm/safe-react-components@https://github.com/gnosis/safe-react-components.git#80f5db6":
|
||||
version "0.5.0"
|
||||
resolved "https://github.com/gnosis/safe-react-components.git#f610327c109810547513079196514b05cda63844"
|
||||
resolved "https://github.com/gnosis/safe-react-components.git#80f5db672d417ea410d58c8d713e46e16e3c7e7f"
|
||||
dependencies:
|
||||
classnames "^2.2.6"
|
||||
react-media "^1.10.0"
|
||||
@@ -3503,7 +3503,12 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.54.tgz#a737488631aca3ec7bd9f6229d77f1079e444793"
|
||||
integrity sha512-c8Lm7+hXdSPmWH4B9z/P/xIXhFK3mCQin4yCYMd2p1qpMG5AfgyJuYZ+3q2dT7qLiMMMGMd5dnkFpdqJARlvtQ==
|
||||
|
||||
"@types/node@^12.0.12", "@types/node@^12.12.6", "@types/node@^12.6.1":
|
||||
"@types/node@^12.0.12":
|
||||
version "12.20.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.5.tgz#4ca82a766f05c359fd6c77505007e5a272f4bb9b"
|
||||
integrity sha512-5Oy7tYZnu3a4pnJ//d4yVvOImExl4Vtwf0D40iKUlU+XlUsyV9iyFWyCFlwy489b72FMAik/EFwRkNLjjOdSPg==
|
||||
|
||||
"@types/node@^12.12.6", "@types/node@^12.6.1":
|
||||
version "12.20.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.4.tgz#73687043dd00fcb6962c60fbf499553a24d6bdf2"
|
||||
integrity sha512-xRCgeE0Q4pT5UZ189TJ3SpYuX/QGl6QIAOAIeDSbAVAd2gX1NxSZup4jNVK7cxIeP8KDSbJgcckun495isP1jQ==
|
||||
@@ -8843,9 +8848,9 @@ entities@~2.1.0:
|
||||
integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
|
||||
|
||||
env-paths@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43"
|
||||
integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
|
||||
integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==
|
||||
|
||||
errlop@^4.0.0:
|
||||
version "4.1.0"
|
||||
|
||||
Reference in New Issue
Block a user