Merge pull request #233 from gnosis/development

Development to master
This commit is contained in:
Mikhail Mikheev 2019-10-24 13:25:20 +04:00 committed by GitHub
commit 625e414fcf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 1412 additions and 850 deletions

3
.gitignore vendored
View File

@ -3,4 +3,5 @@ build_webpack/
build_storybook/
.DS_Store
build/
yarn-error.log
yarn-error.log
.env.*

View File

@ -33,14 +33,14 @@
"dependencies": {
"@gnosis.pm/safe-contracts": "^1.0.0",
"@gnosis.pm/util-contracts": "2.0.4",
"@material-ui/core": "4.5.0",
"@material-ui/icons": "4.4.3",
"@testing-library/jest-dom": "4.1.0",
"@welldone-software/why-did-you-render": "3.3.5",
"@material-ui/core": "4.5.1",
"@material-ui/icons": "4.5.1",
"@testing-library/jest-dom": "4.1.2",
"@welldone-software/why-did-you-render": "3.3.8",
"axios": "0.19.0",
"bignumber.js": "9.0.0",
"connected-react-router": "6.5.2",
"date-fns": "2.4.1",
"date-fns": "2.5.0",
"ethereum-ens": "0.7.8",
"final-form": "4.18.5",
"history": "4.10.1",
@ -54,7 +54,7 @@
"react-dom": "16.10.2",
"react-final-form": "6.3.0",
"react-final-form-listeners": "^1.0.2",
"react-hot-loader": "4.12.14",
"react-hot-loader": "4.12.15",
"react-infinite-scroll-component": "4.5.3",
"react-qr-reader": "^2.2.1",
"react-redux": "7.1.1",
@ -68,8 +68,8 @@
"web3": "1.2.1"
},
"devDependencies": {
"@babel/cli": "7.6.2",
"@babel/core": "7.6.2",
"@babel/cli": "7.6.4",
"@babel/core": "7.6.4",
"@babel/plugin-proposal-class-properties": "7.5.5",
"@babel/plugin-proposal-decorators": "7.6.0",
"@babel/plugin-proposal-do-expressions": "7.6.0",
@ -89,16 +89,16 @@
"@babel/plugin-transform-member-expression-literals": "^7.2.0",
"@babel/plugin-transform-property-literals": "^7.2.0",
"@babel/polyfill": "7.6.0",
"@babel/preset-env": "7.6.2",
"@babel/preset-env": "7.6.3",
"@babel/preset-flow": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"@babel/preset-react": "7.6.3",
"@sambego/storybook-state": "^1.3.6",
"@storybook/addon-actions": "5.2.1",
"@storybook/addon-knobs": "5.2.1",
"@storybook/addon-links": "5.2.1",
"@storybook/react": "5.2.1",
"@storybook/addon-actions": "5.2.4",
"@storybook/addon-knobs": "5.2.4",
"@storybook/addon-links": "5.2.4",
"@storybook/react": "5.2.4",
"@testing-library/react": "9.3.0",
"autoprefixer": "9.6.1",
"autoprefixer": "9.6.5",
"babel-core": "^7.0.0-bridge.0",
"babel-eslint": "10.0.3",
"babel-jest": "24.9.0",
@ -113,9 +113,9 @@
"eslint-config-airbnb": "18.0.1",
"eslint-plugin-flowtype": "4.3.0",
"eslint-plugin-import": "2.18.2",
"eslint-plugin-jest": "22.17.0",
"eslint-plugin-jest": "22.19.0",
"eslint-plugin-jsx-a11y": "6.2.3",
"eslint-plugin-react": "7.15.0",
"eslint-plugin-react": "7.16.0",
"ethereumjs-abi": "0.6.8",
"extract-text-webpack-plugin": "^4.0.0-beta.0",
"file-loader": "4.2.0",
@ -136,13 +136,13 @@
"storybook-host": "5.1.0",
"storybook-router": "^0.3.4",
"style-loader": "1.0.0",
"truffle": "5.0.39",
"truffle": "5.0.40",
"truffle-contract": "4.0.31",
"truffle-solidity-loader": "0.1.32",
"uglifyjs-webpack-plugin": "2.2.0",
"url-loader": "^2.1.0",
"webpack": "4.41.0",
"webpack-bundle-analyzer": "3.5.2",
"url-loader": "2.2.0",
"webpack": "4.41.2",
"webpack-bundle-analyzer": "3.6.0",
"webpack-cli": "3.3.9",
"webpack-dev-server": "3.8.2",
"webpack-manifest-plugin": "2.2.0"

View File

@ -1,14 +1,20 @@
// @flow
import React, { Component } from 'react'
import { Component } from 'react'
import { List } from 'immutable'
import { connect } from 'react-redux'
import { withSnackbar } from 'notistack'
import actions from './actions'
import { type Notification } from '~/logic/notifications/store/models/notification'
import actions, { type Actions } from './actions'
import selector from './selector'
class Notifier extends Component {
type Props = Actions & {
notifications: List<Notification>,
}
class Notifier extends Component<Props> {
displayed = []
shouldComponentUpdate({ notifications: newSnacks = [] }) {
shouldComponentUpdate({ notifications: newSnacks = List() }) {
const { notifications: currentSnacks, closeSnackbar, removeSnackbar } = this.props
if (!newSnacks.size) {
@ -27,7 +33,7 @@ class Notifier extends Component {
if (notExists) {
continue
}
notExists = notExists || !currentSnacks.filter(({ key }) => newSnack.key === key).length
notExists = notExists || !currentSnacks.filter(({ key }) => newSnack.key === key).size
}
return notExists
}

View File

@ -50,8 +50,8 @@ export const minValue = (min: number) => (value: string) => {
return `Should be at least ${min}`
}
export const maxValue = (max: number) => (value: string) => {
if (Number.isNaN(Number(value)) || Number.parseInt(value, 10) <= Number(max)) {
export const maxValue = (max: number | string) => (value: string) => {
if (Number.isNaN(Number(value)) || parseFloat(value, 10) <= parseFloat(max, 10)) {
return undefined
}

View File

@ -1,6 +1,6 @@
// @flow
import * as React from 'react'
import classNames from 'classnames/bind'
import React from 'react'
import { capitalize } from '~/utils/css'
import styles from './index.scss'

View File

@ -2,6 +2,7 @@
import contract from 'truffle-contract'
import ProxyFactorySol from '@gnosis.pm/safe-contracts/build/contracts/ProxyFactory.json'
import GnosisSafeSol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafe.json'
import SafeProxy from '@gnosis.pm/safe-contracts/build/contracts/Proxy.json'
import { ensureOnce } from '~/utils/singleton'
import { getWeb3 } from '~/logic/wallets/getWeb3'
import { calculateGasOf, calculateGasPrice } from '~/logic/wallets/ethTransactions'
@ -99,3 +100,27 @@ export const getGnosisSafeInstanceAt = async (safeAddress: string) => {
return gnosisSafe
}
const cleanByteCodeMetadata = (bytecode: string): string => {
const metaData = 'a165'
return bytecode.substring(0, bytecode.lastIndexOf(metaData))
}
export const validateProxy = async (safeAddress: string): boolean => {
// https://solidity.readthedocs.io/en/latest/metadata.html#usage-for-source-code-verification
const web3 = getWeb3()
const code = await web3.eth.getCode(safeAddress)
const codeWithoutMetadata = cleanByteCodeMetadata(code)
const supportedProxies = [SafeProxy]
for (let i = 0; i < supportedProxies.length; i += 1) {
const proxy = supportedProxies[i]
const proxyCode = proxy.deployedBytecode
const proxyCodeWithoutMetadata = cleanByteCodeMetadata(proxyCode)
if (codeWithoutMetadata === proxyCodeWithoutMetadata) {
return true
}
}
// Old PayingProxyCode
const oldProxyCode = '0x60806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680634555d5c91461008b5780635c60da1b146100b6575b73ffffffffffffffffffffffffffffffffffffffff600054163660008037600080366000845af43d6000803e6000811415610086573d6000fd5b3d6000f35b34801561009757600080fd5b506100a061010d565b6040518082815260200191505060405180910390f35b3480156100c257600080fd5b506100cb610116565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b60006002905090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050905600'
return codeWithoutMetadata === oldProxyCode
}

View File

@ -5,7 +5,7 @@ import { Close as IconClose } from '@material-ui/icons'
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
import { type Notification, NOTIFICATIONS } from './notificationTypes'
type NotificationsQueue = {
export type NotificationsQueue = {
beforeExecution: Notification,
pendingExecution: {
noMoreConfirmationsNeeded: Notification,
@ -104,7 +104,7 @@ const defaultNotificationsQueue: NotificationsQueue = {
afterExecutionError: NOTIFICATIONS.TX_FAILED_MSG,
}
export const getNofiticationsFromTxType = (txType: string) => {
export const getNotificationsFromTxType = (txType: string) => {
let notificationsQueue: NotificationsQueue
switch (txType) {

View File

@ -1,6 +1,6 @@
// @flow
import { createAction } from 'redux-actions'
import type { Dispatch as ReduxDispatch, GetState } from 'redux'
import type { Dispatch as ReduxDispatch } from 'redux'
import { type GlobalState } from '~/store'
import { type NotificationProps } from '~/logic/notifications/store/models/notification'
@ -10,7 +10,6 @@ const addSnackbar = createAction<string, *>(ENQUEUE_SNACKBAR)
const enqueueSnackbar = (notification: NotificationProps) => (
dispatch: ReduxDispatch<GlobalState>,
getState: GetState<GlobalState>,
) => {
const newNotification = {
...notification,

View File

@ -29,7 +29,5 @@ export default handleActions<NotificationReducerState, *>(
return state.delete(key)
},
},
Map({
notifications: Map(),
}),
Map(),
)

View File

@ -5,7 +5,7 @@ import { type GlobalState } from '~/store'
import { NOTIFICATIONS_REDUCER_ID } from '~/logic/notifications/store/reducer/notifications'
import { type Notification } from '~/logic/notifications/store/models/notification'
export const notificationsMapSelector = (
const notificationsMapSelector = (
state: GlobalState,
): Map<string, Notification> => state[NOTIFICATIONS_REDUCER_ID]

View File

@ -0,0 +1,57 @@
// @flow
import GnosisSafeSol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafe.json'
import { type Transaction } from '~/routes/safe/store/models/transaction'
import { getWeb3, getAccountFrom } from '~/logic/wallets/getWeb3'
import { generateSignaturesFromTxConfirmations } from '~/routes/safe/store/actions/processTransaction'
import { calculateGasOf, calculateGasPrice } from '~/logic/wallets/ethTransactions'
import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses'
import { CALL } from '.'
export const estimateTxGasCosts = async (
safeAddress: string,
to: string,
data: string,
tx?: Transaction,
preApprovingOwner?: string,
): Promise<number> => {
try {
const web3 = getWeb3()
const from = await getAccountFrom(web3)
const safeInstance = new web3.eth.Contract(GnosisSafeSol.abi, safeAddress)
const nonce = await safeInstance.methods.nonce().call()
const threshold = await safeInstance.methods.getThreshold().call()
const isExecution = (tx && tx.confirmations.size === threshold) || !!preApprovingOwner || threshold === '1'
let txData
if (isExecution) {
// https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures
const signatures = tx && tx.confirmations
? generateSignaturesFromTxConfirmations(tx.confirmations, preApprovingOwner)
: `0x000000000000000000000000${from.replace(
'0x',
'',
)}000000000000000000000000000000000000000000000000000000000000000001`
txData = await safeInstance.methods
.execTransaction(to, tx ? tx.value : 0, data, CALL, 0, 0, 0, ZERO_ADDRESS, ZERO_ADDRESS, signatures)
.encodeABI()
} else {
const txHash = await safeInstance.methods
.getTransactionHash(to, tx ? tx.value : 0, data, CALL, 0, 0, 0, ZERO_ADDRESS, ZERO_ADDRESS, nonce)
.call({
from,
})
txData = await safeInstance.methods.approveHash(txHash).encodeABI()
}
const gas = await calculateGasOf(txData, from, safeAddress)
const gasPrice = await calculateGasPrice()
return gas * parseInt(gasPrice, 10)
} catch (err) {
console.error('Error while estimating transaction execution gas costs:')
console.error(err)
return 10000
}
}

View File

@ -0,0 +1,47 @@
// @flow
// https://github.com/ethers-io/ethers.js/issues/527
export const ALTERNATIVE_TOKEN_ABI = [
{
constant: true,
inputs: [],
name: 'name',
outputs: [
{
name: '',
type: 'bytes32',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: true,
inputs: [],
name: 'symbol',
outputs: [
{
name: '',
type: 'bytes32',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: true,
inputs: [],
name: 'decimals',
outputs: [
{
name: '',
type: 'uint8',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
]

View File

@ -17,7 +17,11 @@ const lt1000tFormatter = new Intl.NumberFormat([], { maximumFractionDigits: 3, n
export const formatAmount = (number: string | number) => {
let numberFloat = parseFloat(number)
if (numberFloat < 1000) {
if (numberFloat === 0) {
numberFloat = '0.000'
} else if (numberFloat < 0.001) {
numberFloat = '< 0.001'
} else if (numberFloat < 1000) {
numberFloat = lt1kFormatter.format(numberFloat)
} else if (numberFloat < 10000) {
numberFloat = lt10kFormatter.format(numberFloat)

View File

@ -56,6 +56,6 @@ export const calculateGasOf = async (data: Object, from: string, to: string) =>
return gas * 2
} catch (err) {
return Promise.reject(new Error(err))
return Promise.reject(err)
}
}

View File

@ -64,7 +64,7 @@ const getProviderName: Function = (web3Provider): string => {
return name
}
const getAccountFrom: Function = async (web3Provider): Promise<string | null> => {
export const getAccountFrom: Function = async (web3Provider): Promise<string | null> => {
const accounts = await web3Provider.eth.getAccounts()
if (process.env.NODE_ENV === 'test' && window.testAccountIndex) {

View File

@ -64,7 +64,7 @@ const Routes = ({ defaultSafe, location }: RoutesProps) => {
/>
<Route exact path={WELCOME_ADDRESS} component={Welcome} />
<Route exact path={OPEN_ADDRESS} component={Open} />
<Route exact path={SAFE_ADDRESS} component={Safe} />
<Route path={SAFE_ADDRESS} component={Safe} />
<Route exact path={OPENING_ADDRESS} component={Opening} />
<Route exact path={LOAD_ADDRESS} component={Load} />
<Redirect to="/" />

View File

@ -1,7 +1,6 @@
// @flow
import * as React from 'react'
import { withStyles } from '@material-ui/core/styles'
import SafeProxy from '@gnosis.pm/safe-contracts/build/contracts/Proxy.json'
import InputAdornment from '@material-ui/core/InputAdornment'
import CheckCircle from '@material-ui/icons/CheckCircle'
import Field from '~/components/forms/Field'
@ -15,7 +14,7 @@ import Paragraph from '~/components/layout/Paragraph'
import OpenPaper from '~/components/Stepper/OpenPaper'
import { FIELD_LOAD_NAME, FIELD_LOAD_ADDRESS } from '~/routes/load/components/fields'
import { getWeb3 } from '~/logic/wallets/getWeb3'
import { getSafeMasterContract } from '~/logic/contracts/safeContracts'
import { getSafeMasterContract, validateProxy } from '~/logic/contracts/safeContracts'
import { secondary } from '~/theme/variables'
type Props = {
@ -56,15 +55,8 @@ export const safeFieldsValidation = async (values: Object) => {
return errors
}
// https://solidity.readthedocs.io/en/latest/metadata.html#usage-for-source-code-verification
const metaData = 'a165'
const code = await web3.eth.getCode(safeAddress)
const codeWithoutMetadata = code.substring(0, code.lastIndexOf(metaData))
const proxyCode = SafeProxy.deployedBytecode
const proxyCodeWithoutMetadata = proxyCode.substring(0, proxyCode.lastIndexOf(metaData))
const safeInstance = codeWithoutMetadata === proxyCodeWithoutMetadata
if (!safeInstance) {
const isValidProxy = await validateProxy(safeAddress)
if (!isValidProxy) {
errors[FIELD_LOAD_ADDRESS] = SAFE_INSTANCE_ERROR
return errors
}

View File

@ -12,6 +12,7 @@ import OpenPaper from '~/components/Stepper/OpenPaper'
import Col from '~/components/layout/Col'
import Row from '~/components/layout/Row'
import Paragraph from '~/components/layout/Paragraph'
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
import {
sm, md, lg, border, background,
} from '~/theme/variables'
@ -73,7 +74,7 @@ type Props = {
}
const ReviewComponent = ({ values, classes, userAccount }: Props) => {
const [gasCosts, setGasCosts] = useState<string>('0.00')
const [gasCosts, setGasCosts] = useState<string>('< 0.001')
const names = getNamesFrom(values)
const addresses = getAccountsFrom(values)
const numOwners = getNumOwnersFrom(values)
@ -85,9 +86,9 @@ const ReviewComponent = ({ values, classes, userAccount }: Props) => {
const { fromWei, toBN } = web3.utils
const estimatedGasCosts = await estimateGasForDeployingSafe(addresses, numOwners, userAccount)
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
const roundedGasCosts = parseFloat(gasCostsAsEth).toFixed(3)
const formattedGasCosts = formatAmount(gasCostsAsEth)
if (isCurrent) {
setGasCosts(roundedGasCosts)
setGasCosts(formattedGasCosts)
}
}

View File

@ -28,7 +28,7 @@ import { getAccountsFrom } from '~/routes/open/utils/safeDataExtractor'
import Hairline from '~/components/layout/Hairline'
import trash from '~/assets/icons/trash.svg'
import QRIcon from '~/assets/icons/qrcode.svg'
import ScanQRModal from './ScanQRModal'
import ScanQRModal from '~/components/ScanQRModal'
import { getAddressValidator } from './validators'
import { styles } from './style'

View File

@ -1,23 +1,17 @@
// @flow
import React from 'react'
import OpenInNew from '@material-ui/icons/OpenInNew'
import { withStyles } from '@material-ui/core/styles'
import { makeStyles } from '@material-ui/core/styles'
import Row from '~/components/layout/Row'
import Col from '~/components/layout/Col'
import Paragraph from '~/components/layout/Paragraph'
import Link from '~/components/layout/Link'
import EtherscanBtn from '~/components/EtherscanBtn'
import CopyBtn from '~/components/CopyBtn'
import Bold from '~/components/layout/Bold'
import Block from '~/components/layout/Block'
import Identicon from '~/components/Identicon'
import { copyToClipboard } from '~/utils/clipboard'
import { secondary, xs, border } from '~/theme/variables'
import { xs, border } from '~/theme/variables'
const openIconStyle = {
height: '16px',
color: secondary,
}
const styles = () => ({
const useStyles = makeStyles({
balanceContainer: {
fontSize: '12px',
lineHeight: 1.08,
@ -28,20 +22,22 @@ const styles = () => ({
marginTop: xs,
borderRadius: '3px',
},
address: {
marginRight: xs,
},
})
type Props = {
classes: Object,
safeAddress: string,
etherScanLink: string,
safeName: string,
ethBalance: string,
}
const SafeInfo = (props: Props) => {
const {
safeAddress, safeName, etherScanLink, ethBalance, classes,
safeAddress, safeName, ethBalance,
} = props
const classes = useStyles()
return (
<Row margin="md">
@ -52,21 +48,18 @@ const SafeInfo = (props: Props) => {
<Paragraph weight="bolder" noMargin style={{ lineHeight: 1 }}>
{safeName}
</Paragraph>
<Paragraph weight="bolder" onClick={copyToClipboard} noMargin>
{safeAddress}
<Link to={etherScanLink} target="_blank">
<OpenInNew style={openIconStyle} />
</Link>
</Paragraph>
<Block justify="left">
<Paragraph weight="bolder" className={classes.address} noMargin>
{safeAddress}
</Paragraph>
<CopyBtn content={safeAddress} />
<EtherscanBtn type="address" value={safeAddress} />
</Block>
<Block className={classes.balanceContainer}>
<Paragraph noMargin>
Balance:
{' '}
<Bold>
{ethBalance}
{' '}
ETH
</Bold>
<Bold>{`${ethBalance} ETH`}</Bold>
</Paragraph>
</Block>
</Col>
@ -74,4 +67,4 @@ const SafeInfo = (props: Props) => {
)
}
export default withStyles(styles)(SafeInfo)
export default SafeInfo

View File

@ -24,7 +24,6 @@ type Props = {
classes: Object,
isOpen: boolean,
safeAddress: string,
etherScanLink: string,
safeName: string,
ethBalance: string,
tokens: List<Token>,
@ -65,7 +64,6 @@ const Send = ({
isOpen,
classes,
safeAddress,
etherScanLink,
safeName,
ethBalance,
tokens,
@ -113,7 +111,6 @@ const Send = ({
<SendFunds
onClose={onClose}
safeAddress={safeAddress}
etherScanLink={etherScanLink}
safeName={safeName}
ethBalance={ethBalance}
tokens={tokens}
@ -128,17 +125,16 @@ const Send = ({
onClose={onClose}
setActiveScreen={setActiveScreen}
safeAddress={safeAddress}
etherScanLink={etherScanLink}
safeName={safeName}
ethBalance={ethBalance}
createTransaction={createTransaction}
tokens={tokens}
/>
)}
{activeScreen === 'sendCustomTx' && (
<SendCustomTx
onClose={onClose}
safeAddress={safeAddress}
etherScanLink={etherScanLink}
safeName={safeName}
ethBalance={ethBalance}
onSubmit={handleCustomTxCreation}
@ -151,7 +147,6 @@ const Send = ({
onClose={onClose}
setActiveScreen={setActiveScreen}
safeAddress={safeAddress}
etherScanLink={etherScanLink}
safeName={safeName}
ethBalance={ethBalance}
createTransaction={createTransaction}

View File

@ -1,27 +1,27 @@
// @flow
import React from 'react'
import OpenInNew from '@material-ui/icons/OpenInNew'
import React, { useState, useEffect } from 'react'
import { withStyles } from '@material-ui/core/styles'
import Close from '@material-ui/icons/Close'
import IconButton from '@material-ui/core/IconButton'
import { withSnackbar } from 'notistack'
import Paragraph from '~/components/layout/Paragraph'
import Row from '~/components/layout/Row'
import Link from '~/components/layout/Link'
import Col from '~/components/layout/Col'
import Button from '~/components/layout/Button'
import Img from '~/components/layout/Img'
import Block from '~/components/layout/Block'
import Identicon from '~/components/Identicon'
import Hairline from '~/components/layout/Hairline'
import { copyToClipboard } from '~/utils/clipboard'
import EtherscanBtn from '~/components/EtherscanBtn'
import CopyBtn from '~/components/CopyBtn'
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
import { setImageToPlaceholder } from '~/routes/safe/components/Balances/utils'
import { estimateTxGasCosts } from '~/logic/safe/transactions/gasNew'
import { getWeb3 } from '~/logic/wallets/getWeb3'
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
import { getEthAsToken } from '~/logic/tokens/utils/tokenHelpers'
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
import ArrowDown from '../assets/arrow-down.svg'
import { secondary } from '~/theme/variables'
import { styles } from './style'
type Props = {
@ -29,7 +29,6 @@ type Props = {
setActiveScreen: Function,
classes: Object,
safeAddress: string,
etherScanLink: string,
safeName: string,
ethBalance: string,
tx: Object,
@ -38,17 +37,11 @@ type Props = {
closeSnackbar: Function,
}
const openIconStyle = {
height: '16px',
color: secondary,
}
const ReviewCustomTx = ({
onClose,
setActiveScreen,
classes,
safeAddress,
etherScanLink,
safeName,
ethBalance,
tx,
@ -56,10 +49,33 @@ const ReviewCustomTx = ({
enqueueSnackbar,
closeSnackbar,
}: Props) => {
const [gasCosts, setGasCosts] = useState<string>('< 0.001')
useEffect(() => {
let isCurrent = true
const estimateGas = async () => {
const web3 = getWeb3()
const { fromWei, toBN } = web3.utils
const estimatedGasCosts = await estimateTxGasCosts(safeAddress, tx.recipientAddress, tx.data.trim())
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
const formattedGasCosts = formatAmount(gasCostsAsEth)
if (isCurrent) {
setGasCosts(formattedGasCosts)
}
}
estimateGas()
return () => {
isCurrent = false
}
}, [])
const submitTx = async () => {
const web3 = getWeb3()
const txRecipient = tx.recipientAddress
const txData = tx.data
const txData = tx.data.trim()
const txValue = tx.value ? web3.utils.toWei(tx.value, 'ether') : 0
createTransaction(
@ -87,12 +103,7 @@ const ReviewCustomTx = ({
</Row>
<Hairline />
<Block className={classes.container}>
<SafeInfo
safeAddress={safeAddress}
etherScanLink={etherScanLink}
safeName={safeName}
ethBalance={ethBalance}
/>
<SafeInfo safeAddress={safeAddress} safeName={safeName} ethBalance={ethBalance} />
<Row margin="md">
<Col xs={1}>
<img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} />
@ -111,12 +122,13 @@ const ReviewCustomTx = ({
<Identicon address={tx.recipientAddress} diameter={32} />
</Col>
<Col xs={11} layout="column">
<Paragraph weight="bolder" onClick={copyToClipboard} noMargin>
{tx.recipientAddress}
<Link to={etherScanLink} target="_blank">
<OpenInNew style={openIconStyle} />
</Link>
</Paragraph>
<Block justify="left">
<Paragraph weight="bolder" className={classes.address} noMargin>
{tx.recipientAddress}
</Paragraph>
<CopyBtn content={tx.recipientAddress} />
<EtherscanBtn type="address" value={tx.recipientAddress} />
</Block>
</Col>
</Row>
<Row margin="xs">
@ -125,7 +137,7 @@ const ReviewCustomTx = ({
</Paragraph>
</Row>
<Row margin="md" align="center">
<Img src={getEthAsToken().logoUri} height={28} alt="Ether" onError={setImageToPlaceholder} />
<Img src={getEthAsToken('0').logoUri} height={28} alt="Ether" onError={setImageToPlaceholder} />
<Paragraph size="md" noMargin className={classes.value}>
{tx.value || 0}
{' ETH'}
@ -143,6 +155,11 @@ const ReviewCustomTx = ({
</Row>
</Col>
</Row>
<Row>
<Paragraph>
{`You're about to create a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCosts} (fee price) ETH in this wallet to fund this confirmation.`}
</Paragraph>
</Row>
</Block>
<Hairline />
<Row align="center" className={classes.buttonRow}>
@ -165,5 +182,4 @@ const ReviewCustomTx = ({
)
}
export default withStyles(styles)(withSnackbar(ReviewCustomTx))

View File

@ -1,30 +1,32 @@
// @flow
import React from 'react'
import React, { useEffect, useState } from 'react'
import { List } from 'immutable'
import { BigNumber } from 'bignumber.js'
import OpenInNew from '@material-ui/icons/OpenInNew'
import { withStyles } from '@material-ui/core/styles'
import Close from '@material-ui/icons/Close'
import IconButton from '@material-ui/core/IconButton'
import { withSnackbar } from 'notistack'
import Paragraph from '~/components/layout/Paragraph'
import Row from '~/components/layout/Row'
import Link from '~/components/layout/Link'
import Col from '~/components/layout/Col'
import Button from '~/components/layout/Button'
import Img from '~/components/layout/Img'
import Block from '~/components/layout/Block'
import EtherscanBtn from '~/components/EtherscanBtn'
import CopyBtn from '~/components/CopyBtn'
import Identicon from '~/components/Identicon'
import { copyToClipboard } from '~/utils/clipboard'
import Hairline from '~/components/layout/Hairline'
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
import { setImageToPlaceholder } from '~/routes/safe/components/Balances/utils'
import { getHumanFriendlyToken } from '~/logic/tokens/store/actions/fetchTokens'
import { estimateTxGasCosts } from '~/logic/safe/transactions/gasNew'
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
import { type Token } from '~/logic/tokens/store/model/token'
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
import { getWeb3 } from '~/logic/wallets/getWeb3'
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
import { ETH_ADDRESS } from '~/logic/tokens/utils/tokenHelpers'
import ArrowDown from '../assets/arrow-down.svg'
import { secondary } from '~/theme/variables'
import { isEther } from '~/logic/tokens/utils/tokenHelpers'
import { styles } from './style'
type Props = {
@ -32,43 +34,70 @@ type Props = {
setActiveScreen: Function,
classes: Object,
safeAddress: string,
etherScanLink: string,
safeName: string,
ethBalance: string,
tx: Object,
tokens: List<Token>,
createTransaction: Function,
enqueueSnackbar: Function,
closeSnackbar: Function,
}
const openIconStyle = {
height: '16px',
color: secondary,
}
const ReviewTx = ({
onClose,
setActiveScreen,
classes,
safeAddress,
etherScanLink,
safeName,
ethBalance,
tx,
tokens,
createTransaction,
enqueueSnackbar,
closeSnackbar,
}: Props) => {
const [gasCosts, setGasCosts] = useState<string>('< 0.001')
const txToken = tokens.find((token) => token.address === tx.token)
const isSendingETH = txToken.address === ETH_ADDRESS
const txRecipient = isSendingETH ? tx.recipientAddress : txToken.address
useEffect(() => {
let isCurrent = true
const estimateGas = async () => {
const web3 = getWeb3()
const { fromWei, toBN } = web3.utils
let txData = EMPTY_DATA
if (!isSendingETH) {
const StandardToken = await getHumanFriendlyToken()
const tokenInstance = await StandardToken.at(txToken.address)
txData = tokenInstance.contract.methods.transfer(tx.recipientAddress, 0).encodeABI()
}
const estimatedGasCosts = await estimateTxGasCosts(safeAddress, txRecipient, txData)
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
const formattedGasCosts = formatAmount(gasCostsAsEth)
if (isCurrent) {
setGasCosts(formattedGasCosts)
}
}
estimateGas()
return () => {
isCurrent = false
}
}, [])
const submitTx = async () => {
const web3 = getWeb3()
const isSendingETH = isEther(tx.token.symbol)
const txRecipient = isSendingETH ? tx.recipientAddress : tx.token.address
let txData = EMPTY_DATA
let txAmount = web3.utils.toWei(tx.amount, 'ether')
if (!isSendingETH) {
const HumanFriendlyToken = await getHumanFriendlyToken()
const tokenInstance = await HumanFriendlyToken.at(tx.token.address)
const tokenInstance = await HumanFriendlyToken.at(txToken.address)
const decimals = await tokenInstance.decimals()
txAmount = new BigNumber(tx.amount).times(10 ** decimals.toNumber()).toString()
@ -104,12 +133,7 @@ const ReviewTx = ({
</Row>
<Hairline />
<Block className={classes.container}>
<SafeInfo
safeAddress={safeAddress}
etherScanLink={etherScanLink}
safeName={safeName}
ethBalance={ethBalance}
/>
<SafeInfo safeAddress={safeAddress} safeName={safeName} ethBalance={ethBalance} />
<Row margin="md">
<Col xs={1}>
<img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} />
@ -128,12 +152,13 @@ const ReviewTx = ({
<Identicon address={tx.recipientAddress} diameter={32} />
</Col>
<Col xs={11} layout="column">
<Paragraph weight="bolder" onClick={copyToClipboard} noMargin>
{tx.recipientAddress}
<Link to={etherScanLink} target="_blank">
<OpenInNew style={openIconStyle} />
</Link>
</Paragraph>
<Block justify="left">
<Paragraph weight="bolder" className={classes.address} noMargin>
{tx.recipientAddress}
</Paragraph>
<CopyBtn content={tx.recipientAddress} />
<EtherscanBtn type="address" value={tx.recipientAddress} />
</Block>
</Col>
</Row>
<Row margin="xs">
@ -142,11 +167,16 @@ const ReviewTx = ({
</Paragraph>
</Row>
<Row margin="md" align="center">
<Img src={tx.token.logoUri} height={28} alt={tx.token.name} onError={setImageToPlaceholder} />
<Img src={txToken.logoUri} height={28} alt={txToken.name} onError={setImageToPlaceholder} />
<Paragraph size="md" noMargin className={classes.amount}>
{tx.amount}
{' '}
{tx.token.symbol}
{txToken.symbol}
</Paragraph>
</Row>
<Row>
<Paragraph>
{`You're about to create a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCosts} (fee price) ETH in this wallet to fund this confirmation.`}
</Paragraph>
</Row>
</Block>

View File

@ -29,6 +29,9 @@ export const styles = () => ({
amount: {
marginLeft: sm,
},
address: {
marginRight: sm,
},
buttonRow: {
height: '84px',
justifyContent: 'center',

View File

@ -1,5 +1,5 @@
// @flow
import React from 'react'
import React, { useState } from 'react'
import { withStyles } from '@material-ui/core/styles'
import Close from '@material-ui/icons/Close'
import InputAdornment from '@material-ui/core/InputAdornment'
@ -10,19 +10,19 @@ import GnoForm from '~/components/forms/GnoForm'
import AddressInput from '~/components/forms/AddressInput'
import Col from '~/components/layout/Col'
import Button from '~/components/layout/Button'
import ScanQRModal from '~/components/ScanQRModal'
import Block from '~/components/layout/Block'
import Img from '~/components/layout/Img'
import Hairline from '~/components/layout/Hairline'
import ButtonLink from '~/components/layout/ButtonLink'
import Field from '~/components/forms/Field'
import TextField from '~/components/forms/TextField'
import TextareaField from '~/components/forms/TextareaField'
import {
composeValidators,
mustBeFloat,
maxValue,
mustBeEthereumContractAddress,
composeValidators, mustBeFloat, maxValue, mustBeEthereumContractAddress,
} from '~/components/forms/validator'
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
import QRIcon from '~/assets/icons/qrcode.svg'
import ArrowDown from '../assets/arrow-down.svg'
import { styles } from './style'
@ -30,7 +30,6 @@ type Props = {
onClose: () => void,
classes: Object,
safeAddress: string,
etherScanLink: string,
safeName: string,
ethBalance: string,
onSubmit: Function,
@ -41,18 +40,26 @@ const SendCustomTx = ({
classes,
onClose,
safeAddress,
etherScanLink,
safeName,
ethBalance,
onSubmit,
initialValues,
}: Props) => {
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
const handleSubmit = (values: Object) => {
if (values.data || values.value) {
onSubmit(values)
}
}
const openQrModal = () => {
setQrModalOpen(true)
}
const closeQrModal = () => {
setQrModalOpen(false)
}
const formMutators = {
setMax: (args, state, utils) => {
utils.changeValue(state, 'value', () => ethBalance)
@ -78,15 +85,21 @@ const SendCustomTx = ({
{(...args) => {
const mutators = args[3]
const handleScan = (value) => {
let scannedAddress = value
if (scannedAddress.startsWith('ethereum:')) {
scannedAddress = scannedAddress.replace('ethereum:', '')
}
mutators.setRecipient(scannedAddress)
closeQrModal()
}
return (
<>
<Block className={classes.formContainer}>
<SafeInfo
safeAddress={safeAddress}
etherScanLink={etherScanLink}
safeName={safeName}
ethBalance={ethBalance}
/>
<SafeInfo safeAddress={safeAddress} safeName={safeName} ethBalance={ethBalance} />
<Row margin="md">
<Col xs={1}>
<img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} />
@ -96,7 +109,7 @@ const SendCustomTx = ({
</Col>
</Row>
<Row margin="md">
<Col xs={12}>
<Col xs={11}>
<AddressInput
name="recipientAddress"
component={TextField}
@ -107,6 +120,18 @@ const SendCustomTx = ({
validators={[mustBeEthereumContractAddress]}
/>
</Col>
<Col xs={1} center="xs" middle="xs" className={classes}>
<Img
src={QRIcon}
className={classes.qrCodeBtn}
role="button"
height={20}
alt="Scan QR"
onClick={() => {
openQrModal()
}}
/>
</Col>
</Row>
<Row margin="xs">
<Col between="lg">
@ -124,10 +149,7 @@ const SendCustomTx = ({
name="value"
component={TextField}
type="text"
validate={composeValidators(
mustBeFloat,
maxValue(ethBalance),
)}
validate={composeValidators(mustBeFloat, maxValue(ethBalance))}
placeholder="Value*"
text="Value*"
className={classes.addressInput}
@ -164,6 +186,7 @@ const SendCustomTx = ({
Review
</Button>
</Row>
{qrModalOpen && <ScanQRModal isOpen={qrModalOpen} onScan={handleScan} onClose={closeQrModal} />}
</>
)
}}

View File

@ -21,6 +21,9 @@ export const styles = () => ({
height: '35px',
width: '35px',
},
qrCodeBtn: {
cursor: 'pointer',
},
formContainer: {
padding: `${md} ${lg}`,
},

View File

@ -1,5 +1,5 @@
// @flow
import React, { useEffect, useState } from 'react'
import React from 'react'
import { List } from 'immutable'
import { withStyles } from '@material-ui/core/styles'
import MenuItem from '@material-ui/core/MenuItem'
@ -22,63 +22,57 @@ type SelectFieldProps = {
}
type SelectedTokenProps = {
token?: Token,
tokenAddress?: string,
classes: Object,
tokens: List<Token>,
}
const SelectedToken = ({ token, classes }: SelectedTokenProps) => (
<MenuItem className={classes.container}>
{token ? (
<>
<ListItemIcon className={classes.tokenImage}>
<Img src={token.logoUri} height={28} alt={token.name} onError={setImageToPlaceholder} />
</ListItemIcon>
<ListItemText
className={classes.tokenData}
primary={token.name}
secondary={`${formatAmount(token.balance)} ${token.symbol}`}
/>
</>
) : (
<Paragraph color="disabled" size="lg" weight="light" style={{ opacity: 0.5 }}>
Select an asset*
</Paragraph>
)}
</MenuItem>
)
const SelectedTokenStyled = withStyles(selectedTokenStyles)(SelectedToken)
type InitialTokenType = Token | string
const TokenSelectField = ({ tokens, classes, initialValue }: SelectFieldProps) => {
const [initialToken, setInitialToken] = useState<InitialTokenType>('')
useEffect(() => {
const selectedToken = tokens.find((token) => token.name === initialValue)
setInitialToken(selectedToken || '')
}, [initialValue])
const SelectedToken = ({ tokenAddress, tokens, classes }: SelectedTokenProps) => {
const token = tokens.find(({ address }) => address === tokenAddress)
return (
<Field
name="token"
component={SelectField}
classes={{ selectMenu: classes.selectMenu }}
validate={required}
renderValue={(token) => <SelectedTokenStyled token={token} />}
initialValue={initialToken}
displayEmpty
>
{tokens.map((token) => (
<MenuItem key={token.address} value={token}>
<ListItemIcon>
<MenuItem className={classes.container}>
{token ? (
<>
<ListItemIcon className={classes.tokenImage}>
<Img src={token.logoUri} height={28} alt={token.name} onError={setImageToPlaceholder} />
</ListItemIcon>
<ListItemText primary={token.name} secondary={`${formatAmount(token.balance)} ${token.symbol}`} />
</MenuItem>
))}
</Field>
<ListItemText
className={classes.tokenData}
primary={token.name}
secondary={`${formatAmount(token.balance)} ${token.symbol}`}
/>
</>
) : (
<Paragraph color="disabled" size="lg" weight="light" style={{ opacity: 0.5 }}>
Select an asset*
</Paragraph>
)}
</MenuItem>
)
}
const SelectedTokenStyled = withStyles(selectedTokenStyles)(SelectedToken)
const TokenSelectField = ({ tokens, classes, initialValue }: SelectFieldProps) => (
<Field
name="token"
component={SelectField}
classes={{ selectMenu: classes.selectMenu }}
validate={required}
renderValue={(tokenAddress) => <SelectedTokenStyled tokenAddress={tokenAddress} tokens={tokens} />}
initialValue={initialValue}
displayEmpty
>
{tokens.map((token) => (
<MenuItem key={token.address} value={token.address}>
<ListItemIcon>
<Img src={token.logoUri} height={28} alt={token.name} onError={setImageToPlaceholder} />
</ListItemIcon>
<ListItemText primary={token.name} secondary={`${formatAmount(token.balance)} ${token.symbol}`} />
</MenuItem>
))}
</Field>
)
export default withStyles(selectStyles)(TokenSelectField)

View File

@ -1,5 +1,5 @@
// @flow
import React from 'react'
import React, { useState } from 'react'
import { List } from 'immutable'
import { withStyles } from '@material-ui/core/styles'
import { OnChange } from 'react-final-form-listeners'
@ -13,6 +13,7 @@ import AddressInput from '~/components/forms/AddressInput'
import Col from '~/components/layout/Col'
import Button from '~/components/layout/Button'
import Block from '~/components/layout/Block'
import Img from '~/components/layout/Img'
import Hairline from '~/components/layout/Hairline'
import ButtonLink from '~/components/layout/ButtonLink'
import Field from '~/components/forms/Field'
@ -23,14 +24,15 @@ import {
} from '~/components/forms/validator'
import TokenSelectField from '~/routes/safe/components/Balances/SendModal/screens/SendFunds/TokenSelectField'
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
import ScanQRModal from '~/components/ScanQRModal'
import ArrowDown from '../assets/arrow-down.svg'
import QRIcon from '~/assets/icons/qrcode.svg'
import { styles } from './style'
type Props = {
onClose: () => void,
classes: Object,
safeAddress: string,
etherScanLink: string,
safeName: string,
ethBalance: string,
selectedToken: string,
@ -39,11 +41,22 @@ type Props = {
initialValues: Object,
}
const formMutators = {
setMax: (args, state, utils) => {
utils.changeValue(state, 'amount', () => args[0])
},
onTokenChange: (args, state, utils) => {
utils.changeValue(state, 'amount', () => '')
},
setRecipient: (args, state, utils) => {
utils.changeValue(state, 'recipientAddress', () => args[0])
},
}
const SendFunds = ({
classes,
onClose,
safeAddress,
etherScanLink,
safeName,
ethBalance,
tokens,
@ -51,22 +64,18 @@ const SendFunds = ({
initialValues,
onSubmit,
}: Props) => {
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
const handleSubmit = (values) => {
onSubmit(values)
}
const formMutators = {
setMax: (args, state, utils) => {
const { token } = state.formState.values
const openQrModal = () => {
setQrModalOpen(true)
}
utils.changeValue(state, 'amount', () => token && token.balance)
},
onTokenChange: (args, state, utils) => {
utils.changeValue(state, 'amount', () => '')
},
setRecipient: (args, state, utils) => {
utils.changeValue(state, 'recipientAddress', () => args[0])
},
const closeQrModal = () => {
setQrModalOpen(false)
}
return (
@ -85,16 +94,24 @@ const SendFunds = ({
{(...args) => {
const formState = args[2]
const mutators = args[3]
const { token } = formState.values
const { token: tokenAddress } = formState.values
const selectedTokenRecord = tokens.find((token) => token.address === tokenAddress)
const handleScan = (value) => {
let scannedAddress = value
if (scannedAddress.startsWith('ethereum:')) {
scannedAddress = scannedAddress.replace('ethereum:', '')
}
mutators.setRecipient(scannedAddress)
closeQrModal()
}
return (
<>
<Block className={classes.formContainer}>
<SafeInfo
safeAddress={safeAddress}
etherScanLink={etherScanLink}
safeName={safeName}
ethBalance={ethBalance}
/>
<SafeInfo safeAddress={safeAddress} safeName={safeName} ethBalance={ethBalance} />
<Row margin="md">
<Col xs={1}>
<img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} />
@ -104,7 +121,7 @@ const SendFunds = ({
</Col>
</Row>
<Row margin="md">
<Col xs={12}>
<Col xs={11}>
<AddressInput
name="recipientAddress"
component={TextField}
@ -114,6 +131,18 @@ const SendFunds = ({
fieldMutator={mutators.setRecipient}
/>
</Col>
<Col xs={1} center="xs" middle="xs" className={classes}>
<Img
src={QRIcon}
className={classes.qrCodeBtn}
role="button"
height={20}
alt="Scan QR"
onClick={() => {
openQrModal()
}}
/>
</Col>
</Row>
<Row margin="sm">
<Col>
@ -125,7 +154,7 @@ const SendFunds = ({
<Paragraph size="md" color="disabled" style={{ letterSpacing: '-0.5px' }} noMargin>
Amount
</Paragraph>
<ButtonLink weight="bold" onClick={mutators.setMax}>
<ButtonLink weight="bold" onClick={() => mutators.setMax(selectedTokenRecord.balance)}>
Send max
</ButtonLink>
</Col>
@ -140,14 +169,14 @@ const SendFunds = ({
required,
mustBeFloat,
greaterThan(0),
maxValue(token && token.balance),
maxValue(selectedTokenRecord && selectedTokenRecord.balance),
)}
placeholder="Amount*"
text="Amount*"
className={classes.addressInput}
inputAdornment={
token && {
endAdornment: <InputAdornment position="end">{token.symbol}</InputAdornment>,
selectedTokenRecord && {
endAdornment: <InputAdornment position="end">{selectedTokenRecord.symbol}</InputAdornment>,
}
}
/>
@ -175,6 +204,7 @@ const SendFunds = ({
Review
</Button>
</Row>
{qrModalOpen && <ScanQRModal isOpen={qrModalOpen} onScan={handleScan} onClose={closeQrModal} />}
</>
)
}}

View File

@ -21,6 +21,9 @@ export const styles = () => ({
height: '35px',
width: '35px',
},
qrCodeBtn: {
cursor: 'pointer',
},
formContainer: {
padding: `${md} ${lg}`,
},

View File

@ -18,7 +18,7 @@ export type BalanceRow = SortRow<BalanceData>
export const getBalanceData = (activeTokens: List<Token>): List<BalanceRow> => {
const rows = activeTokens.map((token: Token) => ({
[BALANCE_TABLE_ASSET_ID]: { name: token.name, logoUri: token.logoUri },
[BALANCE_TABLE_ASSET_ID]: { name: token.name, logoUri: token.logoUri, address: token.address },
[buildOrderFieldFrom(BALANCE_TABLE_ASSET_ID)]: token.name,
[BALANCE_TABLE_BALANCE_ID]: `${formatAmount(token.balance)} ${token.symbol}`,
[buildOrderFieldFrom(BALANCE_TABLE_BALANCE_ID)]: Number(token.balance),

View File

@ -43,7 +43,6 @@ type Props = {
activeTokens: List<Token>,
safeAddress: string,
safeName: string,
etherScanLink: string,
ethBalance: string,
createTransaction: Function,
}
@ -72,11 +71,11 @@ class Balances extends React.Component<Props, State> {
this.setState(() => ({ [`show${action}`]: false }))
}
showSendFunds = (token: Token) => {
showSendFunds = (tokenAddress: string) => {
this.setState({
sendFunds: {
isOpen: true,
selectedToken: token,
selectedToken: tokenAddress,
},
})
}
@ -107,7 +106,6 @@ class Balances extends React.Component<Props, State> {
safeAddress,
activeTokens,
safeName,
etherScanLink,
ethBalance,
createTransaction,
} = this.props
@ -176,7 +174,7 @@ class Balances extends React.Component<Props, State> {
size="small"
color="primary"
className={classes.send}
onClick={() => this.showSendFunds(row.asset.name)}
onClick={() => this.showSendFunds(row.asset.address)}
testId="balance-send-btn"
>
<CallMade alt="Send Transaction" className={classNames(classes.leftIcon, classes.iconSmall)} />
@ -201,7 +199,6 @@ class Balances extends React.Component<Props, State> {
<SendModal
onClose={this.hideSendFunds}
isOpen={sendFunds.isOpen}
etherScanLink={etherScanLink}
safeAddress={safeAddress}
safeName={safeName}
ethBalance={ethBalance}

View File

@ -1,6 +1,9 @@
// @flow
import * as React from 'react'
import classNames from 'classnames/bind'
import {
Switch, Redirect, Route, withRouter,
} from 'react-router-dom'
import Tabs from '@material-ui/core/Tabs'
import Tab from '@material-ui/core/Tab'
import CallMade from '@material-ui/icons/CallMade'
@ -22,7 +25,6 @@ import NoSafe from '~/components/NoSafe'
import { type SelectorProps } from '~/routes/safe/container/selector'
import { getEtherScanLink } from '~/logic/wallets/getWeb3'
import { border } from '~/theme/variables'
import { copyToClipboard } from '~/utils/clipboard'
import { type Actions } from '../container/actions'
import Balances from './Balances'
import Transactions from './Transactions'
@ -34,10 +36,6 @@ export const SETTINGS_TAB_BTN_TEST_ID = 'settings-tab-btn'
export const TRANSACTIONS_TAB_BTN_TEST_ID = 'transactions-tab-btn'
export const SAFE_VIEW_NAME_HEADING_TEST_ID = 'safe-name-heading'
type State = {
tabIndex: number,
}
type Props = SelectorProps &
Actions & {
classes: Object,
@ -48,176 +46,183 @@ type Props = SelectorProps &
onHide: Function,
showSendFunds: Function,
hideSendFunds: Function,
match: Object,
location: Object,
history: Object,
}
class Layout extends React.Component<Props, State> {
constructor(props) {
super(props)
this.state = {
tabIndex: 0,
}
const Layout = (props: Props) => {
const {
safe,
provider,
network,
classes,
granted,
tokens,
activeTokens,
createTransaction,
processTransaction,
fetchTransactions,
updateSafe,
transactions,
userAddress,
sendFunds,
showReceive,
onShow,
onHide,
showSendFunds,
hideSendFunds,
match,
location,
} = props
const handleCallToRouter = (_, value) => {
const { history } = props
history.push(value)
}
handleChange = (event, tabIndex) => {
this.setState({ tabIndex })
if (!safe) {
return <NoSafe provider={provider} text="Safe not found" />
}
copyAddress = () => {
const { safe } = this.props
const { address, ethBalance, name } = safe
const etherScanLink = getEtherScanLink('address', address)
if (safe.address) {
copyToClipboard(safe.address)
}
}
render() {
const {
safe,
provider,
network,
classes,
granted,
tokens,
activeTokens,
createTransaction,
processTransaction,
fetchTransactions,
updateSafe,
transactions,
userAddress,
sendFunds,
showReceive,
onShow,
onHide,
showSendFunds,
hideSendFunds,
} = this.props
const { tabIndex } = this.state
if (!safe) {
return <NoSafe provider={provider} text="Safe not found" />
}
const { address, ethBalance, name } = safe
const etherScanLink = getEtherScanLink('address', address)
return (
<>
<Block className={classes.container} margin="xl">
<Identicon address={address} diameter={50} />
<Block className={classes.name}>
<Row>
<Heading tag="h2" color="primary" testId={SAFE_VIEW_NAME_HEADING_TEST_ID}>
{name}
</Heading>
{!granted && <Block className={classes.readonly}>Read Only</Block>}
</Row>
<Block justify="center" className={classes.user}>
<Paragraph size="md" color="disabled" className={classes.address} onClick={this.copyAddress} title="Click to copy" noMargin>
{address}
</Paragraph>
<CopyBtn content={address} />
<EtherscanBtn type="address" value={address} />
</Block>
</Block>
<Block className={classes.balance}>
<Row align="end" className={classes.actions}>
<Button
variant="contained"
size="small"
color="primary"
className={classes.send}
onClick={() => showSendFunds('Ether')}
disabled={!granted}
>
<CallMade alt="Send Transaction" className={classNames(classes.leftIcon, classes.iconSmall)} />
Send
</Button>
<Button
variant="contained"
size="small"
color="primary"
className={classes.receive}
onClick={onShow('Receive')}
>
<CallReceived alt="Receive Transaction" className={classNames(classes.leftIcon, classes.iconSmall)} />
Receive
</Button>
</Row>
return (
<>
<Block className={classes.container} margin="xl">
<Identicon address={address} diameter={50} />
<Block className={classes.name}>
<Row>
<Heading tag="h2" color="primary" testId={SAFE_VIEW_NAME_HEADING_TEST_ID}>
{name}
</Heading>
{!granted && <Block className={classes.readonly}>Read Only</Block>}
</Row>
<Block justify="center" className={classes.user}>
<Paragraph size="md" className={classes.address} color="disabled" noMargin>
{address}
</Paragraph>
<CopyBtn content={address} />
<EtherscanBtn type="address" value={address} />
</Block>
</Block>
<Row>
<Tabs value={tabIndex} onChange={this.handleChange} indicatorColor="secondary" textColor="secondary">
<Tab label="Balances" data-testid={BALANCES_TAB_BTN_TEST_ID} />
<Tab label="Transactions" data-testid={TRANSACTIONS_TAB_BTN_TEST_ID} />
<Tab label="Settings" data-testid={SETTINGS_TAB_BTN_TEST_ID} />
</Tabs>
</Row>
<Hairline color={border} style={{ marginTop: '-2px' }} />
{tabIndex === 0 && (
<Balances
ethBalance={ethBalance}
tokens={tokens}
activeTokens={activeTokens}
granted={granted}
safeAddress={address}
safeName={name}
etherScanLink={etherScanLink}
createTransaction={createTransaction}
/>
)}
{tabIndex === 1 && (
<Transactions
threshold={safe.threshold}
owners={safe.owners}
transactions={transactions}
fetchTransactions={fetchTransactions}
safeAddress={address}
userAddress={userAddress}
currentNetwork={network}
granted={granted}
createTransaction={createTransaction}
processTransaction={processTransaction}
/>
)}
{tabIndex === 2 && (
<Settings
granted={granted}
safeAddress={address}
safeName={name}
etherScanLink={etherScanLink}
updateSafe={updateSafe}
threshold={safe.threshold}
owners={safe.owners}
network={network}
userAddress={userAddress}
createTransaction={createTransaction}
/>
)}
<SendModal
onClose={hideSendFunds}
isOpen={sendFunds.isOpen}
etherScanLink={etherScanLink}
safeAddress={address}
safeName={name}
ethBalance={ethBalance}
tokens={activeTokens}
selectedToken={sendFunds.selectedToken}
createTransaction={createTransaction}
activeScreenType="chooseTxType"
/>
<Modal
title="Receive Tokens"
description="Receive Tokens Form"
handleClose={onHide('Receive')}
open={showReceive}
paperClassName={classes.receiveModal}
<Block className={classes.balance}>
<Row align="end" className={classes.actions}>
<Button
variant="contained"
size="small"
color="primary"
className={classes.send}
onClick={() => showSendFunds('Ether')}
disabled={!granted}
>
<CallMade alt="Send Transaction" className={classNames(classes.leftIcon, classes.iconSmall)} />
Send
</Button>
<Button
variant="contained"
size="small"
color="primary"
className={classes.receive}
onClick={onShow('Receive')}
>
<CallReceived alt="Receive Transaction" className={classNames(classes.leftIcon, classes.iconSmall)} />
Receive
</Button>
</Row>
</Block>
</Block>
<Row>
<Tabs
value={location.pathname}
onChange={handleCallToRouter}
indicatorColor="secondary"
textColor="secondary"
>
<Receive safeName={name} safeAddress={address} etherScanLink={etherScanLink} onClose={onHide('Receive')} />
</Modal>
</>
)
}
<Tab label="Balances" value={`${match.url}/balances`} data-testid={BALANCES_TAB_BTN_TEST_ID} />
<Tab label="Transactions" value={`${match.url}/transactions`} data-testid={TRANSACTIONS_TAB_BTN_TEST_ID} />
<Tab label="Settings" value={`${match.url}/settings`} data-testid={SETTINGS_TAB_BTN_TEST_ID} />
</Tabs>
</Row>
<Hairline color={border} style={{ marginTop: '-2px' }} />
<Switch>
<Route
exact
path={`${match.path}/balances`}
render={() => (
<Balances
ethBalance={ethBalance}
tokens={tokens}
activeTokens={activeTokens}
granted={granted}
safeAddress={address}
safeName={name}
createTransaction={createTransaction}
/>
)}
/>
<Route
exact
path={`${match.path}/transactions`}
render={() => (
<Transactions
threshold={safe.threshold}
owners={safe.owners}
transactions={transactions}
fetchTransactions={fetchTransactions}
safeAddress={address}
userAddress={userAddress}
currentNetwork={network}
granted={granted}
createTransaction={createTransaction}
processTransaction={processTransaction}
/>
)}
/>
<Route
exact
path={`${match.path}/settings`}
render={() => (
<Settings
granted={granted}
safeAddress={address}
safeName={name}
etherScanLink={etherScanLink}
updateSafe={updateSafe}
threshold={safe.threshold}
owners={safe.owners}
network={network}
userAddress={userAddress}
createTransaction={createTransaction}
/>
)}
/>
<Redirect to={`${match.path}/balances`} />
</Switch>
<SendModal
onClose={hideSendFunds}
isOpen={sendFunds.isOpen}
safeAddress={address}
safeName={name}
ethBalance={ethBalance}
tokens={activeTokens}
selectedToken={sendFunds.selectedToken}
createTransaction={createTransaction}
activeScreenType="chooseTxType"
/>
<Modal
title="Receive Tokens"
description="Receive Tokens Form"
handleClose={onHide('Receive')}
open={showReceive}
paperClassName={classes.receiveModal}
>
<Receive safeName={name} safeAddress={address} onClose={onHide('Receive')} />
</Modal>
</>
)
}
export default withStyles(styles)(Layout)
export default withStyles(styles)(withRouter(Layout))

View File

@ -12,7 +12,7 @@ import GnoForm from '~/components/forms/GnoForm'
import Row from '~/components/layout/Row'
import Paragraph from '~/components/layout/Paragraph'
import Button from '~/components/layout/Button'
import { getNofiticationsFromTxType, showSnackbar } from '~/logic/notifications'
import { getNotificationsFromTxType, showSnackbar } from '~/logic/notifications'
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
import { styles } from './style'
@ -36,7 +36,7 @@ const ChangeSafeName = (props: Props) => {
const handleSubmit = (values) => {
updateSafe({ address: safeAddress, name: values.safeName })
const notification = getNofiticationsFromTxType(TX_NOTIFICATION_TYPES.SAFE_NAME_CHANGE_TX)
const notification = getNotificationsFromTxType(TX_NOTIFICATION_TYPES.SAFE_NAME_CHANGE_TX)
showSnackbar(notification.afterExecution, enqueueSnackbar, closeSnackbar)
}

View File

@ -16,6 +16,7 @@ const styles = () => ({
width: '775px',
minHeight: '500px',
position: 'static',
height: 'auto',
},
})
@ -146,6 +147,7 @@ const AddOwner = ({
safeName={safeName}
owners={owners}
values={values}
safeAddress={safeAddress}
onClickBack={onClickBack}
onSubmit={onAddOwner}
/>

View File

@ -1,13 +1,14 @@
// @flow
import React from 'react'
import React, { useState, useEffect } from 'react'
import { List } from 'immutable'
import classNames from 'classnames'
import { withStyles } from '@material-ui/core/styles'
import OpenInNew from '@material-ui/icons/OpenInNew'
import Close from '@material-ui/icons/Close'
import IconButton from '@material-ui/core/IconButton'
import Identicon from '~/components/Identicon'
import Link from '~/components/layout/Link'
import EtherscanBtn from '~/components/EtherscanBtn'
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
import CopyBtn from '~/components/CopyBtn'
import Paragraph from '~/components/layout/Paragraph'
import Row from '~/components/layout/Row'
import Col from '~/components/layout/Col'
@ -15,17 +16,13 @@ import Button from '~/components/layout/Button'
import Block from '~/components/layout/Block'
import Hairline from '~/components/layout/Hairline'
import type { Owner } from '~/routes/safe/store/models/owner'
import { getEtherScanLink } from '~/logic/wallets/getWeb3'
import { secondary } from '~/theme/variables'
import { getWeb3 } from '~/logic/wallets/getWeb3'
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
import { estimateTxGasCosts } from '~/logic/safe/transactions/gasNew'
import { styles } from './style'
export const ADD_OWNER_SUBMIT_BTN_TEST_ID = 'add-owner-submit-btn'
const openIconStyle = {
height: '16px',
color: secondary,
}
type Props = {
onClose: () => void,
classes: Object,
@ -34,11 +31,39 @@ type Props = {
values: Object,
onClickBack: Function,
onSubmit: Function,
safeAddress: string,
}
const ReviewAddOwner = ({
classes, onClose, safeName, owners, values, onClickBack, onSubmit,
classes, onClose, safeName, owners, values, onClickBack, onSubmit, safeAddress,
}: Props) => {
const [gasCosts, setGasCosts] = useState<string>('< 0.001')
useEffect(() => {
let isCurrent = true
const estimateGas = async () => {
const web3 = getWeb3()
const { fromWei, toBN } = web3.utils
const safeInstance = await getGnosisSafeInstanceAt(safeAddress)
const txData = safeInstance.contract.methods
.addOwnerWithThreshold(values.ownerAddress, values.threshold)
.encodeABI()
const estimatedGasCosts = await estimateTxGasCosts(safeAddress, safeAddress, txData)
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
const formattedGasCosts = formatAmount(gasCostsAsEth)
if (isCurrent) {
setGasCosts(formattedGasCosts)
}
}
estimateGas()
return () => {
isCurrent = false
}
}, [])
const handleSubmit = () => {
onSubmit()
}
@ -76,12 +101,7 @@ const ReviewAddOwner = ({
Any transaction requires the confirmation of:
</Paragraph>
<Paragraph size="lg" color="primary" noMargin weight="bolder" className={classes.name}>
{values.threshold}
{' '}
out of
{owners.size + 1}
{' '}
owner(s)
{`${values.threshold} out of ${owners.size + 1} owner(s)`}
</Paragraph>
</Block>
</Block>
@ -89,9 +109,7 @@ const ReviewAddOwner = ({
<Col xs={8} layout="column" className={classes.owners}>
<Row className={classes.ownersTitle}>
<Paragraph size="lg" color="primary" noMargin>
{owners.size + 1}
{' '}
Safe owner(s)
{`${owners.size + 1} Safe owner(s)`}
</Paragraph>
</Row>
<Hairline />
@ -107,12 +125,11 @@ const ReviewAddOwner = ({
{owner.name}
</Paragraph>
<Block justify="center" className={classes.user}>
<Paragraph size="md" color="disabled" noMargin>
<Paragraph size="md" color="disabled" className={classes.address} noMargin>
{owner.address}
</Paragraph>
<Link className={classes.open} to={getEtherScanLink('address', owner.address)} target="_blank">
<OpenInNew style={openIconStyle} />
</Link>
<CopyBtn content={owner.address} />
<EtherscanBtn type="address" value={owner.address} />
</Block>
</Block>
</Col>
@ -136,16 +153,11 @@ const ReviewAddOwner = ({
{values.ownerName}
</Paragraph>
<Block justify="center" className={classes.user}>
<Paragraph size="md" color="disabled" noMargin>
<Paragraph size="md" color="disabled" className={classes.address} noMargin>
{values.ownerAddress}
</Paragraph>
<Link
className={classes.open}
to={getEtherScanLink('address', values.ownerAddress)}
target="_blank"
>
<OpenInNew style={openIconStyle} />
</Link>
<CopyBtn content={values.ownerAddress} />
<EtherscanBtn type="address" value={values.ownerAddress} />
</Block>
</Block>
</Col>
@ -155,6 +167,14 @@ const ReviewAddOwner = ({
</Row>
</Block>
<Hairline />
<Block className={classes.gasCostsContainer}>
<Paragraph>
You&apos;re about to create a transaction and will have to confirm it with your currently connected wallet.
<br />
{`Make sure you have ${gasCosts} (fee price) ETH in this wallet to fund this confirmation.`}
</Paragraph>
</Block>
<Hairline />
<Row align="center" className={classes.buttonRow}>
<Button minWidth={140} minHeight={42} onClick={onClickBack}>
Back

View File

@ -39,7 +39,7 @@ export const styles = () => ({
},
details: {
padding: lg,
borderRight: `solid 1px ${border}`,
borderRight: `solid 2px ${border}`,
height: '100%',
},
owners: {
@ -49,6 +49,9 @@ export const styles = () => ({
ownersTitle: {
padding: lg,
},
address: {
marginRight: sm,
},
owner: {
padding: sm,
alignItems: 'center',
@ -75,4 +78,9 @@ export const styles = () => ({
cursor: 'pointer',
},
},
gasCostsContainer: {
padding: `0 ${lg}`,
textAlign: 'center',
width: '100%',
},
})

View File

@ -3,10 +3,10 @@ import React from 'react'
import { withSnackbar } from 'notistack'
import { withStyles } from '@material-ui/core/styles'
import Close from '@material-ui/icons/Close'
import OpenInNew from '@material-ui/icons/OpenInNew'
import IconButton from '@material-ui/core/IconButton'
import EtherscanBtn from '~/components/EtherscanBtn'
import CopyBtn from '~/components/CopyBtn'
import Row from '~/components/layout/Row'
import Link from '~/components/layout/Link'
import Block from '~/components/layout/Block'
import GnoForm from '~/components/forms/GnoForm'
import Button from '~/components/layout/Button'
@ -16,21 +16,15 @@ import TextField from '~/components/forms/TextField'
import Paragraph from '~/components/layout/Paragraph'
import Identicon from '~/components/Identicon'
import { composeValidators, required, minMaxLength } from '~/components/forms/validator'
import { getNofiticationsFromTxType, showSnackbar } from '~/logic/notifications'
import { getNotificationsFromTxType, showSnackbar } from '~/logic/notifications'
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
import { getEtherScanLink } from '~/logic/wallets/getWeb3'
import Modal from '~/components/Modal'
import { styles } from './style'
import { secondary } from '~/theme/variables'
import { sm } from '~/theme/variables'
export const RENAME_OWNER_INPUT_TEST_ID = 'rename-owner-input'
export const SAVE_OWNER_CHANGES_BTN_TEST_ID = 'save-owner-changes-btn'
const openIconStyle = {
height: '16px',
color: secondary,
}
type Props = {
onClose: () => void,
classes: Object,
@ -57,7 +51,7 @@ const EditOwnerComponent = ({
const handleSubmit = (values) => {
editSafeOwner({ safeAddress, ownerAddress, ownerName: values.ownerName })
const notification = getNofiticationsFromTxType(TX_NOTIFICATION_TYPES.OWNER_NAME_CHANGE_TX)
const notification = getNotificationsFromTxType(TX_NOTIFICATION_TYPES.OWNER_NAME_CHANGE_TX)
showSnackbar(notification.afterExecution, enqueueSnackbar, closeSnackbar)
onClose()
@ -100,12 +94,11 @@ const EditOwnerComponent = ({
<Row>
<Block justify="center" className={classes.user}>
<Identicon address={ownerAddress} diameter={32} />
<Paragraph style={{ marginLeft: 10 }} size="md" color="disabled" noMargin>
<Paragraph style={{ marginLeft: sm, marginRight: sm }} size="md" color="disabled" noMargin>
{ownerAddress}
</Paragraph>
<Link className={classes.open} to={getEtherScanLink('address', ownerAddress)} target="_blank">
<OpenInNew style={openIconStyle} />
</Link>
<CopyBtn content={safeAddress} />
<EtherscanBtn type="address" value={safeAddress} />
</Block>
</Row>
</Block>
@ -114,7 +107,14 @@ const EditOwnerComponent = ({
<Button minWidth={140} minHeight={42} onClick={onClose}>
Cancel
</Button>
<Button type="submit" variant="contained" minWidth={140} minHeight={42} color="primary" testId={SAVE_OWNER_CHANGES_BTN_TEST_ID}>
<Button
type="submit"
variant="contained"
minWidth={140}
minHeight={42}
color="primary"
testId={SAVE_OWNER_CHANGES_BTN_TEST_ID}
>
Save
</Button>
</Row>

View File

@ -16,6 +16,7 @@ const styles = () => ({
width: '775px',
minHeight: '500px',
position: 'static',
height: 'auto',
},
})
@ -168,6 +169,7 @@ const RemoveOwner = ({
ownerName={ownerName}
onClickBack={onClickBack}
onSubmit={onRemoveOwner}
safeAddress={safeAddress}
/>
)}
</>

View File

@ -4,26 +4,19 @@ import classNames from 'classnames/bind'
import { withStyles } from '@material-ui/core/styles'
import Close from '@material-ui/icons/Close'
import IconButton from '@material-ui/core/IconButton'
import OpenInNew from '@material-ui/icons/OpenInNew'
import Paragraph from '~/components/layout/Paragraph'
import Row from '~/components/layout/Row'
import Col from '~/components/layout/Col'
import Button from '~/components/layout/Button'
import Block from '~/components/layout/Block'
import Hairline from '~/components/layout/Hairline'
import Link from '~/components/layout/Link'
import EtherscanBtn from '~/components/EtherscanBtn'
import CopyBtn from '~/components/CopyBtn'
import Identicon from '~/components/Identicon'
import { getEtherScanLink } from '~/logic/wallets/getWeb3'
import { styles } from './style'
import { secondary } from '~/theme/variables'
export const REMOVE_OWNER_MODAL_NEXT_BTN_TEST_ID = 'remove-owner-next-btn'
const openIconStyle = {
height: '16px',
color: secondary,
}
type Props = {
onClose: () => void,
classes: Object,
@ -65,12 +58,11 @@ const CheckOwner = ({
{ownerName}
</Paragraph>
<Block justify="center" className={classes.user}>
<Paragraph size="md" color="disabled" noMargin>
<Paragraph size="md" color="disabled" className={classes.address} noMargin>
{ownerAddress}
</Paragraph>
<Link className={classes.open} to={getEtherScanLink('address', ownerAddress)} target="_blank">
<OpenInNew style={openIconStyle} />
</Link>
<CopyBtn content={ownerAddress} />
<EtherscanBtn type="address" value={ownerAddress} />
</Block>
</Block>
</Col>

View File

@ -19,6 +19,9 @@ export const styles = () => ({
manage: {
fontSize: '24px',
},
address: {
marginRight: sm,
},
closeIcon: {
height: '35px',
width: '35px',

View File

@ -1,13 +1,13 @@
// @flow
import React from 'react'
import React, { useState, useEffect } from 'react'
import { List } from 'immutable'
import classNames from 'classnames'
import { withStyles } from '@material-ui/core/styles'
import OpenInNew from '@material-ui/icons/OpenInNew'
import Close from '@material-ui/icons/Close'
import IconButton from '@material-ui/core/IconButton'
import EtherscanBtn from '~/components/EtherscanBtn'
import CopyBtn from '~/components/CopyBtn'
import Identicon from '~/components/Identicon'
import Link from '~/components/layout/Link'
import Paragraph from '~/components/layout/Paragraph'
import Row from '~/components/layout/Row'
import Col from '~/components/layout/Col'
@ -15,17 +15,14 @@ import Button from '~/components/layout/Button'
import Block from '~/components/layout/Block'
import Hairline from '~/components/layout/Hairline'
import type { Owner } from '~/routes/safe/store/models/owner'
import { getEtherScanLink } from '~/logic/wallets/getWeb3'
import { secondary } from '~/theme/variables'
import { getWeb3 } from '~/logic/wallets/getWeb3'
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
import { getGnosisSafeInstanceAt, SENTINEL_ADDRESS } from '~/logic/contracts/safeContracts'
import { estimateTxGasCosts } from '~/logic/safe/transactions/gasNew'
import { styles } from './style'
export const REMOVE_OWNER_REVIEW_BTN_TEST_ID = 'remove-owner-review-btn'
const openIconStyle = {
height: '16px',
color: secondary,
}
type Props = {
onClose: () => void,
classes: Object,
@ -36,6 +33,7 @@ type Props = {
ownerName: string,
onClickBack: Function,
onSubmit: Function,
safeAddress: string,
}
const ReviewRemoveOwner = ({
@ -48,10 +46,36 @@ const ReviewRemoveOwner = ({
ownerName,
onClickBack,
onSubmit,
safeAddress,
}: Props) => {
const handleSubmit = () => {
onSubmit()
}
const [gasCosts, setGasCosts] = useState<string>('< 0.001')
useEffect(() => {
let isCurrent = true
const estimateGas = async () => {
const web3 = getWeb3()
const { fromWei, toBN } = web3.utils
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
const safeOwners = await gnosisSafe.getOwners()
const index = safeOwners.findIndex((owner) => owner.toLowerCase() === ownerAddress.toLowerCase())
const prevAddress = index === 0 ? SENTINEL_ADDRESS : safeOwners[index - 1]
const txData = gnosisSafe.contract.methods.removeOwner(prevAddress, ownerAddress, values.threshold).encodeABI()
const estimatedGasCosts = await estimateTxGasCosts(safeAddress, safeAddress, txData)
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
const formattedGasCosts = formatAmount(gasCostsAsEth)
if (isCurrent) {
setGasCosts(formattedGasCosts)
}
}
estimateGas()
return () => {
isCurrent = false
}
}, [])
return (
<>
@ -87,12 +111,7 @@ const ReviewRemoveOwner = ({
Any transaction requires the confirmation of:
</Paragraph>
<Paragraph size="lg" color="primary" noMargin weight="bolder" className={classes.name}>
{values.threshold}
{' '}
out of
{owners.size - 1}
{' '}
owner(s)
{`${values.threshold} out of ${owners.size - 1} owner(s)`}
</Paragraph>
</Block>
</Block>
@ -100,9 +119,7 @@ const ReviewRemoveOwner = ({
<Col xs={8} layout="column" className={classes.owners}>
<Row className={classes.ownersTitle}>
<Paragraph size="lg" color="primary" noMargin>
{owners.size - 1}
{' '}
Safe owner(s)
{`${owners.size - 1} Safe owner(s)`}
</Paragraph>
</Row>
<Hairline />
@ -119,16 +136,11 @@ const ReviewRemoveOwner = ({
{owner.name}
</Paragraph>
<Block justify="center" className={classes.user}>
<Paragraph size="md" color="disabled" noMargin>
<Paragraph size="md" color="disabled" className={classes.address} noMargin>
{owner.address}
</Paragraph>
<Link
className={classes.open}
to={getEtherScanLink('address', owner.address)}
target="_blank"
>
<OpenInNew style={openIconStyle} />
</Link>
<CopyBtn content={owner.address} />
<EtherscanBtn type="address" value={owner.address} />
</Block>
</Block>
</Col>
@ -153,16 +165,11 @@ const ReviewRemoveOwner = ({
{ownerName}
</Paragraph>
<Block justify="center" className={classes.user}>
<Paragraph size="md" color="disabled" noMargin>
<Paragraph size="md" color="disabled" className={classes.address} noMargin>
{ownerAddress}
</Paragraph>
<Link
className={classes.open}
to={getEtherScanLink('address', ownerAddress)}
target="_blank"
>
<OpenInNew style={openIconStyle} />
</Link>
<CopyBtn content={ownerAddress} />
<EtherscanBtn type="address" value={ownerAddress} />
</Block>
</Block>
</Col>
@ -172,13 +179,21 @@ const ReviewRemoveOwner = ({
</Row>
</Block>
<Hairline />
<Block className={classes.gasCostsContainer}>
<Paragraph>
You&apos;re about to create a transaction and will have to confirm it with your currently connected wallet.
<br />
{`Make sure you have ${gasCosts} (fee price) ETH in this wallet to fund this confirmation.`}
</Paragraph>
</Block>
<Hairline />
<Row align="center" className={classes.buttonRow}>
<Button minWidth={140} minHeight={42} onClick={onClickBack}>
Back
</Button>
<Button
type="submit"
onClick={handleSubmit}
onClick={onSubmit}
variant="contained"
minHeight={42}
minWidth={140}

View File

@ -49,6 +49,9 @@ export const styles = () => ({
ownersTitle: {
padding: lg,
},
address: {
marginRight: sm,
},
owner: {
padding: sm,
alignItems: 'center',
@ -75,4 +78,9 @@ export const styles = () => ({
cursor: 'pointer',
},
},
gasCostsContainer: {
padding: `0 ${lg}`,
textAlign: 'center',
width: '100%',
},
})

View File

@ -15,6 +15,7 @@ const styles = () => ({
width: '775px',
minHeight: '500px',
position: 'static',
height: 'auto',
},
})
@ -155,6 +156,7 @@ const ReplaceOwner = ({
onClickBack={onClickBack}
onSubmit={onReplaceOwner}
threshold={threshold}
safeAddress={safeAddress}
/>
)}
</>

View File

@ -5,7 +5,8 @@ import { List } from 'immutable'
import { withStyles } from '@material-ui/core/styles'
import Close from '@material-ui/icons/Close'
import IconButton from '@material-ui/core/IconButton'
import OpenInNew from '@material-ui/icons/OpenInNew'
import EtherscanBtn from '~/components/EtherscanBtn'
import CopyBtn from '~/components/CopyBtn'
import Paragraph from '~/components/layout/Paragraph'
import Row from '~/components/layout/Row'
import GnoForm from '~/components/forms/GnoForm'
@ -17,24 +18,16 @@ import Hairline from '~/components/layout/Hairline'
import Field from '~/components/forms/Field'
import TextField from '~/components/forms/TextField'
import Identicon from '~/components/Identicon'
import Link from '~/components/layout/Link'
import { getEtherScanLink } from '~/logic/wallets/getWeb3'
import { type Owner } from '~/routes/safe/store/models/owner'
import {
composeValidators, required, minMaxLength, uniqueAddress,
} from '~/components/forms/validator'
import { styles } from './style'
import { secondary } from '~/theme/variables'
export const REPLACE_OWNER_NAME_INPUT_TEST_ID = 'replace-owner-name-input'
export const REPLACE_OWNER_ADDRESS_INPUT_TEST_ID = 'replace-owner-address-testid'
export const REPLACE_OWNER_NEXT_BTN_TEST_ID = 'replace-owner-next-btn'
const openIconStyle = {
height: '16px',
color: secondary,
}
const formMutators = {
setOwnerAddress: (args, state, utils) => {
utils.changeValue(state, 'ownerAddress', () => args[0])
@ -96,16 +89,11 @@ const OwnerForm = ({
{ownerName}
</Paragraph>
<Block justify="center" className={classes.user}>
<Paragraph size="md" color="disabled" noMargin>
<Paragraph size="md" className={classes.address} color="disabled" noMargin>
{ownerAddress}
</Paragraph>
<Link
className={classes.open}
to={getEtherScanLink('address', ownerAddress)}
target="_blank"
>
<OpenInNew style={openIconStyle} />
</Link>
<CopyBtn content={ownerAddress} />
<EtherscanBtn type="address" value={ownerAddress} />
</Block>
</Block>
</Col>

View File

@ -16,6 +16,9 @@ export const styles = () => ({
marginLeft: '20px',
lineHeight: 'normal',
},
address: {
marginRight: sm,
},
manage: {
fontSize: '24px',
},

View File

@ -1,13 +1,13 @@
// @flow
import React from 'react'
import React, { useState, useEffect } from 'react'
import { List } from 'immutable'
import classNames from 'classnames'
import { withStyles } from '@material-ui/core/styles'
import OpenInNew from '@material-ui/icons/OpenInNew'
import Close from '@material-ui/icons/Close'
import IconButton from '@material-ui/core/IconButton'
import EtherscanBtn from '~/components/EtherscanBtn'
import CopyBtn from '~/components/CopyBtn'
import Identicon from '~/components/Identicon'
import Link from '~/components/layout/Link'
import Paragraph from '~/components/layout/Paragraph'
import Row from '~/components/layout/Row'
import Col from '~/components/layout/Col'
@ -15,17 +15,14 @@ import Button from '~/components/layout/Button'
import Block from '~/components/layout/Block'
import Hairline from '~/components/layout/Hairline'
import type { Owner } from '~/routes/safe/store/models/owner'
import { getEtherScanLink } from '~/logic/wallets/getWeb3'
import { secondary } from '~/theme/variables'
import { getWeb3 } from '~/logic/wallets/getWeb3'
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
import { estimateTxGasCosts } from '~/logic/safe/transactions/gasNew'
import { getGnosisSafeInstanceAt, SENTINEL_ADDRESS } from '~/logic/contracts/safeContracts'
import { styles } from './style'
export const REPLACE_OWNER_SUBMIT_BTN_TEST_ID = 'replace-owner-submit-btn'
const openIconStyle = {
height: '16px',
color: secondary,
}
type Props = {
onClose: () => void,
classes: Object,
@ -37,6 +34,7 @@ type Props = {
onClickBack: Function,
onSubmit: Function,
threshold: string,
safeAddress: string,
}
const ReviewRemoveOwner = ({
@ -47,13 +45,37 @@ const ReviewRemoveOwner = ({
values,
ownerAddress,
ownerName,
safeAddress,
onClickBack,
threshold,
onSubmit,
}: Props) => {
const handleSubmit = () => {
onSubmit()
}
const [gasCosts, setGasCosts] = useState<string>('< 0.001')
useEffect(() => {
let isCurrent = true
const estimateGas = async () => {
const web3 = getWeb3()
const { fromWei, toBN } = web3.utils
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
const safeOwners = await gnosisSafe.getOwners()
const index = safeOwners.findIndex((owner) => owner.toLowerCase() === ownerAddress.toLowerCase())
const prevAddress = index === 0 ? SENTINEL_ADDRESS : safeOwners[index - 1]
const txData = gnosisSafe.contract.methods.swapOwner(prevAddress, ownerAddress, values.ownerAddress).encodeABI()
const estimatedGasCosts = await estimateTxGasCosts(safeAddress, safeAddress, txData)
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
const formattedGasCosts = formatAmount(gasCostsAsEth)
if (isCurrent) {
setGasCosts(formattedGasCosts)
}
}
estimateGas()
return () => {
isCurrent = false
}
}, [])
return (
<>
@ -89,13 +111,7 @@ const ReviewRemoveOwner = ({
Any transaction requires the confirmation of:
</Paragraph>
<Paragraph size="lg" color="primary" noMargin weight="bolder" className={classes.name}>
{threshold}
{' '}
out of
{' '}
{owners.size}
{' '}
owner(s)
{`${threshold} out of ${owners.size} owner(s)`}
</Paragraph>
</Block>
</Block>
@ -103,9 +119,7 @@ const ReviewRemoveOwner = ({
<Col xs={8} layout="column" className={classes.owners}>
<Row className={classes.ownersTitle}>
<Paragraph size="lg" color="primary" noMargin>
{owners.size}
{' '}
Safe owner(s)
{`${owners.size} Safe owner(s)`}
</Paragraph>
</Row>
<Hairline />
@ -122,16 +136,11 @@ const ReviewRemoveOwner = ({
{owner.name}
</Paragraph>
<Block justify="center" className={classes.user}>
<Paragraph size="md" color="disabled" noMargin>
<Paragraph size="md" color="disabled" className={classes.address} noMargin>
{owner.address}
</Paragraph>
<Link
className={classes.open}
to={getEtherScanLink('address', owner.address)}
target="_blank"
>
<OpenInNew style={openIconStyle} />
</Link>
<CopyBtn content={owner.address} />
<EtherscanBtn type="address" value={owner.address} />
</Block>
</Block>
</Col>
@ -156,12 +165,11 @@ const ReviewRemoveOwner = ({
{ownerName}
</Paragraph>
<Block justify="center" className={classes.user}>
<Paragraph size="md" color="disabled" noMargin>
<Paragraph size="md" color="disabled" className={classes.address} noMargin>
{ownerAddress}
</Paragraph>
<Link className={classes.open} to={getEtherScanLink('address', ownerAddress)} target="_blank">
<OpenInNew style={openIconStyle} />
</Link>
<CopyBtn content={ownerAddress} />
<EtherscanBtn type="address" value={ownerAddress} />
</Block>
</Block>
</Col>
@ -182,12 +190,11 @@ const ReviewRemoveOwner = ({
{values.ownerName}
</Paragraph>
<Block justify="center" className={classes.user}>
<Paragraph size="md" color="disabled" noMargin>
<Paragraph size="md" color="disabled" className={classes.address} noMargin>
{values.ownerAddress}
</Paragraph>
<Link className={classes.open} to={getEtherScanLink('address', values.ownerAddress)} target="_blank">
<OpenInNew style={openIconStyle} />
</Link>
<CopyBtn content={values.ownerAddress} />
<EtherscanBtn type="address" value={values.ownerAddress} />
</Block>
</Block>
</Col>
@ -197,13 +204,21 @@ const ReviewRemoveOwner = ({
</Row>
</Block>
<Hairline />
<Block className={classes.gasCostsContainer}>
<Paragraph>
You&apos;re about to create a transaction and will have to confirm it with your currently connected wallet.
<br />
{`Make sure you have ${gasCosts} (fee price) ETH in this wallet to fund this confirmation.`}
</Paragraph>
</Block>
<Hairline />
<Row align="center" className={classes.buttonRow}>
<Button minWidth={140} minHeight={42} onClick={onClickBack}>
Back
</Button>
<Button
type="submit"
onClick={handleSubmit}
onClick={onSubmit}
variant="contained"
minHeight={42}
minWidth={140}

View File

@ -22,6 +22,9 @@ export const styles = () => ({
manage: {
fontSize: '24px',
},
address: {
marginRight: sm,
},
closeIcon: {
height: '35px',
width: '35px',
@ -80,4 +83,9 @@ export const styles = () => ({
cursor: 'pointer',
},
},
gasCostsContainer: {
padding: `0 ${lg}`,
textAlign: 'center',
width: '100%',
},
})

View File

@ -1,5 +1,5 @@
// @flow
import React from 'react'
import React, { useState, useEffect } from 'react'
import { List } from 'immutable'
import { withStyles } from '@material-ui/core/styles'
import Close from '@material-ui/icons/Close'
@ -18,11 +18,16 @@ import Block from '~/components/layout/Block'
import Row from '~/components/layout/Row'
import Col from '~/components/layout/Col'
import type { Owner } from '~/routes/safe/store/models/owner'
import { getWeb3 } from '~/logic/wallets/getWeb3'
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
import { estimateTxGasCosts } from '~/logic/safe/transactions/gasNew'
import { styles } from './style'
type Props = {
onClose: () => void,
classes: Object,
safeAddress: string,
threshold: number,
owners: List<Owner>,
onChangeThreshold: Function,
@ -31,13 +36,38 @@ type Props = {
const THRESHOLD_FIELD_NAME = 'threshold'
const ChangeThreshold = ({
onClose, owners, threshold, classes, onChangeThreshold,
onClose, owners, threshold, classes, onChangeThreshold, safeAddress,
}: Props) => {
const handleSubmit = async (values) => {
const [gasCosts, setGasCosts] = useState<string>('< 0.001')
useEffect(() => {
let isCurrent = true
const estimateGasCosts = async () => {
const web3 = getWeb3()
const { fromWei, toBN } = web3.utils
const safeInstance = await getGnosisSafeInstanceAt(safeAddress)
const txData = safeInstance.contract.methods.changeThreshold('1').encodeABI()
const estimatedGasCosts = await estimateTxGasCosts(safeAddress, safeAddress, txData)
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
const formattedGasCosts = formatAmount(gasCostsAsEth)
if (isCurrent) {
setGasCosts(formattedGasCosts)
}
}
estimateGasCosts()
return () => {
isCurrent = false
}
}, [])
const handleSubmit = (values) => {
const newThreshold = values[THRESHOLD_FIELD_NAME]
await onChangeThreshold(newThreshold)
onClose()
onChangeThreshold(newThreshold)
}
return (
@ -62,15 +92,13 @@ const ChangeThreshold = ({
</Paragraph>
</Row>
<Row>
<Paragraph weight="bolder">
Any transaction requires the confirmation of:
</Paragraph>
<Paragraph weight="bolder">Any transaction requires the confirmation of:</Paragraph>
</Row>
<Row margin="xl" align="center" className={classes.inputRow}>
<Col xs={2}>
<Field
name={THRESHOLD_FIELD_NAME}
render={(props) => (
render={(props: Object) => (
<>
<SelectField {...props} disableError>
{[...Array(Number(owners.size))].map((x, index) => (
@ -92,14 +120,15 @@ const ChangeThreshold = ({
</Col>
<Col xs={10}>
<Paragraph size="lg" color="primary" noMargin className={classes.ownersText}>
out of
{' '}
{owners.size}
{' '}
owner(s)
{`out of ${owners.size} owner(s)`}
</Paragraph>
</Col>
</Row>
<Row>
<Paragraph>
{`You're about to create a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCosts} (fee price) ETH in this wallet to fund this confirmation.`}
</Paragraph>
</Row>
</Block>
<Hairline style={{ position: 'absolute', bottom: 85 }} />
<Row align="center" className={classes.buttonRow}>

View File

@ -98,6 +98,7 @@ const ThresholdSettings = ({
onClose={toggleModal}
owners={owners}
threshold={threshold}
safeAddress={safeAddress}
onChangeThreshold={onChangeThreshold}
/>
</Modal>

View File

@ -1,5 +1,5 @@
// @flow
import React, { useState } from 'react'
import React, { useState, useEffect } from 'react'
import Close from '@material-ui/icons/Close'
import IconButton from '@material-ui/core/IconButton'
import { withStyles } from '@material-ui/core/styles'
@ -13,6 +13,9 @@ import Row from '~/components/layout/Row'
import Bold from '~/components/layout/Bold'
import Block from '~/components/layout/Block'
import Paragraph from '~/components/layout/Paragraph'
import { getWeb3 } from '~/logic/wallets/getWeb3'
import { estimateTxGasCosts } from '~/logic/safe/transactions/gasNew'
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
import { type Transaction } from '~/routes/safe/store/models/transaction'
import { styles } from './style'
@ -61,10 +64,39 @@ const ApproveTxModal = ({
enqueueSnackbar,
closeSnackbar,
}: Props) => {
const [approveAndExecute, setApproveAndExecute] = useState<boolean>(false)
const [approveAndExecute, setApproveAndExecute] = useState<boolean>(true)
const [gasCosts, setGasCosts] = useState<string>('< 0.001')
const { title, description } = getModalTitleAndDescription(thresholdReached)
const oneConfirmationLeft = tx.confirmations.size + 1 === threshold
useEffect(() => {
let isCurrent = true
const estimateGas = async () => {
const web3 = getWeb3()
const { fromWei, toBN } = web3.utils
const estimatedGasCosts = await estimateTxGasCosts(
safeAddress,
tx.recipient,
tx.data,
tx,
approveAndExecute ? userAddress : undefined,
)
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
const formattedGasCosts = formatAmount(gasCostsAsEth)
if (isCurrent) {
setGasCosts(formattedGasCosts)
}
}
estimateGas()
return () => {
isCurrent = false
}
}, [approveAndExecute])
const handleExecuteCheckbox = () => setApproveAndExecute((prevApproveAndExecute) => !prevApproveAndExecute)
const approveTx = () => {
@ -102,8 +134,8 @@ const ApproveTxModal = ({
{!thresholdReached && oneConfirmationLeft && (
<>
<Paragraph color="error">
Approving transaction does not execute it immediately. If you want to approve and execute the
transaction right away, click on checkbox below.
Approving this transaction executes it right away. If you want approve but execute the transaction
manually later, click on the checkbox below.
</Paragraph>
<FormControlLabel
control={<Checkbox onChange={handleExecuteCheckbox} checked={approveAndExecute} color="primary" />}
@ -112,6 +144,13 @@ const ApproveTxModal = ({
</>
)}
</Row>
<Row>
<Paragraph>
{`You're about to ${
approveAndExecute ? 'execute' : 'approve'
} a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCosts} (fee price) ETH in this wallet to fund this confirmation.`}
</Paragraph>
</Row>
</Block>
<Row align="center" className={classes.buttonRow}>
<Button minWidth={140} minHeight={42} onClick={onClose}>

View File

@ -1,5 +1,5 @@
// @flow
import React from 'react'
import React, { useEffect, useState } from 'react'
import Close from '@material-ui/icons/Close'
import IconButton from '@material-ui/core/IconButton'
import { withStyles } from '@material-ui/core/styles'
@ -14,6 +14,9 @@ import Paragraph from '~/components/layout/Paragraph'
import { type Transaction } from '~/routes/safe/store/models/transaction'
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
import { getWeb3 } from '~/logic/wallets/getWeb3'
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
import { estimateTxGasCosts } from '~/logic/safe/transactions/gasNew'
import { styles } from './style'
type Props = {
@ -37,6 +40,29 @@ const CancelTxModal = ({
enqueueSnackbar,
closeSnackbar,
}: Props) => {
const [gasCosts, setGasCosts] = useState<string>('< 0.001')
useEffect(() => {
let isCurrent = true
const estimateGasCosts = async () => {
const web3 = getWeb3()
const { fromWei, toBN } = web3.utils
const estimatedGasCosts = await estimateTxGasCosts(safeAddress, safeAddress, EMPTY_DATA)
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
const formattedGasCosts = formatAmount(gasCostsAsEth)
if (isCurrent) {
setGasCosts(formattedGasCosts)
}
}
estimateGasCosts()
return () => {
isCurrent = false
}
}, [])
const sendReplacementTransaction = () => {
createTransaction(
safeAddress,
@ -79,6 +105,11 @@ const CancelTxModal = ({
<Bold className={classes.nonceNumber}>{tx.nonce}</Bold>
</Paragraph>
</Row>
<Row>
<Paragraph>
{`You're about to create a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCosts} (fee price) ETH in this wallet to fund this confirmation.`}
</Paragraph>
</Row>
</Block>
<Row align="center" className={classes.buttonRow}>
<Button minWidth={140} minHeight={42} onClick={onClose}>

View File

@ -116,23 +116,27 @@ const ExpandedTx = ({
/>
</Row>
</Block>
<CancelTxModal
isOpen={openModal === 'cancelTx'}
createTransaction={createTransaction}
onClose={closeModal}
tx={tx}
safeAddress={safeAddress}
/>
<ApproveTxModal
isOpen={openModal === 'approveTx'}
processTransaction={processTransaction}
onClose={closeModal}
tx={tx}
userAddress={userAddress}
safeAddress={safeAddress}
threshold={threshold}
thresholdReached={thresholdReached}
/>
{openModal === 'cancelTx' && (
<CancelTxModal
isOpen
createTransaction={createTransaction}
onClose={closeModal}
tx={tx}
safeAddress={safeAddress}
/>
)}
{openModal === 'approveTx' && (
<ApproveTxModal
isOpen
processTransaction={processTransaction}
onClose={closeModal}
tx={tx}
userAddress={userAddress}
safeAddress={safeAddress}
threshold={threshold}
thresholdReached={thresholdReached}
/>
)}
</>
)
}

View File

@ -1,5 +1,6 @@
// @flow
import { format, getTime, parseISO } from 'date-fns'
import { BigNumber } from 'bignumber.js'
import { List } from 'immutable'
import { type Transaction } from '~/routes/safe/store/models/transaction'
import { type SortRow, buildOrderFieldFrom } from '~/components/Table/sorting'
@ -32,7 +33,8 @@ export const getTxAmount = (tx: Transaction) => {
let txAmount = 'n/a'
if (tx.isTokenTransfer && tx.decodedParams) {
txAmount = `${fromWei(toBN(tx.decodedParams.value), 'ether')} ${tx.symbol}`
const tokenDecimals = tx.decimals.toNumber ? tx.decimals.toNumber() : tx.decimals
txAmount = `${new BigNumber(tx.decodedParams.value).div(10 ** tokenDecimals).toString()} ${tx.symbol}`
} else if (Number(tx.value) > 0) {
txAmount = `${fromWei(toBN(tx.value), 'ether')} ${tx.symbol}`
}

View File

@ -115,8 +115,9 @@ const extendedTransactionsSelector: Selector<GlobalState, RouterProps, List<Tran
let replacementTransaction
if (!tx.isExecuted) {
replacementTransaction = transactions.findLast(
(transaction) => transaction.nonce === tx.nonce
&& isAfter(parseISO(transaction.submissionDate), parseISO(tx.submissionDate)),
(transaction) => (transaction.nonce === tx.nonce
&& isAfter(parseISO(transaction.submissionDate), parseISO(tx.submissionDate)))
|| transaction.nonce > tx.nonce,
)
if (replacementTransaction) {
extendedTx = tx.set('cancelled', true)

View File

@ -1,5 +1,6 @@
// @flow
import type { Dispatch as ReduxDispatch, GetState } from 'redux'
import { push } from 'connected-react-router'
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
import { userAccountSelector } from '~/logic/wallets/store/selectors'
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
@ -17,11 +18,12 @@ import {
import {
type Notification,
type NotificationsQueue,
getNofiticationsFromTxType,
getNotificationsFromTxType,
showSnackbar,
} from '~/logic/notifications'
import { getErrorMessage } from '~/test/utils/ethereumErrors'
import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses'
import { SAFELIST_ADDRESS } from '~/routes/routes'
const createTransaction = (
safeAddress: string,
@ -35,6 +37,8 @@ const createTransaction = (
) => async (dispatch: ReduxDispatch<GlobalState>, getState: GetState<GlobalState>) => {
const state: GlobalState = getState()
dispatch(push(`${SAFELIST_ADDRESS}/${safeAddress}/transactions`))
const safeInstance = await getGnosisSafeInstanceAt(safeAddress)
const from = userAccountSelector(state)
const threshold = await safeInstance.getThreshold()
@ -47,7 +51,7 @@ const createTransaction = (
'',
)}000000000000000000000000000000000000000000000000000000000000000001`
const notificationsQueue: NotificationsQueue = getNofiticationsFromTxType(notifiedTransaction)
const notificationsQueue: NotificationsQueue = getNotificationsFromTxType(notifiedTransaction)
const beforeExecutionKey = showSnackbar(notificationsQueue.beforeExecution, enqueueSnackbar, closeSnackbar)
let pendingExecutionKey
@ -99,6 +103,7 @@ const createTransaction = (
if (isExecution) {
showSnackbar(notificationsQueue.afterExecution, enqueueSnackbar, closeSnackbar)
}
dispatch(fetchTransactions(safeAddress))
return receipt.transactionHash
})
@ -114,8 +119,6 @@ const createTransaction = (
console.error(`Error executing the TX: ${errMsg}`)
}
dispatch(fetchTransactions(safeAddress))
return txHash
}

View File

@ -16,6 +16,7 @@ import { getHumanFriendlyToken } from '~/logic/tokens/store/actions/fetchTokens'
import { isTokenTransfer } from '~/logic/tokens/utils/tokenHelpers'
import { TX_TYPE_EXECUTION } from '~/logic/safe/transactions'
import { decodeParamsFromSafeMethod } from '~/logic/contracts/methodIds'
import { ALTERNATIVE_TOKEN_ABI } from '~/logic/tokens/utils/alternativeAbi'
let web3
@ -59,8 +60,8 @@ export const buildTransactionFrom = async (
)
const modifySettingsTx = tx.to === safeAddress && Number(tx.value) === 0 && !!tx.data
const cancellationTx = tx.to === safeAddress && Number(tx.value) === 0 && !tx.data
const customTx = tx.to !== safeAddress && !!tx.data
const isSendTokenTx = await isTokenTransfer(tx.data, tx.value)
const customTx = tx.to !== safeAddress && !!tx.data && !isSendTokenTx
let executionTxHash
const executionTx = confirmations.find((conf) => conf.type === TX_TYPE_EXECUTION)
@ -70,11 +71,23 @@ export const buildTransactionFrom = async (
}
let symbol = 'ETH'
let decimals = 18
let decodedParams
if (isSendTokenTx) {
const tokenContract = await getHumanFriendlyToken()
const tokenInstance = await tokenContract.at(tx.to)
symbol = await tokenInstance.symbol()
try {
[symbol, decimals] = await Promise.all([tokenInstance.symbol(), tokenInstance.decimals()])
} catch (err) {
const alternativeTokenInstance = new web3.eth.Contract(ALTERNATIVE_TOKEN_ABI, tx.to)
const [tokenSymbol, tokenDecimals] = await Promise.all([
alternativeTokenInstance.methods.symbol().call(),
alternativeTokenInstance.methods.decimals().call(),
])
symbol = web3.utils.toAscii(tokenSymbol)
decimals = tokenDecimals
}
const params = web3.eth.abi.decodeParameters(['address', 'uint256'], tx.data.slice(10))
decodedParams = {
@ -93,6 +106,7 @@ export const buildTransactionFrom = async (
nonce: tx.nonce,
value: tx.value.toString(),
confirmations,
decimals,
recipient: tx.to,
data: tx.data ? tx.data : EMPTY_DATA,
isExecuted: tx.isExecuted,

View File

@ -11,7 +11,7 @@ const loadDefaultSafe = () => async (dispatch: ReduxDispatch<GlobalState>) => {
dispatch(setDefaultSafe(defaultSafe))
} catch (err) {
// eslint-disable-next-line
console.error('Error while getting defautl Safe from storage:', err)
console.error('Error while getting default Safe from storage:', err)
}
}

View File

@ -1,11 +1,14 @@
// @flow
import type { Dispatch as ReduxDispatch, GetState } from 'redux'
import { List } from 'immutable'
import { type Confirmation } from '~/routes/safe/store/models/confirmation'
import { type Transaction } from '~/routes/safe/store/models/transaction'
import { userAccountSelector } from '~/logic/wallets/store/selectors'
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
import { type GlobalState } from '~/store'
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
import {
type NotifiedTransaction,
getApprovalTransaction,
getExecutionTransaction,
CALL,
@ -16,17 +19,17 @@ import {
import {
type Notification,
type NotificationsQueue,
getNofiticationsFromTxType,
getNotificationsFromTxType,
showSnackbar,
} from '~/logic/notifications'
import { getErrorMessage } from '~/test/utils/ethereumErrors'
// https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures
// https://github.com/gnosis/safe-contracts/blob/master/test/gnosisSafeTeamEdition.js#L26
const generateSignaturesFromTxConfirmations = (tx: Transaction, preApprovingOwner?: string) => {
export const generateSignaturesFromTxConfirmations = (confirmations: List<Confirmation>, preApprovingOwner?: string) => {
// The constant parts need to be sorted so that the recovered signers are sorted ascending
// (natural order) by address (not checksummed).
let confirmedAdresses = tx.confirmations.map((conf) => conf.owner.address)
let confirmedAdresses = confirmations.map((conf) => conf.owner.address)
if (preApprovingOwner) {
confirmedAdresses = confirmedAdresses.push(preApprovingOwner)
@ -59,7 +62,7 @@ const processTransaction = (
const threshold = (await safeInstance.getThreshold()).toNumber()
const shouldExecute = threshold === tx.confirmations.size || approveAndExecute
let sigs = generateSignaturesFromTxConfirmations(tx, approveAndExecute && userAddress)
let sigs = generateSignaturesFromTxConfirmations(tx.confirmations, approveAndExecute && userAddress)
// https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures
if (!sigs) {
sigs = `0x000000000000000000000000${from.replace(
@ -68,7 +71,7 @@ const processTransaction = (
)}000000000000000000000000000000000000000000000000000000000000000001`
}
const notificationsQueue: NotificationsQueue = getNofiticationsFromTxType(notifiedTransaction)
const notificationsQueue: NotificationsQueue = getNotificationsFromTxType(notifiedTransaction)
const beforeExecutionKey = showSnackbar(notificationsQueue.beforeExecution, enqueueSnackbar, closeSnackbar)
let pendingExecutionKey
@ -124,6 +127,7 @@ const processTransaction = (
shouldExecute ? TX_TYPE_EXECUTION : TX_TYPE_CONFIRMATION,
)
showSnackbar(notificationsQueue.afterExecution, enqueueSnackbar, closeSnackbar)
dispatch(fetchTransactions(safeAddress))
return receipt.transactionHash
})
@ -137,8 +141,6 @@ const processTransaction = (
console.error(`Error executing the TX: ${errMsg}`)
}
dispatch(fetchTransactions(safeAddress))
return txHash
}

View File

@ -10,7 +10,7 @@ export type SafeProps = {
address: string,
threshold: number,
owners: List<Owner>,
balances: Map<string, string>,
balances?: Map<string, string>,
activeTokens: Set<string>,
ethBalance?: string,
}
@ -21,7 +21,7 @@ const SafeRecord: RecordFactory<SafeProps> = Record({
threshold: 0,
ethBalance: 0,
owners: List([]),
activeTokens: new Set([]),
activeTokens: new Set(),
balances: Map({}),
})

View File

@ -21,6 +21,7 @@ export type TransactionProps = {
customTx: boolean,
safeTxHash: string,
executionTxHash?: string,
decimals?: number,
cancelled?: boolean,
status?: TransactionStatus,
isTokenTransfer: boolean,
@ -45,6 +46,7 @@ export const makeTransaction: RecordFactory<TransactionProps> = Record({
cancellationTx: false,
customTx: false,
status: 'awaiting',
decimals: 18,
isTokenTransfer: false,
decodedParams: {},
})

View File

@ -15,7 +15,7 @@ import provider, { PROVIDER_REDUCER_ID, type State as ProviderState } from '~/lo
import tokens, { TOKEN_REDUCER_ID, type State as TokensState } from '~/logic/tokens/store/reducer/tokens'
import notifications, {
NOTIFICATIONS_REDUCER_ID,
type State as NotificationsState,
type NotificationReducerState as NotificationsState,
} from '~/logic/notifications/store/reducer/notifications'
export const history = createBrowserHistory()

View File

@ -14,7 +14,6 @@ import { TRANSACTION_ROW_TEST_ID } from '~/routes/safe/components/Transactions/T
import { useTestAccountAt, resetTestAccount } from './utils/accounts'
import {
CONFIRM_TX_BTN_TEST_ID,
EXECUTE_TX_BTN_TEST_ID,
} from '~/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/ButtonRow'
import { APPROVE_TX_MODAL_SUBMIT_BTN_TEST_ID } from '~/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal'
@ -67,13 +66,7 @@ describe('DOM > Feature > Sending Funds', () => {
const approveTxBtn = await waitForElement(() => SafeDom.getByTestId(APPROVE_TX_MODAL_SUBMIT_BTN_TEST_ID))
fireEvent.click(approveTxBtn)
// EXECUTE TX
const executeTxBtn = await waitForElement(() => SafeDom.getByTestId(EXECUTE_TX_BTN_TEST_ID))
fireEvent.click(executeTxBtn)
const confirmReviewTxBtn = await waitForElement(() => SafeDom.getByTestId(APPROVE_TX_MODAL_SUBMIT_BTN_TEST_ID))
fireEvent.click(confirmReviewTxBtn)
await sleep(500)
await sleep(1000)
// THEN
const safeFunds = await getBalanceInEtherOf(safeAddress)

581
yarn.lock
View File

@ -2,10 +2,10 @@
# yarn lockfile v1
"@babel/cli@7.6.2":
version "7.6.2"
resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.6.2.tgz#4ce8b5b4b2e4b4c1b7bd841cec62085e2dfc4465"
integrity sha512-JDZ+T/br9pPfT2lmAMJypJDTTTHM9ePD/ED10TRjRzJVdEVy+JB3iRlhzYmTt5YkNgHvxWGlUVnLtdv6ruiDrQ==
"@babel/cli@7.6.4":
version "7.6.4"
resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.6.4.tgz#9b35a4e15fa7d8f487418aaa8229c8b0bc815f20"
integrity sha512-tqrDyvPryBM6xjIyKKUwr3s8CzmmYidwgdswd7Uc/Cv0ogZcuS1TYQTLx/eWKP3UbJ6JxZAiYlBZabXm/rtRsQ==
dependencies:
commander "^2.8.1"
convert-source-map "^1.1.0"
@ -53,18 +53,18 @@
semver "^5.4.1"
source-map "^0.5.0"
"@babel/core@7.6.2":
version "7.6.2"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.6.2.tgz#069a776e8d5e9eefff76236bc8845566bd31dd91"
integrity sha512-l8zto/fuoZIbncm+01p8zPSDZu/VuuJhAfA7d/AbzM09WR7iVhavvfNDYCNpo1VvLk6E6xgAoP9P+/EMJHuRkQ==
"@babel/core@7.6.4":
version "7.6.4"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.6.4.tgz#6ebd9fe00925f6c3e177bb726a188b5f578088ff"
integrity sha512-Rm0HGw101GY8FTzpWSyRbki/jzq+/PkNQJ+nSulrdY6gFGOsNseCqD6KHRYe2E+EdzuBdr2pxCp6s4Uk6eJ+XQ==
dependencies:
"@babel/code-frame" "^7.5.5"
"@babel/generator" "^7.6.2"
"@babel/generator" "^7.6.4"
"@babel/helpers" "^7.6.2"
"@babel/parser" "^7.6.2"
"@babel/parser" "^7.6.4"
"@babel/template" "^7.6.0"
"@babel/traverse" "^7.6.2"
"@babel/types" "^7.6.0"
"@babel/traverse" "^7.6.3"
"@babel/types" "^7.6.3"
convert-source-map "^1.1.0"
debug "^4.1.0"
json5 "^2.1.0"
@ -114,6 +114,26 @@
lodash "^4.17.13"
source-map "^0.5.0"
"@babel/generator@^7.6.3":
version "7.6.3"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.6.3.tgz#71d5375264f93ec7bac7d9f35a67067733f5578e"
integrity sha512-hLhYbAb3pHwxjlijC4AQ7mqZdcoujiNaW7izCT04CIowHK8psN0IN8QjDv0iyFtycF5FowUOTwDloIheI25aMw==
dependencies:
"@babel/types" "^7.6.3"
jsesc "^2.5.1"
lodash "^4.17.13"
source-map "^0.6.1"
"@babel/generator@^7.6.4":
version "7.6.4"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.6.4.tgz#a4f8437287bf9671b07f483b76e3bb731bc97671"
integrity sha512-jsBuXkFoZxk0yWLyGI9llT9oiQ2FeTASmRFE32U+aaDTfoE92t78eroO7PTpU/OrYq38hlcDM6vbfLDaOLy+7w==
dependencies:
"@babel/types" "^7.6.3"
jsesc "^2.5.1"
lodash "^4.17.13"
source-map "^0.5.0"
"@babel/helper-annotate-as-pure@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz#323d39dd0b50e10c7c06ca7d7638e6864d8c5c32"
@ -343,6 +363,16 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.2.tgz#205e9c95e16ba3b8b96090677a67c9d6075b70a1"
integrity sha512-mdFqWrSPCmikBoaBYMuBulzTIKuXVPtEISFbRRVNwMWpCms/hmE2kRq0bblUHaNRKrjRlmVbx1sDHmjmRgD2Xg==
"@babel/parser@^7.6.3":
version "7.6.3"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.3.tgz#9eff8b9c3eeae16a74d8d4ff30da2bd0d6f0487e"
integrity sha512-sUZdXlva1dt2Vw2RqbMkmfoImubO0D0gaCrNngV6Hi0DA4x3o4mlrq0tbfY0dZEUIccH8I6wQ4qgEtwcpOR6Qg==
"@babel/parser@^7.6.4":
version "7.6.4"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.4.tgz#cb9b36a7482110282d5cb6dd424ec9262b473d81"
integrity sha512-D8RHPW5qd0Vbyo3qb+YjO5nvUVRTXFLQ/FsDxJU2Nqz4uB5EnUN0ZQSEYpvTIbRuttig1XbHWU5oMeQwQSAA+A==
"@babel/plugin-proposal-async-generator-functions@^7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz#b289b306669dce4ad20b0252889a15768c9d417e"
@ -719,10 +749,10 @@
"@babel/helper-plugin-utils" "^7.0.0"
lodash "^4.17.13"
"@babel/plugin-transform-block-scoping@^7.6.2":
version "7.6.2"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.2.tgz#96c33ab97a9ae500cc6f5b19e04a7e6553360a79"
integrity sha512-zZT8ivau9LOQQaOGC7bQLQOT4XPkPXgN2ERfUgk1X8ql+mVkLc4E8eKk+FO3o0154kxzqenWCorfmEXpEZcrSQ==
"@babel/plugin-transform-block-scoping@^7.6.3":
version "7.6.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.3.tgz#6e854e51fbbaa84351b15d4ddafe342f3a5d542a"
integrity sha512-7hvrg75dubcO3ZI2rjYTzUrEuh1E9IyDEhhB6qfcooxhDA33xx2MasuLVgdxzcP6R/lipAC6n9ub9maNW6RKdw==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
lodash "^4.17.13"
@ -914,10 +944,10 @@
dependencies:
regexp-tree "^0.1.6"
"@babel/plugin-transform-named-capturing-groups-regex@^7.6.2":
version "7.6.2"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.6.2.tgz#c1ca0bb84b94f385ca302c3932e870b0fb0e522b"
integrity sha512-xBdB+XOs+lgbZc2/4F5BVDVcDNS4tcSKQc96KmlqLEAwz6tpYPEvPdmDfvVG0Ssn8lAhronaRs6Z6KSexIpK5g==
"@babel/plugin-transform-named-capturing-groups-regex@^7.6.3":
version "7.6.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.6.3.tgz#aaa6e409dd4fb2e50b6e2a91f7e3a3149dbce0cf"
integrity sha512-jTkk7/uE6H2s5w6VlMHeWuH+Pcy2lmdwFoeWCVnvIrDUnB5gQqTVI8WfmEAhF2CDEarGrknZcmSFg1+bkfCoSw==
dependencies:
regexpu-core "^4.6.0"
@ -1157,10 +1187,10 @@
js-levenshtein "^1.1.3"
semver "^5.5.0"
"@babel/preset-env@7.6.2":
version "7.6.2"
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.6.2.tgz#abbb3ed785c7fe4220d4c82a53621d71fc0c75d3"
integrity sha512-Ru7+mfzy9M1/YTEtlDS8CD45jd22ngb9tXnn64DvQK3ooyqSw9K4K9DUWmYknTTVk4TqygL9dqCrZgm1HMea/Q==
"@babel/preset-env@7.6.3":
version "7.6.3"
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.6.3.tgz#9e1bf05a2e2d687036d24c40e4639dc46cef2271"
integrity sha512-CWQkn7EVnwzlOdR5NOm2+pfgSNEZmvGjOhlCHBDq0J8/EStr+G+FvPEiz9B56dR6MoiUFjXhfE4hjLoAKKJtIQ==
dependencies:
"@babel/helper-module-imports" "^7.0.0"
"@babel/helper-plugin-utils" "^7.0.0"
@ -1178,7 +1208,7 @@
"@babel/plugin-transform-arrow-functions" "^7.2.0"
"@babel/plugin-transform-async-to-generator" "^7.5.0"
"@babel/plugin-transform-block-scoped-functions" "^7.2.0"
"@babel/plugin-transform-block-scoping" "^7.6.2"
"@babel/plugin-transform-block-scoping" "^7.6.3"
"@babel/plugin-transform-classes" "^7.5.5"
"@babel/plugin-transform-computed-properties" "^7.2.0"
"@babel/plugin-transform-destructuring" "^7.6.0"
@ -1193,7 +1223,7 @@
"@babel/plugin-transform-modules-commonjs" "^7.6.0"
"@babel/plugin-transform-modules-systemjs" "^7.5.0"
"@babel/plugin-transform-modules-umd" "^7.2.0"
"@babel/plugin-transform-named-capturing-groups-regex" "^7.6.2"
"@babel/plugin-transform-named-capturing-groups-regex" "^7.6.3"
"@babel/plugin-transform-new-target" "^7.4.4"
"@babel/plugin-transform-object-super" "^7.5.5"
"@babel/plugin-transform-parameters" "^7.4.4"
@ -1206,7 +1236,7 @@
"@babel/plugin-transform-template-literals" "^7.4.4"
"@babel/plugin-transform-typeof-symbol" "^7.2.0"
"@babel/plugin-transform-unicode-regex" "^7.6.2"
"@babel/types" "^7.6.0"
"@babel/types" "^7.6.3"
browserslist "^4.6.0"
core-js-compat "^3.1.1"
invariant "^2.2.2"
@ -1288,6 +1318,17 @@
"@babel/plugin-transform-react-jsx-self" "^7.0.0"
"@babel/plugin-transform-react-jsx-source" "^7.0.0"
"@babel/preset-react@7.6.3":
version "7.6.3"
resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.6.3.tgz#d5242c828322520205ae4eda5d4f4f618964e2f6"
integrity sha512-07yQhmkZmRAfwREYIQgW0HEwMY9GBJVuPY4Q12UC72AbfaawuupVWa8zQs2tlL+yun45Nv/1KreII/0PLfEsgA==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-transform-react-display-name" "^7.0.0"
"@babel/plugin-transform-react-jsx" "^7.0.0"
"@babel/plugin-transform-react-jsx-self" "^7.0.0"
"@babel/plugin-transform-react-jsx-source" "^7.0.0"
"@babel/preset-typescript@7.3.3":
version "7.3.3"
resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.3.3.tgz#88669911053fa16b2b276ea2ede2ca603b3f307a"
@ -1387,6 +1428,21 @@
globals "^11.1.0"
lodash "^4.17.13"
"@babel/traverse@^7.6.3":
version "7.6.3"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.6.3.tgz#66d7dba146b086703c0fb10dd588b7364cec47f9"
integrity sha512-unn7P4LGsijIxaAJo/wpoU11zN+2IaClkQAxcJWBNCMS6cmVh802IyLHNkAjQ0iYnRS3nnxk5O3fuXW28IMxTw==
dependencies:
"@babel/code-frame" "^7.5.5"
"@babel/generator" "^7.6.3"
"@babel/helper-function-name" "^7.1.0"
"@babel/helper-split-export-declaration" "^7.4.4"
"@babel/parser" "^7.6.3"
"@babel/types" "^7.6.3"
debug "^4.1.0"
globals "^11.1.0"
lodash "^4.17.13"
"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.5.5":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.5.5.tgz#97b9f728e182785909aa4ab56264f090a028d18a"
@ -1405,6 +1461,15 @@
lodash "^4.17.13"
to-fast-properties "^2.0.0"
"@babel/types@^7.6.3":
version "7.6.3"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.6.3.tgz#3f07d96f854f98e2fbd45c64b0cb942d11e8ba09"
integrity sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA==
dependencies:
esutils "^2.0.2"
lodash "^4.17.13"
to-fast-properties "^2.0.0"
"@cnakazawa/watch@^1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef"
@ -1760,10 +1825,10 @@
"@types/istanbul-reports" "^1.1.1"
"@types/yargs" "^13.0.0"
"@material-ui/core@4.5.0":
version "4.5.0"
resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.5.0.tgz#7e57cc40988c71b6340e3b2569b47dbac1820351"
integrity sha512-UHVAjU+1uDtA+OMBNBHb4RlCZOu514XeYPafNJv+GTdXBDr1SCPK7yqRE6TV1/bulxlDusTgu5Q6BAUgpmO4MA==
"@material-ui/core@4.5.1":
version "4.5.1"
resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.5.1.tgz#6f1bbb298cc2893b29169ab9398a00ca5a6ac971"
integrity sha512-6pyk7diT7bflf4qUpqgPCpKYqjhRHPFwsgEV2Gv71lMqwxuRygFGHE2TdZ+l5T249H66Doj2P/j6fW7yzgxTWw==
dependencies:
"@babel/runtime" "^7.4.4"
"@material-ui/styles" "^4.5.0"
@ -1781,10 +1846,10 @@
prop-types "^15.7.2"
react-transition-group "^4.3.0"
"@material-ui/icons@4.4.3":
version "4.4.3"
resolved "https://registry.yarnpkg.com/@material-ui/icons/-/icons-4.4.3.tgz#5d4346ddbb2673a1b57ebc78fd6d50bcd88711db"
integrity sha512-HVVvUyc/78kmaBd93LkfWyGkXMM+zOMKzUfulWXxaV/fFAZ3N0pD0oHjWUd94zrOoF3tZP9JC7EPlIpIcZSNow==
"@material-ui/icons@4.5.1":
version "4.5.1"
resolved "https://registry.yarnpkg.com/@material-ui/icons/-/icons-4.5.1.tgz#6963bad139e938702ece85ca43067688018f04f8"
integrity sha512-YZ/BgJbXX4a0gOuKWb30mBaHaoXRqPanlePam83JQPZ/y4kl+3aW0Wv9tlR70hB5EGAkEJGW5m4ktJwMgxQAeA==
dependencies:
"@babel/runtime" "^7.4.4"
@ -1930,17 +1995,17 @@
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==
"@storybook/addon-actions@5.2.1":
version "5.2.1"
resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-5.2.1.tgz#2096e7f938b289be48af6f0adfd620997e7a420c"
integrity sha512-tu4LGeRGAq+sLlsRPE1PzGyYU9JyM3HMLXnOCh5dvRSS8wnoDw1zQ55LPOXH6aoJGdsrvktiw+uTVf4OyN7ryg==
"@storybook/addon-actions@5.2.4":
version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-5.2.4.tgz#79d13c1b6e58c75dd548eb162aa6626a93dfd041"
integrity sha512-5E8uXopy6Gq5R3MXrPf0VM9QiLaGLxLCXtDYHQ0gku+HhPYR25KQudS/PyuO+OWzuyB0fsvTi240B3zw+zilOg==
dependencies:
"@storybook/addons" "5.2.1"
"@storybook/api" "5.2.1"
"@storybook/client-api" "5.2.1"
"@storybook/components" "5.2.1"
"@storybook/core-events" "5.2.1"
"@storybook/theming" "5.2.1"
"@storybook/addons" "5.2.4"
"@storybook/api" "5.2.4"
"@storybook/client-api" "5.2.4"
"@storybook/components" "5.2.4"
"@storybook/core-events" "5.2.4"
"@storybook/theming" "5.2.4"
core-js "^3.0.1"
fast-deep-equal "^2.0.1"
global "^4.3.2"
@ -1966,17 +2031,18 @@
react-inspector "^2.2.2"
uuid "^3.2.1"
"@storybook/addon-knobs@5.2.1":
version "5.2.1"
resolved "https://registry.yarnpkg.com/@storybook/addon-knobs/-/addon-knobs-5.2.1.tgz#6bc2f7e254ccce09d6f5136e9cce63cd808c9853"
integrity sha512-JCSqrGYyVVBNkudhvla7qc9m0/Mn1UMaMzIxH5kewEE1KWZcCkdXD5hDASN39pkn3mX1yyqveP8jiyIL9vVBLg==
"@storybook/addon-knobs@5.2.4":
version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/addon-knobs/-/addon-knobs-5.2.4.tgz#f5db98235bc2870361dd3c224f8c10f901592c43"
integrity sha512-VYxbDARJs5RwTEOlcfa98tkDXLcRocB7QXLqt8wwCdXPIqkuoVeQLROXGYJm2NzSn49RyHPKUuVWnRhy34qBbQ==
dependencies:
"@storybook/addons" "5.2.1"
"@storybook/api" "5.2.1"
"@storybook/client-api" "5.2.1"
"@storybook/components" "5.2.1"
"@storybook/core-events" "5.2.1"
"@storybook/theming" "5.2.1"
"@storybook/addons" "5.2.4"
"@storybook/api" "5.2.4"
"@storybook/client-api" "5.2.4"
"@storybook/components" "5.2.4"
"@storybook/core-events" "5.2.4"
"@storybook/theming" "5.2.4"
"@types/react-color" "^3.0.1"
copy-to-clipboard "^3.0.8"
core-js "^3.0.1"
escape-html "^1.0.3"
@ -1989,29 +2055,29 @@
react-lifecycles-compat "^3.0.4"
react-select "^3.0.0"
"@storybook/addon-links@5.2.1":
version "5.2.1"
resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-5.2.1.tgz#ec1fc92ed4d840ba758f40167c752f48562a906f"
integrity sha512-N5f+lzai+ctHfzHoYWECYsg3lKGJuqhkVctro46fHSW7s/GB8+l78nDcV7hDjNEXDES8QN5C1fPYihatdgpSJA==
"@storybook/addon-links@5.2.4":
version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-5.2.4.tgz#5a5d631dbd66bf5800d4be7660b79568085a210d"
integrity sha512-MG+Qne4gUWGYx2qQuLQXNcl7oOBF4PbIcR0oboZNrkZ+D+6f3nHwyb53CTtzVTc+SF45CFFYLHvFdGZvv5fcAw==
dependencies:
"@storybook/addons" "5.2.1"
"@storybook/core-events" "5.2.1"
"@storybook/router" "5.2.1"
"@storybook/addons" "5.2.4"
"@storybook/core-events" "5.2.4"
"@storybook/router" "5.2.4"
common-tags "^1.8.0"
core-js "^3.0.1"
global "^4.3.2"
prop-types "^15.7.2"
qs "^6.6.0"
"@storybook/addons@5.2.1":
version "5.2.1"
resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-5.2.1.tgz#6e52aa1fa2737e170fb675eb1fcceebd0a915a0b"
integrity sha512-kdx97tTKsMf/lBlT40uLYsHMF1J71mn2j41RNaCXmWw/PrKCDmiNfinemN2wtbwRSvGqb3q/BAqjKLvUtWynGg==
"@storybook/addons@5.2.4":
version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-5.2.4.tgz#5c4f031e403c90a517cd6d208ec51d7e2455683a"
integrity sha512-Q+bnVlBA308qnELxnh18hBDRSUgltR9KbV537285dUL/okv/NC6n51mxJwIaG+ksBW2wU+5e6tqSayaKF3uHLw==
dependencies:
"@storybook/api" "5.2.1"
"@storybook/channels" "5.2.1"
"@storybook/client-logger" "5.2.1"
"@storybook/core-events" "5.2.1"
"@storybook/api" "5.2.4"
"@storybook/channels" "5.2.4"
"@storybook/client-logger" "5.2.4"
"@storybook/core-events" "5.2.4"
core-js "^3.0.1"
global "^4.3.2"
util-deprecate "^1.0.2"
@ -2051,16 +2117,16 @@
telejson "^2.2.1"
util-deprecate "^1.0.2"
"@storybook/api@5.2.1":
version "5.2.1"
resolved "https://registry.yarnpkg.com/@storybook/api/-/api-5.2.1.tgz#b9cd6639019e044a8ade6fb358cade79c0e3b5d3"
integrity sha512-EXN6sqkGHRuNq0W6BZXOlxe2I2dmN0yUdQLiUOpzH2I3mXnVHpad/0v76dRc9fZbC4LaYUSxR8lBTr0rqIb4mA==
"@storybook/api@5.2.4":
version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/api/-/api-5.2.4.tgz#b0b3dbd93444d163a80b455fb877d816a37b3149"
integrity sha512-KqAB+NkHIHdwu749NDP+7i44jy1bFgpq7GTJlG+sx/XLZHQveK/8yn109g9bXHFth7SvdXI1+9GA/apzwBU/Mw==
dependencies:
"@storybook/channels" "5.2.1"
"@storybook/client-logger" "5.2.1"
"@storybook/core-events" "5.2.1"
"@storybook/router" "5.2.1"
"@storybook/theming" "5.2.1"
"@storybook/channels" "5.2.4"
"@storybook/client-logger" "5.2.4"
"@storybook/core-events" "5.2.4"
"@storybook/router" "5.2.4"
"@storybook/theming" "5.2.4"
core-js "^3.0.1"
fast-deep-equal "^2.0.1"
global "^4.3.2"
@ -2071,19 +2137,19 @@
semver "^6.0.0"
shallow-equal "^1.1.0"
store2 "^2.7.1"
telejson "^2.2.2"
telejson "^3.0.2"
util-deprecate "^1.0.2"
"@storybook/channel-postmessage@5.2.1":
version "5.2.1"
resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-5.2.1.tgz#85541f926d61eedbe2a687bb394d37fc06252751"
integrity sha512-gmnn9qU1iLCpfF6bZuEM3QQOZsAviWeIpiezjrd/qkxatgr3qtbXd4EoZpcVuQw314etarWtNxVpcX6PXcASjQ==
"@storybook/channel-postmessage@5.2.4":
version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-5.2.4.tgz#e3735bdce42156e54bf462083aebaf23245ab5c3"
integrity sha512-ic7/Ho8z2/aOMjoEbr5p8rijOfO3SZdJnwMvDdUxrqvYq7yACZWidPo3w2+iBwQi9HLqEsWesP1c2doJBxVGRw==
dependencies:
"@storybook/channels" "5.2.1"
"@storybook/client-logger" "5.2.1"
"@storybook/channels" "5.2.4"
"@storybook/client-logger" "5.2.4"
core-js "^3.0.1"
global "^4.3.2"
telejson "^2.2.2"
telejson "^3.0.2"
"@storybook/channels@5.1.9":
version "5.1.9"
@ -2092,24 +2158,24 @@
dependencies:
core-js "^3.0.1"
"@storybook/channels@5.2.1":
version "5.2.1"
resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-5.2.1.tgz#e5e35f6d9fb1b1fba4f18b171f31d5f6540f3bef"
integrity sha512-AsF/Hwx91SDOgiOGOBSWS8EJAgqVm939n2nkfdLSJQQmX5EdPRAc3EIE3f13tyQub2yNx0OR4UzQDWgjwfVsEQ==
"@storybook/channels@5.2.4":
version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-5.2.4.tgz#7ab5c9478517ddb9e8ac0c4188b408b9d7e6b7e4"
integrity sha512-/r39yEZ5QiGdiq95DhXBypdBo7urkD3Sp1WDyK48uGkZ0gdHWSPy3BBy8OJhEhfNz7nVisTiVIBr4gIrubKDjw==
dependencies:
core-js "^3.0.1"
"@storybook/client-api@5.2.1":
version "5.2.1"
resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-5.2.1.tgz#bdd335187279a4ab45e20d6d5e9131e5f7098acf"
integrity sha512-VxexqxrbORCGqwx2j0/91Eu1A/vq+rSVIesWwzIowmoLfBwRwDdskO20Yn9U7iMSpux4RvHGF6y1Q1ZtnXm9aA==
"@storybook/client-api@5.2.4":
version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-5.2.4.tgz#2507a3a739a6f6b13e4afefb938a6f3eab34ed55"
integrity sha512-SOwzEFHoNapURhNqdcI7HA76o5tkWvs2+2s++i/S7xsAd3KyefIVDOdqSMlAxJkxZb8Mlrb3UNRxlrpA8SZqNA==
dependencies:
"@storybook/addons" "5.2.1"
"@storybook/channel-postmessage" "5.2.1"
"@storybook/channels" "5.2.1"
"@storybook/client-logger" "5.2.1"
"@storybook/core-events" "5.2.1"
"@storybook/router" "5.2.1"
"@storybook/addons" "5.2.4"
"@storybook/channel-postmessage" "5.2.4"
"@storybook/channels" "5.2.4"
"@storybook/client-logger" "5.2.4"
"@storybook/core-events" "5.2.4"
"@storybook/router" "5.2.4"
common-tags "^1.8.0"
core-js "^3.0.1"
eventemitter3 "^4.0.0"
@ -2127,10 +2193,10 @@
dependencies:
core-js "^3.0.1"
"@storybook/client-logger@5.2.1":
version "5.2.1"
resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-5.2.1.tgz#5c1f122b65386f04a6ad648808dfa89f2d852d7a"
integrity sha512-wzxSE9t3DaLCdd/gnGFnjevmYRZ92F3TEwhUP/QDXM9cZkNsRKHkjE61qjiO5aQPaZQG6Ea9ayWEQEMgZXDucg==
"@storybook/client-logger@5.2.4":
version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-5.2.4.tgz#6ebe37cbc92e0efe27c7119f52d94f647fbb470c"
integrity sha512-ofp6QQPQZBU+RvlAH5KpZRsfAFHecCZDnl/7YG6FwjHseJr3jHTYmBGGjJDMHFHq+Q7FGQu/yVb9lMFgoQ43QQ==
dependencies:
core-js "^3.0.1"
@ -2143,13 +2209,13 @@
glamorous "^4.12.1"
prop-types "^15.6.1"
"@storybook/components@5.2.1":
version "5.2.1"
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-5.2.1.tgz#a4519c5d435c2c25c481e2b64a768e1e568a223f"
integrity sha512-cik5J/mTm1b1TOI17qM+2Mikk3rjb3SbBD4WlNz3Zvn+Hw0ukgbx6kQwVBgujhMlDtsHreidyEgIg4TM13S0Tg==
"@storybook/components@5.2.4":
version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-5.2.4.tgz#9ecd080416eac4e8453030dd601cbeaa32ce4126"
integrity sha512-APhw+XGag0RTCRJ8eCWKVr8dLt9SRqnS8LtzcZJbokCYRxRTFzhmX2eVEE1v+d0gHib1/yh2COxOjMzv3m/rQA==
dependencies:
"@storybook/client-logger" "5.2.1"
"@storybook/theming" "5.2.1"
"@storybook/client-logger" "5.2.4"
"@storybook/theming" "5.2.4"
"@types/react-syntax-highlighter" "10.1.0"
core-js "^3.0.1"
global "^4.3.2"
@ -2174,32 +2240,32 @@
dependencies:
core-js "^3.0.1"
"@storybook/core-events@5.2.1":
version "5.2.1"
resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-5.2.1.tgz#bc28d704938d26dd544d0362d38ef08e8cfed916"
integrity sha512-AIYV/I+baQ0KxvEM7QAKqUedLn2os0XU9HTdtfZJTC3U9wjmR2ah2ScD6T0n7PBz3MderkvZG6dNjs9h8gRquQ==
"@storybook/core-events@5.2.4":
version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-5.2.4.tgz#210c968e96e2fc031cad1d3a09b06fa69ae433fc"
integrity sha512-nQknCmaz2S2HW6PSGcuFzve7Y1Js2Cb268vUG0ZMNtJZwFawqYc+KSQHqmOY0pVm8dyROTcWCudPA0k+hk6N5Q==
dependencies:
core-js "^3.0.1"
"@storybook/core@5.2.1":
version "5.2.1"
resolved "https://registry.yarnpkg.com/@storybook/core/-/core-5.2.1.tgz#3aa17c6fa9b02704723501d32884453869e3c06c"
integrity sha512-mGGvN3GWeLxZ9lYZ4IuD1IoJD+cn6XXm2Arzw+k6KEtJJDFrC5SjESTDGLVFienX5s2tgH4FjYb9Ps9sKfhHlg==
"@storybook/core@5.2.4":
version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/core/-/core-5.2.4.tgz#188509ac0eb0c85144816c385c21acfb5c8fbefd"
integrity sha512-r5kDgZETNawHxpsAPw+h+pRk6l/mJhsSHeDo9/OdYtYFW7lmk2gadViXOTM+6gIWc6vQ8y750bgkahmyIIY0nQ==
dependencies:
"@babel/plugin-proposal-class-properties" "^7.3.3"
"@babel/plugin-proposal-object-rest-spread" "^7.3.2"
"@babel/plugin-syntax-dynamic-import" "^7.2.0"
"@babel/plugin-transform-react-constant-elements" "^7.2.0"
"@babel/preset-env" "^7.4.5"
"@storybook/addons" "5.2.1"
"@storybook/channel-postmessage" "5.2.1"
"@storybook/client-api" "5.2.1"
"@storybook/client-logger" "5.2.1"
"@storybook/core-events" "5.2.1"
"@storybook/node-logger" "5.2.1"
"@storybook/router" "5.2.1"
"@storybook/theming" "5.2.1"
"@storybook/ui" "5.2.1"
"@storybook/addons" "5.2.4"
"@storybook/channel-postmessage" "5.2.4"
"@storybook/client-api" "5.2.4"
"@storybook/client-logger" "5.2.4"
"@storybook/core-events" "5.2.4"
"@storybook/node-logger" "5.2.4"
"@storybook/router" "5.2.4"
"@storybook/theming" "5.2.4"
"@storybook/ui" "5.2.4"
airbnb-js-shims "^1 || ^2"
ansi-to-html "^0.6.11"
autoprefixer "^9.4.9"
@ -2255,10 +2321,10 @@
webpack-dev-middleware "^3.7.0"
webpack-hot-middleware "^2.25.0"
"@storybook/node-logger@5.2.1":
version "5.2.1"
resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-5.2.1.tgz#00d8c0dc9dfd482e7d1d244a59c46726c6b761d9"
integrity sha512-rz+snXZyKwTegKEf15w4uaFWIKpgaWzTw+Ar8mxa+mX7C2DP65TOc+JGYZ7lsXdred+0WP0DhnmhGu2cX8z3lA==
"@storybook/node-logger@5.2.4":
version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-5.2.4.tgz#52dc380c76bdac68de35e7d72efe920c3ba52a23"
integrity sha512-4OOzce02IAfrRv+Y7h3icyw6WIuDekpWF2eYjgYVVvAJYklCEwgeBTBCY0/2TJjPPTBDPUKHVP1Bdz3Vpci9pA==
dependencies:
chalk "^2.4.2"
core-js "^3.0.1"
@ -2266,18 +2332,19 @@
pretty-hrtime "^1.0.3"
regenerator-runtime "^0.12.1"
"@storybook/react@5.2.1":
version "5.2.1"
resolved "https://registry.yarnpkg.com/@storybook/react/-/react-5.2.1.tgz#860970fa8f0d49967862b496af4ef3712f0b96dd"
integrity sha512-brUG8iK2+1Fk5VFZWpAoSokCx21MaPX1zSAVA+Z/Ia0I0sFfurhpQgAGlVePTy9r7dtEEEdniZVtJOH/tHqk4Q==
"@storybook/react@5.2.4":
version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/react/-/react-5.2.4.tgz#305db3fa7cd113c185aba97e5239d7754dc14859"
integrity sha512-AO0qwbD/2UGe5CrVizbaek+gCAPWkPVc0KUk38cT1mcuLpXwt1zZe7iHLQf2zOeBVSiBkPLOHrEtzDfnIJXKFQ==
dependencies:
"@babel/plugin-transform-react-constant-elements" "^7.2.0"
"@babel/preset-flow" "^7.0.0"
"@babel/preset-react" "^7.0.0"
"@storybook/addons" "5.2.1"
"@storybook/core" "5.2.1"
"@storybook/node-logger" "5.2.1"
"@storybook/addons" "5.2.4"
"@storybook/core" "5.2.4"
"@storybook/node-logger" "5.2.4"
"@svgr/webpack" "^4.0.3"
"@types/webpack-env" "^1.13.7"
babel-plugin-add-react-displayname "^0.0.5"
babel-plugin-named-asset-import "^0.3.1"
babel-plugin-react-docgen "^3.0.0"
@ -2304,10 +2371,10 @@
memoizerific "^1.11.3"
qs "^6.6.0"
"@storybook/router@5.2.1":
version "5.2.1"
resolved "https://registry.yarnpkg.com/@storybook/router/-/router-5.2.1.tgz#9c49df79343d3be10c7f984858fb5c9ae3eb7491"
integrity sha512-Mlk275cyPoKtnP4DwQ5D8gTfnaRPL6kDZOSn0wbTMa6pQOfYKgJsa7tjzeAtZuZ/j8hKI4gAfT/auMgH6g+94A==
"@storybook/router@5.2.4":
version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/router/-/router-5.2.4.tgz#e47af60478da7730c7d82de3d089d12c17077333"
integrity sha512-GL7eGdj5oYST0mE9fThJB9ye9tTTgrP+aP3okZ6MeMGtNytb7bmJRpAD2E4ouuPTQVppyHI5re8g/HUxUNOT1g==
dependencies:
"@reach/router" "^1.2.1"
"@types/reach__router" "^1.2.3"
@ -2335,14 +2402,14 @@
prop-types "^15.7.2"
resolve-from "^5.0.0"
"@storybook/theming@5.2.1":
version "5.2.1"
resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-5.2.1.tgz#913e383632e4702035a107c2cc5e5cb27231b389"
integrity sha512-lbAfcyI7Tx8swduIPmlu/jdWzqTBN/v82IEQbZbPR4LS5OHRPmhXPNgFGrcH4kFAiD0GoezSsdum1x0ZZpsQUQ==
"@storybook/theming@5.2.4":
version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-5.2.4.tgz#79c99f65992082fdb4d39fb652db435dd6bebaca"
integrity sha512-2ZlqBrmnm8N0352Fnu2+GB3pEsHL4Eb2eKxV0VLLgkjJuAlm7CK6+I/e4ZknQWxwYm2pQj1y6ta68A62fGBYyA==
dependencies:
"@emotion/core" "^10.0.14"
"@emotion/styled" "^10.0.14"
"@storybook/client-logger" "5.2.1"
"@storybook/client-logger" "5.2.4"
common-tags "^1.8.0"
core-js "^3.0.1"
deep-object-diff "^1.1.0"
@ -2353,21 +2420,19 @@
prop-types "^15.7.2"
resolve-from "^5.0.0"
"@storybook/ui@5.2.1":
version "5.2.1"
resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-5.2.1.tgz#ceba1657a232efd10f839027f8ae274e370c89f6"
integrity sha512-h6Yf1ro/nZcz4nQAU+eSVPxVmpqv7uT7RMb3Vz+VLTY59IEA/sWcoIgA4MIxwf14nVcWOqSmVBJzNKWwc+NGJw==
"@storybook/ui@5.2.4":
version "5.2.4"
resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-5.2.4.tgz#7116c9be0dcdcc5d5b425d998263de1e27cdc078"
integrity sha512-zsS43k1h4bWEW6oj9FNHlUL3niHoJJ8v7iqYbRtVM12rxrYhV3K8TGVG3LCuNB75i3Be0Myy+/RHA4x9kco08A==
dependencies:
"@storybook/addon-actions" "5.2.1"
"@storybook/addon-knobs" "5.2.1"
"@storybook/addons" "5.2.1"
"@storybook/api" "5.2.1"
"@storybook/channels" "5.2.1"
"@storybook/client-logger" "5.2.1"
"@storybook/components" "5.2.1"
"@storybook/core-events" "5.2.1"
"@storybook/router" "5.2.1"
"@storybook/theming" "5.2.1"
"@storybook/addons" "5.2.4"
"@storybook/api" "5.2.4"
"@storybook/channels" "5.2.4"
"@storybook/client-logger" "5.2.4"
"@storybook/components" "5.2.4"
"@storybook/core-events" "5.2.4"
"@storybook/router" "5.2.4"
"@storybook/theming" "5.2.4"
copy-to-clipboard "^3.0.8"
core-js "^3.0.1"
core-js-pure "^3.0.1"
@ -2383,7 +2448,7 @@
qs "^6.6.0"
react "^16.8.3"
react-dom "^16.8.3"
react-draggable "^3.3.2"
react-draggable "^4.0.3"
react-helmet-async "^1.0.2"
react-hotkeys "2.0.0-pre4"
react-sizeme "^2.6.7"
@ -2391,7 +2456,7 @@
resolve-from "^5.0.0"
semver "^6.0.0"
store2 "^2.7.1"
telejson "^2.2.2"
telejson "^3.0.2"
util-deprecate "^1.0.2"
"@svgr/babel-plugin-add-jsx-attribute@^4.2.0":
@ -2516,10 +2581,10 @@
pretty-format "^24.8.0"
wait-for-expect "^1.3.0"
"@testing-library/jest-dom@4.1.0":
version "4.1.0"
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-4.1.0.tgz#69d372e54e4e33be3fd34f3848ec0e8e9d099276"
integrity sha512-cKAONDmJKGJ2DSu6R/+lgA8i8uyZIx4CaOiiK0yMjp+2UecH6kfjunJiy5hfExKMtR74eyzFriqO1w9aTC8VyQ==
"@testing-library/jest-dom@4.1.2":
version "4.1.2"
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-4.1.2.tgz#e523047191379abd67cf0896dfd93cabc7e33eab"
integrity sha512-fNf2rCfu0dBD4DmpzqR2ibsaFlFojrWI/EuU8LLqv73CzFIMvT2RMq88p5JVRe4DfeNj0mu0MQ5FTG4mQ0qFaA==
dependencies:
"@babel/runtime" "^7.5.1"
chalk "^2.4.1"
@ -2628,6 +2693,11 @@
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.3.tgz#856c99cdc1551d22c22b18b5402719affec9839a"
integrity sha512-cS5owqtwzLN5kY+l+KgKdRJ/Cee8tlmQoGQuIE9tWnSmS3JMKzmxo2HIAk2wODMifGwO20d62xZQLYz+RLfXmw==
"@types/is-function@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/is-function/-/is-function-1.0.0.tgz#1b0b819b1636c7baf0d6785d030d12edf70c3e83"
integrity sha512-iTs9HReBu7evG77Q4EC8hZnqRt57irBDkK9nvmHroiOIVwYMQc4IvYvdRgwKfYepunIY7Oh/dBuuld+Gj9uo6w==
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff"
@ -2691,6 +2761,13 @@
"@types/history" "*"
"@types/react" "*"
"@types/react-color@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@types/react-color/-/react-color-3.0.1.tgz#5433e2f503ea0e0831cbc6fd0c20f8157d93add0"
integrity sha512-J6mYm43Sid9y+OjZ7NDfJ2VVkeeuTPNVImNFITgQNXodHteKfl/t/5pAR5Z9buodZ2tCctsZjgiMlQOpfntakw==
dependencies:
"@types/react" "*"
"@types/react-dom@*":
version "16.9.0"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.0.tgz#ba6ddb00bf5de700b0eb91daa452081ffccbfdea"
@ -2752,6 +2829,11 @@
resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.2.tgz#721ca5c5d1a2988b4a886e35c2ffc5735b6afbdf"
integrity sha512-PeHg/AtdW6aaIO2a+98Xj7rWY4KC1E6yOy7AFknJQ7VXUGNrMlyxDFxJo7HqLtjQms/ZhhQX52mLVW/EX3JGOw==
"@types/webpack-env@^1.13.7":
version "1.14.0"
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.14.0.tgz#8edfc5f8e6eae20eeed3ca0d02974ed4ee5e4efc"
integrity sha512-Fv+0gYJzE/czLoRKq+gnXWr4yBpPM3tO3C8pDLFwqVKlMICQUq5OsxwwFZYDaVr7+L6mgNDp16iOcJHEz3J5RQ==
"@types/yargs-parser@*":
version "13.0.0"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-13.0.0.tgz#453743c5bbf9f1bed61d959baab5b06be029b2d0"
@ -2942,10 +3024,10 @@
"@webassemblyjs/wast-parser" "1.8.5"
"@xtuc/long" "4.2.2"
"@welldone-software/why-did-you-render@3.3.5":
version "3.3.5"
resolved "https://registry.yarnpkg.com/@welldone-software/why-did-you-render/-/why-did-you-render-3.3.5.tgz#ba301216ebd7b0283e85995357706223186e6afb"
integrity sha512-hn3U5eU7tVEk3A14tRuYHolbBQv6IRmVd/ukNDeyKmC1IbxBck8j8HnFji4ARvw7Z2kJhUwKtXpRI2+gxVolBA==
"@welldone-software/why-did-you-render@3.3.8":
version "3.3.8"
resolved "https://registry.yarnpkg.com/@welldone-software/why-did-you-render/-/why-did-you-render-3.3.8.tgz#a8b69e6d64e36bab676c03ca4630032bdf6ea5b1"
integrity sha512-DtmXat8vPJuQlSG9BD0dW9baViZUzFtHMMTHAGnQpRosq7U0kOkEoPXoLY/mRKEfI3SJkxX9R/dwIVh2CQ+rKw==
dependencies:
lodash "^4"
@ -3600,7 +3682,20 @@ atob@^2.1.1:
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
autoprefixer@9.6.1, autoprefixer@^9.4.9:
autoprefixer@9.6.5:
version "9.6.5"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.6.5.tgz#98f4afe7e93cccf323287515d426019619775e5e"
integrity sha512-rGd50YV8LgwFQ2WQp4XzOTG69u1qQsXn0amww7tjqV5jJuNazgFKYEVItEBngyyvVITKOg20zr2V+9VsrXJQ2g==
dependencies:
browserslist "^4.7.0"
caniuse-lite "^1.0.30000999"
chalk "^2.4.2"
normalize-range "^0.1.2"
num2fraction "^1.2.2"
postcss "^7.0.18"
postcss-value-parser "^4.0.2"
autoprefixer@^9.4.9:
version "9.6.1"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.6.1.tgz#51967a02d2d2300bb01866c1611ec8348d355a47"
integrity sha512-aVo5WxR3VyvyJxcJC3h4FKfwCQvQWb1tSI5VHNibddCVWrcD1NvlxEweg3TSgiPztMnWfjpy2FURKA2kvDE+Tw==
@ -5013,6 +5108,15 @@ browserslist@^4.0.0, browserslist@^4.5.2, browserslist@^4.6.0, browserslist@^4.6
electron-to-chromium "^1.3.191"
node-releases "^1.1.25"
browserslist@^4.7.0:
version "4.7.0"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.7.0.tgz#9ee89225ffc07db03409f2fee524dc8227458a17"
integrity sha512-9rGNDtnj+HaahxiVV38Gn8n8Lr8REKsel68v1sPFfIGEK6uSXTY3h9acgiT1dZVtOOUtifo/Dn8daDQ5dUgVsA==
dependencies:
caniuse-lite "^1.0.30000989"
electron-to-chromium "^1.3.247"
node-releases "^1.1.29"
bs58@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/bs58/-/bs58-2.0.1.tgz#55908d58f1982aba2008fa1bed8f91998a29bf8d"
@ -5314,6 +5418,11 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000844, caniuse-lite@^1.0.30000955, can
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000985.tgz#0eb40f6c8a8c219155cbe43c4975c0efb4a0f77f"
integrity sha512-1ngiwkgqAYPG0JSSUp3PUDGPKKY59EK7NrGGX+VOxaKCNzRbNc7uXMny+c3VJfZxtoK3wSImTvG9T9sXiTw2+w==
caniuse-lite@^1.0.30000989, caniuse-lite@^1.0.30000999:
version "1.0.30000999"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000999.tgz#427253a69ad7bea4aa8d8345687b8eec51ca0e43"
integrity sha512-1CUyKyecPeksKwXZvYw0tEoaMCo/RwBlXmEtN5vVnabvO0KPd9RQLcaAuR9/1F+KDMv6esmOFWlsXuzDk+8rxg==
capture-exit@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4"
@ -6423,10 +6532,10 @@ data-urls@^1.0.0:
whatwg-mimetype "^2.2.0"
whatwg-url "^7.0.0"
date-fns@2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.4.1.tgz#b53f9bb65ae6bd9239437035710e01cf383b625e"
integrity sha512-2RhmH/sjDSCYW2F3ZQxOUx/I7PvzXpi89aQL2d3OAxSTwLx6NilATeUbe0menFE3Lu5lFkOFci36ivimwYHHxw==
date-fns@2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.5.0.tgz#b939f17c2902ce81cffe449702ba22c0781b38ec"
integrity sha512-I6Tkis01//nRcmvMQw/MRE1HAtcuA5Ie6jGPb8bJZJub7494LGOObqkV3ParnsSVviAjk5C8mNKDqYVBzCopWg==
date-now@^0.1.4:
version "0.1.4"
@ -7015,6 +7124,11 @@ electron-to-chromium@^1.3.122, electron-to-chromium@^1.3.191, electron-to-chromi
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.199.tgz#f9a62a74cda77854310a2abffde8b75591ea09a1"
integrity sha512-gachlDdHSK47s0N2e58GH9HMC6Z4ip0SfmYUa5iEbE50AKaOUXysaJnXMfKj0xB245jWbYcyFSH+th3rqsF8hA==
electron-to-chromium@^1.3.247:
version "1.3.277"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.277.tgz#38b7b297f9b3f67ea900a965c1b11a555de526ec"
integrity sha512-Czmsrgng89DOgJlIknnw9bn5431QdtnUwGp5YYiPwU1DbZQUxCLF+rc1ZC09VNAdalOPcvH6AE8BaA0H5HjI/w==
element-resize-detector@^1.1.15:
version "1.1.15"
resolved "https://registry.yarnpkg.com/element-resize-detector/-/element-resize-detector-1.1.15.tgz#48eba1a2eaa26969a4c998d972171128c971d8d2"
@ -7313,10 +7427,10 @@ eslint-plugin-import@2.18.2:
read-pkg-up "^2.0.0"
resolve "^1.11.0"
eslint-plugin-jest@22.17.0:
version "22.17.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.17.0.tgz#dc170ec8369cd1bff9c5dd8589344e3f73c88cf6"
integrity sha512-WT4DP4RoGBhIQjv+5D0FM20fAdAUstfYAf/mkufLNTojsfgzc5/IYW22cIg/Q4QBavAZsROQlqppiWDpFZDS8Q==
eslint-plugin-jest@22.19.0:
version "22.19.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.19.0.tgz#0cf90946a8c927d40a2c64458c89bb635d0f2a0b"
integrity sha512-4zUc3rh36ds0SXdl2LywT4YWA3zRe8sfLhz8bPp8qQPIKvynTTkNGwmSCMpl5d9QiZE2JxSinGF+WD8yU+O0Lg==
dependencies:
"@typescript-eslint/experimental-utils" "^1.13.0"
@ -7335,10 +7449,10 @@ eslint-plugin-jsx-a11y@6.2.3:
has "^1.0.3"
jsx-ast-utils "^2.2.1"
eslint-plugin-react@7.15.0:
version "7.15.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.15.0.tgz#4808b19cf7b4c439454099d4eb8f0cf0e9fe31dd"
integrity sha512-NbIh/yVXoltm8Df28PiPRanfCZAYubGqXU391MTCpW955Vum7S0nZdQYXGAvDh9ye4aNCmOR6YcYZsfMbEQZQA==
eslint-plugin-react@7.16.0:
version "7.16.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.16.0.tgz#9928e4f3e2122ed3ba6a5b56d0303ba3e41d8c09"
integrity sha512-GacBAATewhhptbK3/vTP09CbFrgUJmBSaaRcWdbQLFvUZy9yVcQxigBNHGPU/KE2AyHpzj3AWXpxoMTsIDiHug==
dependencies:
array-includes "^3.0.3"
doctrine "^2.1.0"
@ -9104,7 +9218,7 @@ global-prefix@^3.0.0:
kind-of "^6.0.2"
which "^1.3.1"
global@^4.3.0, global@^4.3.2:
global@^4.3.0, global@^4.3.2, global@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406"
integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==
@ -12737,6 +12851,13 @@ node-releases@^1.1.13, node-releases@^1.1.25:
dependencies:
semver "^5.3.0"
node-releases@^1.1.29:
version "1.1.34"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.34.tgz#ced4655ee1ba9c3a2c5dcbac385e19434155fd40"
integrity sha512-fNn12JTEfniTuCqo0r9jXgl44+KxRH/huV7zM/KAGOKxDKrHr6EbT7SSs4B+DNxyBE2mks28AD+Jw6PkfY5uwA==
dependencies:
semver "^6.3.0"
nopt@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
@ -14042,6 +14163,11 @@ postcss-value-parser@^4.0.0:
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.0.tgz#99a983d365f7b2ad8d0f9b8c3094926eab4b936d"
integrity sha512-ESPktioptiSUchCKgggAkzdmkgzKfmp0EU8jXH+5kbIUB+unr0Y4CY9SRMvibuvYUBjNh1ACLbxqYNpdTQOteQ==
postcss-value-parser@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz#482282c09a42706d1fc9a069b73f44ec08391dc9"
integrity sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==
postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.5, postcss@^7.0.6:
version "7.0.17"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.17.tgz#4da1bdff5322d4a0acaab4d87f3e782436bad31f"
@ -14051,6 +14177,15 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.1
source-map "^0.6.1"
supports-color "^6.1.0"
postcss@^7.0.18:
version "7.0.18"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.18.tgz#4b9cda95ae6c069c67a4d933029eddd4838ac233"
integrity sha512-/7g1QXXgegpF+9GJj4iN7ChGF40sYuGYJ8WZu8DZWnmhQ/G36hfdk3q9LBJmoK+lZ+yzZ5KYpOoxq7LF1BxE8g==
dependencies:
chalk "^2.4.2"
source-map "^0.6.1"
supports-color "^6.1.0"
pre-commit@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/pre-commit/-/pre-commit-1.2.2.tgz#dbcee0ee9de7235e57f79c56d7ce94641a69eec6"
@ -14629,10 +14764,10 @@ react-dom@^16.8.3:
prop-types "^15.6.2"
scheduler "^0.13.6"
react-draggable@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-3.3.2.tgz#966ef1d90f2387af3c2d8bd3516f601ea42ca359"
integrity sha512-oaz8a6enjbPtx5qb0oDWxtDNuybOylvto1QLydsXgKmwT7e3GXC2eMVDwEMIUYJIFqVG72XpOv673UuuAq6LhA==
react-draggable@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.0.3.tgz#6b9f76f66431c47b9070e9b805bbc520df8ca481"
integrity sha512-4vD6zms+9QGeZ2RQXzlUBw8PBYUXy+dzYX5r22idjp9YwQKIIvD/EojL0rbjS1GK4C3P0rAJnmKa8gDQYWUDyA==
dependencies:
classnames "^2.2.5"
prop-types "^15.6.0"
@ -14683,10 +14818,10 @@ react-helmet-async@^1.0.2:
react-fast-compare "2.0.4"
shallowequal "1.1.0"
react-hot-loader@4.12.14:
version "4.12.14"
resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.12.14.tgz#81ca06ffda0b90aad15d6069339f73ed6428340a"
integrity sha512-ecxH4eBvEaJ9onT8vkEmK1FAAJUh1PqzGqds9S3k+GeihSp7nKAp4fOxytO+Ghr491LiBD38jaKyDXYnnpI9pQ==
react-hot-loader@4.12.15:
version "4.12.15"
resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.12.15.tgz#6bf3984e52edbdf02ea8952777f53da1b3c68c95"
integrity sha512-sgkN6g+tgPE6xZzD0Ysqll7KUFYJbMX0DrczT5OxD6S7hZlSnmqSC3ceudwCkiDd65ZTtm+Ayk4Y9k5xxCvpOw==
dependencies:
fast-levenshtein "^2.0.6"
global "^4.3.0"
@ -15858,6 +15993,14 @@ schema-utils@^2.0.1:
ajv "^6.1.0"
ajv-keywords "^3.1.0"
schema-utils@^2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.4.1.tgz#e89ade5d056dc8bcaca377574bb4a9c4e1b8be56"
integrity sha512-RqYLpkPZX5Oc3fw/kHHHyP56fg5Y+XBpIpV8nCg0znIALfq3OH+Ea9Hfeac9BAMwG5IICltiZ0vxFvJQONfA5w==
dependencies:
ajv "^6.10.2"
ajv-keywords "^3.4.1"
scrypt-js@2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-2.0.3.tgz#bb0040be03043da9a012a2cea9fc9f852cfc87d4"
@ -17040,17 +17183,18 @@ telejson@^2.2.1:
lodash.get "^4.4.2"
memoizerific "^1.11.3"
telejson@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/telejson/-/telejson-2.2.2.tgz#d61d721d21849a6e4070d547aab302a9bd22c720"
integrity sha512-YyNwnKY0ilabOwYgC/J754En1xOe5PBIUIw+C9e0+5HjVVcnQE5/gdu2yET2pmSbp5bxIDqYNjvndj2PUkIiYA==
telejson@^3.0.2:
version "3.0.3"
resolved "https://registry.yarnpkg.com/telejson/-/telejson-3.0.3.tgz#442af55f78d791d3744c9e7a696be6cdf789a4b5"
integrity sha512-gUOh6wox1zJjbGMg+e26NquZcp/F18EbIaqVvjiGqikRqVB4fYEAM8Nyin8smgwX30XhaRBOg+kCj4vInmvwAg==
dependencies:
global "^4.3.2"
"@types/is-function" "^1.0.0"
global "^4.4.0"
is-function "^1.0.1"
is-regex "^1.0.4"
is-symbol "^1.0.2"
isobject "^3.0.1"
lodash "^4.17.11"
isobject "^4.0.0"
lodash "^4.17.15"
memoizerific "^1.11.3"
temp@^0.8.3:
@ -17865,10 +18009,10 @@ truffle-workflow-compile@^2.1.3:
truffle-external-compile "^1.0.15"
truffle-resolver "^5.0.15"
truffle@5.0.39:
version "5.0.39"
resolved "https://registry.yarnpkg.com/truffle/-/truffle-5.0.39.tgz#5710ba8f60a7184d9eb51d632308f2af0a2e8aff"
integrity sha512-2a17t4o6r0rNMpeQXBc51nXigtIaP9/sU8N2zflaazvzYgDgLMZfqh/dir2mTfyybOsrR47NL310p+6+c8u8VA==
truffle@5.0.40:
version "5.0.40"
resolved "https://registry.yarnpkg.com/truffle/-/truffle-5.0.40.tgz#3fb238f0e17662df871f862981bea4109e68bf9f"
integrity sha512-UO2bVpDJRzR1oF6LgdxdpqUHdbvYJxfaqEkVDUrziIRs6Sr7CkOzHew8hexUgkqxA7wtbDmM9FNcj+9Yoa5csA==
dependencies:
app-module-path "^2.2.0"
mocha "5.2.0"
@ -18242,7 +18386,16 @@ urix@^0.1.0:
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
url-loader@^2.0.1, url-loader@^2.1.0:
url-loader@2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-2.2.0.tgz#af321aece1fd0d683adc8aaeb27829f29c75b46e"
integrity sha512-G8nk3np8ZAnwhHXas1JxJEwJyQdqFXAKJehfgZ/XrC48volFBRtO+FIKtF2u0Ma3bw+4vnDVjHPAQYlF9p2vsw==
dependencies:
loader-utils "^1.2.3"
mime "^2.4.4"
schema-utils "^2.4.1"
url-loader@^2.0.1:
version "2.1.0"
resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-2.1.0.tgz#bcc1ecabbd197e913eca23f5e0378e24b4412961"
integrity sha512-kVrp/8VfEm5fUt+fl2E0FQyrpmOYgMEkBsv8+UDP1wFhszECq5JyGF33I7cajlVY90zRZ6MyfgKXngLvHYZX8A==
@ -19577,10 +19730,10 @@ webidl-conversions@^4.0.2:
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
webpack-bundle-analyzer@3.5.2:
version "3.5.2"
resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.5.2.tgz#ac02834f4b31de8e27d71e6c7a612301ebddb79f"
integrity sha512-g9spCNe25QYUVqHRDkwG414GTok2m7pTTP0wr6l0J50Z3YLS04+BGodTqqoVBL7QfU/U/9p/oiI5XFOyfZ7S/A==
webpack-bundle-analyzer@3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.6.0.tgz#39b3a8f829ca044682bc6f9e011c95deb554aefd"
integrity sha512-orUfvVYEfBMDXgEKAKVvab5iQ2wXneIEorGNsyuOyVYpjYrI7CUOhhXNDd3huMwQ3vNNWWlGP+hzflMFYNzi2g==
dependencies:
acorn "^6.0.7"
acorn-walk "^6.1.1"
@ -19717,10 +19870,10 @@ webpack-sources@^1.4.0, webpack-sources@^1.4.1:
source-list-map "^2.0.0"
source-map "~0.6.1"
webpack@4.41.0:
version "4.41.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.0.tgz#db6a254bde671769f7c14e90a1a55e73602fc70b"
integrity sha512-yNV98U4r7wX1VJAj5kyMsu36T8RPPQntcb5fJLOsMz/pt/WrKC0Vp1bAlqPLkA1LegSwQwf6P+kAbyhRKVQ72g==
webpack@4.41.2:
version "4.41.2"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.2.tgz#c34ec76daa3a8468c9b61a50336d8e3303dce74e"
integrity sha512-Zhw69edTGfbz9/8JJoyRQ/pq8FYUoY0diOXqW0T6yhgdhCv6wr0hra5DwwWexNRns2Z2+gsnrNcbe9hbGBgk/A==
dependencies:
"@webassemblyjs/ast" "1.8.5"
"@webassemblyjs/helper-module-context" "1.8.5"