commit
625e414fcf
|
@ -3,4 +3,5 @@ build_webpack/
|
||||||
build_storybook/
|
build_storybook/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
build/
|
build/
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
|
.env.*
|
42
package.json
42
package.json
|
@ -33,14 +33,14 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gnosis.pm/safe-contracts": "^1.0.0",
|
"@gnosis.pm/safe-contracts": "^1.0.0",
|
||||||
"@gnosis.pm/util-contracts": "2.0.4",
|
"@gnosis.pm/util-contracts": "2.0.4",
|
||||||
"@material-ui/core": "4.5.0",
|
"@material-ui/core": "4.5.1",
|
||||||
"@material-ui/icons": "4.4.3",
|
"@material-ui/icons": "4.5.1",
|
||||||
"@testing-library/jest-dom": "4.1.0",
|
"@testing-library/jest-dom": "4.1.2",
|
||||||
"@welldone-software/why-did-you-render": "3.3.5",
|
"@welldone-software/why-did-you-render": "3.3.8",
|
||||||
"axios": "0.19.0",
|
"axios": "0.19.0",
|
||||||
"bignumber.js": "9.0.0",
|
"bignumber.js": "9.0.0",
|
||||||
"connected-react-router": "6.5.2",
|
"connected-react-router": "6.5.2",
|
||||||
"date-fns": "2.4.1",
|
"date-fns": "2.5.0",
|
||||||
"ethereum-ens": "0.7.8",
|
"ethereum-ens": "0.7.8",
|
||||||
"final-form": "4.18.5",
|
"final-form": "4.18.5",
|
||||||
"history": "4.10.1",
|
"history": "4.10.1",
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
"react-dom": "16.10.2",
|
"react-dom": "16.10.2",
|
||||||
"react-final-form": "6.3.0",
|
"react-final-form": "6.3.0",
|
||||||
"react-final-form-listeners": "^1.0.2",
|
"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-infinite-scroll-component": "4.5.3",
|
||||||
"react-qr-reader": "^2.2.1",
|
"react-qr-reader": "^2.2.1",
|
||||||
"react-redux": "7.1.1",
|
"react-redux": "7.1.1",
|
||||||
|
@ -68,8 +68,8 @@
|
||||||
"web3": "1.2.1"
|
"web3": "1.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "7.6.2",
|
"@babel/cli": "7.6.4",
|
||||||
"@babel/core": "7.6.2",
|
"@babel/core": "7.6.4",
|
||||||
"@babel/plugin-proposal-class-properties": "7.5.5",
|
"@babel/plugin-proposal-class-properties": "7.5.5",
|
||||||
"@babel/plugin-proposal-decorators": "7.6.0",
|
"@babel/plugin-proposal-decorators": "7.6.0",
|
||||||
"@babel/plugin-proposal-do-expressions": "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-member-expression-literals": "^7.2.0",
|
||||||
"@babel/plugin-transform-property-literals": "^7.2.0",
|
"@babel/plugin-transform-property-literals": "^7.2.0",
|
||||||
"@babel/polyfill": "7.6.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-flow": "^7.0.0",
|
||||||
"@babel/preset-react": "^7.0.0",
|
"@babel/preset-react": "7.6.3",
|
||||||
"@sambego/storybook-state": "^1.3.6",
|
"@sambego/storybook-state": "^1.3.6",
|
||||||
"@storybook/addon-actions": "5.2.1",
|
"@storybook/addon-actions": "5.2.4",
|
||||||
"@storybook/addon-knobs": "5.2.1",
|
"@storybook/addon-knobs": "5.2.4",
|
||||||
"@storybook/addon-links": "5.2.1",
|
"@storybook/addon-links": "5.2.4",
|
||||||
"@storybook/react": "5.2.1",
|
"@storybook/react": "5.2.4",
|
||||||
"@testing-library/react": "9.3.0",
|
"@testing-library/react": "9.3.0",
|
||||||
"autoprefixer": "9.6.1",
|
"autoprefixer": "9.6.5",
|
||||||
"babel-core": "^7.0.0-bridge.0",
|
"babel-core": "^7.0.0-bridge.0",
|
||||||
"babel-eslint": "10.0.3",
|
"babel-eslint": "10.0.3",
|
||||||
"babel-jest": "24.9.0",
|
"babel-jest": "24.9.0",
|
||||||
|
@ -113,9 +113,9 @@
|
||||||
"eslint-config-airbnb": "18.0.1",
|
"eslint-config-airbnb": "18.0.1",
|
||||||
"eslint-plugin-flowtype": "4.3.0",
|
"eslint-plugin-flowtype": "4.3.0",
|
||||||
"eslint-plugin-import": "2.18.2",
|
"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-jsx-a11y": "6.2.3",
|
||||||
"eslint-plugin-react": "7.15.0",
|
"eslint-plugin-react": "7.16.0",
|
||||||
"ethereumjs-abi": "0.6.8",
|
"ethereumjs-abi": "0.6.8",
|
||||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||||
"file-loader": "4.2.0",
|
"file-loader": "4.2.0",
|
||||||
|
@ -136,13 +136,13 @@
|
||||||
"storybook-host": "5.1.0",
|
"storybook-host": "5.1.0",
|
||||||
"storybook-router": "^0.3.4",
|
"storybook-router": "^0.3.4",
|
||||||
"style-loader": "1.0.0",
|
"style-loader": "1.0.0",
|
||||||
"truffle": "5.0.39",
|
"truffle": "5.0.40",
|
||||||
"truffle-contract": "4.0.31",
|
"truffle-contract": "4.0.31",
|
||||||
"truffle-solidity-loader": "0.1.32",
|
"truffle-solidity-loader": "0.1.32",
|
||||||
"uglifyjs-webpack-plugin": "2.2.0",
|
"uglifyjs-webpack-plugin": "2.2.0",
|
||||||
"url-loader": "^2.1.0",
|
"url-loader": "2.2.0",
|
||||||
"webpack": "4.41.0",
|
"webpack": "4.41.2",
|
||||||
"webpack-bundle-analyzer": "3.5.2",
|
"webpack-bundle-analyzer": "3.6.0",
|
||||||
"webpack-cli": "3.3.9",
|
"webpack-cli": "3.3.9",
|
||||||
"webpack-dev-server": "3.8.2",
|
"webpack-dev-server": "3.8.2",
|
||||||
"webpack-manifest-plugin": "2.2.0"
|
"webpack-manifest-plugin": "2.2.0"
|
||||||
|
|
|
@ -1,14 +1,20 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React, { Component } from 'react'
|
import { Component } from 'react'
|
||||||
|
import { List } from 'immutable'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { withSnackbar } from 'notistack'
|
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'
|
import selector from './selector'
|
||||||
|
|
||||||
class Notifier extends Component {
|
type Props = Actions & {
|
||||||
|
notifications: List<Notification>,
|
||||||
|
}
|
||||||
|
|
||||||
|
class Notifier extends Component<Props> {
|
||||||
displayed = []
|
displayed = []
|
||||||
|
|
||||||
shouldComponentUpdate({ notifications: newSnacks = [] }) {
|
shouldComponentUpdate({ notifications: newSnacks = List() }) {
|
||||||
const { notifications: currentSnacks, closeSnackbar, removeSnackbar } = this.props
|
const { notifications: currentSnacks, closeSnackbar, removeSnackbar } = this.props
|
||||||
|
|
||||||
if (!newSnacks.size) {
|
if (!newSnacks.size) {
|
||||||
|
@ -27,7 +33,7 @@ class Notifier extends Component {
|
||||||
if (notExists) {
|
if (notExists) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
notExists = notExists || !currentSnacks.filter(({ key }) => newSnack.key === key).length
|
notExists = notExists || !currentSnacks.filter(({ key }) => newSnack.key === key).size
|
||||||
}
|
}
|
||||||
return notExists
|
return notExists
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,8 +50,8 @@ export const minValue = (min: number) => (value: string) => {
|
||||||
return `Should be at least ${min}`
|
return `Should be at least ${min}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const maxValue = (max: number) => (value: string) => {
|
export const maxValue = (max: number | string) => (value: string) => {
|
||||||
if (Number.isNaN(Number(value)) || Number.parseInt(value, 10) <= Number(max)) {
|
if (Number.isNaN(Number(value)) || parseFloat(value, 10) <= parseFloat(max, 10)) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
import * as React from 'react'
|
||||||
import classNames from 'classnames/bind'
|
import classNames from 'classnames/bind'
|
||||||
import React from 'react'
|
|
||||||
import { capitalize } from '~/utils/css'
|
import { capitalize } from '~/utils/css'
|
||||||
import styles from './index.scss'
|
import styles from './index.scss'
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import contract from 'truffle-contract'
|
import contract from 'truffle-contract'
|
||||||
import ProxyFactorySol from '@gnosis.pm/safe-contracts/build/contracts/ProxyFactory.json'
|
import ProxyFactorySol from '@gnosis.pm/safe-contracts/build/contracts/ProxyFactory.json'
|
||||||
import GnosisSafeSol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafe.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 { ensureOnce } from '~/utils/singleton'
|
||||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||||
import { calculateGasOf, calculateGasPrice } from '~/logic/wallets/ethTransactions'
|
import { calculateGasOf, calculateGasPrice } from '~/logic/wallets/ethTransactions'
|
||||||
|
@ -99,3 +100,27 @@ export const getGnosisSafeInstanceAt = async (safeAddress: string) => {
|
||||||
|
|
||||||
return gnosisSafe
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { Close as IconClose } from '@material-ui/icons'
|
||||||
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
|
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
|
||||||
import { type Notification, NOTIFICATIONS } from './notificationTypes'
|
import { type Notification, NOTIFICATIONS } from './notificationTypes'
|
||||||
|
|
||||||
type NotificationsQueue = {
|
export type NotificationsQueue = {
|
||||||
beforeExecution: Notification,
|
beforeExecution: Notification,
|
||||||
pendingExecution: {
|
pendingExecution: {
|
||||||
noMoreConfirmationsNeeded: Notification,
|
noMoreConfirmationsNeeded: Notification,
|
||||||
|
@ -104,7 +104,7 @@ const defaultNotificationsQueue: NotificationsQueue = {
|
||||||
afterExecutionError: NOTIFICATIONS.TX_FAILED_MSG,
|
afterExecutionError: NOTIFICATIONS.TX_FAILED_MSG,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getNofiticationsFromTxType = (txType: string) => {
|
export const getNotificationsFromTxType = (txType: string) => {
|
||||||
let notificationsQueue: NotificationsQueue
|
let notificationsQueue: NotificationsQueue
|
||||||
|
|
||||||
switch (txType) {
|
switch (txType) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { createAction } from 'redux-actions'
|
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 GlobalState } from '~/store'
|
||||||
import { type NotificationProps } from '~/logic/notifications/store/models/notification'
|
import { type NotificationProps } from '~/logic/notifications/store/models/notification'
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ const addSnackbar = createAction<string, *>(ENQUEUE_SNACKBAR)
|
||||||
|
|
||||||
const enqueueSnackbar = (notification: NotificationProps) => (
|
const enqueueSnackbar = (notification: NotificationProps) => (
|
||||||
dispatch: ReduxDispatch<GlobalState>,
|
dispatch: ReduxDispatch<GlobalState>,
|
||||||
getState: GetState<GlobalState>,
|
|
||||||
) => {
|
) => {
|
||||||
const newNotification = {
|
const newNotification = {
|
||||||
...notification,
|
...notification,
|
||||||
|
|
|
@ -29,7 +29,5 @@ export default handleActions<NotificationReducerState, *>(
|
||||||
return state.delete(key)
|
return state.delete(key)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Map({
|
Map(),
|
||||||
notifications: Map(),
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { type GlobalState } from '~/store'
|
||||||
import { NOTIFICATIONS_REDUCER_ID } from '~/logic/notifications/store/reducer/notifications'
|
import { NOTIFICATIONS_REDUCER_ID } from '~/logic/notifications/store/reducer/notifications'
|
||||||
import { type Notification } from '~/logic/notifications/store/models/notification'
|
import { type Notification } from '~/logic/notifications/store/models/notification'
|
||||||
|
|
||||||
export const notificationsMapSelector = (
|
const notificationsMapSelector = (
|
||||||
state: GlobalState,
|
state: GlobalState,
|
||||||
): Map<string, Notification> => state[NOTIFICATIONS_REDUCER_ID]
|
): Map<string, Notification> => state[NOTIFICATIONS_REDUCER_ID]
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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',
|
||||||
|
},
|
||||||
|
]
|
|
@ -17,7 +17,11 @@ const lt1000tFormatter = new Intl.NumberFormat([], { maximumFractionDigits: 3, n
|
||||||
export const formatAmount = (number: string | number) => {
|
export const formatAmount = (number: string | number) => {
|
||||||
let numberFloat = parseFloat(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)
|
numberFloat = lt1kFormatter.format(numberFloat)
|
||||||
} else if (numberFloat < 10000) {
|
} else if (numberFloat < 10000) {
|
||||||
numberFloat = lt10kFormatter.format(numberFloat)
|
numberFloat = lt10kFormatter.format(numberFloat)
|
||||||
|
|
|
@ -56,6 +56,6 @@ export const calculateGasOf = async (data: Object, from: string, to: string) =>
|
||||||
|
|
||||||
return gas * 2
|
return gas * 2
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return Promise.reject(new Error(err))
|
return Promise.reject(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,7 @@ const getProviderName: Function = (web3Provider): string => {
|
||||||
return name
|
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()
|
const accounts = await web3Provider.eth.getAccounts()
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'test' && window.testAccountIndex) {
|
if (process.env.NODE_ENV === 'test' && window.testAccountIndex) {
|
||||||
|
|
|
@ -64,7 +64,7 @@ const Routes = ({ defaultSafe, location }: RoutesProps) => {
|
||||||
/>
|
/>
|
||||||
<Route exact path={WELCOME_ADDRESS} component={Welcome} />
|
<Route exact path={WELCOME_ADDRESS} component={Welcome} />
|
||||||
<Route exact path={OPEN_ADDRESS} component={Open} />
|
<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={OPENING_ADDRESS} component={Opening} />
|
||||||
<Route exact path={LOAD_ADDRESS} component={Load} />
|
<Route exact path={LOAD_ADDRESS} component={Load} />
|
||||||
<Redirect to="/" />
|
<Redirect to="/" />
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
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 InputAdornment from '@material-ui/core/InputAdornment'
|
||||||
import CheckCircle from '@material-ui/icons/CheckCircle'
|
import CheckCircle from '@material-ui/icons/CheckCircle'
|
||||||
import Field from '~/components/forms/Field'
|
import Field from '~/components/forms/Field'
|
||||||
|
@ -15,7 +14,7 @@ import Paragraph from '~/components/layout/Paragraph'
|
||||||
import OpenPaper from '~/components/Stepper/OpenPaper'
|
import OpenPaper from '~/components/Stepper/OpenPaper'
|
||||||
import { FIELD_LOAD_NAME, FIELD_LOAD_ADDRESS } from '~/routes/load/components/fields'
|
import { FIELD_LOAD_NAME, FIELD_LOAD_ADDRESS } from '~/routes/load/components/fields'
|
||||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||||
import { getSafeMasterContract } from '~/logic/contracts/safeContracts'
|
import { getSafeMasterContract, validateProxy } from '~/logic/contracts/safeContracts'
|
||||||
import { secondary } from '~/theme/variables'
|
import { secondary } from '~/theme/variables'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -56,15 +55,8 @@ export const safeFieldsValidation = async (values: Object) => {
|
||||||
return errors
|
return errors
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://solidity.readthedocs.io/en/latest/metadata.html#usage-for-source-code-verification
|
const isValidProxy = await validateProxy(safeAddress)
|
||||||
const metaData = 'a165'
|
if (!isValidProxy) {
|
||||||
|
|
||||||
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) {
|
|
||||||
errors[FIELD_LOAD_ADDRESS] = SAFE_INSTANCE_ERROR
|
errors[FIELD_LOAD_ADDRESS] = SAFE_INSTANCE_ERROR
|
||||||
return errors
|
return errors
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import OpenPaper from '~/components/Stepper/OpenPaper'
|
||||||
import Col from '~/components/layout/Col'
|
import Col from '~/components/layout/Col'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
|
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
|
||||||
import {
|
import {
|
||||||
sm, md, lg, border, background,
|
sm, md, lg, border, background,
|
||||||
} from '~/theme/variables'
|
} from '~/theme/variables'
|
||||||
|
@ -73,7 +74,7 @@ type Props = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReviewComponent = ({ values, classes, userAccount }: 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 names = getNamesFrom(values)
|
||||||
const addresses = getAccountsFrom(values)
|
const addresses = getAccountsFrom(values)
|
||||||
const numOwners = getNumOwnersFrom(values)
|
const numOwners = getNumOwnersFrom(values)
|
||||||
|
@ -85,9 +86,9 @@ const ReviewComponent = ({ values, classes, userAccount }: Props) => {
|
||||||
const { fromWei, toBN } = web3.utils
|
const { fromWei, toBN } = web3.utils
|
||||||
const estimatedGasCosts = await estimateGasForDeployingSafe(addresses, numOwners, userAccount)
|
const estimatedGasCosts = await estimateGasForDeployingSafe(addresses, numOwners, userAccount)
|
||||||
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
|
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
|
||||||
const roundedGasCosts = parseFloat(gasCostsAsEth).toFixed(3)
|
const formattedGasCosts = formatAmount(gasCostsAsEth)
|
||||||
if (isCurrent) {
|
if (isCurrent) {
|
||||||
setGasCosts(roundedGasCosts)
|
setGasCosts(formattedGasCosts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ import { getAccountsFrom } from '~/routes/open/utils/safeDataExtractor'
|
||||||
import Hairline from '~/components/layout/Hairline'
|
import Hairline from '~/components/layout/Hairline'
|
||||||
import trash from '~/assets/icons/trash.svg'
|
import trash from '~/assets/icons/trash.svg'
|
||||||
import QRIcon from '~/assets/icons/qrcode.svg'
|
import QRIcon from '~/assets/icons/qrcode.svg'
|
||||||
import ScanQRModal from './ScanQRModal'
|
import ScanQRModal from '~/components/ScanQRModal'
|
||||||
import { getAddressValidator } from './validators'
|
import { getAddressValidator } from './validators'
|
||||||
import { styles } from './style'
|
import { styles } from './style'
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,17 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import OpenInNew from '@material-ui/icons/OpenInNew'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
import Col from '~/components/layout/Col'
|
import Col from '~/components/layout/Col'
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
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 Bold from '~/components/layout/Bold'
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
import Identicon from '~/components/Identicon'
|
import Identicon from '~/components/Identicon'
|
||||||
import { copyToClipboard } from '~/utils/clipboard'
|
import { xs, border } from '~/theme/variables'
|
||||||
import { secondary, xs, border } from '~/theme/variables'
|
|
||||||
|
|
||||||
const openIconStyle = {
|
const useStyles = makeStyles({
|
||||||
height: '16px',
|
|
||||||
color: secondary,
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = () => ({
|
|
||||||
balanceContainer: {
|
balanceContainer: {
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
lineHeight: 1.08,
|
lineHeight: 1.08,
|
||||||
|
@ -28,20 +22,22 @@ const styles = () => ({
|
||||||
marginTop: xs,
|
marginTop: xs,
|
||||||
borderRadius: '3px',
|
borderRadius: '3px',
|
||||||
},
|
},
|
||||||
|
address: {
|
||||||
|
marginRight: xs,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
classes: Object,
|
|
||||||
safeAddress: string,
|
safeAddress: string,
|
||||||
etherScanLink: string,
|
|
||||||
safeName: string,
|
safeName: string,
|
||||||
ethBalance: string,
|
ethBalance: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
const SafeInfo = (props: Props) => {
|
const SafeInfo = (props: Props) => {
|
||||||
const {
|
const {
|
||||||
safeAddress, safeName, etherScanLink, ethBalance, classes,
|
safeAddress, safeName, ethBalance,
|
||||||
} = props
|
} = props
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row margin="md">
|
<Row margin="md">
|
||||||
|
@ -52,21 +48,18 @@ const SafeInfo = (props: Props) => {
|
||||||
<Paragraph weight="bolder" noMargin style={{ lineHeight: 1 }}>
|
<Paragraph weight="bolder" noMargin style={{ lineHeight: 1 }}>
|
||||||
{safeName}
|
{safeName}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Paragraph weight="bolder" onClick={copyToClipboard} noMargin>
|
<Block justify="left">
|
||||||
{safeAddress}
|
<Paragraph weight="bolder" className={classes.address} noMargin>
|
||||||
<Link to={etherScanLink} target="_blank">
|
{safeAddress}
|
||||||
<OpenInNew style={openIconStyle} />
|
</Paragraph>
|
||||||
</Link>
|
<CopyBtn content={safeAddress} />
|
||||||
</Paragraph>
|
<EtherscanBtn type="address" value={safeAddress} />
|
||||||
|
</Block>
|
||||||
<Block className={classes.balanceContainer}>
|
<Block className={classes.balanceContainer}>
|
||||||
<Paragraph noMargin>
|
<Paragraph noMargin>
|
||||||
Balance:
|
Balance:
|
||||||
{' '}
|
{' '}
|
||||||
<Bold>
|
<Bold>{`${ethBalance} ETH`}</Bold>
|
||||||
{ethBalance}
|
|
||||||
{' '}
|
|
||||||
ETH
|
|
||||||
</Bold>
|
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Block>
|
</Block>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -74,4 +67,4 @@ const SafeInfo = (props: Props) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(SafeInfo)
|
export default SafeInfo
|
||||||
|
|
|
@ -24,7 +24,6 @@ type Props = {
|
||||||
classes: Object,
|
classes: Object,
|
||||||
isOpen: boolean,
|
isOpen: boolean,
|
||||||
safeAddress: string,
|
safeAddress: string,
|
||||||
etherScanLink: string,
|
|
||||||
safeName: string,
|
safeName: string,
|
||||||
ethBalance: string,
|
ethBalance: string,
|
||||||
tokens: List<Token>,
|
tokens: List<Token>,
|
||||||
|
@ -65,7 +64,6 @@ const Send = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
classes,
|
classes,
|
||||||
safeAddress,
|
safeAddress,
|
||||||
etherScanLink,
|
|
||||||
safeName,
|
safeName,
|
||||||
ethBalance,
|
ethBalance,
|
||||||
tokens,
|
tokens,
|
||||||
|
@ -113,7 +111,6 @@ const Send = ({
|
||||||
<SendFunds
|
<SendFunds
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
safeAddress={safeAddress}
|
safeAddress={safeAddress}
|
||||||
etherScanLink={etherScanLink}
|
|
||||||
safeName={safeName}
|
safeName={safeName}
|
||||||
ethBalance={ethBalance}
|
ethBalance={ethBalance}
|
||||||
tokens={tokens}
|
tokens={tokens}
|
||||||
|
@ -128,17 +125,16 @@ const Send = ({
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
setActiveScreen={setActiveScreen}
|
setActiveScreen={setActiveScreen}
|
||||||
safeAddress={safeAddress}
|
safeAddress={safeAddress}
|
||||||
etherScanLink={etherScanLink}
|
|
||||||
safeName={safeName}
|
safeName={safeName}
|
||||||
ethBalance={ethBalance}
|
ethBalance={ethBalance}
|
||||||
createTransaction={createTransaction}
|
createTransaction={createTransaction}
|
||||||
|
tokens={tokens}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{activeScreen === 'sendCustomTx' && (
|
{activeScreen === 'sendCustomTx' && (
|
||||||
<SendCustomTx
|
<SendCustomTx
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
safeAddress={safeAddress}
|
safeAddress={safeAddress}
|
||||||
etherScanLink={etherScanLink}
|
|
||||||
safeName={safeName}
|
safeName={safeName}
|
||||||
ethBalance={ethBalance}
|
ethBalance={ethBalance}
|
||||||
onSubmit={handleCustomTxCreation}
|
onSubmit={handleCustomTxCreation}
|
||||||
|
@ -151,7 +147,6 @@ const Send = ({
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
setActiveScreen={setActiveScreen}
|
setActiveScreen={setActiveScreen}
|
||||||
safeAddress={safeAddress}
|
safeAddress={safeAddress}
|
||||||
etherScanLink={etherScanLink}
|
|
||||||
safeName={safeName}
|
safeName={safeName}
|
||||||
ethBalance={ethBalance}
|
ethBalance={ethBalance}
|
||||||
createTransaction={createTransaction}
|
createTransaction={createTransaction}
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import OpenInNew from '@material-ui/icons/OpenInNew'
|
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
import Close from '@material-ui/icons/Close'
|
import Close from '@material-ui/icons/Close'
|
||||||
import IconButton from '@material-ui/core/IconButton'
|
import IconButton from '@material-ui/core/IconButton'
|
||||||
import { withSnackbar } from 'notistack'
|
import { withSnackbar } from 'notistack'
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
import Link from '~/components/layout/Link'
|
|
||||||
import Col from '~/components/layout/Col'
|
import Col from '~/components/layout/Col'
|
||||||
import Button from '~/components/layout/Button'
|
import Button from '~/components/layout/Button'
|
||||||
import Img from '~/components/layout/Img'
|
import Img from '~/components/layout/Img'
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
import Identicon from '~/components/Identicon'
|
import Identicon from '~/components/Identicon'
|
||||||
import Hairline from '~/components/layout/Hairline'
|
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 SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
|
||||||
import { setImageToPlaceholder } from '~/routes/safe/components/Balances/utils'
|
import { setImageToPlaceholder } from '~/routes/safe/components/Balances/utils'
|
||||||
|
import { estimateTxGasCosts } from '~/logic/safe/transactions/gasNew'
|
||||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||||
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
|
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
|
||||||
import { getEthAsToken } from '~/logic/tokens/utils/tokenHelpers'
|
import { getEthAsToken } from '~/logic/tokens/utils/tokenHelpers'
|
||||||
|
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
|
||||||
import ArrowDown from '../assets/arrow-down.svg'
|
import ArrowDown from '../assets/arrow-down.svg'
|
||||||
import { secondary } from '~/theme/variables'
|
|
||||||
import { styles } from './style'
|
import { styles } from './style'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -29,7 +29,6 @@ type Props = {
|
||||||
setActiveScreen: Function,
|
setActiveScreen: Function,
|
||||||
classes: Object,
|
classes: Object,
|
||||||
safeAddress: string,
|
safeAddress: string,
|
||||||
etherScanLink: string,
|
|
||||||
safeName: string,
|
safeName: string,
|
||||||
ethBalance: string,
|
ethBalance: string,
|
||||||
tx: Object,
|
tx: Object,
|
||||||
|
@ -38,17 +37,11 @@ type Props = {
|
||||||
closeSnackbar: Function,
|
closeSnackbar: Function,
|
||||||
}
|
}
|
||||||
|
|
||||||
const openIconStyle = {
|
|
||||||
height: '16px',
|
|
||||||
color: secondary,
|
|
||||||
}
|
|
||||||
|
|
||||||
const ReviewCustomTx = ({
|
const ReviewCustomTx = ({
|
||||||
onClose,
|
onClose,
|
||||||
setActiveScreen,
|
setActiveScreen,
|
||||||
classes,
|
classes,
|
||||||
safeAddress,
|
safeAddress,
|
||||||
etherScanLink,
|
|
||||||
safeName,
|
safeName,
|
||||||
ethBalance,
|
ethBalance,
|
||||||
tx,
|
tx,
|
||||||
|
@ -56,10 +49,33 @@ const ReviewCustomTx = ({
|
||||||
enqueueSnackbar,
|
enqueueSnackbar,
|
||||||
closeSnackbar,
|
closeSnackbar,
|
||||||
}: Props) => {
|
}: 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 submitTx = async () => {
|
||||||
const web3 = getWeb3()
|
const web3 = getWeb3()
|
||||||
const txRecipient = tx.recipientAddress
|
const txRecipient = tx.recipientAddress
|
||||||
const txData = tx.data
|
const txData = tx.data.trim()
|
||||||
const txValue = tx.value ? web3.utils.toWei(tx.value, 'ether') : 0
|
const txValue = tx.value ? web3.utils.toWei(tx.value, 'ether') : 0
|
||||||
|
|
||||||
createTransaction(
|
createTransaction(
|
||||||
|
@ -87,12 +103,7 @@ const ReviewCustomTx = ({
|
||||||
</Row>
|
</Row>
|
||||||
<Hairline />
|
<Hairline />
|
||||||
<Block className={classes.container}>
|
<Block className={classes.container}>
|
||||||
<SafeInfo
|
<SafeInfo safeAddress={safeAddress} safeName={safeName} ethBalance={ethBalance} />
|
||||||
safeAddress={safeAddress}
|
|
||||||
etherScanLink={etherScanLink}
|
|
||||||
safeName={safeName}
|
|
||||||
ethBalance={ethBalance}
|
|
||||||
/>
|
|
||||||
<Row margin="md">
|
<Row margin="md">
|
||||||
<Col xs={1}>
|
<Col xs={1}>
|
||||||
<img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} />
|
<img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} />
|
||||||
|
@ -111,12 +122,13 @@ const ReviewCustomTx = ({
|
||||||
<Identicon address={tx.recipientAddress} diameter={32} />
|
<Identicon address={tx.recipientAddress} diameter={32} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={11} layout="column">
|
<Col xs={11} layout="column">
|
||||||
<Paragraph weight="bolder" onClick={copyToClipboard} noMargin>
|
<Block justify="left">
|
||||||
{tx.recipientAddress}
|
<Paragraph weight="bolder" className={classes.address} noMargin>
|
||||||
<Link to={etherScanLink} target="_blank">
|
{tx.recipientAddress}
|
||||||
<OpenInNew style={openIconStyle} />
|
</Paragraph>
|
||||||
</Link>
|
<CopyBtn content={tx.recipientAddress} />
|
||||||
</Paragraph>
|
<EtherscanBtn type="address" value={tx.recipientAddress} />
|
||||||
|
</Block>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row margin="xs">
|
<Row margin="xs">
|
||||||
|
@ -125,7 +137,7 @@ const ReviewCustomTx = ({
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Row>
|
</Row>
|
||||||
<Row margin="md" align="center">
|
<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}>
|
<Paragraph size="md" noMargin className={classes.value}>
|
||||||
{tx.value || 0}
|
{tx.value || 0}
|
||||||
{' ETH'}
|
{' ETH'}
|
||||||
|
@ -143,6 +155,11 @@ const ReviewCustomTx = ({
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</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>
|
</Block>
|
||||||
<Hairline />
|
<Hairline />
|
||||||
<Row align="center" className={classes.buttonRow}>
|
<Row align="center" className={classes.buttonRow}>
|
||||||
|
@ -165,5 +182,4 @@ const ReviewCustomTx = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default withStyles(styles)(withSnackbar(ReviewCustomTx))
|
export default withStyles(styles)(withSnackbar(ReviewCustomTx))
|
||||||
|
|
|
@ -1,30 +1,32 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { List } from 'immutable'
|
||||||
import { BigNumber } from 'bignumber.js'
|
import { BigNumber } from 'bignumber.js'
|
||||||
import OpenInNew from '@material-ui/icons/OpenInNew'
|
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
import Close from '@material-ui/icons/Close'
|
import Close from '@material-ui/icons/Close'
|
||||||
import IconButton from '@material-ui/core/IconButton'
|
import IconButton from '@material-ui/core/IconButton'
|
||||||
import { withSnackbar } from 'notistack'
|
import { withSnackbar } from 'notistack'
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
import Link from '~/components/layout/Link'
|
|
||||||
import Col from '~/components/layout/Col'
|
import Col from '~/components/layout/Col'
|
||||||
import Button from '~/components/layout/Button'
|
import Button from '~/components/layout/Button'
|
||||||
import Img from '~/components/layout/Img'
|
import Img from '~/components/layout/Img'
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
|
import EtherscanBtn from '~/components/EtherscanBtn'
|
||||||
|
import CopyBtn from '~/components/CopyBtn'
|
||||||
import Identicon from '~/components/Identicon'
|
import Identicon from '~/components/Identicon'
|
||||||
import { copyToClipboard } from '~/utils/clipboard'
|
|
||||||
import Hairline from '~/components/layout/Hairline'
|
import Hairline from '~/components/layout/Hairline'
|
||||||
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
|
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
|
||||||
import { setImageToPlaceholder } from '~/routes/safe/components/Balances/utils'
|
import { setImageToPlaceholder } from '~/routes/safe/components/Balances/utils'
|
||||||
import { getHumanFriendlyToken } from '~/logic/tokens/store/actions/fetchTokens'
|
import { getHumanFriendlyToken } from '~/logic/tokens/store/actions/fetchTokens'
|
||||||
|
import { estimateTxGasCosts } from '~/logic/safe/transactions/gasNew'
|
||||||
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
|
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 { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||||
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
|
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
|
||||||
|
import { ETH_ADDRESS } from '~/logic/tokens/utils/tokenHelpers'
|
||||||
import ArrowDown from '../assets/arrow-down.svg'
|
import ArrowDown from '../assets/arrow-down.svg'
|
||||||
import { secondary } from '~/theme/variables'
|
|
||||||
import { isEther } from '~/logic/tokens/utils/tokenHelpers'
|
|
||||||
import { styles } from './style'
|
import { styles } from './style'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -32,43 +34,70 @@ type Props = {
|
||||||
setActiveScreen: Function,
|
setActiveScreen: Function,
|
||||||
classes: Object,
|
classes: Object,
|
||||||
safeAddress: string,
|
safeAddress: string,
|
||||||
etherScanLink: string,
|
|
||||||
safeName: string,
|
safeName: string,
|
||||||
ethBalance: string,
|
ethBalance: string,
|
||||||
tx: Object,
|
tx: Object,
|
||||||
|
tokens: List<Token>,
|
||||||
createTransaction: Function,
|
createTransaction: Function,
|
||||||
enqueueSnackbar: Function,
|
enqueueSnackbar: Function,
|
||||||
closeSnackbar: Function,
|
closeSnackbar: Function,
|
||||||
}
|
}
|
||||||
|
|
||||||
const openIconStyle = {
|
|
||||||
height: '16px',
|
|
||||||
color: secondary,
|
|
||||||
}
|
|
||||||
|
|
||||||
const ReviewTx = ({
|
const ReviewTx = ({
|
||||||
onClose,
|
onClose,
|
||||||
setActiveScreen,
|
setActiveScreen,
|
||||||
classes,
|
classes,
|
||||||
safeAddress,
|
safeAddress,
|
||||||
etherScanLink,
|
|
||||||
safeName,
|
safeName,
|
||||||
ethBalance,
|
ethBalance,
|
||||||
tx,
|
tx,
|
||||||
|
tokens,
|
||||||
createTransaction,
|
createTransaction,
|
||||||
enqueueSnackbar,
|
enqueueSnackbar,
|
||||||
closeSnackbar,
|
closeSnackbar,
|
||||||
}: Props) => {
|
}: 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 submitTx = async () => {
|
||||||
const web3 = getWeb3()
|
const web3 = getWeb3()
|
||||||
const isSendingETH = isEther(tx.token.symbol)
|
|
||||||
const txRecipient = isSendingETH ? tx.recipientAddress : tx.token.address
|
|
||||||
let txData = EMPTY_DATA
|
let txData = EMPTY_DATA
|
||||||
let txAmount = web3.utils.toWei(tx.amount, 'ether')
|
let txAmount = web3.utils.toWei(tx.amount, 'ether')
|
||||||
|
|
||||||
if (!isSendingETH) {
|
if (!isSendingETH) {
|
||||||
const HumanFriendlyToken = await getHumanFriendlyToken()
|
const HumanFriendlyToken = await getHumanFriendlyToken()
|
||||||
const tokenInstance = await HumanFriendlyToken.at(tx.token.address)
|
const tokenInstance = await HumanFriendlyToken.at(txToken.address)
|
||||||
const decimals = await tokenInstance.decimals()
|
const decimals = await tokenInstance.decimals()
|
||||||
txAmount = new BigNumber(tx.amount).times(10 ** decimals.toNumber()).toString()
|
txAmount = new BigNumber(tx.amount).times(10 ** decimals.toNumber()).toString()
|
||||||
|
|
||||||
|
@ -104,12 +133,7 @@ const ReviewTx = ({
|
||||||
</Row>
|
</Row>
|
||||||
<Hairline />
|
<Hairline />
|
||||||
<Block className={classes.container}>
|
<Block className={classes.container}>
|
||||||
<SafeInfo
|
<SafeInfo safeAddress={safeAddress} safeName={safeName} ethBalance={ethBalance} />
|
||||||
safeAddress={safeAddress}
|
|
||||||
etherScanLink={etherScanLink}
|
|
||||||
safeName={safeName}
|
|
||||||
ethBalance={ethBalance}
|
|
||||||
/>
|
|
||||||
<Row margin="md">
|
<Row margin="md">
|
||||||
<Col xs={1}>
|
<Col xs={1}>
|
||||||
<img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} />
|
<img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} />
|
||||||
|
@ -128,12 +152,13 @@ const ReviewTx = ({
|
||||||
<Identicon address={tx.recipientAddress} diameter={32} />
|
<Identicon address={tx.recipientAddress} diameter={32} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={11} layout="column">
|
<Col xs={11} layout="column">
|
||||||
<Paragraph weight="bolder" onClick={copyToClipboard} noMargin>
|
<Block justify="left">
|
||||||
{tx.recipientAddress}
|
<Paragraph weight="bolder" className={classes.address} noMargin>
|
||||||
<Link to={etherScanLink} target="_blank">
|
{tx.recipientAddress}
|
||||||
<OpenInNew style={openIconStyle} />
|
</Paragraph>
|
||||||
</Link>
|
<CopyBtn content={tx.recipientAddress} />
|
||||||
</Paragraph>
|
<EtherscanBtn type="address" value={tx.recipientAddress} />
|
||||||
|
</Block>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row margin="xs">
|
<Row margin="xs">
|
||||||
|
@ -142,11 +167,16 @@ const ReviewTx = ({
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Row>
|
</Row>
|
||||||
<Row margin="md" align="center">
|
<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}>
|
<Paragraph size="md" noMargin className={classes.amount}>
|
||||||
{tx.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>
|
</Paragraph>
|
||||||
</Row>
|
</Row>
|
||||||
</Block>
|
</Block>
|
||||||
|
|
|
@ -29,6 +29,9 @@ export const styles = () => ({
|
||||||
amount: {
|
amount: {
|
||||||
marginLeft: sm,
|
marginLeft: sm,
|
||||||
},
|
},
|
||||||
|
address: {
|
||||||
|
marginRight: sm,
|
||||||
|
},
|
||||||
buttonRow: {
|
buttonRow: {
|
||||||
height: '84px',
|
height: '84px',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react'
|
import React, { useState } from 'react'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
import Close from '@material-ui/icons/Close'
|
import Close from '@material-ui/icons/Close'
|
||||||
import InputAdornment from '@material-ui/core/InputAdornment'
|
import InputAdornment from '@material-ui/core/InputAdornment'
|
||||||
|
@ -10,19 +10,19 @@ import GnoForm from '~/components/forms/GnoForm'
|
||||||
import AddressInput from '~/components/forms/AddressInput'
|
import AddressInput from '~/components/forms/AddressInput'
|
||||||
import Col from '~/components/layout/Col'
|
import Col from '~/components/layout/Col'
|
||||||
import Button from '~/components/layout/Button'
|
import Button from '~/components/layout/Button'
|
||||||
|
import ScanQRModal from '~/components/ScanQRModal'
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
|
import Img from '~/components/layout/Img'
|
||||||
import Hairline from '~/components/layout/Hairline'
|
import Hairline from '~/components/layout/Hairline'
|
||||||
import ButtonLink from '~/components/layout/ButtonLink'
|
import ButtonLink from '~/components/layout/ButtonLink'
|
||||||
import Field from '~/components/forms/Field'
|
import Field from '~/components/forms/Field'
|
||||||
import TextField from '~/components/forms/TextField'
|
import TextField from '~/components/forms/TextField'
|
||||||
import TextareaField from '~/components/forms/TextareaField'
|
import TextareaField from '~/components/forms/TextareaField'
|
||||||
import {
|
import {
|
||||||
composeValidators,
|
composeValidators, mustBeFloat, maxValue, mustBeEthereumContractAddress,
|
||||||
mustBeFloat,
|
|
||||||
maxValue,
|
|
||||||
mustBeEthereumContractAddress,
|
|
||||||
} from '~/components/forms/validator'
|
} from '~/components/forms/validator'
|
||||||
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
|
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
|
||||||
|
import QRIcon from '~/assets/icons/qrcode.svg'
|
||||||
import ArrowDown from '../assets/arrow-down.svg'
|
import ArrowDown from '../assets/arrow-down.svg'
|
||||||
import { styles } from './style'
|
import { styles } from './style'
|
||||||
|
|
||||||
|
@ -30,7 +30,6 @@ type Props = {
|
||||||
onClose: () => void,
|
onClose: () => void,
|
||||||
classes: Object,
|
classes: Object,
|
||||||
safeAddress: string,
|
safeAddress: string,
|
||||||
etherScanLink: string,
|
|
||||||
safeName: string,
|
safeName: string,
|
||||||
ethBalance: string,
|
ethBalance: string,
|
||||||
onSubmit: Function,
|
onSubmit: Function,
|
||||||
|
@ -41,18 +40,26 @@ const SendCustomTx = ({
|
||||||
classes,
|
classes,
|
||||||
onClose,
|
onClose,
|
||||||
safeAddress,
|
safeAddress,
|
||||||
etherScanLink,
|
|
||||||
safeName,
|
safeName,
|
||||||
ethBalance,
|
ethBalance,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
initialValues,
|
initialValues,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
|
||||||
const handleSubmit = (values: Object) => {
|
const handleSubmit = (values: Object) => {
|
||||||
if (values.data || values.value) {
|
if (values.data || values.value) {
|
||||||
onSubmit(values)
|
onSubmit(values)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const openQrModal = () => {
|
||||||
|
setQrModalOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeQrModal = () => {
|
||||||
|
setQrModalOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
const formMutators = {
|
const formMutators = {
|
||||||
setMax: (args, state, utils) => {
|
setMax: (args, state, utils) => {
|
||||||
utils.changeValue(state, 'value', () => ethBalance)
|
utils.changeValue(state, 'value', () => ethBalance)
|
||||||
|
@ -78,15 +85,21 @@ const SendCustomTx = ({
|
||||||
{(...args) => {
|
{(...args) => {
|
||||||
const mutators = args[3]
|
const mutators = args[3]
|
||||||
|
|
||||||
|
const handleScan = (value) => {
|
||||||
|
let scannedAddress = value
|
||||||
|
|
||||||
|
if (scannedAddress.startsWith('ethereum:')) {
|
||||||
|
scannedAddress = scannedAddress.replace('ethereum:', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
mutators.setRecipient(scannedAddress)
|
||||||
|
closeQrModal()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Block className={classes.formContainer}>
|
<Block className={classes.formContainer}>
|
||||||
<SafeInfo
|
<SafeInfo safeAddress={safeAddress} safeName={safeName} ethBalance={ethBalance} />
|
||||||
safeAddress={safeAddress}
|
|
||||||
etherScanLink={etherScanLink}
|
|
||||||
safeName={safeName}
|
|
||||||
ethBalance={ethBalance}
|
|
||||||
/>
|
|
||||||
<Row margin="md">
|
<Row margin="md">
|
||||||
<Col xs={1}>
|
<Col xs={1}>
|
||||||
<img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} />
|
<img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} />
|
||||||
|
@ -96,7 +109,7 @@ const SendCustomTx = ({
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row margin="md">
|
<Row margin="md">
|
||||||
<Col xs={12}>
|
<Col xs={11}>
|
||||||
<AddressInput
|
<AddressInput
|
||||||
name="recipientAddress"
|
name="recipientAddress"
|
||||||
component={TextField}
|
component={TextField}
|
||||||
|
@ -107,6 +120,18 @@ const SendCustomTx = ({
|
||||||
validators={[mustBeEthereumContractAddress]}
|
validators={[mustBeEthereumContractAddress]}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</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>
|
||||||
<Row margin="xs">
|
<Row margin="xs">
|
||||||
<Col between="lg">
|
<Col between="lg">
|
||||||
|
@ -124,10 +149,7 @@ const SendCustomTx = ({
|
||||||
name="value"
|
name="value"
|
||||||
component={TextField}
|
component={TextField}
|
||||||
type="text"
|
type="text"
|
||||||
validate={composeValidators(
|
validate={composeValidators(mustBeFloat, maxValue(ethBalance))}
|
||||||
mustBeFloat,
|
|
||||||
maxValue(ethBalance),
|
|
||||||
)}
|
|
||||||
placeholder="Value*"
|
placeholder="Value*"
|
||||||
text="Value*"
|
text="Value*"
|
||||||
className={classes.addressInput}
|
className={classes.addressInput}
|
||||||
|
@ -164,6 +186,7 @@ const SendCustomTx = ({
|
||||||
Review
|
Review
|
||||||
</Button>
|
</Button>
|
||||||
</Row>
|
</Row>
|
||||||
|
{qrModalOpen && <ScanQRModal isOpen={qrModalOpen} onScan={handleScan} onClose={closeQrModal} />}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -21,6 +21,9 @@ export const styles = () => ({
|
||||||
height: '35px',
|
height: '35px',
|
||||||
width: '35px',
|
width: '35px',
|
||||||
},
|
},
|
||||||
|
qrCodeBtn: {
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
formContainer: {
|
formContainer: {
|
||||||
padding: `${md} ${lg}`,
|
padding: `${md} ${lg}`,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React, { useEffect, useState } from 'react'
|
import React from 'react'
|
||||||
import { List } from 'immutable'
|
import { List } from 'immutable'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
import MenuItem from '@material-ui/core/MenuItem'
|
import MenuItem from '@material-ui/core/MenuItem'
|
||||||
|
@ -22,63 +22,57 @@ type SelectFieldProps = {
|
||||||
}
|
}
|
||||||
|
|
||||||
type SelectedTokenProps = {
|
type SelectedTokenProps = {
|
||||||
token?: Token,
|
tokenAddress?: string,
|
||||||
classes: Object,
|
classes: Object,
|
||||||
|
tokens: List<Token>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const SelectedToken = ({ token, classes }: SelectedTokenProps) => (
|
const SelectedToken = ({ tokenAddress, tokens, classes }: SelectedTokenProps) => {
|
||||||
<MenuItem className={classes.container}>
|
const token = tokens.find(({ address }) => address === tokenAddress)
|
||||||
{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])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Field
|
<MenuItem className={classes.container}>
|
||||||
name="token"
|
{token ? (
|
||||||
component={SelectField}
|
<>
|
||||||
classes={{ selectMenu: classes.selectMenu }}
|
<ListItemIcon className={classes.tokenImage}>
|
||||||
validate={required}
|
|
||||||
renderValue={(token) => <SelectedTokenStyled token={token} />}
|
|
||||||
initialValue={initialToken}
|
|
||||||
displayEmpty
|
|
||||||
>
|
|
||||||
{tokens.map((token) => (
|
|
||||||
<MenuItem key={token.address} value={token}>
|
|
||||||
<ListItemIcon>
|
|
||||||
<Img src={token.logoUri} height={28} alt={token.name} onError={setImageToPlaceholder} />
|
<Img src={token.logoUri} height={28} alt={token.name} onError={setImageToPlaceholder} />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText primary={token.name} secondary={`${formatAmount(token.balance)} ${token.symbol}`} />
|
<ListItemText
|
||||||
</MenuItem>
|
className={classes.tokenData}
|
||||||
))}
|
primary={token.name}
|
||||||
</Field>
|
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)
|
export default withStyles(selectStyles)(TokenSelectField)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react'
|
import React, { useState } from 'react'
|
||||||
import { List } from 'immutable'
|
import { List } from 'immutable'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
import { OnChange } from 'react-final-form-listeners'
|
import { OnChange } from 'react-final-form-listeners'
|
||||||
|
@ -13,6 +13,7 @@ import AddressInput from '~/components/forms/AddressInput'
|
||||||
import Col from '~/components/layout/Col'
|
import Col from '~/components/layout/Col'
|
||||||
import Button from '~/components/layout/Button'
|
import Button from '~/components/layout/Button'
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
|
import Img from '~/components/layout/Img'
|
||||||
import Hairline from '~/components/layout/Hairline'
|
import Hairline from '~/components/layout/Hairline'
|
||||||
import ButtonLink from '~/components/layout/ButtonLink'
|
import ButtonLink from '~/components/layout/ButtonLink'
|
||||||
import Field from '~/components/forms/Field'
|
import Field from '~/components/forms/Field'
|
||||||
|
@ -23,14 +24,15 @@ import {
|
||||||
} from '~/components/forms/validator'
|
} from '~/components/forms/validator'
|
||||||
import TokenSelectField from '~/routes/safe/components/Balances/SendModal/screens/SendFunds/TokenSelectField'
|
import TokenSelectField from '~/routes/safe/components/Balances/SendModal/screens/SendFunds/TokenSelectField'
|
||||||
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
|
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
|
||||||
|
import ScanQRModal from '~/components/ScanQRModal'
|
||||||
import ArrowDown from '../assets/arrow-down.svg'
|
import ArrowDown from '../assets/arrow-down.svg'
|
||||||
|
import QRIcon from '~/assets/icons/qrcode.svg'
|
||||||
import { styles } from './style'
|
import { styles } from './style'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClose: () => void,
|
onClose: () => void,
|
||||||
classes: Object,
|
classes: Object,
|
||||||
safeAddress: string,
|
safeAddress: string,
|
||||||
etherScanLink: string,
|
|
||||||
safeName: string,
|
safeName: string,
|
||||||
ethBalance: string,
|
ethBalance: string,
|
||||||
selectedToken: string,
|
selectedToken: string,
|
||||||
|
@ -39,11 +41,22 @@ type Props = {
|
||||||
initialValues: Object,
|
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 = ({
|
const SendFunds = ({
|
||||||
classes,
|
classes,
|
||||||
onClose,
|
onClose,
|
||||||
safeAddress,
|
safeAddress,
|
||||||
etherScanLink,
|
|
||||||
safeName,
|
safeName,
|
||||||
ethBalance,
|
ethBalance,
|
||||||
tokens,
|
tokens,
|
||||||
|
@ -51,22 +64,18 @@ const SendFunds = ({
|
||||||
initialValues,
|
initialValues,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
|
||||||
|
|
||||||
const handleSubmit = (values) => {
|
const handleSubmit = (values) => {
|
||||||
onSubmit(values)
|
onSubmit(values)
|
||||||
}
|
}
|
||||||
|
|
||||||
const formMutators = {
|
const openQrModal = () => {
|
||||||
setMax: (args, state, utils) => {
|
setQrModalOpen(true)
|
||||||
const { token } = state.formState.values
|
}
|
||||||
|
|
||||||
utils.changeValue(state, 'amount', () => token && token.balance)
|
const closeQrModal = () => {
|
||||||
},
|
setQrModalOpen(false)
|
||||||
onTokenChange: (args, state, utils) => {
|
|
||||||
utils.changeValue(state, 'amount', () => '')
|
|
||||||
},
|
|
||||||
setRecipient: (args, state, utils) => {
|
|
||||||
utils.changeValue(state, 'recipientAddress', () => args[0])
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -85,16 +94,24 @@ const SendFunds = ({
|
||||||
{(...args) => {
|
{(...args) => {
|
||||||
const formState = args[2]
|
const formState = args[2]
|
||||||
const mutators = args[3]
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<Block className={classes.formContainer}>
|
<Block className={classes.formContainer}>
|
||||||
<SafeInfo
|
<SafeInfo safeAddress={safeAddress} safeName={safeName} ethBalance={ethBalance} />
|
||||||
safeAddress={safeAddress}
|
|
||||||
etherScanLink={etherScanLink}
|
|
||||||
safeName={safeName}
|
|
||||||
ethBalance={ethBalance}
|
|
||||||
/>
|
|
||||||
<Row margin="md">
|
<Row margin="md">
|
||||||
<Col xs={1}>
|
<Col xs={1}>
|
||||||
<img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} />
|
<img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} />
|
||||||
|
@ -104,7 +121,7 @@ const SendFunds = ({
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row margin="md">
|
<Row margin="md">
|
||||||
<Col xs={12}>
|
<Col xs={11}>
|
||||||
<AddressInput
|
<AddressInput
|
||||||
name="recipientAddress"
|
name="recipientAddress"
|
||||||
component={TextField}
|
component={TextField}
|
||||||
|
@ -114,6 +131,18 @@ const SendFunds = ({
|
||||||
fieldMutator={mutators.setRecipient}
|
fieldMutator={mutators.setRecipient}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</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>
|
||||||
<Row margin="sm">
|
<Row margin="sm">
|
||||||
<Col>
|
<Col>
|
||||||
|
@ -125,7 +154,7 @@ const SendFunds = ({
|
||||||
<Paragraph size="md" color="disabled" style={{ letterSpacing: '-0.5px' }} noMargin>
|
<Paragraph size="md" color="disabled" style={{ letterSpacing: '-0.5px' }} noMargin>
|
||||||
Amount
|
Amount
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<ButtonLink weight="bold" onClick={mutators.setMax}>
|
<ButtonLink weight="bold" onClick={() => mutators.setMax(selectedTokenRecord.balance)}>
|
||||||
Send max
|
Send max
|
||||||
</ButtonLink>
|
</ButtonLink>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -140,14 +169,14 @@ const SendFunds = ({
|
||||||
required,
|
required,
|
||||||
mustBeFloat,
|
mustBeFloat,
|
||||||
greaterThan(0),
|
greaterThan(0),
|
||||||
maxValue(token && token.balance),
|
maxValue(selectedTokenRecord && selectedTokenRecord.balance),
|
||||||
)}
|
)}
|
||||||
placeholder="Amount*"
|
placeholder="Amount*"
|
||||||
text="Amount*"
|
text="Amount*"
|
||||||
className={classes.addressInput}
|
className={classes.addressInput}
|
||||||
inputAdornment={
|
inputAdornment={
|
||||||
token && {
|
selectedTokenRecord && {
|
||||||
endAdornment: <InputAdornment position="end">{token.symbol}</InputAdornment>,
|
endAdornment: <InputAdornment position="end">{selectedTokenRecord.symbol}</InputAdornment>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -175,6 +204,7 @@ const SendFunds = ({
|
||||||
Review
|
Review
|
||||||
</Button>
|
</Button>
|
||||||
</Row>
|
</Row>
|
||||||
|
{qrModalOpen && <ScanQRModal isOpen={qrModalOpen} onScan={handleScan} onClose={closeQrModal} />}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -21,6 +21,9 @@ export const styles = () => ({
|
||||||
height: '35px',
|
height: '35px',
|
||||||
width: '35px',
|
width: '35px',
|
||||||
},
|
},
|
||||||
|
qrCodeBtn: {
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
formContainer: {
|
formContainer: {
|
||||||
padding: `${md} ${lg}`,
|
padding: `${md} ${lg}`,
|
||||||
},
|
},
|
||||||
|
|
|
@ -18,7 +18,7 @@ export type BalanceRow = SortRow<BalanceData>
|
||||||
|
|
||||||
export const getBalanceData = (activeTokens: List<Token>): List<BalanceRow> => {
|
export const getBalanceData = (activeTokens: List<Token>): List<BalanceRow> => {
|
||||||
const rows = activeTokens.map((token: Token) => ({
|
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,
|
[buildOrderFieldFrom(BALANCE_TABLE_ASSET_ID)]: token.name,
|
||||||
[BALANCE_TABLE_BALANCE_ID]: `${formatAmount(token.balance)} ${token.symbol}`,
|
[BALANCE_TABLE_BALANCE_ID]: `${formatAmount(token.balance)} ${token.symbol}`,
|
||||||
[buildOrderFieldFrom(BALANCE_TABLE_BALANCE_ID)]: Number(token.balance),
|
[buildOrderFieldFrom(BALANCE_TABLE_BALANCE_ID)]: Number(token.balance),
|
||||||
|
|
|
@ -43,7 +43,6 @@ type Props = {
|
||||||
activeTokens: List<Token>,
|
activeTokens: List<Token>,
|
||||||
safeAddress: string,
|
safeAddress: string,
|
||||||
safeName: string,
|
safeName: string,
|
||||||
etherScanLink: string,
|
|
||||||
ethBalance: string,
|
ethBalance: string,
|
||||||
createTransaction: Function,
|
createTransaction: Function,
|
||||||
}
|
}
|
||||||
|
@ -72,11 +71,11 @@ class Balances extends React.Component<Props, State> {
|
||||||
this.setState(() => ({ [`show${action}`]: false }))
|
this.setState(() => ({ [`show${action}`]: false }))
|
||||||
}
|
}
|
||||||
|
|
||||||
showSendFunds = (token: Token) => {
|
showSendFunds = (tokenAddress: string) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
sendFunds: {
|
sendFunds: {
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
selectedToken: token,
|
selectedToken: tokenAddress,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -107,7 +106,6 @@ class Balances extends React.Component<Props, State> {
|
||||||
safeAddress,
|
safeAddress,
|
||||||
activeTokens,
|
activeTokens,
|
||||||
safeName,
|
safeName,
|
||||||
etherScanLink,
|
|
||||||
ethBalance,
|
ethBalance,
|
||||||
createTransaction,
|
createTransaction,
|
||||||
} = this.props
|
} = this.props
|
||||||
|
@ -176,7 +174,7 @@ class Balances extends React.Component<Props, State> {
|
||||||
size="small"
|
size="small"
|
||||||
color="primary"
|
color="primary"
|
||||||
className={classes.send}
|
className={classes.send}
|
||||||
onClick={() => this.showSendFunds(row.asset.name)}
|
onClick={() => this.showSendFunds(row.asset.address)}
|
||||||
testId="balance-send-btn"
|
testId="balance-send-btn"
|
||||||
>
|
>
|
||||||
<CallMade alt="Send Transaction" className={classNames(classes.leftIcon, classes.iconSmall)} />
|
<CallMade alt="Send Transaction" className={classNames(classes.leftIcon, classes.iconSmall)} />
|
||||||
|
@ -201,7 +199,6 @@ class Balances extends React.Component<Props, State> {
|
||||||
<SendModal
|
<SendModal
|
||||||
onClose={this.hideSendFunds}
|
onClose={this.hideSendFunds}
|
||||||
isOpen={sendFunds.isOpen}
|
isOpen={sendFunds.isOpen}
|
||||||
etherScanLink={etherScanLink}
|
|
||||||
safeAddress={safeAddress}
|
safeAddress={safeAddress}
|
||||||
safeName={safeName}
|
safeName={safeName}
|
||||||
ethBalance={ethBalance}
|
ethBalance={ethBalance}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import classNames from 'classnames/bind'
|
import classNames from 'classnames/bind'
|
||||||
|
import {
|
||||||
|
Switch, Redirect, Route, withRouter,
|
||||||
|
} from 'react-router-dom'
|
||||||
import Tabs from '@material-ui/core/Tabs'
|
import Tabs from '@material-ui/core/Tabs'
|
||||||
import Tab from '@material-ui/core/Tab'
|
import Tab from '@material-ui/core/Tab'
|
||||||
import CallMade from '@material-ui/icons/CallMade'
|
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 { type SelectorProps } from '~/routes/safe/container/selector'
|
||||||
import { getEtherScanLink } from '~/logic/wallets/getWeb3'
|
import { getEtherScanLink } from '~/logic/wallets/getWeb3'
|
||||||
import { border } from '~/theme/variables'
|
import { border } from '~/theme/variables'
|
||||||
import { copyToClipboard } from '~/utils/clipboard'
|
|
||||||
import { type Actions } from '../container/actions'
|
import { type Actions } from '../container/actions'
|
||||||
import Balances from './Balances'
|
import Balances from './Balances'
|
||||||
import Transactions from './Transactions'
|
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 TRANSACTIONS_TAB_BTN_TEST_ID = 'transactions-tab-btn'
|
||||||
export const SAFE_VIEW_NAME_HEADING_TEST_ID = 'safe-name-heading'
|
export const SAFE_VIEW_NAME_HEADING_TEST_ID = 'safe-name-heading'
|
||||||
|
|
||||||
type State = {
|
|
||||||
tabIndex: number,
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = SelectorProps &
|
type Props = SelectorProps &
|
||||||
Actions & {
|
Actions & {
|
||||||
classes: Object,
|
classes: Object,
|
||||||
|
@ -48,176 +46,183 @@ type Props = SelectorProps &
|
||||||
onHide: Function,
|
onHide: Function,
|
||||||
showSendFunds: Function,
|
showSendFunds: Function,
|
||||||
hideSendFunds: Function,
|
hideSendFunds: Function,
|
||||||
|
match: Object,
|
||||||
|
location: Object,
|
||||||
|
history: Object,
|
||||||
}
|
}
|
||||||
|
|
||||||
class Layout extends React.Component<Props, State> {
|
const Layout = (props: Props) => {
|
||||||
constructor(props) {
|
const {
|
||||||
super(props)
|
safe,
|
||||||
this.state = {
|
provider,
|
||||||
tabIndex: 0,
|
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) => {
|
if (!safe) {
|
||||||
this.setState({ tabIndex })
|
return <NoSafe provider={provider} text="Safe not found" />
|
||||||
}
|
}
|
||||||
|
|
||||||
copyAddress = () => {
|
const { address, ethBalance, name } = safe
|
||||||
const { safe } = this.props
|
const etherScanLink = getEtherScanLink('address', address)
|
||||||
|
|
||||||
if (safe.address) {
|
return (
|
||||||
copyToClipboard(safe.address)
|
<>
|
||||||
}
|
<Block className={classes.container} margin="xl">
|
||||||
}
|
<Identicon address={address} diameter={50} />
|
||||||
|
<Block className={classes.name}>
|
||||||
render() {
|
<Row>
|
||||||
const {
|
<Heading tag="h2" color="primary" testId={SAFE_VIEW_NAME_HEADING_TEST_ID}>
|
||||||
safe,
|
{name}
|
||||||
provider,
|
</Heading>
|
||||||
network,
|
{!granted && <Block className={classes.readonly}>Read Only</Block>}
|
||||||
classes,
|
</Row>
|
||||||
granted,
|
<Block justify="center" className={classes.user}>
|
||||||
tokens,
|
<Paragraph size="md" className={classes.address} color="disabled" noMargin>
|
||||||
activeTokens,
|
{address}
|
||||||
createTransaction,
|
</Paragraph>
|
||||||
processTransaction,
|
<CopyBtn content={address} />
|
||||||
fetchTransactions,
|
<EtherscanBtn type="address" value={address} />
|
||||||
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>
|
|
||||||
</Block>
|
</Block>
|
||||||
</Block>
|
</Block>
|
||||||
<Row>
|
<Block className={classes.balance}>
|
||||||
<Tabs value={tabIndex} onChange={this.handleChange} indicatorColor="secondary" textColor="secondary">
|
<Row align="end" className={classes.actions}>
|
||||||
<Tab label="Balances" data-testid={BALANCES_TAB_BTN_TEST_ID} />
|
<Button
|
||||||
<Tab label="Transactions" data-testid={TRANSACTIONS_TAB_BTN_TEST_ID} />
|
variant="contained"
|
||||||
<Tab label="Settings" data-testid={SETTINGS_TAB_BTN_TEST_ID} />
|
size="small"
|
||||||
</Tabs>
|
color="primary"
|
||||||
</Row>
|
className={classes.send}
|
||||||
<Hairline color={border} style={{ marginTop: '-2px' }} />
|
onClick={() => showSendFunds('Ether')}
|
||||||
{tabIndex === 0 && (
|
disabled={!granted}
|
||||||
<Balances
|
>
|
||||||
ethBalance={ethBalance}
|
<CallMade alt="Send Transaction" className={classNames(classes.leftIcon, classes.iconSmall)} />
|
||||||
tokens={tokens}
|
Send
|
||||||
activeTokens={activeTokens}
|
</Button>
|
||||||
granted={granted}
|
<Button
|
||||||
safeAddress={address}
|
variant="contained"
|
||||||
safeName={name}
|
size="small"
|
||||||
etherScanLink={etherScanLink}
|
color="primary"
|
||||||
createTransaction={createTransaction}
|
className={classes.receive}
|
||||||
/>
|
onClick={onShow('Receive')}
|
||||||
)}
|
>
|
||||||
{tabIndex === 1 && (
|
<CallReceived alt="Receive Transaction" className={classNames(classes.leftIcon, classes.iconSmall)} />
|
||||||
<Transactions
|
Receive
|
||||||
threshold={safe.threshold}
|
</Button>
|
||||||
owners={safe.owners}
|
</Row>
|
||||||
transactions={transactions}
|
</Block>
|
||||||
fetchTransactions={fetchTransactions}
|
</Block>
|
||||||
safeAddress={address}
|
<Row>
|
||||||
userAddress={userAddress}
|
<Tabs
|
||||||
currentNetwork={network}
|
value={location.pathname}
|
||||||
granted={granted}
|
onChange={handleCallToRouter}
|
||||||
createTransaction={createTransaction}
|
indicatorColor="secondary"
|
||||||
processTransaction={processTransaction}
|
textColor="secondary"
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{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}
|
|
||||||
>
|
>
|
||||||
<Receive safeName={name} safeAddress={address} etherScanLink={etherScanLink} onClose={onHide('Receive')} />
|
<Tab label="Balances" value={`${match.url}/balances`} data-testid={BALANCES_TAB_BTN_TEST_ID} />
|
||||||
</Modal>
|
<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))
|
||||||
|
|
|
@ -12,7 +12,7 @@ import GnoForm from '~/components/forms/GnoForm'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
import Button from '~/components/layout/Button'
|
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 { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
|
||||||
import { styles } from './style'
|
import { styles } from './style'
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ const ChangeSafeName = (props: Props) => {
|
||||||
const handleSubmit = (values) => {
|
const handleSubmit = (values) => {
|
||||||
updateSafe({ address: safeAddress, name: values.safeName })
|
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)
|
showSnackbar(notification.afterExecution, enqueueSnackbar, closeSnackbar)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ const styles = () => ({
|
||||||
width: '775px',
|
width: '775px',
|
||||||
minHeight: '500px',
|
minHeight: '500px',
|
||||||
position: 'static',
|
position: 'static',
|
||||||
|
height: 'auto',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -146,6 +147,7 @@ const AddOwner = ({
|
||||||
safeName={safeName}
|
safeName={safeName}
|
||||||
owners={owners}
|
owners={owners}
|
||||||
values={values}
|
values={values}
|
||||||
|
safeAddress={safeAddress}
|
||||||
onClickBack={onClickBack}
|
onClickBack={onClickBack}
|
||||||
onSubmit={onAddOwner}
|
onSubmit={onAddOwner}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import { List } from 'immutable'
|
import { List } from 'immutable'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
import OpenInNew from '@material-ui/icons/OpenInNew'
|
|
||||||
import Close from '@material-ui/icons/Close'
|
import Close from '@material-ui/icons/Close'
|
||||||
import IconButton from '@material-ui/core/IconButton'
|
import IconButton from '@material-ui/core/IconButton'
|
||||||
import Identicon from '~/components/Identicon'
|
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 Paragraph from '~/components/layout/Paragraph'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
import Col from '~/components/layout/Col'
|
import Col from '~/components/layout/Col'
|
||||||
|
@ -15,17 +16,13 @@ import Button from '~/components/layout/Button'
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
import Hairline from '~/components/layout/Hairline'
|
import Hairline from '~/components/layout/Hairline'
|
||||||
import type { Owner } from '~/routes/safe/store/models/owner'
|
import type { Owner } from '~/routes/safe/store/models/owner'
|
||||||
import { getEtherScanLink } from '~/logic/wallets/getWeb3'
|
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||||
import { secondary } from '~/theme/variables'
|
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
|
||||||
|
import { estimateTxGasCosts } from '~/logic/safe/transactions/gasNew'
|
||||||
import { styles } from './style'
|
import { styles } from './style'
|
||||||
|
|
||||||
export const ADD_OWNER_SUBMIT_BTN_TEST_ID = 'add-owner-submit-btn'
|
export const ADD_OWNER_SUBMIT_BTN_TEST_ID = 'add-owner-submit-btn'
|
||||||
|
|
||||||
const openIconStyle = {
|
|
||||||
height: '16px',
|
|
||||||
color: secondary,
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClose: () => void,
|
onClose: () => void,
|
||||||
classes: Object,
|
classes: Object,
|
||||||
|
@ -34,11 +31,39 @@ type Props = {
|
||||||
values: Object,
|
values: Object,
|
||||||
onClickBack: Function,
|
onClickBack: Function,
|
||||||
onSubmit: Function,
|
onSubmit: Function,
|
||||||
|
safeAddress: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReviewAddOwner = ({
|
const ReviewAddOwner = ({
|
||||||
classes, onClose, safeName, owners, values, onClickBack, onSubmit,
|
classes, onClose, safeName, owners, values, onClickBack, onSubmit, safeAddress,
|
||||||
}: Props) => {
|
}: 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 = () => {
|
const handleSubmit = () => {
|
||||||
onSubmit()
|
onSubmit()
|
||||||
}
|
}
|
||||||
|
@ -76,12 +101,7 @@ const ReviewAddOwner = ({
|
||||||
Any transaction requires the confirmation of:
|
Any transaction requires the confirmation of:
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Paragraph size="lg" color="primary" noMargin weight="bolder" className={classes.name}>
|
<Paragraph size="lg" color="primary" noMargin weight="bolder" className={classes.name}>
|
||||||
{values.threshold}
|
{`${values.threshold} out of ${owners.size + 1} owner(s)`}
|
||||||
{' '}
|
|
||||||
out of
|
|
||||||
{owners.size + 1}
|
|
||||||
{' '}
|
|
||||||
owner(s)
|
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Block>
|
</Block>
|
||||||
</Block>
|
</Block>
|
||||||
|
@ -89,9 +109,7 @@ const ReviewAddOwner = ({
|
||||||
<Col xs={8} layout="column" className={classes.owners}>
|
<Col xs={8} layout="column" className={classes.owners}>
|
||||||
<Row className={classes.ownersTitle}>
|
<Row className={classes.ownersTitle}>
|
||||||
<Paragraph size="lg" color="primary" noMargin>
|
<Paragraph size="lg" color="primary" noMargin>
|
||||||
{owners.size + 1}
|
{`${owners.size + 1} Safe owner(s)`}
|
||||||
{' '}
|
|
||||||
Safe owner(s)
|
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Row>
|
</Row>
|
||||||
<Hairline />
|
<Hairline />
|
||||||
|
@ -107,12 +125,11 @@ const ReviewAddOwner = ({
|
||||||
{owner.name}
|
{owner.name}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Block justify="center" className={classes.user}>
|
<Block justify="center" className={classes.user}>
|
||||||
<Paragraph size="md" color="disabled" noMargin>
|
<Paragraph size="md" color="disabled" className={classes.address} noMargin>
|
||||||
{owner.address}
|
{owner.address}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Link className={classes.open} to={getEtherScanLink('address', owner.address)} target="_blank">
|
<CopyBtn content={owner.address} />
|
||||||
<OpenInNew style={openIconStyle} />
|
<EtherscanBtn type="address" value={owner.address} />
|
||||||
</Link>
|
|
||||||
</Block>
|
</Block>
|
||||||
</Block>
|
</Block>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -136,16 +153,11 @@ const ReviewAddOwner = ({
|
||||||
{values.ownerName}
|
{values.ownerName}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Block justify="center" className={classes.user}>
|
<Block justify="center" className={classes.user}>
|
||||||
<Paragraph size="md" color="disabled" noMargin>
|
<Paragraph size="md" color="disabled" className={classes.address} noMargin>
|
||||||
{values.ownerAddress}
|
{values.ownerAddress}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Link
|
<CopyBtn content={values.ownerAddress} />
|
||||||
className={classes.open}
|
<EtherscanBtn type="address" value={values.ownerAddress} />
|
||||||
to={getEtherScanLink('address', values.ownerAddress)}
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<OpenInNew style={openIconStyle} />
|
|
||||||
</Link>
|
|
||||||
</Block>
|
</Block>
|
||||||
</Block>
|
</Block>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -155,6 +167,14 @@ const ReviewAddOwner = ({
|
||||||
</Row>
|
</Row>
|
||||||
</Block>
|
</Block>
|
||||||
<Hairline />
|
<Hairline />
|
||||||
|
<Block className={classes.gasCostsContainer}>
|
||||||
|
<Paragraph>
|
||||||
|
You'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}>
|
<Row align="center" className={classes.buttonRow}>
|
||||||
<Button minWidth={140} minHeight={42} onClick={onClickBack}>
|
<Button minWidth={140} minHeight={42} onClick={onClickBack}>
|
||||||
Back
|
Back
|
||||||
|
|
|
@ -39,7 +39,7 @@ export const styles = () => ({
|
||||||
},
|
},
|
||||||
details: {
|
details: {
|
||||||
padding: lg,
|
padding: lg,
|
||||||
borderRight: `solid 1px ${border}`,
|
borderRight: `solid 2px ${border}`,
|
||||||
height: '100%',
|
height: '100%',
|
||||||
},
|
},
|
||||||
owners: {
|
owners: {
|
||||||
|
@ -49,6 +49,9 @@ export const styles = () => ({
|
||||||
ownersTitle: {
|
ownersTitle: {
|
||||||
padding: lg,
|
padding: lg,
|
||||||
},
|
},
|
||||||
|
address: {
|
||||||
|
marginRight: sm,
|
||||||
|
},
|
||||||
owner: {
|
owner: {
|
||||||
padding: sm,
|
padding: sm,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
@ -75,4 +78,9 @@ export const styles = () => ({
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
gasCostsContainer: {
|
||||||
|
padding: `0 ${lg}`,
|
||||||
|
textAlign: 'center',
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,10 +3,10 @@ import React from 'react'
|
||||||
import { withSnackbar } from 'notistack'
|
import { withSnackbar } from 'notistack'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
import Close from '@material-ui/icons/Close'
|
import Close from '@material-ui/icons/Close'
|
||||||
import OpenInNew from '@material-ui/icons/OpenInNew'
|
|
||||||
import IconButton from '@material-ui/core/IconButton'
|
import IconButton from '@material-ui/core/IconButton'
|
||||||
|
import EtherscanBtn from '~/components/EtherscanBtn'
|
||||||
|
import CopyBtn from '~/components/CopyBtn'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
import Link from '~/components/layout/Link'
|
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
import GnoForm from '~/components/forms/GnoForm'
|
import GnoForm from '~/components/forms/GnoForm'
|
||||||
import Button from '~/components/layout/Button'
|
import Button from '~/components/layout/Button'
|
||||||
|
@ -16,21 +16,15 @@ import TextField from '~/components/forms/TextField'
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
import Identicon from '~/components/Identicon'
|
import Identicon from '~/components/Identicon'
|
||||||
import { composeValidators, required, minMaxLength } from '~/components/forms/validator'
|
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 { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
|
||||||
import { getEtherScanLink } from '~/logic/wallets/getWeb3'
|
|
||||||
import Modal from '~/components/Modal'
|
import Modal from '~/components/Modal'
|
||||||
import { styles } from './style'
|
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 RENAME_OWNER_INPUT_TEST_ID = 'rename-owner-input'
|
||||||
export const SAVE_OWNER_CHANGES_BTN_TEST_ID = 'save-owner-changes-btn'
|
export const SAVE_OWNER_CHANGES_BTN_TEST_ID = 'save-owner-changes-btn'
|
||||||
|
|
||||||
const openIconStyle = {
|
|
||||||
height: '16px',
|
|
||||||
color: secondary,
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClose: () => void,
|
onClose: () => void,
|
||||||
classes: Object,
|
classes: Object,
|
||||||
|
@ -57,7 +51,7 @@ const EditOwnerComponent = ({
|
||||||
const handleSubmit = (values) => {
|
const handleSubmit = (values) => {
|
||||||
editSafeOwner({ safeAddress, ownerAddress, ownerName: values.ownerName })
|
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)
|
showSnackbar(notification.afterExecution, enqueueSnackbar, closeSnackbar)
|
||||||
|
|
||||||
onClose()
|
onClose()
|
||||||
|
@ -100,12 +94,11 @@ const EditOwnerComponent = ({
|
||||||
<Row>
|
<Row>
|
||||||
<Block justify="center" className={classes.user}>
|
<Block justify="center" className={classes.user}>
|
||||||
<Identicon address={ownerAddress} diameter={32} />
|
<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}
|
{ownerAddress}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Link className={classes.open} to={getEtherScanLink('address', ownerAddress)} target="_blank">
|
<CopyBtn content={safeAddress} />
|
||||||
<OpenInNew style={openIconStyle} />
|
<EtherscanBtn type="address" value={safeAddress} />
|
||||||
</Link>
|
|
||||||
</Block>
|
</Block>
|
||||||
</Row>
|
</Row>
|
||||||
</Block>
|
</Block>
|
||||||
|
@ -114,7 +107,14 @@ const EditOwnerComponent = ({
|
||||||
<Button minWidth={140} minHeight={42} onClick={onClose}>
|
<Button minWidth={140} minHeight={42} onClick={onClose}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</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
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
@ -16,6 +16,7 @@ const styles = () => ({
|
||||||
width: '775px',
|
width: '775px',
|
||||||
minHeight: '500px',
|
minHeight: '500px',
|
||||||
position: 'static',
|
position: 'static',
|
||||||
|
height: 'auto',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -168,6 +169,7 @@ const RemoveOwner = ({
|
||||||
ownerName={ownerName}
|
ownerName={ownerName}
|
||||||
onClickBack={onClickBack}
|
onClickBack={onClickBack}
|
||||||
onSubmit={onRemoveOwner}
|
onSubmit={onRemoveOwner}
|
||||||
|
safeAddress={safeAddress}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -4,26 +4,19 @@ import classNames from 'classnames/bind'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
import Close from '@material-ui/icons/Close'
|
import Close from '@material-ui/icons/Close'
|
||||||
import IconButton from '@material-ui/core/IconButton'
|
import IconButton from '@material-ui/core/IconButton'
|
||||||
import OpenInNew from '@material-ui/icons/OpenInNew'
|
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
import Col from '~/components/layout/Col'
|
import Col from '~/components/layout/Col'
|
||||||
import Button from '~/components/layout/Button'
|
import Button from '~/components/layout/Button'
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
import Hairline from '~/components/layout/Hairline'
|
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 Identicon from '~/components/Identicon'
|
||||||
import { getEtherScanLink } from '~/logic/wallets/getWeb3'
|
|
||||||
import { styles } from './style'
|
import { styles } from './style'
|
||||||
import { secondary } from '~/theme/variables'
|
|
||||||
|
|
||||||
export const REMOVE_OWNER_MODAL_NEXT_BTN_TEST_ID = 'remove-owner-next-btn'
|
export const REMOVE_OWNER_MODAL_NEXT_BTN_TEST_ID = 'remove-owner-next-btn'
|
||||||
|
|
||||||
const openIconStyle = {
|
|
||||||
height: '16px',
|
|
||||||
color: secondary,
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClose: () => void,
|
onClose: () => void,
|
||||||
classes: Object,
|
classes: Object,
|
||||||
|
@ -65,12 +58,11 @@ const CheckOwner = ({
|
||||||
{ownerName}
|
{ownerName}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Block justify="center" className={classes.user}>
|
<Block justify="center" className={classes.user}>
|
||||||
<Paragraph size="md" color="disabled" noMargin>
|
<Paragraph size="md" color="disabled" className={classes.address} noMargin>
|
||||||
{ownerAddress}
|
{ownerAddress}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Link className={classes.open} to={getEtherScanLink('address', ownerAddress)} target="_blank">
|
<CopyBtn content={ownerAddress} />
|
||||||
<OpenInNew style={openIconStyle} />
|
<EtherscanBtn type="address" value={ownerAddress} />
|
||||||
</Link>
|
|
||||||
</Block>
|
</Block>
|
||||||
</Block>
|
</Block>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
|
@ -19,6 +19,9 @@ export const styles = () => ({
|
||||||
manage: {
|
manage: {
|
||||||
fontSize: '24px',
|
fontSize: '24px',
|
||||||
},
|
},
|
||||||
|
address: {
|
||||||
|
marginRight: sm,
|
||||||
|
},
|
||||||
closeIcon: {
|
closeIcon: {
|
||||||
height: '35px',
|
height: '35px',
|
||||||
width: '35px',
|
width: '35px',
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import { List } from 'immutable'
|
import { List } from 'immutable'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
import OpenInNew from '@material-ui/icons/OpenInNew'
|
|
||||||
import Close from '@material-ui/icons/Close'
|
import Close from '@material-ui/icons/Close'
|
||||||
import IconButton from '@material-ui/core/IconButton'
|
import IconButton from '@material-ui/core/IconButton'
|
||||||
|
import EtherscanBtn from '~/components/EtherscanBtn'
|
||||||
|
import CopyBtn from '~/components/CopyBtn'
|
||||||
import Identicon from '~/components/Identicon'
|
import Identicon from '~/components/Identicon'
|
||||||
import Link from '~/components/layout/Link'
|
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
import Col from '~/components/layout/Col'
|
import Col from '~/components/layout/Col'
|
||||||
|
@ -15,17 +15,14 @@ import Button from '~/components/layout/Button'
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
import Hairline from '~/components/layout/Hairline'
|
import Hairline from '~/components/layout/Hairline'
|
||||||
import type { Owner } from '~/routes/safe/store/models/owner'
|
import type { Owner } from '~/routes/safe/store/models/owner'
|
||||||
import { getEtherScanLink } from '~/logic/wallets/getWeb3'
|
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||||
import { secondary } from '~/theme/variables'
|
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'
|
import { styles } from './style'
|
||||||
|
|
||||||
export const REMOVE_OWNER_REVIEW_BTN_TEST_ID = 'remove-owner-review-btn'
|
export const REMOVE_OWNER_REVIEW_BTN_TEST_ID = 'remove-owner-review-btn'
|
||||||
|
|
||||||
const openIconStyle = {
|
|
||||||
height: '16px',
|
|
||||||
color: secondary,
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClose: () => void,
|
onClose: () => void,
|
||||||
classes: Object,
|
classes: Object,
|
||||||
|
@ -36,6 +33,7 @@ type Props = {
|
||||||
ownerName: string,
|
ownerName: string,
|
||||||
onClickBack: Function,
|
onClickBack: Function,
|
||||||
onSubmit: Function,
|
onSubmit: Function,
|
||||||
|
safeAddress: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReviewRemoveOwner = ({
|
const ReviewRemoveOwner = ({
|
||||||
|
@ -48,10 +46,36 @@ const ReviewRemoveOwner = ({
|
||||||
ownerName,
|
ownerName,
|
||||||
onClickBack,
|
onClickBack,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
|
safeAddress,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const handleSubmit = () => {
|
const [gasCosts, setGasCosts] = useState<string>('< 0.001')
|
||||||
onSubmit()
|
|
||||||
}
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -87,12 +111,7 @@ const ReviewRemoveOwner = ({
|
||||||
Any transaction requires the confirmation of:
|
Any transaction requires the confirmation of:
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Paragraph size="lg" color="primary" noMargin weight="bolder" className={classes.name}>
|
<Paragraph size="lg" color="primary" noMargin weight="bolder" className={classes.name}>
|
||||||
{values.threshold}
|
{`${values.threshold} out of ${owners.size - 1} owner(s)`}
|
||||||
{' '}
|
|
||||||
out of
|
|
||||||
{owners.size - 1}
|
|
||||||
{' '}
|
|
||||||
owner(s)
|
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Block>
|
</Block>
|
||||||
</Block>
|
</Block>
|
||||||
|
@ -100,9 +119,7 @@ const ReviewRemoveOwner = ({
|
||||||
<Col xs={8} layout="column" className={classes.owners}>
|
<Col xs={8} layout="column" className={classes.owners}>
|
||||||
<Row className={classes.ownersTitle}>
|
<Row className={classes.ownersTitle}>
|
||||||
<Paragraph size="lg" color="primary" noMargin>
|
<Paragraph size="lg" color="primary" noMargin>
|
||||||
{owners.size - 1}
|
{`${owners.size - 1} Safe owner(s)`}
|
||||||
{' '}
|
|
||||||
Safe owner(s)
|
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Row>
|
</Row>
|
||||||
<Hairline />
|
<Hairline />
|
||||||
|
@ -119,16 +136,11 @@ const ReviewRemoveOwner = ({
|
||||||
{owner.name}
|
{owner.name}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Block justify="center" className={classes.user}>
|
<Block justify="center" className={classes.user}>
|
||||||
<Paragraph size="md" color="disabled" noMargin>
|
<Paragraph size="md" color="disabled" className={classes.address} noMargin>
|
||||||
{owner.address}
|
{owner.address}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Link
|
<CopyBtn content={owner.address} />
|
||||||
className={classes.open}
|
<EtherscanBtn type="address" value={owner.address} />
|
||||||
to={getEtherScanLink('address', owner.address)}
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<OpenInNew style={openIconStyle} />
|
|
||||||
</Link>
|
|
||||||
</Block>
|
</Block>
|
||||||
</Block>
|
</Block>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -153,16 +165,11 @@ const ReviewRemoveOwner = ({
|
||||||
{ownerName}
|
{ownerName}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Block justify="center" className={classes.user}>
|
<Block justify="center" className={classes.user}>
|
||||||
<Paragraph size="md" color="disabled" noMargin>
|
<Paragraph size="md" color="disabled" className={classes.address} noMargin>
|
||||||
{ownerAddress}
|
{ownerAddress}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Link
|
<CopyBtn content={ownerAddress} />
|
||||||
className={classes.open}
|
<EtherscanBtn type="address" value={ownerAddress} />
|
||||||
to={getEtherScanLink('address', ownerAddress)}
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<OpenInNew style={openIconStyle} />
|
|
||||||
</Link>
|
|
||||||
</Block>
|
</Block>
|
||||||
</Block>
|
</Block>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -172,13 +179,21 @@ const ReviewRemoveOwner = ({
|
||||||
</Row>
|
</Row>
|
||||||
</Block>
|
</Block>
|
||||||
<Hairline />
|
<Hairline />
|
||||||
|
<Block className={classes.gasCostsContainer}>
|
||||||
|
<Paragraph>
|
||||||
|
You'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}>
|
<Row align="center" className={classes.buttonRow}>
|
||||||
<Button minWidth={140} minHeight={42} onClick={onClickBack}>
|
<Button minWidth={140} minHeight={42} onClick={onClickBack}>
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={handleSubmit}
|
onClick={onSubmit}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
minHeight={42}
|
minHeight={42}
|
||||||
minWidth={140}
|
minWidth={140}
|
||||||
|
|
|
@ -49,6 +49,9 @@ export const styles = () => ({
|
||||||
ownersTitle: {
|
ownersTitle: {
|
||||||
padding: lg,
|
padding: lg,
|
||||||
},
|
},
|
||||||
|
address: {
|
||||||
|
marginRight: sm,
|
||||||
|
},
|
||||||
owner: {
|
owner: {
|
||||||
padding: sm,
|
padding: sm,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
@ -75,4 +78,9 @@ export const styles = () => ({
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
gasCostsContainer: {
|
||||||
|
padding: `0 ${lg}`,
|
||||||
|
textAlign: 'center',
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -15,6 +15,7 @@ const styles = () => ({
|
||||||
width: '775px',
|
width: '775px',
|
||||||
minHeight: '500px',
|
minHeight: '500px',
|
||||||
position: 'static',
|
position: 'static',
|
||||||
|
height: 'auto',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -155,6 +156,7 @@ const ReplaceOwner = ({
|
||||||
onClickBack={onClickBack}
|
onClickBack={onClickBack}
|
||||||
onSubmit={onReplaceOwner}
|
onSubmit={onReplaceOwner}
|
||||||
threshold={threshold}
|
threshold={threshold}
|
||||||
|
safeAddress={safeAddress}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -5,7 +5,8 @@ import { List } from 'immutable'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
import Close from '@material-ui/icons/Close'
|
import Close from '@material-ui/icons/Close'
|
||||||
import IconButton from '@material-ui/core/IconButton'
|
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 Paragraph from '~/components/layout/Paragraph'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
import GnoForm from '~/components/forms/GnoForm'
|
import GnoForm from '~/components/forms/GnoForm'
|
||||||
|
@ -17,24 +18,16 @@ import Hairline from '~/components/layout/Hairline'
|
||||||
import Field from '~/components/forms/Field'
|
import Field from '~/components/forms/Field'
|
||||||
import TextField from '~/components/forms/TextField'
|
import TextField from '~/components/forms/TextField'
|
||||||
import Identicon from '~/components/Identicon'
|
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 { type Owner } from '~/routes/safe/store/models/owner'
|
||||||
import {
|
import {
|
||||||
composeValidators, required, minMaxLength, uniqueAddress,
|
composeValidators, required, minMaxLength, uniqueAddress,
|
||||||
} from '~/components/forms/validator'
|
} from '~/components/forms/validator'
|
||||||
import { styles } from './style'
|
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_NAME_INPUT_TEST_ID = 'replace-owner-name-input'
|
||||||
export const REPLACE_OWNER_ADDRESS_INPUT_TEST_ID = 'replace-owner-address-testid'
|
export const REPLACE_OWNER_ADDRESS_INPUT_TEST_ID = 'replace-owner-address-testid'
|
||||||
export const REPLACE_OWNER_NEXT_BTN_TEST_ID = 'replace-owner-next-btn'
|
export const REPLACE_OWNER_NEXT_BTN_TEST_ID = 'replace-owner-next-btn'
|
||||||
|
|
||||||
const openIconStyle = {
|
|
||||||
height: '16px',
|
|
||||||
color: secondary,
|
|
||||||
}
|
|
||||||
|
|
||||||
const formMutators = {
|
const formMutators = {
|
||||||
setOwnerAddress: (args, state, utils) => {
|
setOwnerAddress: (args, state, utils) => {
|
||||||
utils.changeValue(state, 'ownerAddress', () => args[0])
|
utils.changeValue(state, 'ownerAddress', () => args[0])
|
||||||
|
@ -96,16 +89,11 @@ const OwnerForm = ({
|
||||||
{ownerName}
|
{ownerName}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Block justify="center" className={classes.user}>
|
<Block justify="center" className={classes.user}>
|
||||||
<Paragraph size="md" color="disabled" noMargin>
|
<Paragraph size="md" className={classes.address} color="disabled" noMargin>
|
||||||
{ownerAddress}
|
{ownerAddress}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Link
|
<CopyBtn content={ownerAddress} />
|
||||||
className={classes.open}
|
<EtherscanBtn type="address" value={ownerAddress} />
|
||||||
to={getEtherScanLink('address', ownerAddress)}
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<OpenInNew style={openIconStyle} />
|
|
||||||
</Link>
|
|
||||||
</Block>
|
</Block>
|
||||||
</Block>
|
</Block>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
|
@ -16,6 +16,9 @@ export const styles = () => ({
|
||||||
marginLeft: '20px',
|
marginLeft: '20px',
|
||||||
lineHeight: 'normal',
|
lineHeight: 'normal',
|
||||||
},
|
},
|
||||||
|
address: {
|
||||||
|
marginRight: sm,
|
||||||
|
},
|
||||||
manage: {
|
manage: {
|
||||||
fontSize: '24px',
|
fontSize: '24px',
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import { List } from 'immutable'
|
import { List } from 'immutable'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
import OpenInNew from '@material-ui/icons/OpenInNew'
|
|
||||||
import Close from '@material-ui/icons/Close'
|
import Close from '@material-ui/icons/Close'
|
||||||
import IconButton from '@material-ui/core/IconButton'
|
import IconButton from '@material-ui/core/IconButton'
|
||||||
|
import EtherscanBtn from '~/components/EtherscanBtn'
|
||||||
|
import CopyBtn from '~/components/CopyBtn'
|
||||||
import Identicon from '~/components/Identicon'
|
import Identicon from '~/components/Identicon'
|
||||||
import Link from '~/components/layout/Link'
|
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
import Col from '~/components/layout/Col'
|
import Col from '~/components/layout/Col'
|
||||||
|
@ -15,17 +15,14 @@ import Button from '~/components/layout/Button'
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
import Hairline from '~/components/layout/Hairline'
|
import Hairline from '~/components/layout/Hairline'
|
||||||
import type { Owner } from '~/routes/safe/store/models/owner'
|
import type { Owner } from '~/routes/safe/store/models/owner'
|
||||||
import { getEtherScanLink } from '~/logic/wallets/getWeb3'
|
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||||
import { secondary } from '~/theme/variables'
|
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'
|
import { styles } from './style'
|
||||||
|
|
||||||
export const REPLACE_OWNER_SUBMIT_BTN_TEST_ID = 'replace-owner-submit-btn'
|
export const REPLACE_OWNER_SUBMIT_BTN_TEST_ID = 'replace-owner-submit-btn'
|
||||||
|
|
||||||
const openIconStyle = {
|
|
||||||
height: '16px',
|
|
||||||
color: secondary,
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClose: () => void,
|
onClose: () => void,
|
||||||
classes: Object,
|
classes: Object,
|
||||||
|
@ -37,6 +34,7 @@ type Props = {
|
||||||
onClickBack: Function,
|
onClickBack: Function,
|
||||||
onSubmit: Function,
|
onSubmit: Function,
|
||||||
threshold: string,
|
threshold: string,
|
||||||
|
safeAddress: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReviewRemoveOwner = ({
|
const ReviewRemoveOwner = ({
|
||||||
|
@ -47,13 +45,37 @@ const ReviewRemoveOwner = ({
|
||||||
values,
|
values,
|
||||||
ownerAddress,
|
ownerAddress,
|
||||||
ownerName,
|
ownerName,
|
||||||
|
safeAddress,
|
||||||
onClickBack,
|
onClickBack,
|
||||||
threshold,
|
threshold,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const handleSubmit = () => {
|
const [gasCosts, setGasCosts] = useState<string>('< 0.001')
|
||||||
onSubmit()
|
|
||||||
}
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -89,13 +111,7 @@ const ReviewRemoveOwner = ({
|
||||||
Any transaction requires the confirmation of:
|
Any transaction requires the confirmation of:
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Paragraph size="lg" color="primary" noMargin weight="bolder" className={classes.name}>
|
<Paragraph size="lg" color="primary" noMargin weight="bolder" className={classes.name}>
|
||||||
{threshold}
|
{`${threshold} out of ${owners.size} owner(s)`}
|
||||||
{' '}
|
|
||||||
out of
|
|
||||||
{' '}
|
|
||||||
{owners.size}
|
|
||||||
{' '}
|
|
||||||
owner(s)
|
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Block>
|
</Block>
|
||||||
</Block>
|
</Block>
|
||||||
|
@ -103,9 +119,7 @@ const ReviewRemoveOwner = ({
|
||||||
<Col xs={8} layout="column" className={classes.owners}>
|
<Col xs={8} layout="column" className={classes.owners}>
|
||||||
<Row className={classes.ownersTitle}>
|
<Row className={classes.ownersTitle}>
|
||||||
<Paragraph size="lg" color="primary" noMargin>
|
<Paragraph size="lg" color="primary" noMargin>
|
||||||
{owners.size}
|
{`${owners.size} Safe owner(s)`}
|
||||||
{' '}
|
|
||||||
Safe owner(s)
|
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Row>
|
</Row>
|
||||||
<Hairline />
|
<Hairline />
|
||||||
|
@ -122,16 +136,11 @@ const ReviewRemoveOwner = ({
|
||||||
{owner.name}
|
{owner.name}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Block justify="center" className={classes.user}>
|
<Block justify="center" className={classes.user}>
|
||||||
<Paragraph size="md" color="disabled" noMargin>
|
<Paragraph size="md" color="disabled" className={classes.address} noMargin>
|
||||||
{owner.address}
|
{owner.address}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Link
|
<CopyBtn content={owner.address} />
|
||||||
className={classes.open}
|
<EtherscanBtn type="address" value={owner.address} />
|
||||||
to={getEtherScanLink('address', owner.address)}
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<OpenInNew style={openIconStyle} />
|
|
||||||
</Link>
|
|
||||||
</Block>
|
</Block>
|
||||||
</Block>
|
</Block>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -156,12 +165,11 @@ const ReviewRemoveOwner = ({
|
||||||
{ownerName}
|
{ownerName}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Block justify="center" className={classes.user}>
|
<Block justify="center" className={classes.user}>
|
||||||
<Paragraph size="md" color="disabled" noMargin>
|
<Paragraph size="md" color="disabled" className={classes.address} noMargin>
|
||||||
{ownerAddress}
|
{ownerAddress}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Link className={classes.open} to={getEtherScanLink('address', ownerAddress)} target="_blank">
|
<CopyBtn content={ownerAddress} />
|
||||||
<OpenInNew style={openIconStyle} />
|
<EtherscanBtn type="address" value={ownerAddress} />
|
||||||
</Link>
|
|
||||||
</Block>
|
</Block>
|
||||||
</Block>
|
</Block>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -182,12 +190,11 @@ const ReviewRemoveOwner = ({
|
||||||
{values.ownerName}
|
{values.ownerName}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Block justify="center" className={classes.user}>
|
<Block justify="center" className={classes.user}>
|
||||||
<Paragraph size="md" color="disabled" noMargin>
|
<Paragraph size="md" color="disabled" className={classes.address} noMargin>
|
||||||
{values.ownerAddress}
|
{values.ownerAddress}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Link className={classes.open} to={getEtherScanLink('address', values.ownerAddress)} target="_blank">
|
<CopyBtn content={values.ownerAddress} />
|
||||||
<OpenInNew style={openIconStyle} />
|
<EtherscanBtn type="address" value={values.ownerAddress} />
|
||||||
</Link>
|
|
||||||
</Block>
|
</Block>
|
||||||
</Block>
|
</Block>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -197,13 +204,21 @@ const ReviewRemoveOwner = ({
|
||||||
</Row>
|
</Row>
|
||||||
</Block>
|
</Block>
|
||||||
<Hairline />
|
<Hairline />
|
||||||
|
<Block className={classes.gasCostsContainer}>
|
||||||
|
<Paragraph>
|
||||||
|
You'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}>
|
<Row align="center" className={classes.buttonRow}>
|
||||||
<Button minWidth={140} minHeight={42} onClick={onClickBack}>
|
<Button minWidth={140} minHeight={42} onClick={onClickBack}>
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={handleSubmit}
|
onClick={onSubmit}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
minHeight={42}
|
minHeight={42}
|
||||||
minWidth={140}
|
minWidth={140}
|
||||||
|
|
|
@ -22,6 +22,9 @@ export const styles = () => ({
|
||||||
manage: {
|
manage: {
|
||||||
fontSize: '24px',
|
fontSize: '24px',
|
||||||
},
|
},
|
||||||
|
address: {
|
||||||
|
marginRight: sm,
|
||||||
|
},
|
||||||
closeIcon: {
|
closeIcon: {
|
||||||
height: '35px',
|
height: '35px',
|
||||||
width: '35px',
|
width: '35px',
|
||||||
|
@ -80,4 +83,9 @@ export const styles = () => ({
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
gasCostsContainer: {
|
||||||
|
padding: `0 ${lg}`,
|
||||||
|
textAlign: 'center',
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import { List } from 'immutable'
|
import { List } from 'immutable'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
import Close from '@material-ui/icons/Close'
|
import Close from '@material-ui/icons/Close'
|
||||||
|
@ -18,11 +18,16 @@ import Block from '~/components/layout/Block'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
import Col from '~/components/layout/Col'
|
import Col from '~/components/layout/Col'
|
||||||
import type { Owner } from '~/routes/safe/store/models/owner'
|
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'
|
import { styles } from './style'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClose: () => void,
|
onClose: () => void,
|
||||||
classes: Object,
|
classes: Object,
|
||||||
|
safeAddress: string,
|
||||||
threshold: number,
|
threshold: number,
|
||||||
owners: List<Owner>,
|
owners: List<Owner>,
|
||||||
onChangeThreshold: Function,
|
onChangeThreshold: Function,
|
||||||
|
@ -31,13 +36,38 @@ type Props = {
|
||||||
const THRESHOLD_FIELD_NAME = 'threshold'
|
const THRESHOLD_FIELD_NAME = 'threshold'
|
||||||
|
|
||||||
const ChangeThreshold = ({
|
const ChangeThreshold = ({
|
||||||
onClose, owners, threshold, classes, onChangeThreshold,
|
onClose, owners, threshold, classes, onChangeThreshold, safeAddress,
|
||||||
}: Props) => {
|
}: 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]
|
const newThreshold = values[THRESHOLD_FIELD_NAME]
|
||||||
|
|
||||||
await onChangeThreshold(newThreshold)
|
|
||||||
onClose()
|
onClose()
|
||||||
|
onChangeThreshold(newThreshold)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -62,15 +92,13 @@ const ChangeThreshold = ({
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<Paragraph weight="bolder">
|
<Paragraph weight="bolder">Any transaction requires the confirmation of:</Paragraph>
|
||||||
Any transaction requires the confirmation of:
|
|
||||||
</Paragraph>
|
|
||||||
</Row>
|
</Row>
|
||||||
<Row margin="xl" align="center" className={classes.inputRow}>
|
<Row margin="xl" align="center" className={classes.inputRow}>
|
||||||
<Col xs={2}>
|
<Col xs={2}>
|
||||||
<Field
|
<Field
|
||||||
name={THRESHOLD_FIELD_NAME}
|
name={THRESHOLD_FIELD_NAME}
|
||||||
render={(props) => (
|
render={(props: Object) => (
|
||||||
<>
|
<>
|
||||||
<SelectField {...props} disableError>
|
<SelectField {...props} disableError>
|
||||||
{[...Array(Number(owners.size))].map((x, index) => (
|
{[...Array(Number(owners.size))].map((x, index) => (
|
||||||
|
@ -92,14 +120,15 @@ const ChangeThreshold = ({
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={10}>
|
<Col xs={10}>
|
||||||
<Paragraph size="lg" color="primary" noMargin className={classes.ownersText}>
|
<Paragraph size="lg" color="primary" noMargin className={classes.ownersText}>
|
||||||
out of
|
{`out of ${owners.size} owner(s)`}
|
||||||
{' '}
|
|
||||||
{owners.size}
|
|
||||||
{' '}
|
|
||||||
owner(s)
|
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</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>
|
</Block>
|
||||||
<Hairline style={{ position: 'absolute', bottom: 85 }} />
|
<Hairline style={{ position: 'absolute', bottom: 85 }} />
|
||||||
<Row align="center" className={classes.buttonRow}>
|
<Row align="center" className={classes.buttonRow}>
|
||||||
|
|
|
@ -98,6 +98,7 @@ const ThresholdSettings = ({
|
||||||
onClose={toggleModal}
|
onClose={toggleModal}
|
||||||
owners={owners}
|
owners={owners}
|
||||||
threshold={threshold}
|
threshold={threshold}
|
||||||
|
safeAddress={safeAddress}
|
||||||
onChangeThreshold={onChangeThreshold}
|
onChangeThreshold={onChangeThreshold}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React, { useState } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import Close from '@material-ui/icons/Close'
|
import Close from '@material-ui/icons/Close'
|
||||||
import IconButton from '@material-ui/core/IconButton'
|
import IconButton from '@material-ui/core/IconButton'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { withStyles } from '@material-ui/core/styles'
|
||||||
|
@ -13,6 +13,9 @@ import Row from '~/components/layout/Row'
|
||||||
import Bold from '~/components/layout/Bold'
|
import Bold from '~/components/layout/Bold'
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
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 { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
|
||||||
import { type Transaction } from '~/routes/safe/store/models/transaction'
|
import { type Transaction } from '~/routes/safe/store/models/transaction'
|
||||||
import { styles } from './style'
|
import { styles } from './style'
|
||||||
|
@ -61,10 +64,39 @@ const ApproveTxModal = ({
|
||||||
enqueueSnackbar,
|
enqueueSnackbar,
|
||||||
closeSnackbar,
|
closeSnackbar,
|
||||||
}: Props) => {
|
}: 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 { title, description } = getModalTitleAndDescription(thresholdReached)
|
||||||
const oneConfirmationLeft = tx.confirmations.size + 1 === threshold
|
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 handleExecuteCheckbox = () => setApproveAndExecute((prevApproveAndExecute) => !prevApproveAndExecute)
|
||||||
|
|
||||||
const approveTx = () => {
|
const approveTx = () => {
|
||||||
|
@ -102,8 +134,8 @@ const ApproveTxModal = ({
|
||||||
{!thresholdReached && oneConfirmationLeft && (
|
{!thresholdReached && oneConfirmationLeft && (
|
||||||
<>
|
<>
|
||||||
<Paragraph color="error">
|
<Paragraph color="error">
|
||||||
Approving transaction does not execute it immediately. If you want to approve and execute the
|
Approving this transaction executes it right away. If you want approve but execute the transaction
|
||||||
transaction right away, click on checkbox below.
|
manually later, click on the checkbox below.
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={<Checkbox onChange={handleExecuteCheckbox} checked={approveAndExecute} color="primary" />}
|
control={<Checkbox onChange={handleExecuteCheckbox} checked={approveAndExecute} color="primary" />}
|
||||||
|
@ -112,6 +144,13 @@ const ApproveTxModal = ({
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Row>
|
</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>
|
</Block>
|
||||||
<Row align="center" className={classes.buttonRow}>
|
<Row align="center" className={classes.buttonRow}>
|
||||||
<Button minWidth={140} minHeight={42} onClick={onClose}>
|
<Button minWidth={140} minHeight={42} onClick={onClose}>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import Close from '@material-ui/icons/Close'
|
import Close from '@material-ui/icons/Close'
|
||||||
import IconButton from '@material-ui/core/IconButton'
|
import IconButton from '@material-ui/core/IconButton'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
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 { type Transaction } from '~/routes/safe/store/models/transaction'
|
||||||
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
|
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
|
||||||
import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
|
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'
|
import { styles } from './style'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -37,6 +40,29 @@ const CancelTxModal = ({
|
||||||
enqueueSnackbar,
|
enqueueSnackbar,
|
||||||
closeSnackbar,
|
closeSnackbar,
|
||||||
}: Props) => {
|
}: 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 = () => {
|
const sendReplacementTransaction = () => {
|
||||||
createTransaction(
|
createTransaction(
|
||||||
safeAddress,
|
safeAddress,
|
||||||
|
@ -79,6 +105,11 @@ const CancelTxModal = ({
|
||||||
<Bold className={classes.nonceNumber}>{tx.nonce}</Bold>
|
<Bold className={classes.nonceNumber}>{tx.nonce}</Bold>
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Row>
|
</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>
|
</Block>
|
||||||
<Row align="center" className={classes.buttonRow}>
|
<Row align="center" className={classes.buttonRow}>
|
||||||
<Button minWidth={140} minHeight={42} onClick={onClose}>
|
<Button minWidth={140} minHeight={42} onClick={onClose}>
|
||||||
|
|
|
@ -116,23 +116,27 @@ const ExpandedTx = ({
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
</Block>
|
</Block>
|
||||||
<CancelTxModal
|
{openModal === 'cancelTx' && (
|
||||||
isOpen={openModal === 'cancelTx'}
|
<CancelTxModal
|
||||||
createTransaction={createTransaction}
|
isOpen
|
||||||
onClose={closeModal}
|
createTransaction={createTransaction}
|
||||||
tx={tx}
|
onClose={closeModal}
|
||||||
safeAddress={safeAddress}
|
tx={tx}
|
||||||
/>
|
safeAddress={safeAddress}
|
||||||
<ApproveTxModal
|
/>
|
||||||
isOpen={openModal === 'approveTx'}
|
)}
|
||||||
processTransaction={processTransaction}
|
{openModal === 'approveTx' && (
|
||||||
onClose={closeModal}
|
<ApproveTxModal
|
||||||
tx={tx}
|
isOpen
|
||||||
userAddress={userAddress}
|
processTransaction={processTransaction}
|
||||||
safeAddress={safeAddress}
|
onClose={closeModal}
|
||||||
threshold={threshold}
|
tx={tx}
|
||||||
thresholdReached={thresholdReached}
|
userAddress={userAddress}
|
||||||
/>
|
safeAddress={safeAddress}
|
||||||
|
threshold={threshold}
|
||||||
|
thresholdReached={thresholdReached}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { format, getTime, parseISO } from 'date-fns'
|
import { format, getTime, parseISO } from 'date-fns'
|
||||||
|
import { BigNumber } from 'bignumber.js'
|
||||||
import { List } from 'immutable'
|
import { List } from 'immutable'
|
||||||
import { type Transaction } from '~/routes/safe/store/models/transaction'
|
import { type Transaction } from '~/routes/safe/store/models/transaction'
|
||||||
import { type SortRow, buildOrderFieldFrom } from '~/components/Table/sorting'
|
import { type SortRow, buildOrderFieldFrom } from '~/components/Table/sorting'
|
||||||
|
@ -32,7 +33,8 @@ export const getTxAmount = (tx: Transaction) => {
|
||||||
let txAmount = 'n/a'
|
let txAmount = 'n/a'
|
||||||
|
|
||||||
if (tx.isTokenTransfer && tx.decodedParams) {
|
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) {
|
} else if (Number(tx.value) > 0) {
|
||||||
txAmount = `${fromWei(toBN(tx.value), 'ether')} ${tx.symbol}`
|
txAmount = `${fromWei(toBN(tx.value), 'ether')} ${tx.symbol}`
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,8 +115,9 @@ const extendedTransactionsSelector: Selector<GlobalState, RouterProps, List<Tran
|
||||||
let replacementTransaction
|
let replacementTransaction
|
||||||
if (!tx.isExecuted) {
|
if (!tx.isExecuted) {
|
||||||
replacementTransaction = transactions.findLast(
|
replacementTransaction = transactions.findLast(
|
||||||
(transaction) => transaction.nonce === tx.nonce
|
(transaction) => (transaction.nonce === tx.nonce
|
||||||
&& isAfter(parseISO(transaction.submissionDate), parseISO(tx.submissionDate)),
|
&& isAfter(parseISO(transaction.submissionDate), parseISO(tx.submissionDate)))
|
||||||
|
|| transaction.nonce > tx.nonce,
|
||||||
)
|
)
|
||||||
if (replacementTransaction) {
|
if (replacementTransaction) {
|
||||||
extendedTx = tx.set('cancelled', true)
|
extendedTx = tx.set('cancelled', true)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import type { Dispatch as ReduxDispatch, GetState } from 'redux'
|
import type { Dispatch as ReduxDispatch, GetState } from 'redux'
|
||||||
|
import { push } from 'connected-react-router'
|
||||||
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
|
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
|
||||||
import { userAccountSelector } from '~/logic/wallets/store/selectors'
|
import { userAccountSelector } from '~/logic/wallets/store/selectors'
|
||||||
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
|
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
|
||||||
|
@ -17,11 +18,12 @@ import {
|
||||||
import {
|
import {
|
||||||
type Notification,
|
type Notification,
|
||||||
type NotificationsQueue,
|
type NotificationsQueue,
|
||||||
getNofiticationsFromTxType,
|
getNotificationsFromTxType,
|
||||||
showSnackbar,
|
showSnackbar,
|
||||||
} from '~/logic/notifications'
|
} from '~/logic/notifications'
|
||||||
import { getErrorMessage } from '~/test/utils/ethereumErrors'
|
import { getErrorMessage } from '~/test/utils/ethereumErrors'
|
||||||
import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses'
|
import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses'
|
||||||
|
import { SAFELIST_ADDRESS } from '~/routes/routes'
|
||||||
|
|
||||||
const createTransaction = (
|
const createTransaction = (
|
||||||
safeAddress: string,
|
safeAddress: string,
|
||||||
|
@ -35,6 +37,8 @@ const createTransaction = (
|
||||||
) => async (dispatch: ReduxDispatch<GlobalState>, getState: GetState<GlobalState>) => {
|
) => async (dispatch: ReduxDispatch<GlobalState>, getState: GetState<GlobalState>) => {
|
||||||
const state: GlobalState = getState()
|
const state: GlobalState = getState()
|
||||||
|
|
||||||
|
dispatch(push(`${SAFELIST_ADDRESS}/${safeAddress}/transactions`))
|
||||||
|
|
||||||
const safeInstance = await getGnosisSafeInstanceAt(safeAddress)
|
const safeInstance = await getGnosisSafeInstanceAt(safeAddress)
|
||||||
const from = userAccountSelector(state)
|
const from = userAccountSelector(state)
|
||||||
const threshold = await safeInstance.getThreshold()
|
const threshold = await safeInstance.getThreshold()
|
||||||
|
@ -47,7 +51,7 @@ const createTransaction = (
|
||||||
'',
|
'',
|
||||||
)}000000000000000000000000000000000000000000000000000000000000000001`
|
)}000000000000000000000000000000000000000000000000000000000000000001`
|
||||||
|
|
||||||
const notificationsQueue: NotificationsQueue = getNofiticationsFromTxType(notifiedTransaction)
|
const notificationsQueue: NotificationsQueue = getNotificationsFromTxType(notifiedTransaction)
|
||||||
const beforeExecutionKey = showSnackbar(notificationsQueue.beforeExecution, enqueueSnackbar, closeSnackbar)
|
const beforeExecutionKey = showSnackbar(notificationsQueue.beforeExecution, enqueueSnackbar, closeSnackbar)
|
||||||
let pendingExecutionKey
|
let pendingExecutionKey
|
||||||
|
|
||||||
|
@ -99,6 +103,7 @@ const createTransaction = (
|
||||||
if (isExecution) {
|
if (isExecution) {
|
||||||
showSnackbar(notificationsQueue.afterExecution, enqueueSnackbar, closeSnackbar)
|
showSnackbar(notificationsQueue.afterExecution, enqueueSnackbar, closeSnackbar)
|
||||||
}
|
}
|
||||||
|
dispatch(fetchTransactions(safeAddress))
|
||||||
|
|
||||||
return receipt.transactionHash
|
return receipt.transactionHash
|
||||||
})
|
})
|
||||||
|
@ -114,8 +119,6 @@ const createTransaction = (
|
||||||
console.error(`Error executing the TX: ${errMsg}`)
|
console.error(`Error executing the TX: ${errMsg}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(fetchTransactions(safeAddress))
|
|
||||||
|
|
||||||
return txHash
|
return txHash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { getHumanFriendlyToken } from '~/logic/tokens/store/actions/fetchTokens'
|
||||||
import { isTokenTransfer } from '~/logic/tokens/utils/tokenHelpers'
|
import { isTokenTransfer } from '~/logic/tokens/utils/tokenHelpers'
|
||||||
import { TX_TYPE_EXECUTION } from '~/logic/safe/transactions'
|
import { TX_TYPE_EXECUTION } from '~/logic/safe/transactions'
|
||||||
import { decodeParamsFromSafeMethod } from '~/logic/contracts/methodIds'
|
import { decodeParamsFromSafeMethod } from '~/logic/contracts/methodIds'
|
||||||
|
import { ALTERNATIVE_TOKEN_ABI } from '~/logic/tokens/utils/alternativeAbi'
|
||||||
|
|
||||||
let web3
|
let web3
|
||||||
|
|
||||||
|
@ -59,8 +60,8 @@ export const buildTransactionFrom = async (
|
||||||
)
|
)
|
||||||
const modifySettingsTx = tx.to === safeAddress && Number(tx.value) === 0 && !!tx.data
|
const modifySettingsTx = tx.to === safeAddress && Number(tx.value) === 0 && !!tx.data
|
||||||
const cancellationTx = 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 isSendTokenTx = await isTokenTransfer(tx.data, tx.value)
|
||||||
|
const customTx = tx.to !== safeAddress && !!tx.data && !isSendTokenTx
|
||||||
|
|
||||||
let executionTxHash
|
let executionTxHash
|
||||||
const executionTx = confirmations.find((conf) => conf.type === TX_TYPE_EXECUTION)
|
const executionTx = confirmations.find((conf) => conf.type === TX_TYPE_EXECUTION)
|
||||||
|
@ -70,11 +71,23 @@ export const buildTransactionFrom = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
let symbol = 'ETH'
|
let symbol = 'ETH'
|
||||||
|
let decimals = 18
|
||||||
let decodedParams
|
let decodedParams
|
||||||
if (isSendTokenTx) {
|
if (isSendTokenTx) {
|
||||||
const tokenContract = await getHumanFriendlyToken()
|
const tokenContract = await getHumanFriendlyToken()
|
||||||
const tokenInstance = await tokenContract.at(tx.to)
|
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))
|
const params = web3.eth.abi.decodeParameters(['address', 'uint256'], tx.data.slice(10))
|
||||||
decodedParams = {
|
decodedParams = {
|
||||||
|
@ -93,6 +106,7 @@ export const buildTransactionFrom = async (
|
||||||
nonce: tx.nonce,
|
nonce: tx.nonce,
|
||||||
value: tx.value.toString(),
|
value: tx.value.toString(),
|
||||||
confirmations,
|
confirmations,
|
||||||
|
decimals,
|
||||||
recipient: tx.to,
|
recipient: tx.to,
|
||||||
data: tx.data ? tx.data : EMPTY_DATA,
|
data: tx.data ? tx.data : EMPTY_DATA,
|
||||||
isExecuted: tx.isExecuted,
|
isExecuted: tx.isExecuted,
|
||||||
|
|
|
@ -11,7 +11,7 @@ const loadDefaultSafe = () => async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||||
dispatch(setDefaultSafe(defaultSafe))
|
dispatch(setDefaultSafe(defaultSafe))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
console.error('Error while getting defautl Safe from storage:', err)
|
console.error('Error while getting default Safe from storage:', err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
// @flow
|
// @flow
|
||||||
import type { Dispatch as ReduxDispatch, GetState } from 'redux'
|
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 { type Transaction } from '~/routes/safe/store/models/transaction'
|
||||||
import { userAccountSelector } from '~/logic/wallets/store/selectors'
|
import { userAccountSelector } from '~/logic/wallets/store/selectors'
|
||||||
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
|
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
|
||||||
import { type GlobalState } from '~/store'
|
import { type GlobalState } from '~/store'
|
||||||
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
|
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
|
||||||
import {
|
import {
|
||||||
|
type NotifiedTransaction,
|
||||||
getApprovalTransaction,
|
getApprovalTransaction,
|
||||||
getExecutionTransaction,
|
getExecutionTransaction,
|
||||||
CALL,
|
CALL,
|
||||||
|
@ -16,17 +19,17 @@ import {
|
||||||
import {
|
import {
|
||||||
type Notification,
|
type Notification,
|
||||||
type NotificationsQueue,
|
type NotificationsQueue,
|
||||||
getNofiticationsFromTxType,
|
getNotificationsFromTxType,
|
||||||
showSnackbar,
|
showSnackbar,
|
||||||
} from '~/logic/notifications'
|
} from '~/logic/notifications'
|
||||||
import { getErrorMessage } from '~/test/utils/ethereumErrors'
|
import { getErrorMessage } from '~/test/utils/ethereumErrors'
|
||||||
|
|
||||||
// https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures
|
// 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
|
// 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
|
// The constant parts need to be sorted so that the recovered signers are sorted ascending
|
||||||
// (natural order) by address (not checksummed).
|
// (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) {
|
if (preApprovingOwner) {
|
||||||
confirmedAdresses = confirmedAdresses.push(preApprovingOwner)
|
confirmedAdresses = confirmedAdresses.push(preApprovingOwner)
|
||||||
|
@ -59,7 +62,7 @@ const processTransaction = (
|
||||||
const threshold = (await safeInstance.getThreshold()).toNumber()
|
const threshold = (await safeInstance.getThreshold()).toNumber()
|
||||||
const shouldExecute = threshold === tx.confirmations.size || approveAndExecute
|
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
|
// https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures
|
||||||
if (!sigs) {
|
if (!sigs) {
|
||||||
sigs = `0x000000000000000000000000${from.replace(
|
sigs = `0x000000000000000000000000${from.replace(
|
||||||
|
@ -68,7 +71,7 @@ const processTransaction = (
|
||||||
)}000000000000000000000000000000000000000000000000000000000000000001`
|
)}000000000000000000000000000000000000000000000000000000000000000001`
|
||||||
}
|
}
|
||||||
|
|
||||||
const notificationsQueue: NotificationsQueue = getNofiticationsFromTxType(notifiedTransaction)
|
const notificationsQueue: NotificationsQueue = getNotificationsFromTxType(notifiedTransaction)
|
||||||
const beforeExecutionKey = showSnackbar(notificationsQueue.beforeExecution, enqueueSnackbar, closeSnackbar)
|
const beforeExecutionKey = showSnackbar(notificationsQueue.beforeExecution, enqueueSnackbar, closeSnackbar)
|
||||||
let pendingExecutionKey
|
let pendingExecutionKey
|
||||||
|
|
||||||
|
@ -124,6 +127,7 @@ const processTransaction = (
|
||||||
shouldExecute ? TX_TYPE_EXECUTION : TX_TYPE_CONFIRMATION,
|
shouldExecute ? TX_TYPE_EXECUTION : TX_TYPE_CONFIRMATION,
|
||||||
)
|
)
|
||||||
showSnackbar(notificationsQueue.afterExecution, enqueueSnackbar, closeSnackbar)
|
showSnackbar(notificationsQueue.afterExecution, enqueueSnackbar, closeSnackbar)
|
||||||
|
dispatch(fetchTransactions(safeAddress))
|
||||||
|
|
||||||
return receipt.transactionHash
|
return receipt.transactionHash
|
||||||
})
|
})
|
||||||
|
@ -137,8 +141,6 @@ const processTransaction = (
|
||||||
console.error(`Error executing the TX: ${errMsg}`)
|
console.error(`Error executing the TX: ${errMsg}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(fetchTransactions(safeAddress))
|
|
||||||
|
|
||||||
return txHash
|
return txHash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ export type SafeProps = {
|
||||||
address: string,
|
address: string,
|
||||||
threshold: number,
|
threshold: number,
|
||||||
owners: List<Owner>,
|
owners: List<Owner>,
|
||||||
balances: Map<string, string>,
|
balances?: Map<string, string>,
|
||||||
activeTokens: Set<string>,
|
activeTokens: Set<string>,
|
||||||
ethBalance?: string,
|
ethBalance?: string,
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ const SafeRecord: RecordFactory<SafeProps> = Record({
|
||||||
threshold: 0,
|
threshold: 0,
|
||||||
ethBalance: 0,
|
ethBalance: 0,
|
||||||
owners: List([]),
|
owners: List([]),
|
||||||
activeTokens: new Set([]),
|
activeTokens: new Set(),
|
||||||
balances: Map({}),
|
balances: Map({}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ export type TransactionProps = {
|
||||||
customTx: boolean,
|
customTx: boolean,
|
||||||
safeTxHash: string,
|
safeTxHash: string,
|
||||||
executionTxHash?: string,
|
executionTxHash?: string,
|
||||||
|
decimals?: number,
|
||||||
cancelled?: boolean,
|
cancelled?: boolean,
|
||||||
status?: TransactionStatus,
|
status?: TransactionStatus,
|
||||||
isTokenTransfer: boolean,
|
isTokenTransfer: boolean,
|
||||||
|
@ -45,6 +46,7 @@ export const makeTransaction: RecordFactory<TransactionProps> = Record({
|
||||||
cancellationTx: false,
|
cancellationTx: false,
|
||||||
customTx: false,
|
customTx: false,
|
||||||
status: 'awaiting',
|
status: 'awaiting',
|
||||||
|
decimals: 18,
|
||||||
isTokenTransfer: false,
|
isTokenTransfer: false,
|
||||||
decodedParams: {},
|
decodedParams: {},
|
||||||
})
|
})
|
||||||
|
|
|
@ -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 tokens, { TOKEN_REDUCER_ID, type State as TokensState } from '~/logic/tokens/store/reducer/tokens'
|
||||||
import notifications, {
|
import notifications, {
|
||||||
NOTIFICATIONS_REDUCER_ID,
|
NOTIFICATIONS_REDUCER_ID,
|
||||||
type State as NotificationsState,
|
type NotificationReducerState as NotificationsState,
|
||||||
} from '~/logic/notifications/store/reducer/notifications'
|
} from '~/logic/notifications/store/reducer/notifications'
|
||||||
|
|
||||||
export const history = createBrowserHistory()
|
export const history = createBrowserHistory()
|
||||||
|
|
|
@ -14,7 +14,6 @@ import { TRANSACTION_ROW_TEST_ID } from '~/routes/safe/components/Transactions/T
|
||||||
import { useTestAccountAt, resetTestAccount } from './utils/accounts'
|
import { useTestAccountAt, resetTestAccount } from './utils/accounts'
|
||||||
import {
|
import {
|
||||||
CONFIRM_TX_BTN_TEST_ID,
|
CONFIRM_TX_BTN_TEST_ID,
|
||||||
EXECUTE_TX_BTN_TEST_ID,
|
|
||||||
} from '~/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/ButtonRow'
|
} 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'
|
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))
|
const approveTxBtn = await waitForElement(() => SafeDom.getByTestId(APPROVE_TX_MODAL_SUBMIT_BTN_TEST_ID))
|
||||||
fireEvent.click(approveTxBtn)
|
fireEvent.click(approveTxBtn)
|
||||||
|
|
||||||
// EXECUTE TX
|
await sleep(1000)
|
||||||
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)
|
|
||||||
|
|
||||||
// THEN
|
// THEN
|
||||||
const safeFunds = await getBalanceInEtherOf(safeAddress)
|
const safeFunds = await getBalanceInEtherOf(safeAddress)
|
||||||
|
|
581
yarn.lock
581
yarn.lock
|
@ -2,10 +2,10 @@
|
||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
"@babel/cli@7.6.2":
|
"@babel/cli@7.6.4":
|
||||||
version "7.6.2"
|
version "7.6.4"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.6.2.tgz#4ce8b5b4b2e4b4c1b7bd841cec62085e2dfc4465"
|
resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.6.4.tgz#9b35a4e15fa7d8f487418aaa8229c8b0bc815f20"
|
||||||
integrity sha512-JDZ+T/br9pPfT2lmAMJypJDTTTHM9ePD/ED10TRjRzJVdEVy+JB3iRlhzYmTt5YkNgHvxWGlUVnLtdv6ruiDrQ==
|
integrity sha512-tqrDyvPryBM6xjIyKKUwr3s8CzmmYidwgdswd7Uc/Cv0ogZcuS1TYQTLx/eWKP3UbJ6JxZAiYlBZabXm/rtRsQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
commander "^2.8.1"
|
commander "^2.8.1"
|
||||||
convert-source-map "^1.1.0"
|
convert-source-map "^1.1.0"
|
||||||
|
@ -53,18 +53,18 @@
|
||||||
semver "^5.4.1"
|
semver "^5.4.1"
|
||||||
source-map "^0.5.0"
|
source-map "^0.5.0"
|
||||||
|
|
||||||
"@babel/core@7.6.2":
|
"@babel/core@7.6.4":
|
||||||
version "7.6.2"
|
version "7.6.4"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.6.2.tgz#069a776e8d5e9eefff76236bc8845566bd31dd91"
|
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.6.4.tgz#6ebd9fe00925f6c3e177bb726a188b5f578088ff"
|
||||||
integrity sha512-l8zto/fuoZIbncm+01p8zPSDZu/VuuJhAfA7d/AbzM09WR7iVhavvfNDYCNpo1VvLk6E6xgAoP9P+/EMJHuRkQ==
|
integrity sha512-Rm0HGw101GY8FTzpWSyRbki/jzq+/PkNQJ+nSulrdY6gFGOsNseCqD6KHRYe2E+EdzuBdr2pxCp6s4Uk6eJ+XQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/code-frame" "^7.5.5"
|
"@babel/code-frame" "^7.5.5"
|
||||||
"@babel/generator" "^7.6.2"
|
"@babel/generator" "^7.6.4"
|
||||||
"@babel/helpers" "^7.6.2"
|
"@babel/helpers" "^7.6.2"
|
||||||
"@babel/parser" "^7.6.2"
|
"@babel/parser" "^7.6.4"
|
||||||
"@babel/template" "^7.6.0"
|
"@babel/template" "^7.6.0"
|
||||||
"@babel/traverse" "^7.6.2"
|
"@babel/traverse" "^7.6.3"
|
||||||
"@babel/types" "^7.6.0"
|
"@babel/types" "^7.6.3"
|
||||||
convert-source-map "^1.1.0"
|
convert-source-map "^1.1.0"
|
||||||
debug "^4.1.0"
|
debug "^4.1.0"
|
||||||
json5 "^2.1.0"
|
json5 "^2.1.0"
|
||||||
|
@ -114,6 +114,26 @@
|
||||||
lodash "^4.17.13"
|
lodash "^4.17.13"
|
||||||
source-map "^0.5.0"
|
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":
|
"@babel/helper-annotate-as-pure@^7.0.0":
|
||||||
version "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"
|
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"
|
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.2.tgz#205e9c95e16ba3b8b96090677a67c9d6075b70a1"
|
||||||
integrity sha512-mdFqWrSPCmikBoaBYMuBulzTIKuXVPtEISFbRRVNwMWpCms/hmE2kRq0bblUHaNRKrjRlmVbx1sDHmjmRgD2Xg==
|
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":
|
"@babel/plugin-proposal-async-generator-functions@^7.2.0":
|
||||||
version "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"
|
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"
|
"@babel/helper-plugin-utils" "^7.0.0"
|
||||||
lodash "^4.17.13"
|
lodash "^4.17.13"
|
||||||
|
|
||||||
"@babel/plugin-transform-block-scoping@^7.6.2":
|
"@babel/plugin-transform-block-scoping@^7.6.3":
|
||||||
version "7.6.2"
|
version "7.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.2.tgz#96c33ab97a9ae500cc6f5b19e04a7e6553360a79"
|
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.3.tgz#6e854e51fbbaa84351b15d4ddafe342f3a5d542a"
|
||||||
integrity sha512-zZT8ivau9LOQQaOGC7bQLQOT4XPkPXgN2ERfUgk1X8ql+mVkLc4E8eKk+FO3o0154kxzqenWCorfmEXpEZcrSQ==
|
integrity sha512-7hvrg75dubcO3ZI2rjYTzUrEuh1E9IyDEhhB6qfcooxhDA33xx2MasuLVgdxzcP6R/lipAC6n9ub9maNW6RKdw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/helper-plugin-utils" "^7.0.0"
|
"@babel/helper-plugin-utils" "^7.0.0"
|
||||||
lodash "^4.17.13"
|
lodash "^4.17.13"
|
||||||
|
@ -914,10 +944,10 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
regexp-tree "^0.1.6"
|
regexp-tree "^0.1.6"
|
||||||
|
|
||||||
"@babel/plugin-transform-named-capturing-groups-regex@^7.6.2":
|
"@babel/plugin-transform-named-capturing-groups-regex@^7.6.3":
|
||||||
version "7.6.2"
|
version "7.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.6.2.tgz#c1ca0bb84b94f385ca302c3932e870b0fb0e522b"
|
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-xBdB+XOs+lgbZc2/4F5BVDVcDNS4tcSKQc96KmlqLEAwz6tpYPEvPdmDfvVG0Ssn8lAhronaRs6Z6KSexIpK5g==
|
integrity sha512-jTkk7/uE6H2s5w6VlMHeWuH+Pcy2lmdwFoeWCVnvIrDUnB5gQqTVI8WfmEAhF2CDEarGrknZcmSFg1+bkfCoSw==
|
||||||
dependencies:
|
dependencies:
|
||||||
regexpu-core "^4.6.0"
|
regexpu-core "^4.6.0"
|
||||||
|
|
||||||
|
@ -1157,10 +1187,10 @@
|
||||||
js-levenshtein "^1.1.3"
|
js-levenshtein "^1.1.3"
|
||||||
semver "^5.5.0"
|
semver "^5.5.0"
|
||||||
|
|
||||||
"@babel/preset-env@7.6.2":
|
"@babel/preset-env@7.6.3":
|
||||||
version "7.6.2"
|
version "7.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.6.2.tgz#abbb3ed785c7fe4220d4c82a53621d71fc0c75d3"
|
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.6.3.tgz#9e1bf05a2e2d687036d24c40e4639dc46cef2271"
|
||||||
integrity sha512-Ru7+mfzy9M1/YTEtlDS8CD45jd22ngb9tXnn64DvQK3ooyqSw9K4K9DUWmYknTTVk4TqygL9dqCrZgm1HMea/Q==
|
integrity sha512-CWQkn7EVnwzlOdR5NOm2+pfgSNEZmvGjOhlCHBDq0J8/EStr+G+FvPEiz9B56dR6MoiUFjXhfE4hjLoAKKJtIQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/helper-module-imports" "^7.0.0"
|
"@babel/helper-module-imports" "^7.0.0"
|
||||||
"@babel/helper-plugin-utils" "^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-arrow-functions" "^7.2.0"
|
||||||
"@babel/plugin-transform-async-to-generator" "^7.5.0"
|
"@babel/plugin-transform-async-to-generator" "^7.5.0"
|
||||||
"@babel/plugin-transform-block-scoped-functions" "^7.2.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-classes" "^7.5.5"
|
||||||
"@babel/plugin-transform-computed-properties" "^7.2.0"
|
"@babel/plugin-transform-computed-properties" "^7.2.0"
|
||||||
"@babel/plugin-transform-destructuring" "^7.6.0"
|
"@babel/plugin-transform-destructuring" "^7.6.0"
|
||||||
|
@ -1193,7 +1223,7 @@
|
||||||
"@babel/plugin-transform-modules-commonjs" "^7.6.0"
|
"@babel/plugin-transform-modules-commonjs" "^7.6.0"
|
||||||
"@babel/plugin-transform-modules-systemjs" "^7.5.0"
|
"@babel/plugin-transform-modules-systemjs" "^7.5.0"
|
||||||
"@babel/plugin-transform-modules-umd" "^7.2.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-new-target" "^7.4.4"
|
||||||
"@babel/plugin-transform-object-super" "^7.5.5"
|
"@babel/plugin-transform-object-super" "^7.5.5"
|
||||||
"@babel/plugin-transform-parameters" "^7.4.4"
|
"@babel/plugin-transform-parameters" "^7.4.4"
|
||||||
|
@ -1206,7 +1236,7 @@
|
||||||
"@babel/plugin-transform-template-literals" "^7.4.4"
|
"@babel/plugin-transform-template-literals" "^7.4.4"
|
||||||
"@babel/plugin-transform-typeof-symbol" "^7.2.0"
|
"@babel/plugin-transform-typeof-symbol" "^7.2.0"
|
||||||
"@babel/plugin-transform-unicode-regex" "^7.6.2"
|
"@babel/plugin-transform-unicode-regex" "^7.6.2"
|
||||||
"@babel/types" "^7.6.0"
|
"@babel/types" "^7.6.3"
|
||||||
browserslist "^4.6.0"
|
browserslist "^4.6.0"
|
||||||
core-js-compat "^3.1.1"
|
core-js-compat "^3.1.1"
|
||||||
invariant "^2.2.2"
|
invariant "^2.2.2"
|
||||||
|
@ -1288,6 +1318,17 @@
|
||||||
"@babel/plugin-transform-react-jsx-self" "^7.0.0"
|
"@babel/plugin-transform-react-jsx-self" "^7.0.0"
|
||||||
"@babel/plugin-transform-react-jsx-source" "^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":
|
"@babel/preset-typescript@7.3.3":
|
||||||
version "7.3.3"
|
version "7.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.3.3.tgz#88669911053fa16b2b276ea2ede2ca603b3f307a"
|
resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.3.3.tgz#88669911053fa16b2b276ea2ede2ca603b3f307a"
|
||||||
|
@ -1387,6 +1428,21 @@
|
||||||
globals "^11.1.0"
|
globals "^11.1.0"
|
||||||
lodash "^4.17.13"
|
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":
|
"@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"
|
version "7.5.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.5.5.tgz#97b9f728e182785909aa4ab56264f090a028d18a"
|
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.5.5.tgz#97b9f728e182785909aa4ab56264f090a028d18a"
|
||||||
|
@ -1405,6 +1461,15 @@
|
||||||
lodash "^4.17.13"
|
lodash "^4.17.13"
|
||||||
to-fast-properties "^2.0.0"
|
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":
|
"@cnakazawa/watch@^1.0.3":
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef"
|
resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef"
|
||||||
|
@ -1760,10 +1825,10 @@
|
||||||
"@types/istanbul-reports" "^1.1.1"
|
"@types/istanbul-reports" "^1.1.1"
|
||||||
"@types/yargs" "^13.0.0"
|
"@types/yargs" "^13.0.0"
|
||||||
|
|
||||||
"@material-ui/core@4.5.0":
|
"@material-ui/core@4.5.1":
|
||||||
version "4.5.0"
|
version "4.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.5.0.tgz#7e57cc40988c71b6340e3b2569b47dbac1820351"
|
resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.5.1.tgz#6f1bbb298cc2893b29169ab9398a00ca5a6ac971"
|
||||||
integrity sha512-UHVAjU+1uDtA+OMBNBHb4RlCZOu514XeYPafNJv+GTdXBDr1SCPK7yqRE6TV1/bulxlDusTgu5Q6BAUgpmO4MA==
|
integrity sha512-6pyk7diT7bflf4qUpqgPCpKYqjhRHPFwsgEV2Gv71lMqwxuRygFGHE2TdZ+l5T249H66Doj2P/j6fW7yzgxTWw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.4.4"
|
"@babel/runtime" "^7.4.4"
|
||||||
"@material-ui/styles" "^4.5.0"
|
"@material-ui/styles" "^4.5.0"
|
||||||
|
@ -1781,10 +1846,10 @@
|
||||||
prop-types "^15.7.2"
|
prop-types "^15.7.2"
|
||||||
react-transition-group "^4.3.0"
|
react-transition-group "^4.3.0"
|
||||||
|
|
||||||
"@material-ui/icons@4.4.3":
|
"@material-ui/icons@4.5.1":
|
||||||
version "4.4.3"
|
version "4.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/@material-ui/icons/-/icons-4.4.3.tgz#5d4346ddbb2673a1b57ebc78fd6d50bcd88711db"
|
resolved "https://registry.yarnpkg.com/@material-ui/icons/-/icons-4.5.1.tgz#6963bad139e938702ece85ca43067688018f04f8"
|
||||||
integrity sha512-HVVvUyc/78kmaBd93LkfWyGkXMM+zOMKzUfulWXxaV/fFAZ3N0pD0oHjWUd94zrOoF3tZP9JC7EPlIpIcZSNow==
|
integrity sha512-YZ/BgJbXX4a0gOuKWb30mBaHaoXRqPanlePam83JQPZ/y4kl+3aW0Wv9tlR70hB5EGAkEJGW5m4ktJwMgxQAeA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.4.4"
|
"@babel/runtime" "^7.4.4"
|
||||||
|
|
||||||
|
@ -1930,17 +1995,17 @@
|
||||||
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
|
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
|
||||||
integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==
|
integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==
|
||||||
|
|
||||||
"@storybook/addon-actions@5.2.1":
|
"@storybook/addon-actions@5.2.4":
|
||||||
version "5.2.1"
|
version "5.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-5.2.1.tgz#2096e7f938b289be48af6f0adfd620997e7a420c"
|
resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-5.2.4.tgz#79d13c1b6e58c75dd548eb162aa6626a93dfd041"
|
||||||
integrity sha512-tu4LGeRGAq+sLlsRPE1PzGyYU9JyM3HMLXnOCh5dvRSS8wnoDw1zQ55LPOXH6aoJGdsrvktiw+uTVf4OyN7ryg==
|
integrity sha512-5E8uXopy6Gq5R3MXrPf0VM9QiLaGLxLCXtDYHQ0gku+HhPYR25KQudS/PyuO+OWzuyB0fsvTi240B3zw+zilOg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@storybook/addons" "5.2.1"
|
"@storybook/addons" "5.2.4"
|
||||||
"@storybook/api" "5.2.1"
|
"@storybook/api" "5.2.4"
|
||||||
"@storybook/client-api" "5.2.1"
|
"@storybook/client-api" "5.2.4"
|
||||||
"@storybook/components" "5.2.1"
|
"@storybook/components" "5.2.4"
|
||||||
"@storybook/core-events" "5.2.1"
|
"@storybook/core-events" "5.2.4"
|
||||||
"@storybook/theming" "5.2.1"
|
"@storybook/theming" "5.2.4"
|
||||||
core-js "^3.0.1"
|
core-js "^3.0.1"
|
||||||
fast-deep-equal "^2.0.1"
|
fast-deep-equal "^2.0.1"
|
||||||
global "^4.3.2"
|
global "^4.3.2"
|
||||||
|
@ -1966,17 +2031,18 @@
|
||||||
react-inspector "^2.2.2"
|
react-inspector "^2.2.2"
|
||||||
uuid "^3.2.1"
|
uuid "^3.2.1"
|
||||||
|
|
||||||
"@storybook/addon-knobs@5.2.1":
|
"@storybook/addon-knobs@5.2.4":
|
||||||
version "5.2.1"
|
version "5.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/addon-knobs/-/addon-knobs-5.2.1.tgz#6bc2f7e254ccce09d6f5136e9cce63cd808c9853"
|
resolved "https://registry.yarnpkg.com/@storybook/addon-knobs/-/addon-knobs-5.2.4.tgz#f5db98235bc2870361dd3c224f8c10f901592c43"
|
||||||
integrity sha512-JCSqrGYyVVBNkudhvla7qc9m0/Mn1UMaMzIxH5kewEE1KWZcCkdXD5hDASN39pkn3mX1yyqveP8jiyIL9vVBLg==
|
integrity sha512-VYxbDARJs5RwTEOlcfa98tkDXLcRocB7QXLqt8wwCdXPIqkuoVeQLROXGYJm2NzSn49RyHPKUuVWnRhy34qBbQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@storybook/addons" "5.2.1"
|
"@storybook/addons" "5.2.4"
|
||||||
"@storybook/api" "5.2.1"
|
"@storybook/api" "5.2.4"
|
||||||
"@storybook/client-api" "5.2.1"
|
"@storybook/client-api" "5.2.4"
|
||||||
"@storybook/components" "5.2.1"
|
"@storybook/components" "5.2.4"
|
||||||
"@storybook/core-events" "5.2.1"
|
"@storybook/core-events" "5.2.4"
|
||||||
"@storybook/theming" "5.2.1"
|
"@storybook/theming" "5.2.4"
|
||||||
|
"@types/react-color" "^3.0.1"
|
||||||
copy-to-clipboard "^3.0.8"
|
copy-to-clipboard "^3.0.8"
|
||||||
core-js "^3.0.1"
|
core-js "^3.0.1"
|
||||||
escape-html "^1.0.3"
|
escape-html "^1.0.3"
|
||||||
|
@ -1989,29 +2055,29 @@
|
||||||
react-lifecycles-compat "^3.0.4"
|
react-lifecycles-compat "^3.0.4"
|
||||||
react-select "^3.0.0"
|
react-select "^3.0.0"
|
||||||
|
|
||||||
"@storybook/addon-links@5.2.1":
|
"@storybook/addon-links@5.2.4":
|
||||||
version "5.2.1"
|
version "5.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-5.2.1.tgz#ec1fc92ed4d840ba758f40167c752f48562a906f"
|
resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-5.2.4.tgz#5a5d631dbd66bf5800d4be7660b79568085a210d"
|
||||||
integrity sha512-N5f+lzai+ctHfzHoYWECYsg3lKGJuqhkVctro46fHSW7s/GB8+l78nDcV7hDjNEXDES8QN5C1fPYihatdgpSJA==
|
integrity sha512-MG+Qne4gUWGYx2qQuLQXNcl7oOBF4PbIcR0oboZNrkZ+D+6f3nHwyb53CTtzVTc+SF45CFFYLHvFdGZvv5fcAw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@storybook/addons" "5.2.1"
|
"@storybook/addons" "5.2.4"
|
||||||
"@storybook/core-events" "5.2.1"
|
"@storybook/core-events" "5.2.4"
|
||||||
"@storybook/router" "5.2.1"
|
"@storybook/router" "5.2.4"
|
||||||
common-tags "^1.8.0"
|
common-tags "^1.8.0"
|
||||||
core-js "^3.0.1"
|
core-js "^3.0.1"
|
||||||
global "^4.3.2"
|
global "^4.3.2"
|
||||||
prop-types "^15.7.2"
|
prop-types "^15.7.2"
|
||||||
qs "^6.6.0"
|
qs "^6.6.0"
|
||||||
|
|
||||||
"@storybook/addons@5.2.1":
|
"@storybook/addons@5.2.4":
|
||||||
version "5.2.1"
|
version "5.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-5.2.1.tgz#6e52aa1fa2737e170fb675eb1fcceebd0a915a0b"
|
resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-5.2.4.tgz#5c4f031e403c90a517cd6d208ec51d7e2455683a"
|
||||||
integrity sha512-kdx97tTKsMf/lBlT40uLYsHMF1J71mn2j41RNaCXmWw/PrKCDmiNfinemN2wtbwRSvGqb3q/BAqjKLvUtWynGg==
|
integrity sha512-Q+bnVlBA308qnELxnh18hBDRSUgltR9KbV537285dUL/okv/NC6n51mxJwIaG+ksBW2wU+5e6tqSayaKF3uHLw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@storybook/api" "5.2.1"
|
"@storybook/api" "5.2.4"
|
||||||
"@storybook/channels" "5.2.1"
|
"@storybook/channels" "5.2.4"
|
||||||
"@storybook/client-logger" "5.2.1"
|
"@storybook/client-logger" "5.2.4"
|
||||||
"@storybook/core-events" "5.2.1"
|
"@storybook/core-events" "5.2.4"
|
||||||
core-js "^3.0.1"
|
core-js "^3.0.1"
|
||||||
global "^4.3.2"
|
global "^4.3.2"
|
||||||
util-deprecate "^1.0.2"
|
util-deprecate "^1.0.2"
|
||||||
|
@ -2051,16 +2117,16 @@
|
||||||
telejson "^2.2.1"
|
telejson "^2.2.1"
|
||||||
util-deprecate "^1.0.2"
|
util-deprecate "^1.0.2"
|
||||||
|
|
||||||
"@storybook/api@5.2.1":
|
"@storybook/api@5.2.4":
|
||||||
version "5.2.1"
|
version "5.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/api/-/api-5.2.1.tgz#b9cd6639019e044a8ade6fb358cade79c0e3b5d3"
|
resolved "https://registry.yarnpkg.com/@storybook/api/-/api-5.2.4.tgz#b0b3dbd93444d163a80b455fb877d816a37b3149"
|
||||||
integrity sha512-EXN6sqkGHRuNq0W6BZXOlxe2I2dmN0yUdQLiUOpzH2I3mXnVHpad/0v76dRc9fZbC4LaYUSxR8lBTr0rqIb4mA==
|
integrity sha512-KqAB+NkHIHdwu749NDP+7i44jy1bFgpq7GTJlG+sx/XLZHQveK/8yn109g9bXHFth7SvdXI1+9GA/apzwBU/Mw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@storybook/channels" "5.2.1"
|
"@storybook/channels" "5.2.4"
|
||||||
"@storybook/client-logger" "5.2.1"
|
"@storybook/client-logger" "5.2.4"
|
||||||
"@storybook/core-events" "5.2.1"
|
"@storybook/core-events" "5.2.4"
|
||||||
"@storybook/router" "5.2.1"
|
"@storybook/router" "5.2.4"
|
||||||
"@storybook/theming" "5.2.1"
|
"@storybook/theming" "5.2.4"
|
||||||
core-js "^3.0.1"
|
core-js "^3.0.1"
|
||||||
fast-deep-equal "^2.0.1"
|
fast-deep-equal "^2.0.1"
|
||||||
global "^4.3.2"
|
global "^4.3.2"
|
||||||
|
@ -2071,19 +2137,19 @@
|
||||||
semver "^6.0.0"
|
semver "^6.0.0"
|
||||||
shallow-equal "^1.1.0"
|
shallow-equal "^1.1.0"
|
||||||
store2 "^2.7.1"
|
store2 "^2.7.1"
|
||||||
telejson "^2.2.2"
|
telejson "^3.0.2"
|
||||||
util-deprecate "^1.0.2"
|
util-deprecate "^1.0.2"
|
||||||
|
|
||||||
"@storybook/channel-postmessage@5.2.1":
|
"@storybook/channel-postmessage@5.2.4":
|
||||||
version "5.2.1"
|
version "5.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-5.2.1.tgz#85541f926d61eedbe2a687bb394d37fc06252751"
|
resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-5.2.4.tgz#e3735bdce42156e54bf462083aebaf23245ab5c3"
|
||||||
integrity sha512-gmnn9qU1iLCpfF6bZuEM3QQOZsAviWeIpiezjrd/qkxatgr3qtbXd4EoZpcVuQw314etarWtNxVpcX6PXcASjQ==
|
integrity sha512-ic7/Ho8z2/aOMjoEbr5p8rijOfO3SZdJnwMvDdUxrqvYq7yACZWidPo3w2+iBwQi9HLqEsWesP1c2doJBxVGRw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@storybook/channels" "5.2.1"
|
"@storybook/channels" "5.2.4"
|
||||||
"@storybook/client-logger" "5.2.1"
|
"@storybook/client-logger" "5.2.4"
|
||||||
core-js "^3.0.1"
|
core-js "^3.0.1"
|
||||||
global "^4.3.2"
|
global "^4.3.2"
|
||||||
telejson "^2.2.2"
|
telejson "^3.0.2"
|
||||||
|
|
||||||
"@storybook/channels@5.1.9":
|
"@storybook/channels@5.1.9":
|
||||||
version "5.1.9"
|
version "5.1.9"
|
||||||
|
@ -2092,24 +2158,24 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
core-js "^3.0.1"
|
core-js "^3.0.1"
|
||||||
|
|
||||||
"@storybook/channels@5.2.1":
|
"@storybook/channels@5.2.4":
|
||||||
version "5.2.1"
|
version "5.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-5.2.1.tgz#e5e35f6d9fb1b1fba4f18b171f31d5f6540f3bef"
|
resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-5.2.4.tgz#7ab5c9478517ddb9e8ac0c4188b408b9d7e6b7e4"
|
||||||
integrity sha512-AsF/Hwx91SDOgiOGOBSWS8EJAgqVm939n2nkfdLSJQQmX5EdPRAc3EIE3f13tyQub2yNx0OR4UzQDWgjwfVsEQ==
|
integrity sha512-/r39yEZ5QiGdiq95DhXBypdBo7urkD3Sp1WDyK48uGkZ0gdHWSPy3BBy8OJhEhfNz7nVisTiVIBr4gIrubKDjw==
|
||||||
dependencies:
|
dependencies:
|
||||||
core-js "^3.0.1"
|
core-js "^3.0.1"
|
||||||
|
|
||||||
"@storybook/client-api@5.2.1":
|
"@storybook/client-api@5.2.4":
|
||||||
version "5.2.1"
|
version "5.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-5.2.1.tgz#bdd335187279a4ab45e20d6d5e9131e5f7098acf"
|
resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-5.2.4.tgz#2507a3a739a6f6b13e4afefb938a6f3eab34ed55"
|
||||||
integrity sha512-VxexqxrbORCGqwx2j0/91Eu1A/vq+rSVIesWwzIowmoLfBwRwDdskO20Yn9U7iMSpux4RvHGF6y1Q1ZtnXm9aA==
|
integrity sha512-SOwzEFHoNapURhNqdcI7HA76o5tkWvs2+2s++i/S7xsAd3KyefIVDOdqSMlAxJkxZb8Mlrb3UNRxlrpA8SZqNA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@storybook/addons" "5.2.1"
|
"@storybook/addons" "5.2.4"
|
||||||
"@storybook/channel-postmessage" "5.2.1"
|
"@storybook/channel-postmessage" "5.2.4"
|
||||||
"@storybook/channels" "5.2.1"
|
"@storybook/channels" "5.2.4"
|
||||||
"@storybook/client-logger" "5.2.1"
|
"@storybook/client-logger" "5.2.4"
|
||||||
"@storybook/core-events" "5.2.1"
|
"@storybook/core-events" "5.2.4"
|
||||||
"@storybook/router" "5.2.1"
|
"@storybook/router" "5.2.4"
|
||||||
common-tags "^1.8.0"
|
common-tags "^1.8.0"
|
||||||
core-js "^3.0.1"
|
core-js "^3.0.1"
|
||||||
eventemitter3 "^4.0.0"
|
eventemitter3 "^4.0.0"
|
||||||
|
@ -2127,10 +2193,10 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
core-js "^3.0.1"
|
core-js "^3.0.1"
|
||||||
|
|
||||||
"@storybook/client-logger@5.2.1":
|
"@storybook/client-logger@5.2.4":
|
||||||
version "5.2.1"
|
version "5.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-5.2.1.tgz#5c1f122b65386f04a6ad648808dfa89f2d852d7a"
|
resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-5.2.4.tgz#6ebe37cbc92e0efe27c7119f52d94f647fbb470c"
|
||||||
integrity sha512-wzxSE9t3DaLCdd/gnGFnjevmYRZ92F3TEwhUP/QDXM9cZkNsRKHkjE61qjiO5aQPaZQG6Ea9ayWEQEMgZXDucg==
|
integrity sha512-ofp6QQPQZBU+RvlAH5KpZRsfAFHecCZDnl/7YG6FwjHseJr3jHTYmBGGjJDMHFHq+Q7FGQu/yVb9lMFgoQ43QQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
core-js "^3.0.1"
|
core-js "^3.0.1"
|
||||||
|
|
||||||
|
@ -2143,13 +2209,13 @@
|
||||||
glamorous "^4.12.1"
|
glamorous "^4.12.1"
|
||||||
prop-types "^15.6.1"
|
prop-types "^15.6.1"
|
||||||
|
|
||||||
"@storybook/components@5.2.1":
|
"@storybook/components@5.2.4":
|
||||||
version "5.2.1"
|
version "5.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-5.2.1.tgz#a4519c5d435c2c25c481e2b64a768e1e568a223f"
|
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-5.2.4.tgz#9ecd080416eac4e8453030dd601cbeaa32ce4126"
|
||||||
integrity sha512-cik5J/mTm1b1TOI17qM+2Mikk3rjb3SbBD4WlNz3Zvn+Hw0ukgbx6kQwVBgujhMlDtsHreidyEgIg4TM13S0Tg==
|
integrity sha512-APhw+XGag0RTCRJ8eCWKVr8dLt9SRqnS8LtzcZJbokCYRxRTFzhmX2eVEE1v+d0gHib1/yh2COxOjMzv3m/rQA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@storybook/client-logger" "5.2.1"
|
"@storybook/client-logger" "5.2.4"
|
||||||
"@storybook/theming" "5.2.1"
|
"@storybook/theming" "5.2.4"
|
||||||
"@types/react-syntax-highlighter" "10.1.0"
|
"@types/react-syntax-highlighter" "10.1.0"
|
||||||
core-js "^3.0.1"
|
core-js "^3.0.1"
|
||||||
global "^4.3.2"
|
global "^4.3.2"
|
||||||
|
@ -2174,32 +2240,32 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
core-js "^3.0.1"
|
core-js "^3.0.1"
|
||||||
|
|
||||||
"@storybook/core-events@5.2.1":
|
"@storybook/core-events@5.2.4":
|
||||||
version "5.2.1"
|
version "5.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-5.2.1.tgz#bc28d704938d26dd544d0362d38ef08e8cfed916"
|
resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-5.2.4.tgz#210c968e96e2fc031cad1d3a09b06fa69ae433fc"
|
||||||
integrity sha512-AIYV/I+baQ0KxvEM7QAKqUedLn2os0XU9HTdtfZJTC3U9wjmR2ah2ScD6T0n7PBz3MderkvZG6dNjs9h8gRquQ==
|
integrity sha512-nQknCmaz2S2HW6PSGcuFzve7Y1Js2Cb268vUG0ZMNtJZwFawqYc+KSQHqmOY0pVm8dyROTcWCudPA0k+hk6N5Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
core-js "^3.0.1"
|
core-js "^3.0.1"
|
||||||
|
|
||||||
"@storybook/core@5.2.1":
|
"@storybook/core@5.2.4":
|
||||||
version "5.2.1"
|
version "5.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/core/-/core-5.2.1.tgz#3aa17c6fa9b02704723501d32884453869e3c06c"
|
resolved "https://registry.yarnpkg.com/@storybook/core/-/core-5.2.4.tgz#188509ac0eb0c85144816c385c21acfb5c8fbefd"
|
||||||
integrity sha512-mGGvN3GWeLxZ9lYZ4IuD1IoJD+cn6XXm2Arzw+k6KEtJJDFrC5SjESTDGLVFienX5s2tgH4FjYb9Ps9sKfhHlg==
|
integrity sha512-r5kDgZETNawHxpsAPw+h+pRk6l/mJhsSHeDo9/OdYtYFW7lmk2gadViXOTM+6gIWc6vQ8y750bgkahmyIIY0nQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/plugin-proposal-class-properties" "^7.3.3"
|
"@babel/plugin-proposal-class-properties" "^7.3.3"
|
||||||
"@babel/plugin-proposal-object-rest-spread" "^7.3.2"
|
"@babel/plugin-proposal-object-rest-spread" "^7.3.2"
|
||||||
"@babel/plugin-syntax-dynamic-import" "^7.2.0"
|
"@babel/plugin-syntax-dynamic-import" "^7.2.0"
|
||||||
"@babel/plugin-transform-react-constant-elements" "^7.2.0"
|
"@babel/plugin-transform-react-constant-elements" "^7.2.0"
|
||||||
"@babel/preset-env" "^7.4.5"
|
"@babel/preset-env" "^7.4.5"
|
||||||
"@storybook/addons" "5.2.1"
|
"@storybook/addons" "5.2.4"
|
||||||
"@storybook/channel-postmessage" "5.2.1"
|
"@storybook/channel-postmessage" "5.2.4"
|
||||||
"@storybook/client-api" "5.2.1"
|
"@storybook/client-api" "5.2.4"
|
||||||
"@storybook/client-logger" "5.2.1"
|
"@storybook/client-logger" "5.2.4"
|
||||||
"@storybook/core-events" "5.2.1"
|
"@storybook/core-events" "5.2.4"
|
||||||
"@storybook/node-logger" "5.2.1"
|
"@storybook/node-logger" "5.2.4"
|
||||||
"@storybook/router" "5.2.1"
|
"@storybook/router" "5.2.4"
|
||||||
"@storybook/theming" "5.2.1"
|
"@storybook/theming" "5.2.4"
|
||||||
"@storybook/ui" "5.2.1"
|
"@storybook/ui" "5.2.4"
|
||||||
airbnb-js-shims "^1 || ^2"
|
airbnb-js-shims "^1 || ^2"
|
||||||
ansi-to-html "^0.6.11"
|
ansi-to-html "^0.6.11"
|
||||||
autoprefixer "^9.4.9"
|
autoprefixer "^9.4.9"
|
||||||
|
@ -2255,10 +2321,10 @@
|
||||||
webpack-dev-middleware "^3.7.0"
|
webpack-dev-middleware "^3.7.0"
|
||||||
webpack-hot-middleware "^2.25.0"
|
webpack-hot-middleware "^2.25.0"
|
||||||
|
|
||||||
"@storybook/node-logger@5.2.1":
|
"@storybook/node-logger@5.2.4":
|
||||||
version "5.2.1"
|
version "5.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-5.2.1.tgz#00d8c0dc9dfd482e7d1d244a59c46726c6b761d9"
|
resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-5.2.4.tgz#52dc380c76bdac68de35e7d72efe920c3ba52a23"
|
||||||
integrity sha512-rz+snXZyKwTegKEf15w4uaFWIKpgaWzTw+Ar8mxa+mX7C2DP65TOc+JGYZ7lsXdred+0WP0DhnmhGu2cX8z3lA==
|
integrity sha512-4OOzce02IAfrRv+Y7h3icyw6WIuDekpWF2eYjgYVVvAJYklCEwgeBTBCY0/2TJjPPTBDPUKHVP1Bdz3Vpci9pA==
|
||||||
dependencies:
|
dependencies:
|
||||||
chalk "^2.4.2"
|
chalk "^2.4.2"
|
||||||
core-js "^3.0.1"
|
core-js "^3.0.1"
|
||||||
|
@ -2266,18 +2332,19 @@
|
||||||
pretty-hrtime "^1.0.3"
|
pretty-hrtime "^1.0.3"
|
||||||
regenerator-runtime "^0.12.1"
|
regenerator-runtime "^0.12.1"
|
||||||
|
|
||||||
"@storybook/react@5.2.1":
|
"@storybook/react@5.2.4":
|
||||||
version "5.2.1"
|
version "5.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/react/-/react-5.2.1.tgz#860970fa8f0d49967862b496af4ef3712f0b96dd"
|
resolved "https://registry.yarnpkg.com/@storybook/react/-/react-5.2.4.tgz#305db3fa7cd113c185aba97e5239d7754dc14859"
|
||||||
integrity sha512-brUG8iK2+1Fk5VFZWpAoSokCx21MaPX1zSAVA+Z/Ia0I0sFfurhpQgAGlVePTy9r7dtEEEdniZVtJOH/tHqk4Q==
|
integrity sha512-AO0qwbD/2UGe5CrVizbaek+gCAPWkPVc0KUk38cT1mcuLpXwt1zZe7iHLQf2zOeBVSiBkPLOHrEtzDfnIJXKFQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/plugin-transform-react-constant-elements" "^7.2.0"
|
"@babel/plugin-transform-react-constant-elements" "^7.2.0"
|
||||||
"@babel/preset-flow" "^7.0.0"
|
"@babel/preset-flow" "^7.0.0"
|
||||||
"@babel/preset-react" "^7.0.0"
|
"@babel/preset-react" "^7.0.0"
|
||||||
"@storybook/addons" "5.2.1"
|
"@storybook/addons" "5.2.4"
|
||||||
"@storybook/core" "5.2.1"
|
"@storybook/core" "5.2.4"
|
||||||
"@storybook/node-logger" "5.2.1"
|
"@storybook/node-logger" "5.2.4"
|
||||||
"@svgr/webpack" "^4.0.3"
|
"@svgr/webpack" "^4.0.3"
|
||||||
|
"@types/webpack-env" "^1.13.7"
|
||||||
babel-plugin-add-react-displayname "^0.0.5"
|
babel-plugin-add-react-displayname "^0.0.5"
|
||||||
babel-plugin-named-asset-import "^0.3.1"
|
babel-plugin-named-asset-import "^0.3.1"
|
||||||
babel-plugin-react-docgen "^3.0.0"
|
babel-plugin-react-docgen "^3.0.0"
|
||||||
|
@ -2304,10 +2371,10 @@
|
||||||
memoizerific "^1.11.3"
|
memoizerific "^1.11.3"
|
||||||
qs "^6.6.0"
|
qs "^6.6.0"
|
||||||
|
|
||||||
"@storybook/router@5.2.1":
|
"@storybook/router@5.2.4":
|
||||||
version "5.2.1"
|
version "5.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/router/-/router-5.2.1.tgz#9c49df79343d3be10c7f984858fb5c9ae3eb7491"
|
resolved "https://registry.yarnpkg.com/@storybook/router/-/router-5.2.4.tgz#e47af60478da7730c7d82de3d089d12c17077333"
|
||||||
integrity sha512-Mlk275cyPoKtnP4DwQ5D8gTfnaRPL6kDZOSn0wbTMa6pQOfYKgJsa7tjzeAtZuZ/j8hKI4gAfT/auMgH6g+94A==
|
integrity sha512-GL7eGdj5oYST0mE9fThJB9ye9tTTgrP+aP3okZ6MeMGtNytb7bmJRpAD2E4ouuPTQVppyHI5re8g/HUxUNOT1g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@reach/router" "^1.2.1"
|
"@reach/router" "^1.2.1"
|
||||||
"@types/reach__router" "^1.2.3"
|
"@types/reach__router" "^1.2.3"
|
||||||
|
@ -2335,14 +2402,14 @@
|
||||||
prop-types "^15.7.2"
|
prop-types "^15.7.2"
|
||||||
resolve-from "^5.0.0"
|
resolve-from "^5.0.0"
|
||||||
|
|
||||||
"@storybook/theming@5.2.1":
|
"@storybook/theming@5.2.4":
|
||||||
version "5.2.1"
|
version "5.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-5.2.1.tgz#913e383632e4702035a107c2cc5e5cb27231b389"
|
resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-5.2.4.tgz#79c99f65992082fdb4d39fb652db435dd6bebaca"
|
||||||
integrity sha512-lbAfcyI7Tx8swduIPmlu/jdWzqTBN/v82IEQbZbPR4LS5OHRPmhXPNgFGrcH4kFAiD0GoezSsdum1x0ZZpsQUQ==
|
integrity sha512-2ZlqBrmnm8N0352Fnu2+GB3pEsHL4Eb2eKxV0VLLgkjJuAlm7CK6+I/e4ZknQWxwYm2pQj1y6ta68A62fGBYyA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@emotion/core" "^10.0.14"
|
"@emotion/core" "^10.0.14"
|
||||||
"@emotion/styled" "^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"
|
common-tags "^1.8.0"
|
||||||
core-js "^3.0.1"
|
core-js "^3.0.1"
|
||||||
deep-object-diff "^1.1.0"
|
deep-object-diff "^1.1.0"
|
||||||
|
@ -2353,21 +2420,19 @@
|
||||||
prop-types "^15.7.2"
|
prop-types "^15.7.2"
|
||||||
resolve-from "^5.0.0"
|
resolve-from "^5.0.0"
|
||||||
|
|
||||||
"@storybook/ui@5.2.1":
|
"@storybook/ui@5.2.4":
|
||||||
version "5.2.1"
|
version "5.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-5.2.1.tgz#ceba1657a232efd10f839027f8ae274e370c89f6"
|
resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-5.2.4.tgz#7116c9be0dcdcc5d5b425d998263de1e27cdc078"
|
||||||
integrity sha512-h6Yf1ro/nZcz4nQAU+eSVPxVmpqv7uT7RMb3Vz+VLTY59IEA/sWcoIgA4MIxwf14nVcWOqSmVBJzNKWwc+NGJw==
|
integrity sha512-zsS43k1h4bWEW6oj9FNHlUL3niHoJJ8v7iqYbRtVM12rxrYhV3K8TGVG3LCuNB75i3Be0Myy+/RHA4x9kco08A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@storybook/addon-actions" "5.2.1"
|
"@storybook/addons" "5.2.4"
|
||||||
"@storybook/addon-knobs" "5.2.1"
|
"@storybook/api" "5.2.4"
|
||||||
"@storybook/addons" "5.2.1"
|
"@storybook/channels" "5.2.4"
|
||||||
"@storybook/api" "5.2.1"
|
"@storybook/client-logger" "5.2.4"
|
||||||
"@storybook/channels" "5.2.1"
|
"@storybook/components" "5.2.4"
|
||||||
"@storybook/client-logger" "5.2.1"
|
"@storybook/core-events" "5.2.4"
|
||||||
"@storybook/components" "5.2.1"
|
"@storybook/router" "5.2.4"
|
||||||
"@storybook/core-events" "5.2.1"
|
"@storybook/theming" "5.2.4"
|
||||||
"@storybook/router" "5.2.1"
|
|
||||||
"@storybook/theming" "5.2.1"
|
|
||||||
copy-to-clipboard "^3.0.8"
|
copy-to-clipboard "^3.0.8"
|
||||||
core-js "^3.0.1"
|
core-js "^3.0.1"
|
||||||
core-js-pure "^3.0.1"
|
core-js-pure "^3.0.1"
|
||||||
|
@ -2383,7 +2448,7 @@
|
||||||
qs "^6.6.0"
|
qs "^6.6.0"
|
||||||
react "^16.8.3"
|
react "^16.8.3"
|
||||||
react-dom "^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-helmet-async "^1.0.2"
|
||||||
react-hotkeys "2.0.0-pre4"
|
react-hotkeys "2.0.0-pre4"
|
||||||
react-sizeme "^2.6.7"
|
react-sizeme "^2.6.7"
|
||||||
|
@ -2391,7 +2456,7 @@
|
||||||
resolve-from "^5.0.0"
|
resolve-from "^5.0.0"
|
||||||
semver "^6.0.0"
|
semver "^6.0.0"
|
||||||
store2 "^2.7.1"
|
store2 "^2.7.1"
|
||||||
telejson "^2.2.2"
|
telejson "^3.0.2"
|
||||||
util-deprecate "^1.0.2"
|
util-deprecate "^1.0.2"
|
||||||
|
|
||||||
"@svgr/babel-plugin-add-jsx-attribute@^4.2.0":
|
"@svgr/babel-plugin-add-jsx-attribute@^4.2.0":
|
||||||
|
@ -2516,10 +2581,10 @@
|
||||||
pretty-format "^24.8.0"
|
pretty-format "^24.8.0"
|
||||||
wait-for-expect "^1.3.0"
|
wait-for-expect "^1.3.0"
|
||||||
|
|
||||||
"@testing-library/jest-dom@4.1.0":
|
"@testing-library/jest-dom@4.1.2":
|
||||||
version "4.1.0"
|
version "4.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-4.1.0.tgz#69d372e54e4e33be3fd34f3848ec0e8e9d099276"
|
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-4.1.2.tgz#e523047191379abd67cf0896dfd93cabc7e33eab"
|
||||||
integrity sha512-cKAONDmJKGJ2DSu6R/+lgA8i8uyZIx4CaOiiK0yMjp+2UecH6kfjunJiy5hfExKMtR74eyzFriqO1w9aTC8VyQ==
|
integrity sha512-fNf2rCfu0dBD4DmpzqR2ibsaFlFojrWI/EuU8LLqv73CzFIMvT2RMq88p5JVRe4DfeNj0mu0MQ5FTG4mQ0qFaA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.5.1"
|
"@babel/runtime" "^7.5.1"
|
||||||
chalk "^2.4.1"
|
chalk "^2.4.1"
|
||||||
|
@ -2628,6 +2693,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.3.tgz#856c99cdc1551d22c22b18b5402719affec9839a"
|
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.3.tgz#856c99cdc1551d22c22b18b5402719affec9839a"
|
||||||
integrity sha512-cS5owqtwzLN5kY+l+KgKdRJ/Cee8tlmQoGQuIE9tWnSmS3JMKzmxo2HIAk2wODMifGwO20d62xZQLYz+RLfXmw==
|
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":
|
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff"
|
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff"
|
||||||
|
@ -2691,6 +2761,13 @@
|
||||||
"@types/history" "*"
|
"@types/history" "*"
|
||||||
"@types/react" "*"
|
"@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@*":
|
"@types/react-dom@*":
|
||||||
version "16.9.0"
|
version "16.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.0.tgz#ba6ddb00bf5de700b0eb91daa452081ffccbfdea"
|
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"
|
resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.2.tgz#721ca5c5d1a2988b4a886e35c2ffc5735b6afbdf"
|
||||||
integrity sha512-PeHg/AtdW6aaIO2a+98Xj7rWY4KC1E6yOy7AFknJQ7VXUGNrMlyxDFxJo7HqLtjQms/ZhhQX52mLVW/EX3JGOw==
|
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@*":
|
"@types/yargs-parser@*":
|
||||||
version "13.0.0"
|
version "13.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-13.0.0.tgz#453743c5bbf9f1bed61d959baab5b06be029b2d0"
|
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"
|
"@webassemblyjs/wast-parser" "1.8.5"
|
||||||
"@xtuc/long" "4.2.2"
|
"@xtuc/long" "4.2.2"
|
||||||
|
|
||||||
"@welldone-software/why-did-you-render@3.3.5":
|
"@welldone-software/why-did-you-render@3.3.8":
|
||||||
version "3.3.5"
|
version "3.3.8"
|
||||||
resolved "https://registry.yarnpkg.com/@welldone-software/why-did-you-render/-/why-did-you-render-3.3.5.tgz#ba301216ebd7b0283e85995357706223186e6afb"
|
resolved "https://registry.yarnpkg.com/@welldone-software/why-did-you-render/-/why-did-you-render-3.3.8.tgz#a8b69e6d64e36bab676c03ca4630032bdf6ea5b1"
|
||||||
integrity sha512-hn3U5eU7tVEk3A14tRuYHolbBQv6IRmVd/ukNDeyKmC1IbxBck8j8HnFji4ARvw7Z2kJhUwKtXpRI2+gxVolBA==
|
integrity sha512-DtmXat8vPJuQlSG9BD0dW9baViZUzFtHMMTHAGnQpRosq7U0kOkEoPXoLY/mRKEfI3SJkxX9R/dwIVh2CQ+rKw==
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash "^4"
|
lodash "^4"
|
||||||
|
|
||||||
|
@ -3600,7 +3682,20 @@ atob@^2.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
||||||
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
|
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"
|
version "9.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.6.1.tgz#51967a02d2d2300bb01866c1611ec8348d355a47"
|
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.6.1.tgz#51967a02d2d2300bb01866c1611ec8348d355a47"
|
||||||
integrity sha512-aVo5WxR3VyvyJxcJC3h4FKfwCQvQWb1tSI5VHNibddCVWrcD1NvlxEweg3TSgiPztMnWfjpy2FURKA2kvDE+Tw==
|
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"
|
electron-to-chromium "^1.3.191"
|
||||||
node-releases "^1.1.25"
|
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:
|
bs58@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/bs58/-/bs58-2.0.1.tgz#55908d58f1982aba2008fa1bed8f91998a29bf8d"
|
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"
|
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000985.tgz#0eb40f6c8a8c219155cbe43c4975c0efb4a0f77f"
|
||||||
integrity sha512-1ngiwkgqAYPG0JSSUp3PUDGPKKY59EK7NrGGX+VOxaKCNzRbNc7uXMny+c3VJfZxtoK3wSImTvG9T9sXiTw2+w==
|
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:
|
capture-exit@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4"
|
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-mimetype "^2.2.0"
|
||||||
whatwg-url "^7.0.0"
|
whatwg-url "^7.0.0"
|
||||||
|
|
||||||
date-fns@2.4.1:
|
date-fns@2.5.0:
|
||||||
version "2.4.1"
|
version "2.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.4.1.tgz#b53f9bb65ae6bd9239437035710e01cf383b625e"
|
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.5.0.tgz#b939f17c2902ce81cffe449702ba22c0781b38ec"
|
||||||
integrity sha512-2RhmH/sjDSCYW2F3ZQxOUx/I7PvzXpi89aQL2d3OAxSTwLx6NilATeUbe0menFE3Lu5lFkOFci36ivimwYHHxw==
|
integrity sha512-I6Tkis01//nRcmvMQw/MRE1HAtcuA5Ie6jGPb8bJZJub7494LGOObqkV3ParnsSVviAjk5C8mNKDqYVBzCopWg==
|
||||||
|
|
||||||
date-now@^0.1.4:
|
date-now@^0.1.4:
|
||||||
version "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"
|
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.199.tgz#f9a62a74cda77854310a2abffde8b75591ea09a1"
|
||||||
integrity sha512-gachlDdHSK47s0N2e58GH9HMC6Z4ip0SfmYUa5iEbE50AKaOUXysaJnXMfKj0xB245jWbYcyFSH+th3rqsF8hA==
|
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:
|
element-resize-detector@^1.1.15:
|
||||||
version "1.1.15"
|
version "1.1.15"
|
||||||
resolved "https://registry.yarnpkg.com/element-resize-detector/-/element-resize-detector-1.1.15.tgz#48eba1a2eaa26969a4c998d972171128c971d8d2"
|
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"
|
read-pkg-up "^2.0.0"
|
||||||
resolve "^1.11.0"
|
resolve "^1.11.0"
|
||||||
|
|
||||||
eslint-plugin-jest@22.17.0:
|
eslint-plugin-jest@22.19.0:
|
||||||
version "22.17.0"
|
version "22.19.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.17.0.tgz#dc170ec8369cd1bff9c5dd8589344e3f73c88cf6"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.19.0.tgz#0cf90946a8c927d40a2c64458c89bb635d0f2a0b"
|
||||||
integrity sha512-WT4DP4RoGBhIQjv+5D0FM20fAdAUstfYAf/mkufLNTojsfgzc5/IYW22cIg/Q4QBavAZsROQlqppiWDpFZDS8Q==
|
integrity sha512-4zUc3rh36ds0SXdl2LywT4YWA3zRe8sfLhz8bPp8qQPIKvynTTkNGwmSCMpl5d9QiZE2JxSinGF+WD8yU+O0Lg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/experimental-utils" "^1.13.0"
|
"@typescript-eslint/experimental-utils" "^1.13.0"
|
||||||
|
|
||||||
|
@ -7335,10 +7449,10 @@ eslint-plugin-jsx-a11y@6.2.3:
|
||||||
has "^1.0.3"
|
has "^1.0.3"
|
||||||
jsx-ast-utils "^2.2.1"
|
jsx-ast-utils "^2.2.1"
|
||||||
|
|
||||||
eslint-plugin-react@7.15.0:
|
eslint-plugin-react@7.16.0:
|
||||||
version "7.15.0"
|
version "7.16.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.15.0.tgz#4808b19cf7b4c439454099d4eb8f0cf0e9fe31dd"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.16.0.tgz#9928e4f3e2122ed3ba6a5b56d0303ba3e41d8c09"
|
||||||
integrity sha512-NbIh/yVXoltm8Df28PiPRanfCZAYubGqXU391MTCpW955Vum7S0nZdQYXGAvDh9ye4aNCmOR6YcYZsfMbEQZQA==
|
integrity sha512-GacBAATewhhptbK3/vTP09CbFrgUJmBSaaRcWdbQLFvUZy9yVcQxigBNHGPU/KE2AyHpzj3AWXpxoMTsIDiHug==
|
||||||
dependencies:
|
dependencies:
|
||||||
array-includes "^3.0.3"
|
array-includes "^3.0.3"
|
||||||
doctrine "^2.1.0"
|
doctrine "^2.1.0"
|
||||||
|
@ -9104,7 +9218,7 @@ global-prefix@^3.0.0:
|
||||||
kind-of "^6.0.2"
|
kind-of "^6.0.2"
|
||||||
which "^1.3.1"
|
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"
|
version "4.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406"
|
resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406"
|
||||||
integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==
|
integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==
|
||||||
|
@ -12737,6 +12851,13 @@ node-releases@^1.1.13, node-releases@^1.1.25:
|
||||||
dependencies:
|
dependencies:
|
||||||
semver "^5.3.0"
|
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:
|
nopt@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
|
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"
|
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.0.tgz#99a983d365f7b2ad8d0f9b8c3094926eab4b936d"
|
||||||
integrity sha512-ESPktioptiSUchCKgggAkzdmkgzKfmp0EU8jXH+5kbIUB+unr0Y4CY9SRMvibuvYUBjNh1ACLbxqYNpdTQOteQ==
|
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:
|
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"
|
version "7.0.17"
|
||||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.17.tgz#4da1bdff5322d4a0acaab4d87f3e782436bad31f"
|
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"
|
source-map "^0.6.1"
|
||||||
supports-color "^6.1.0"
|
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:
|
pre-commit@^1.2.2:
|
||||||
version "1.2.2"
|
version "1.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/pre-commit/-/pre-commit-1.2.2.tgz#dbcee0ee9de7235e57f79c56d7ce94641a69eec6"
|
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"
|
prop-types "^15.6.2"
|
||||||
scheduler "^0.13.6"
|
scheduler "^0.13.6"
|
||||||
|
|
||||||
react-draggable@^3.3.2:
|
react-draggable@^4.0.3:
|
||||||
version "3.3.2"
|
version "4.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-3.3.2.tgz#966ef1d90f2387af3c2d8bd3516f601ea42ca359"
|
resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.0.3.tgz#6b9f76f66431c47b9070e9b805bbc520df8ca481"
|
||||||
integrity sha512-oaz8a6enjbPtx5qb0oDWxtDNuybOylvto1QLydsXgKmwT7e3GXC2eMVDwEMIUYJIFqVG72XpOv673UuuAq6LhA==
|
integrity sha512-4vD6zms+9QGeZ2RQXzlUBw8PBYUXy+dzYX5r22idjp9YwQKIIvD/EojL0rbjS1GK4C3P0rAJnmKa8gDQYWUDyA==
|
||||||
dependencies:
|
dependencies:
|
||||||
classnames "^2.2.5"
|
classnames "^2.2.5"
|
||||||
prop-types "^15.6.0"
|
prop-types "^15.6.0"
|
||||||
|
@ -14683,10 +14818,10 @@ react-helmet-async@^1.0.2:
|
||||||
react-fast-compare "2.0.4"
|
react-fast-compare "2.0.4"
|
||||||
shallowequal "1.1.0"
|
shallowequal "1.1.0"
|
||||||
|
|
||||||
react-hot-loader@4.12.14:
|
react-hot-loader@4.12.15:
|
||||||
version "4.12.14"
|
version "4.12.15"
|
||||||
resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.12.14.tgz#81ca06ffda0b90aad15d6069339f73ed6428340a"
|
resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.12.15.tgz#6bf3984e52edbdf02ea8952777f53da1b3c68c95"
|
||||||
integrity sha512-ecxH4eBvEaJ9onT8vkEmK1FAAJUh1PqzGqds9S3k+GeihSp7nKAp4fOxytO+Ghr491LiBD38jaKyDXYnnpI9pQ==
|
integrity sha512-sgkN6g+tgPE6xZzD0Ysqll7KUFYJbMX0DrczT5OxD6S7hZlSnmqSC3ceudwCkiDd65ZTtm+Ayk4Y9k5xxCvpOw==
|
||||||
dependencies:
|
dependencies:
|
||||||
fast-levenshtein "^2.0.6"
|
fast-levenshtein "^2.0.6"
|
||||||
global "^4.3.0"
|
global "^4.3.0"
|
||||||
|
@ -15858,6 +15993,14 @@ schema-utils@^2.0.1:
|
||||||
ajv "^6.1.0"
|
ajv "^6.1.0"
|
||||||
ajv-keywords "^3.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:
|
scrypt-js@2.0.3:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-2.0.3.tgz#bb0040be03043da9a012a2cea9fc9f852cfc87d4"
|
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"
|
lodash.get "^4.4.2"
|
||||||
memoizerific "^1.11.3"
|
memoizerific "^1.11.3"
|
||||||
|
|
||||||
telejson@^2.2.2:
|
telejson@^3.0.2:
|
||||||
version "2.2.2"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/telejson/-/telejson-2.2.2.tgz#d61d721d21849a6e4070d547aab302a9bd22c720"
|
resolved "https://registry.yarnpkg.com/telejson/-/telejson-3.0.3.tgz#442af55f78d791d3744c9e7a696be6cdf789a4b5"
|
||||||
integrity sha512-YyNwnKY0ilabOwYgC/J754En1xOe5PBIUIw+C9e0+5HjVVcnQE5/gdu2yET2pmSbp5bxIDqYNjvndj2PUkIiYA==
|
integrity sha512-gUOh6wox1zJjbGMg+e26NquZcp/F18EbIaqVvjiGqikRqVB4fYEAM8Nyin8smgwX30XhaRBOg+kCj4vInmvwAg==
|
||||||
dependencies:
|
dependencies:
|
||||||
global "^4.3.2"
|
"@types/is-function" "^1.0.0"
|
||||||
|
global "^4.4.0"
|
||||||
is-function "^1.0.1"
|
is-function "^1.0.1"
|
||||||
is-regex "^1.0.4"
|
is-regex "^1.0.4"
|
||||||
is-symbol "^1.0.2"
|
is-symbol "^1.0.2"
|
||||||
isobject "^3.0.1"
|
isobject "^4.0.0"
|
||||||
lodash "^4.17.11"
|
lodash "^4.17.15"
|
||||||
memoizerific "^1.11.3"
|
memoizerific "^1.11.3"
|
||||||
|
|
||||||
temp@^0.8.3:
|
temp@^0.8.3:
|
||||||
|
@ -17865,10 +18009,10 @@ truffle-workflow-compile@^2.1.3:
|
||||||
truffle-external-compile "^1.0.15"
|
truffle-external-compile "^1.0.15"
|
||||||
truffle-resolver "^5.0.15"
|
truffle-resolver "^5.0.15"
|
||||||
|
|
||||||
truffle@5.0.39:
|
truffle@5.0.40:
|
||||||
version "5.0.39"
|
version "5.0.40"
|
||||||
resolved "https://registry.yarnpkg.com/truffle/-/truffle-5.0.39.tgz#5710ba8f60a7184d9eb51d632308f2af0a2e8aff"
|
resolved "https://registry.yarnpkg.com/truffle/-/truffle-5.0.40.tgz#3fb238f0e17662df871f862981bea4109e68bf9f"
|
||||||
integrity sha512-2a17t4o6r0rNMpeQXBc51nXigtIaP9/sU8N2zflaazvzYgDgLMZfqh/dir2mTfyybOsrR47NL310p+6+c8u8VA==
|
integrity sha512-UO2bVpDJRzR1oF6LgdxdpqUHdbvYJxfaqEkVDUrziIRs6Sr7CkOzHew8hexUgkqxA7wtbDmM9FNcj+9Yoa5csA==
|
||||||
dependencies:
|
dependencies:
|
||||||
app-module-path "^2.2.0"
|
app-module-path "^2.2.0"
|
||||||
mocha "5.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"
|
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
|
||||||
integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
|
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"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-2.1.0.tgz#bcc1ecabbd197e913eca23f5e0378e24b4412961"
|
resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-2.1.0.tgz#bcc1ecabbd197e913eca23f5e0378e24b4412961"
|
||||||
integrity sha512-kVrp/8VfEm5fUt+fl2E0FQyrpmOYgMEkBsv8+UDP1wFhszECq5JyGF33I7cajlVY90zRZ6MyfgKXngLvHYZX8A==
|
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"
|
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
|
||||||
integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
|
integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
|
||||||
|
|
||||||
webpack-bundle-analyzer@3.5.2:
|
webpack-bundle-analyzer@3.6.0:
|
||||||
version "3.5.2"
|
version "3.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.5.2.tgz#ac02834f4b31de8e27d71e6c7a612301ebddb79f"
|
resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.6.0.tgz#39b3a8f829ca044682bc6f9e011c95deb554aefd"
|
||||||
integrity sha512-g9spCNe25QYUVqHRDkwG414GTok2m7pTTP0wr6l0J50Z3YLS04+BGodTqqoVBL7QfU/U/9p/oiI5XFOyfZ7S/A==
|
integrity sha512-orUfvVYEfBMDXgEKAKVvab5iQ2wXneIEorGNsyuOyVYpjYrI7CUOhhXNDd3huMwQ3vNNWWlGP+hzflMFYNzi2g==
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn "^6.0.7"
|
acorn "^6.0.7"
|
||||||
acorn-walk "^6.1.1"
|
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-list-map "^2.0.0"
|
||||||
source-map "~0.6.1"
|
source-map "~0.6.1"
|
||||||
|
|
||||||
webpack@4.41.0:
|
webpack@4.41.2:
|
||||||
version "4.41.0"
|
version "4.41.2"
|
||||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.0.tgz#db6a254bde671769f7c14e90a1a55e73602fc70b"
|
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.2.tgz#c34ec76daa3a8468c9b61a50336d8e3303dce74e"
|
||||||
integrity sha512-yNV98U4r7wX1VJAj5kyMsu36T8RPPQntcb5fJLOsMz/pt/WrKC0Vp1bAlqPLkA1LegSwQwf6P+kAbyhRKVQ72g==
|
integrity sha512-Zhw69edTGfbz9/8JJoyRQ/pq8FYUoY0diOXqW0T6yhgdhCv6wr0hra5DwwWexNRns2Z2+gsnrNcbe9hbGBgk/A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@webassemblyjs/ast" "1.8.5"
|
"@webassemblyjs/ast" "1.8.5"
|
||||||
"@webassemblyjs/helper-module-context" "1.8.5"
|
"@webassemblyjs/helper-module-context" "1.8.5"
|
||||||
|
|
Loading…
Reference in New Issue