Merge branch 'development' into master-pre-release

# Conflicts:
#	package.json
#	src/components/CookiesBanner/index.jsx
#	src/components/EtherscanBtn/index.jsx
#	src/components/EtherscanLink/index.jsx
#	src/components/Footer/index.jsx
#	src/components/Header/components/CircleDot.jsx
#	src/components/Header/components/Layout.jsx
#	src/components/Header/components/NetworkLabel.jsx
#	src/components/Header/components/SafeListHeader/index.jsx
#	src/components/Sidebar/LegalLinks.jsx
#	src/components/Sidebar/index.jsx
#	src/components/Sidebar/style.js
#	src/components/Spacer/index.jsx
#	src/components/layout/Hairline/index.js
#	src/config/index.js
#	src/logic/addressBook/model/addressBook.js
#	src/logic/addressBook/store/actions/addAddressBookEntry.js
#	src/logic/addressBook/store/actions/loadAddressBook.js
#	src/logic/addressBook/store/actions/loadAddressBookFromStorage.js
#	src/logic/addressBook/store/actions/removeAddressBookEntry.js
#	src/logic/addressBook/store/actions/updateAddressBookEntry.js
#	src/logic/addressBook/store/middleware/addressBookMiddleware.js
#	src/logic/addressBook/store/reducer/addressBook.js
#	src/logic/addressBook/store/selectors/index.js
#	src/logic/addressBook/utils/index.js
#	src/logic/notifications/notificationBuilder.js
#	src/logic/safe/transactions/awaitingTransactions.js
#	src/logic/safe/utils/safeVersion.js
#	src/logic/tokens/utils/tokenHelpers.js
#	src/logic/wallets/ethAddresses.js
#	src/routes/load/components/OwnerList/index.jsx
#	src/routes/load/components/ReviewInformation/index.jsx
#	src/routes/open/components/ReviewInformation/index.jsx
#	src/routes/open/components/SafeOwnersConfirmationsForm/style.js
#	src/routes/safe/components/AddressBook/CreateEditEntryModal/index.jsx
#	src/routes/safe/components/AddressBook/CreateEditEntryModal/style.js
#	src/routes/safe/components/AddressBook/DeleteEntryModal/index.jsx
#	src/routes/safe/components/AddressBook/DeleteEntryModal/style.js
#	src/routes/safe/components/AddressBook/EllipsisTransactionDetails/index.jsx
#	src/routes/safe/components/AddressBook/columns.js
#	src/routes/safe/components/AddressBook/index.jsx
#	src/routes/safe/components/AddressBook/style.js
#	src/routes/safe/components/Balances/Receive/index.jsx
#	src/routes/safe/components/Balances/SendModal/screens/AddressBookInput/index.jsx
#	src/routes/safe/components/Balances/SendModal/screens/AddressBookInput/style.js
#	src/routes/safe/components/Balances/SendModal/screens/SendCustomTx/index.jsx
#	src/routes/safe/components/Balances/SendModal/screens/SendFunds/index.jsx
#	src/routes/safe/components/Balances/index.jsx
#	src/routes/safe/components/Layout.jsx
#	src/routes/safe/components/Settings/ManageOwners/EditOwnerModal/index.jsx
#	src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell/index.jsx
#	src/routes/safe/components/Settings/ManageOwners/index.jsx
#	src/routes/safe/components/Settings/RemoveSafeModal/index.jsx
#	src/routes/safe/components/Settings/SafeDetails/index.jsx
#	src/routes/safe/components/Settings/assets/icons/OwnersIcon.jsx
#	src/routes/safe/components/Settings/assets/icons/RequiredConfirmationsIcon.jsx
#	src/routes/safe/components/Settings/assets/icons/SafeDetailsIcon.jsx
#	src/routes/safe/components/Settings/style.js
#	src/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal/index.jsx
#	src/routes/safe/components/Transactions/TxsTable/ExpandedTx/IncomingTxDescription/index.jsx
#	src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/OwnerComponent.jsx
#	src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/index.jsx
#	src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/style.js
#	src/routes/safe/components/Transactions/TxsTable/ExpandedTx/RejectTxModal/index.jsx
#	src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/index.jsx
#	src/routes/safe/components/Transactions/TxsTable/ExpandedTx/index.jsx
#	src/routes/safe/components/Transactions/TxsTable/columns.js
#	src/routes/safe/components/Transactions/TxsTable/index.jsx
#	src/routes/safe/components/assets/AddressBookIcon.jsx
#	src/routes/safe/components/assets/BalancesIcon.jsx
#	src/routes/safe/components/assets/SettingsIcon.jsx
#	src/routes/safe/components/assets/TransactionsIcon.jsx
#	src/routes/safe/components/style.js
#	src/routes/safe/container/selector.js
#	src/routes/safe/store/actions/fetchSafe.js
#	src/routes/safe/store/actions/fetchTransactions.js
#	src/routes/safe/store/middleware/notificationsMiddleware.js
#	src/routes/safe/store/middleware/safeStorage.js
#	src/routes/safe/store/selectors/index.js
#	src/store/index.js
#	yarn.lock
This commit is contained in:
fernandomg 2020-02-19 14:15:17 -03:00
commit c97cf75ba8
257 changed files with 4620 additions and 3758 deletions

View File

@ -1,7 +1,15 @@
node_modules
!.eslintrc.js
build_webpack
config
contracts
flow-typed
flow-typed/npm
config
scripts
migrations
node_modules
public
scripts
src/assets
src/config
test
*.spec*
*.test*

View File

@ -1,33 +1,57 @@
{
"extends": ["airbnb", "plugin:flowtype/recommended", "plugin:jsx-a11y/recommended"],
"settings": {
"react": {
"flowVersion": "0.112.0",
"pragma": "React",
"version": "16.12.0"
},
"import/extensions": [".js", ".jsx"]
},
"parser": "babel-eslint",
"plugins": ["jsx-a11y", "jest", "flowtype"],
"plugins": ["react", "flowtype", "import", "jsx-a11y", "prettier"],
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:flowtype/recommended",
"plugin:import/errors",
"plugin:import/warnings",
"plugin:jsx-a11y/recommended",
"plugin:prettier/recommended",
"prettier/react",
"prettier/flowtype"
],
"parserOptions": {
"ecmaFeatures": {
"jsx": true
}
},
"env": {
"browser": true,
"amd": true,
"node": true,
"es6": true
},
"rules": {
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
"react/forbid-prop-types": [1, { "forbid": ["object", "any"] }],
"class-methods-use-this": 0,
"semi": ["error", "never"],
"max-len": [
"error",
120,
2,
{
"ignoreUrls": true,
"ignoreComments": false,
"ignoreRegExpLiterals": true,
"ignoreStrings": true,
"ignoreTemplateLiterals": true
"ignoreTemplateLiterals": true,
"ignoreUrls": true
}
],
"import/no-unresolved": 0,
"import/no-extraneous-dependencies": 0,
"import/extensions": 0,
"import/prefer-default-export": 0,
"react/default-props-match-prop-types": ["error", { "allowRequiredDefaults": true }],
// https://github.com/yannickcr/eslint-plugin-react/issues/1593 ^
"jsx-a11y/label-has-for": 0,
"indent": ["error", 2, { "SwitchCase": 1 }],
"no-console": ["error", { "allow": ["warn", "error"] }],
"no-console": [
"error",
{
"allow": ["warn", "error"]
}
],
"semi": ["error", "never"],
"flowtype/require-valid-file-annotation": [
2,
"always",
@ -35,6 +59,10 @@
"annotationStyle": "line"
}
],
"import/extensions": 0,
"import/no-extraneous-dependencies": 0,
"import/no-unresolved": 0,
"import/prefer-default-export": 0,
"jsx-a11y/anchor-is-valid": [
"error",
{
@ -43,13 +71,24 @@
"aspects": ["noHref", "invalidHref", "preferButton"]
}
],
"react/require-default-props": 0,
"react/default-props-match-prop-types": ["error", { "allowRequiredDefaults": true }],
"react/forbid-prop-types": [
1,
{
"forbid": ["object", "any"]
}
],
"react/no-array-index-key": 0,
"react/require-default-props": 0,
"react/state-in-constructor": 0,
"react/jsx-filename-extension": [
1,
{
"extensions": [".js", ".jsx"]
}
],
"react/jsx-props-no-spreading": 0,
"react/state-in-constructor": 0
},
"env": {
"jest/globals": true,
"browser": true
"prettier/prettier": "error",
"jsx-a11y/no-autofocus": "warn"
}
}

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
10

15
.prettierignore Normal file
View File

@ -0,0 +1,15 @@
!.eslintrc.js
build_webpack
config
contracts
flow-typed
flow-typed/npm
migrations
node_modules
public
scripts
src/assets
src/config
test
*.spec*
*.test*

7
.prettierrc Normal file
View File

@ -0,0 +1,7 @@
{
"tabWidth": 2,
"printWidth": 120,
"trailingComma": "all",
"singleQuote": true,
"semi": false
}

View File

@ -1,6 +1,6 @@
{
"name": "safe-react",
"version": "1.7.1",
"version": "1.7.2",
"description": "Allowing crypto users manage funds in a safer way",
"homepage": "https://github.com/gnosis/safe-react#readme",
"bugs": {
@ -19,25 +19,33 @@
"build": "REACT_APP_APP_VERSION=$npm_package_version node scripts/build.js",
"build-mainnet": "REACT_APP_NETWORK=mainnet yarn build",
"flow": "flow",
"precommit": "./precommit.sh",
"format:staged": "lint-staged",
"lint:check": "eslint './src/**/*.{js,jsx}'",
"lint:fix": "yarn lint:check --fix",
"prettier": "prettier './src/**/*.{js,jsx}'",
"prettier:check": "yarn prettier --check",
"prettier:fix": "yarn prettier --write",
"start": "node scripts/start.js",
"start-mainnet": "REACT_APP_NETWORK=mainnet yarn start",
"test": "NODE_ENV=test && node scripts/test.js --env=jsdom",
"format": "prettier-eslint $PWD'/src/**/*.{js,jsx}' --write",
"format:staged": "lint-staged"
"test": "NODE_ENV=test && node scripts/test.js --env=jsdom"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged --allow-empty"
}
},
"lint-staged": {
"./src/**/*.{js,jsx}": "prettier-eslint --write"
"src/**/*.{js,jsx}": [
"eslint --fix",
"prettier --write"
]
},
"pre-commit": [
"precommit"
],
"dependencies": {
"@gnosis.pm/safe-contracts": "1.0.0",
"@gnosis.pm/util-contracts": "2.0.4",
"@gnosis.pm/safe-contracts": "1.1.1-dev.1",
"@gnosis.pm/util-contracts": "2.0.6",
"@material-ui/core": "4.8.0",
"@material-ui/icons": "4.5.1",
"@material-ui/lab": "^4.0.0-alpha.39",
"@material-ui/lab": "4.0.0-alpha.39",
"@portis/web3": "^2.0.0-beta.45",
"@testing-library/jest-dom": "4.2.4",
"@toruslabs/torus-embed": "0.2.10",
@ -50,7 +58,7 @@
"currency-flags": "^2.1.1",
"date-fns": "2.8.1",
"dotenv": "^8.2.0",
"ethereum-ens": "0.7.8",
"ethereum-ens": "0.8.0",
"final-form": "4.18.6",
"fortmatic": "^1.0.1",
"history": "4.10.1",
@ -81,8 +89,9 @@
"redux-thunk": "^2.3.0",
"reselect": "^4.0.0",
"semver": "^7.1.1",
"styled-components": "^5.0.1",
"web3": "1.2.4",
"web3connect": "^1.0.0-beta.25"
"web3connect": "1.0.0-beta.25"
},
"devDependencies": {
"@babel/cli": "7.7.5",
@ -123,13 +132,13 @@
"css-loader": "3.4.0",
"detect-port": "^1.3.0",
"dotenv-expand": "^5.1.0",
"eslint": "6.7.2",
"eslint-config-airbnb": "18.0.1",
"eslint-plugin-flowtype": "4.5.2",
"eslint-plugin-import": "2.19.1",
"eslint-plugin-jest": "23.1.1",
"eslint-plugin-jsx-a11y": "6.2.3",
"eslint-plugin-react": "7.17.0",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.10.0",
"eslint-plugin-flowtype": "^4.6.0",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-prettier": "^3.1.2",
"eslint-plugin-react": "^7.18.3",
"ethereumjs-abi": "0.6.8",
"extract-text-webpack-plugin": "^4.0.0-beta.0",
"file-loader": "5.0.2",
@ -137,6 +146,7 @@
"fs-extra": "8.1.0",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"husky": "^4.2.2",
"jest": "24.9.0",
"jest-dom": "4.0.0",
"json-loader": "^0.5.7",
@ -144,8 +154,7 @@
"postcss-loader": "^3.0.0",
"postcss-mixins": "6.2.3",
"postcss-simple-vars": "^5.0.2",
"pre-commit": "^1.2.2",
"prettier-eslint-cli": "5.0.0",
"prettier": "^1.19.1",
"run-with-testrpc": "0.3.1",
"style-loader": "1.0.2",
"terser-webpack-plugin": "2.3.1",

View File

@ -1,41 +0,0 @@
#!/bin/bash
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep ".jsx\{0,1\}$")
ESLINT="$(git rev-parse --show-toplevel)/node_modules/.bin/eslint"
if [[ "$STAGED_FILES" = "" ]]; then
exit 0
fi
PASS=true
echo -e "\nValidating Javascript:\n"
# Check for eslint
if [[ ! -x "$ESLINT" ]]; then
echo -e "\t\033[41mPlease install ESlint\033[0m (npm i --save-dev eslint)"
exit 1
fi
for FILE in $STAGED_FILES
do
"$ESLINT" "$FILE" "--fix"
if [[ "$?" == 0 ]]; then
echo -e "\t\033[32mESLint Passed: $FILE\033[0m"
else
echo -e "\t\033[41mESLint Failed: $FILE\033[0m"
PASS=false
fi
done
echo -e "\nJavascript validation completed!\n"
if ! $PASS; then
echo -e "\033[0;31mCOMMIT FAILED, PLEASE FIX ESLINT ERRORS:\033[0m Your commit contains files that should pass ESLint but do not. Please fix the ESLint errors in a new commit.\n"
exit 1
else
echo -e "\033[42mCOMMIT SUCCEEDED\033[0m\n"
fi
exit $?

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="21" viewBox="0 0 13 21">
<path fill="#B2B5B2" fill-rule="evenodd" d="M8.7 11.266V0H4.27v11.266H0l6.484 9.172 6.493-9.172z"/>
</svg>

After

Width:  |  Height:  |  Size: 195 B

View File

@ -0,0 +1,30 @@
// @flow
import React from 'react'
import styled from 'styled-components'
import Hairline from '~/components/layout/Hairline'
import { sm, md } from '~/theme/variables'
import ArrowDown from './arrow-down.svg'
const Wrapper = styled.div`
display: flex;
align-items: center;
margin: ${md} 0;
img {
margin: 0 ${sm};
}
`
type Props = {
withArrow: boolean,
}
const DividerLine = ({ withArrow }: Props) => (
<Wrapper>
{withArrow && <img src={ArrowDown} alt="Arrow Down" />}
<Hairline />
</Wrapper>
)
export default DividerLine

View File

@ -0,0 +1,21 @@
// @flow
import React from 'react'
import styled from 'styled-components'
import { border } from '~/theme/variables'
type Props = {
children: React.Node,
}
const Box = styled.p`
padding: 10px;
word-wrap: break-word;
border: solid 2px ${border};
`
const TextBox = ({ children }: Props) => {
return <Box>{children}</Box>
}
export default TextBox

View File

@ -0,0 +1,3 @@
// @flow
export { default as DividerLine } from './DividerLine'
export { default as TextBox } from './TextBox'

View File

@ -0,0 +1,20 @@
// @flow
import React from 'react'
import CircularProgress from '@material-ui/core/CircularProgress'
import styled from 'styled-components'
const Wrapper = styled.div`
display: flex;
height: 100%;
width: 100%;
justify-content: center;
align-items: center;
`
const Loader = () => (
<Wrapper>
<CircularProgress size={60} />
</Wrapper>
)
export default Loader

View File

@ -0,0 +1,2 @@
// @flow
export { default as Loader } from './Loader'

View File

@ -0,0 +1,7 @@
// @flow
export * from './dataDisplay'
export * from './feedback'
export * from './layouts'
export * from './safeUtils'
export * from './surfaces'
export * from './utils'

View File

@ -0,0 +1,46 @@
// @flow
import styled from 'styled-components'
export const Wrapper = styled.div`
display: grid;
grid-template-columns: 245px auto;
grid-template-rows: 62px auto 25px;
min-height: 500px;
.background {
box-shadow: 1px 2px 10px 0 rgba(212, 212, 211, 0.59);
background-color: white;
}
`
export const Nav = styled.div`
grid-column: 1/3;
grid-row: 1;
margin: 8px 0;
padding: 16px 0;
box-sizing: border-box;
display: flex;
justify-content: flex-end;
`
export const Menu = styled.div.attrs(() => ({ className: 'background' }))`
grid-column: 1;
grid-row: 2/4;
border-right: solid 2px #e8e7e6;
border-top-left-radius: 8px;
border-bottom-left-radius: 8px;
`
export const Content = styled.div.attrs(() => ({ className: 'background' }))`
grid-column: 2;
grid-row: 2;
border-top-right-radius: 8px;
`
export const Footer = styled.div.attrs(() => ({ className: 'background' }))`
grid-column: 2;
grid-row: 3;
border-bottom-right-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
`

View File

@ -0,0 +1,56 @@
// @flow
import React from 'react'
import styled from 'styled-components'
import cn from 'classnames'
import { withStyles } from '@material-ui/core/styles'
// TODO: move these styles to a generic place
import styles from './style'
const Wrapper = styled.div``
const Item = styled.div`
border-bottom: solid 2px rgb(232, 231, 230);
.container {
display: flex;
align-items: flex-end;
}
`
const IconImg = styled.img`
width: 20px;
margin-right: 10px;
`
type Props = {
items: Array<{
id: string,
name: string,
iconUrl?: string,
}>,
activeItem: string,
onItemClick: () => void,
classes: Object,
}
const List = ({ items, activeItem, onItemClick, classes }: Props) => {
return (
<Wrapper>
{items.map(i => (
<Item
key={i.id}
className={cn(classes.menuOption, activeItem === i.id && classes.active)}
onClick={() => onItemClick(i.id)}
>
<div className="container">
{i.iconUrl && <IconImg src={i.iconUrl} alt={i.name} />}
<span>{i.name}</span>
</div>
</Item>
))}
</Wrapper>
)
}
export default withStyles(styles)(List)

View File

@ -0,0 +1,10 @@
// @flow
import * as LayoutComponents from './Layout'
import List from './List'
const ListContentLayout = {
...LayoutComponents,
List,
}
export default ListContentLayout

View File

@ -0,0 +1,66 @@
// @flow
import {
xs,
sm,
md,
border,
secondary,
bolderFont,
background,
largeFontSize,
fontColor,
screenSm,
} from '~/theme/variables'
const styles = () => ({
menuOption: {
alignItems: 'center',
borderRight: `solid 1px ${border}`,
boxSizing: 'border-box',
cursor: 'pointer',
flexGrow: '1',
flexShrink: '1',
fontSize: '13px',
justifyContent: 'center',
lineHeight: '1.2',
minWidth: '0',
padding: `${md} ${sm}`,
width: '100%',
[`@media (min-width: ${screenSm}px)`]: {
borderRight: 'none',
flexGrow: '0',
fontSize: largeFontSize,
justifyContent: 'flex-start',
padding: `${md} 0 ${md} ${md}`,
},
'&:last-of-type': {
borderRight: 'none',
},
'&:first-child': {
borderTopLeftRadius: sm,
},
'& svg': {
display: 'block',
marginRight: xs,
maxWidth: '16px',
[`@media (min-width: ${screenSm}px)`]: {
marginRight: sm,
},
},
'& .fill': {
fill: fontColor,
},
},
active: {
backgroundColor: background,
color: secondary,
fontWeight: bolderFont,
'& .fill': {
fill: secondary,
},
},
})
export default styles

View File

@ -0,0 +1,2 @@
// @flow
export { default as ListContentLayout } from './ListContentLayout'

View File

@ -0,0 +1,77 @@
// @flow
import React from 'react'
import styled from 'styled-components'
import Paragraph from '~/components/layout/Paragraph'
import EtherscanBtn from '~/components/EtherscanBtn'
import CopyBtn from '~/components/CopyBtn'
import Identicon from '~/components/Identicon'
import Bold from '~/components/layout/Bold'
import { xs, border } from '~/theme/variables'
import Block from '~/components/layout/Block'
const Wrapper = styled.div`
display: flex;
align-items: center;
.icon-section {
margin-right: 10px;
}
.data-section {
display: flex;
flex-direction: column;
.address {
display: flex;
}
}
`
const StyledBlock = styled(Block)`
font-size: 12px;
line-height: 1.08;
letter-spacing: -0.5;
background-color: ${border};
width: fit-content;
padding: 5px 10px;
margin-top: ${xs};
border-radius: 3px;
`
type Props = {
safeName: string,
safeAddress: string,
ethBalance: string,
}
const AddressInfo = ({ safeName, safeAddress, ethBalance }: Props) => {
return (
<Wrapper>
<div className="icon-section">
<Identicon address={safeAddress} diameter={32} />
</div>
<div className="data-section">
{safeName && (
<Paragraph weight="bolder" noMargin>
{safeName}
</Paragraph>
)}
<div className="address">
<Paragraph weight="bolder" noMargin>
{safeAddress}
</Paragraph>
<CopyBtn content={safeAddress} />
<EtherscanBtn type="address" value={safeAddress} />
</div>
{ethBalance && (
<StyledBlock>
<Paragraph noMargin>
Balance: <Bold>{`${ethBalance} ETH`}</Bold>
</Paragraph>
</StyledBlock>
)}
</div>
</Wrapper>
)
}
export default AddressInfo

View File

@ -0,0 +1,2 @@
// @flow
export { default as AddressInfo } from './AddressInfo'

View File

@ -0,0 +1,48 @@
// @flow
import React from 'react'
import styled from 'styled-components'
import CollapseMUI from '@material-ui/core/Collapse'
import IconButton from '@material-ui/core/IconButton'
import ExpandLess from '@material-ui/icons/ExpandLess'
import ExpandMore from '@material-ui/icons/ExpandMore'
const Wrapper = styled.div``
const Header = styled.div`
display: flex;
align-items: center;
`
const Title = styled.div``
type Props = {
title: string,
children: React.Node,
description: React.Node,
}
const Collapse = ({ title, description, children }: Props) => {
const [open, setOpen] = React.useState(false)
const handleClick = () => {
setOpen(!open)
}
return (
<Wrapper>
<Title>{title}</Title>
<Header>
<IconButton disableRipple size="small" onClick={handleClick}>
{open ? <ExpandLess /> : <ExpandMore />}
</IconButton>
{description}
</Header>
<CollapseMUI in={open} timeout="auto" unmountOnExit>
{children}
</CollapseMUI>
</Wrapper>
)
}
export default Collapse

View File

@ -0,0 +1,2 @@
// @flow
export { default as Collapse } from './Collapse'

View File

@ -0,0 +1,2 @@
// @flow
export * from './modals'

View File

@ -0,0 +1,73 @@
// @flow
import React from 'react'
import styled from 'styled-components'
import Close from '@material-ui/icons/Close'
import { makeStyles, createStyles } from '@material-ui/core/styles'
import IconButton from '@material-ui/core/IconButton'
import Modal from '~/components/Modal'
import Hairline from '~/components/layout/Hairline'
const TitleSection = styled.div`
display: flex;
justify-content: space-between;
margin: 10px 20px;
`
const BodySection = styled.div`
padding: 10px 20px;
max-height: 460px;
overflow-y: auto;
`
const FooterSection = styled.div`
margin: 10px 20px;
`
const StyledClose = styled(Close)`
&& {
height: 35px;
width: 35px;
}
`
const useStyles = makeStyles(() =>
createStyles({
paper: {
height: 'auto',
position: 'static',
},
}),
)
type Props = {
title: string,
body: React.Node,
footer: React.Node,
onClose: () => void,
}
const GenericModal = ({ title, body, footer, onClose }: Props) => {
const classes = useStyles()
return (
<Modal title="GenericModal" description="GenericModal" handleClose={onClose} paperClassName={classes.paper} open>
<TitleSection>
{title}
<IconButton onClick={onClose} disableRipple>
<StyledClose />
</IconButton>
</TitleSection>
<Hairline />
<BodySection>{body}</BodySection>
{footer && (
<>
<Hairline />
<FooterSection>{footer}</FooterSection>
</>
)}
</Modal>
)
}
export default GenericModal

View File

@ -0,0 +1,3 @@
// @flow
export { default as GenericModal } from './GenericModal'
export * from './utils'

View File

@ -0,0 +1,60 @@
// @flow
import React from 'react'
import styled from 'styled-components'
import Paragraph from '~/components/layout/Paragraph'
import Button from '~/components/layout/Button'
import { lg } from '~/theme/variables'
const StyledParagraph = styled(Paragraph)`
&& {
font-size: ${lg};
}
`
const IconImg = styled.img`
width: 20px;
margin-right: 10px;
`
const TitleWrapper = styled.div`
display: flex;
align-items: center;
`
export const ModalTitle = ({ title, iconUrl }: { title: string, iconUrl: string }) => {
return (
<TitleWrapper>
{iconUrl && <IconImg src={iconUrl} alt={title} />}
<StyledParagraph weight="bolder" noMargin>
{title}
</StyledParagraph>
</TitleWrapper>
)
}
const FooterWrapper = styled.div`
display: flex;
justify-content: space-around;
`
export const ModalFooterConfirmation = ({
okText,
cancelText,
handleOk,
handleCancel,
}: {
okText: string,
cancelText: string,
handleOk: () => void,
handleCancel: () => void,
}) => {
return (
<FooterWrapper>
<Button minWidth={130} onClick={handleCancel}>
{cancelText}
</Button>
<Button color="primary" minWidth={130} onClick={handleOk} variant="contained">
{okText}
</Button>
</FooterWrapper>
)
}

View File

@ -7,9 +7,7 @@ import { useDispatch, useSelector } from 'react-redux'
import cn from 'classnames'
import Link from '~/components/layout/Link'
import Button from '~/components/layout/Button'
import {
primary, mainFontFamily, md, screenSm,
} from '~/theme/variables'
import { primary, mainFontFamily, md, screenSm } from '~/theme/variables'
import type { CookiesProps } from '~/logic/cookies/model/cookie'
import { COOKIES_KEY } from '~/logic/cookies/model/cookie'
import { loadFromCookie, saveCookie } from '~/logic/cookies/utils'
@ -134,17 +132,21 @@ const CookiesBanner = () => {
const cookieBannerContent = (
<div className={classes.container}>
<span role="button" tabIndex="0" onClick={closeCookiesBannerHandler} onKeyDown={closeCookiesBannerHandler} className={cn(classes.acceptPreferences, classes.text)}>
<span
role="button"
tabIndex="0"
onClick={closeCookiesBannerHandler}
onKeyDown={closeCookiesBannerHandler}
className={cn(classes.acceptPreferences, classes.text)}
>
Accept preferences &gt;
</span>
<div className={classes.content}>
<p className={classes.text}>
We use cookies to give you the best experience and to help improve our website. Please read our
{' '}
We use cookies to give you the best experience and to help improve our website. Please read our{' '}
<Link className={classes.link} to="https://safe.gnosis.io/cookie">
Cookie Policy
</Link>
{' '}
</Link>{' '}
for more information. By clicking &quot;Accept all&quot;, you agree to the storing of cookies on your device
to enhance site navigation, analyze site usage and provide customer support.
</p>
@ -155,7 +157,7 @@ const CookiesBanner = () => {
disabled
label="Necessary"
name="Necessary"
onChange={() => setLocalNecessary((prev) => !prev)}
onChange={() => setLocalNecessary(prev => !prev)}
value={localNecessary}
control={<Checkbox disabled />}
/>
@ -164,7 +166,7 @@ const CookiesBanner = () => {
<FormControlLabel
label="Analytics"
name="Analytics"
onChange={() => setLocalAnalytics((prev) => !prev)}
onChange={() => setLocalAnalytics(prev => !prev)}
value={localAnalytics}
control={<Checkbox checked={localAnalytics} />}
/>

View File

@ -33,9 +33,7 @@ type EtherscanBtnProps = {
value: string,
}
const EtherscanBtn = ({
type, value, className, increaseZindex = false,
}: EtherscanBtnProps) => {
const EtherscanBtn = ({ type, value, className, increaseZindex = false }: EtherscanBtnProps) => {
const classes = useStyles()
const customClasses = increaseZindex ? { popper: classes.increasedPopperZindex } : {}

View File

@ -18,14 +18,9 @@ type EtherscanLinkProps = {
value: string,
}
const EtherscanLink = ({
type, value, cut, classes, knownAddress,
}: EtherscanLinkProps) => (
const EtherscanLink = ({ type, value, cut, classes, knownAddress }: EtherscanLinkProps) => (
<Block className={classes.etherscanLink}>
<Span
className={cn(knownAddress && classes.addressParagraph, classes.address)}
size="md"
>
<Span className={cn(knownAddress && classes.addressParagraph, classes.address)} size="md">
{cut ? shortVersionOf(value, cut) : value}
</Span>
<CopyBtn className={cn(classes.button, classes.firstButton)} content={value} />

View File

@ -55,12 +55,7 @@ const Footer = () => {
return (
<footer className={classes.footer}>
<span className={classes.item}>
©
{date.getFullYear()}
{' '}
Gnosis
</span>
<span className={classes.item}>©{date.getFullYear()} Gnosis</span>
<span className={classes.sep}>|</span>
<Link className={cn(classes.item, classes.link)} to="https://safe.gnosis.io/terms" target="_blank">
Terms

View File

@ -4,9 +4,7 @@ import { withStyles } from '@material-ui/core/styles'
import Dot from '@material-ui/icons/FiberManualRecord'
import Block from '~/components/layout/Block'
import Img from '~/components/layout/Img'
import {
fancy, border, warning, screenSm,
} from '~/theme/variables'
import { fancy, border, warning, screenSm } from '~/theme/variables'
const key = require('../assets/key.svg')
const triangle = require('../assets/triangle.svg')

View File

@ -12,9 +12,7 @@ import Col from '~/components/layout/Col'
import Img from '~/components/layout/Img'
import Row from '~/components/layout/Row'
import Spacer from '~/components/Spacer'
import {
border, sm, md, headerHeight, screenSm,
} from '~/theme/variables'
import { border, sm, md, headerHeight, screenSm } from '~/theme/variables'
import Provider from './Provider'
import NetworkLabel from './NetworkLabel'
import SafeListHeader from './SafeListHeader'
@ -24,7 +22,7 @@ const logo = require('../assets/gnosis-safe-multisig-logo.svg')
type Props = Open & {
classes: Object,
providerDetails: React.Node,
providerInfo: React.Node
providerInfo: React.Node,
}
const styles = () => ({
@ -64,55 +62,42 @@ const styles = () => ({
},
})
const Layout = openHoc(
({
open,
toggle,
clickAway,
classes,
providerInfo,
providerDetails,
}: Props) => (
<Row className={classes.summary}>
<Col start="xs" middle="xs" className={classes.logo}>
<Link to="/">
<Img src={logo} height={32} alt="Gnosis Team Safe" />
</Link>
</Col>
<Divider />
<SafeListHeader />
<Divider />
<NetworkLabel />
<Spacer />
<Provider open={open} toggle={toggle} info={providerInfo}>
{(providerRef) => (
<Popper
anchorEl={providerRef.current}
className={classes.popper}
open={open}
placement="bottom"
popperOptions={{ positionFixed: true }}
>
{({ TransitionProps }) => (
<Grow {...TransitionProps}>
<>
<ClickAwayListener
onClickAway={clickAway}
mouseEvent="onClick"
touchEvent={false}
>
<List className={classes.root} component="div">
{providerDetails}
</List>
</ClickAwayListener>
</>
</Grow>
)}
</Popper>
const Layout = openHoc(({ open, toggle, clickAway, classes, providerInfo, providerDetails }: Props) => (
<Row className={classes.summary}>
<Col start="xs" middle="xs" className={classes.logo}>
<Link to="/">
<Img src={logo} height={32} alt="Gnosis Team Safe" />
</Link>
</Col>
<Divider />
<SafeListHeader />
<Divider />
<NetworkLabel />
<Spacer />
<Provider open={open} toggle={toggle} info={providerInfo}>
{providerRef => (
<Popper
anchorEl={providerRef.current}
className={classes.popper}
open={open}
placement="bottom"
popperOptions={{ positionFixed: true }}
>
{({ TransitionProps }) => (
<Grow {...TransitionProps}>
<>
<ClickAwayListener onClickAway={clickAway} mouseEvent="onClick" touchEvent={false}>
<List className={classes.root} component="div">
{providerDetails}
</List>
</ClickAwayListener>
</>
</Grow>
)}
</Provider>
</Row>
),
)
</Popper>
)}
</Provider>
</Row>
))
export default withStyles(styles)(Layout)

View File

@ -4,9 +4,7 @@ import { makeStyles } from '@material-ui/core/styles'
import { getNetwork } from '~/config'
import Paragraph from '~/components/layout/Paragraph'
import Col from '~/components/layout/Col'
import {
xs, sm, md, border, screenSm,
} from '~/theme/variables'
import { xs, sm, md, border, screenSm } from '~/theme/variables'
const network = getNetwork()
const formattedNetwork = network[0].toUpperCase() + network.substring(1).toLowerCase()

View File

@ -56,9 +56,7 @@ class Provider extends React.Component<Props> {
}
render() {
const {
open, toggle, children, classes, info,
} = this.props
const { open, toggle, children, classes, info } = this.props
return (
<>

View File

@ -13,9 +13,7 @@ import Img from '~/components/layout/Img'
import Row from '~/components/layout/Row'
import Block from '~/components/layout/Block'
import Spacer from '~/components/Spacer'
import {
xs, sm, md, lg, background, warning, connected as connectedBg,
} from '~/theme/variables'
import { xs, sm, md, lg, background, warning, connected as connectedBg } from '~/theme/variables'
import { upperFirst } from '~/utils/css'
import { shortVersionOf } from '~/logic/wallets/ethAddresses'
import CircleDot from '~/components/Header/components/CircleDot'
@ -93,9 +91,7 @@ const styles = () => ({
},
})
const UserDetails = ({
provider, connected, network, userAddress, classes, onDisconnect,
}: Props) => {
const UserDetails = ({ provider, connected, network, userAddress, classes, onDisconnect }: Props) => {
const status = connected ? 'Connected' : 'Connection error'
const address = userAddress ? shortVersionOf(userAddress, 4) : 'Address not available'
const identiconAddress = userAddress || 'random'

View File

@ -54,9 +54,7 @@ const styles = () => ({
},
})
const ProviderInfo = ({
provider, network, userAddress, connected, classes,
}: Props) => {
const ProviderInfo = ({ provider, network, userAddress, connected, classes }: Props) => {
const providerText = `${provider} [${network}]`
const cutAddress = connected ? shortVersionOf(userAddress, 4) : 'Connection Error'
const color = connected ? 'primary' : 'warning'

View File

@ -7,9 +7,7 @@ import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
import ExpandLessIcon from '@material-ui/icons/ExpandLess'
import Paragraph from '~/components/layout/Paragraph'
import Col from '~/components/layout/Col'
import {
xs, sm, md, border, screenSm,
} from '~/theme/variables'
import { xs, sm, md, border, screenSm } from '~/theme/variables'
import { safesCountSelector } from '~/routes/safe/store/selectors'
import { SidebarContext } from '~/components/Sidebar'
@ -64,8 +62,8 @@ const SafeListHeader = ({ safesCount }: Props) => {
)
}
export default connect<Object, Object,?Function,?Object>(
export default connect<Object, Object, ?Function, ?Object>(
// $FlowFixMe
(state) => ({ safesCount: safesCountSelector(state) }),
state => ({ safesCount: safesCountSelector(state) }),
null,
)(SafeListHeader)

View File

@ -58,9 +58,7 @@ class HeaderComponent extends React.PureComponent<Props, State> {
getProviderInfoBased = () => {
const { hasError } = this.state
const {
loaded, available, provider, network, userAddress,
} = this.props
const { loaded, available, provider, network, userAddress } = this.props
if (hasError || !loaded) {
return <ProviderDisconnected />
@ -71,9 +69,7 @@ class HeaderComponent extends React.PureComponent<Props, State> {
getProviderDetailsBased = () => {
const { hasError } = this.state
const {
loaded, available, provider, network, userAddress,
} = this.props
const { loaded, available, provider, network, userAddress } = this.props
if (hasError || !loaded) {
return <ConnectDetails />
@ -98,7 +94,4 @@ class HeaderComponent extends React.PureComponent<Props, State> {
}
}
export default connect(
selector,
actions,
)(withSnackbar(HeaderComponent))
export default connect(selector, actions)(withSnackbar(HeaderComponent))

View File

@ -62,13 +62,10 @@ export default class Identicon extends React.PureComponent<Props> {
return image
}
render() {
const { diameter, className } = this.props
const style = this.getStyleFrom(diameter)
return (
<div className={className} style={style} ref={this.identicon} />
)
return <div className={className} style={style} ref={this.identicon} />
}
}

View File

@ -20,24 +20,16 @@ const styles = {
class ListItemText extends React.PureComponent<Props> {
render() {
const {
primary, secondary, classes, cut = false,
} = this.props
const { primary, secondary, classes, cut = false } = this.props
const cutStyle = cut ? {
secondary: classes.itemTextSecondary,
} : undefined
const cutStyle = cut
? {
secondary: classes.itemTextSecondary,
}
: undefined
return (
<MuiListItemText
classes={cutStyle}
inset
primary={primary}
secondary={secondary}
/>
)
return <MuiListItemText classes={cutStyle} inset primary={primary} secondary={secondary} />
}
}
export default withStyles(styles)(ListItemText)

View File

@ -40,7 +40,14 @@ const styles = () => ({
})
const GnoModal = ({
title, description, open, children, handleClose, modalClassName, classes, paperClassName,
title,
description,
open,
children,
handleClose,
modalClassName,
classes,
paperClassName,
}: Props) => (
<Modal
aria-labelledby={title}

View File

@ -43,7 +43,7 @@ class Notifier extends Component<Props> {
componentDidUpdate() {
const { notifications = [], enqueueSnackbar, removeSnackbar } = this.props
notifications.forEach((notification) => {
notifications.forEach(notification => {
// Do nothing if snackbar is already displayed
if (this.displayed.includes(notification.key)) {
return
@ -66,7 +66,7 @@ class Notifier extends Component<Props> {
})
}
storeDisplayed = (id) => {
storeDisplayed = id => {
this.displayed = [...this.displayed, id]
}

View File

@ -24,9 +24,7 @@ type Props = {
isOpen: boolean,
}
const ScanQRModal = ({
classes, onClose, isOpen, onScan,
}: Props) => {
const ScanQRModal = ({ classes, onClose, isOpen, onScan }: Props) => {
const [hasWebcam, setHasWebcam] = useState(null)
const scannerRef: Object = React.createRef()
const openImageDialog = () => {
@ -73,10 +71,10 @@ const ScanQRModal = ({
<QrReader
ref={scannerRef}
legacyMode={!hasWebcam}
onScan={(data) => {
onScan={data => {
if (data) onScan(data)
}}
onError={(err) => {
onError={err => {
console.error(err)
}}
style={{ width: '400px', height: '400px' }}
@ -85,12 +83,7 @@ const ScanQRModal = ({
</Col>
<Hairline />
<Row align="center" className={classes.buttonRow}>
<Button
color="secondary"
className={classes.button}
minWidth={154}
onClick={onClose}
>
<Button color="secondary" className={classes.button} minWidth={154} onClick={onClose}>
Close
</Button>
<Button

View File

@ -1,7 +1,5 @@
// @flow
import {
lg, sm, background, secondaryText,
} from '~/theme/variables'
import { lg, sm, background, secondaryText } from '~/theme/variables'
export const styles = () => ({
heading: {

View File

@ -1,15 +1,17 @@
// @flow
navigator.getMedia = navigator.getUserMedia // use the proper vendor prefix
|| navigator.webkitGetUserMedia
|| navigator.mozGetUserMedia
|| navigator.msGetUserMedia
navigator.getMedia =
navigator.getUserMedia || // use the proper vendor prefix
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia
export const checkWebcam = (success: Function, err: Function) => navigator.getMedia(
{ video: true },
() => {
success()
},
() => {
err()
},
)
export const checkWebcam = (success: Function, err: Function) =>
navigator.getMedia(
{ video: true },
() => {
success()
},
() => {
err()
},
)

View File

@ -11,9 +11,7 @@ import Hairline from '~/components/layout/Hairline'
import Paragraph from '~/components/layout/Paragraph'
import ButtonLink from '~/components/layout/ButtonLink'
import Identicon from '~/components/Identicon'
import {
mediumFontSize, sm, primary, disabled, md,
} from '~/theme/variables'
import { mediumFontSize, sm, primary, disabled, md } from '~/theme/variables'
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
import { shortVersionOf, sameAddress } from '~/logic/wallets/ethAddresses'
import { type Safe } from '~/routes/safe/store/models/safe'
@ -74,14 +72,12 @@ const useStyles = makeStyles({
},
})
const SafeList = ({
safes, onSafeClick, setDefaultSafe, defaultSafe, currentSafe,
}: SafeListProps) => {
const SafeList = ({ safes, onSafeClick, setDefaultSafe, defaultSafe, currentSafe }: SafeListProps) => {
const classes = useStyles()
return (
<MuiList className={classes.list}>
{safes.map((safe) => (
{safes.map(safe => (
<React.Fragment key={safe.address}>
<Link
to={`${SAFELIST_ADDRESS}/${safe.address}/balances`}
@ -89,18 +85,23 @@ const SafeList = ({
data-testid={SIDEBAR_SAFELIST_ROW_TESTID}
>
<ListItem classes={{ root: classes.listItemRoot }}>
{ sameAddress(currentSafe, safe.address) ? (
{sameAddress(currentSafe, safe.address) ? (
<ListItemIcon>
<Img src={check} alt="check" className={classes.checkIcon} />
</ListItemIcon>
) : <div className={classes.noIcon}>placeholder</div> }
) : (
<div className={classes.noIcon}>placeholder</div>
)}
<ListItemIcon>
<Identicon address={safe.address} diameter={32} className={classes.icon} />
</ListItemIcon>
<ListItemText
primary={safe.name}
secondary={shortVersionOf(safe.address, 4)}
classes={{ primary: classes.safeName, secondary: classes.safeAddress }}
classes={{
primary: classes.safeName,
secondary: classes.safeAddress,
}}
/>
<Paragraph size="lg" color="primary">
{`${formatAmount(safe.ethBalance)} ETH`}
@ -111,7 +112,7 @@ const SafeList = ({
<ButtonLink
className={classes.makeDefaultBtn}
size="sm"
onClick={(e) => {
onClick={e => {
e.preventDefault()
e.stopPropagation()

View File

@ -14,10 +14,7 @@ import Hairline from '~/components/layout/Hairline'
import Row from '~/components/layout/Row'
import { WELCOME_ADDRESS } from '~/routes/routes'
import { type Safe } from '~/routes/safe/store/models/safe'
import {
defaultSafeSelector,
safeParamAddressFromStateSelector,
} from '~/routes/safe/store/selectors'
import { defaultSafeSelector, safeParamAddressFromStateSelector } from '~/routes/safe/store/selectors'
import setDefaultSafe from '~/routes/safe/store/actions/setDefaultSafe'
import { sortedSafeListSelector } from './selectors'
import SafeList from './SafeList'
@ -27,7 +24,7 @@ const { useState, useEffect, useMemo } = React
type TSidebarContext = {
isOpen: boolean,
toggleSidebar: Function
toggleSidebar: Function,
}
export const SidebarContext = React.createContext<TSidebarContext>({
@ -40,22 +37,18 @@ type SidebarProps = {
safes: List<Safe>,
setDefaultSafeAction: Function,
defaultSafe: string,
currentSafe: string
currentSafe: string,
}
const filterBy = (filter: string, safes: List<Safe>): List<Safe> => safes.filter(
(safe: Safe) => !filter
|| safe.address.toLowerCase().includes(filter.toLowerCase())
|| safe.name.toLowerCase().includes(filter.toLowerCase()),
)
const filterBy = (filter: string, safes: List<Safe>): List<Safe> =>
safes.filter(
(safe: Safe) =>
!filter ||
safe.address.toLowerCase().includes(filter.toLowerCase()) ||
safe.name.toLowerCase().includes(filter.toLowerCase()),
)
const Sidebar = ({
children,
safes,
setDefaultSafeAction,
defaultSafe,
currentSafe,
}: SidebarProps) => {
const Sidebar = ({ children, safes, setDefaultSafeAction, defaultSafe, currentSafe }: SidebarProps) => {
const [isOpen, setIsOpen] = useState<boolean>(false)
const [filter, setFilter] = useState<string>('')
const classes = useSidebarStyles()
@ -74,7 +67,7 @@ const Sidebar = ({
}
const toggleSidebar = () => {
setIsOpen((prevIsOpen) => !prevIsOpen)
setIsOpen(prevIsOpen => !prevIsOpen)
}
const handleFilterChange = (value: string) => {
@ -147,7 +140,7 @@ const Sidebar = ({
export default connect<Object, Object, ?Function, ?Object>(
// $FlowFixMe
(state) => ({
state => ({
safes: sortedSafeListSelector(state),
defaultSafe: defaultSafeSelector(state),
currentSafe: safeParamAddressFromStateSelector(state),

View File

@ -1,8 +1,6 @@
// @flow
import { makeStyles } from '@material-ui/core/styles'
import {
xs, mediumFontSize, secondaryText, md, headerHeight, screenSm,
} from '~/theme/variables'
import { xs, mediumFontSize, secondaryText, md, headerHeight, screenSm } from '~/theme/variables'
const sidebarWidth = '400px'
const sidebarMarginLeft = '12px'

View File

@ -9,4 +9,5 @@ const style = {
flexGrow: 1,
}
// eslint-disable-next-line react/display-name
export default ({ className }: Props) => <div className={className} style={style} />

View File

@ -30,15 +30,7 @@ type Props = {
buttonLabels?: Array<string>,
}
const Controls = ({
onPrevious,
firstPage,
penultimate,
lastPage,
disabled,
currentStep,
buttonLabels,
}: Props) => {
const Controls = ({ onPrevious, firstPage, penultimate, lastPage, disabled, currentStep, buttonLabels }: Props) => {
const back = firstPage ? 'Cancel' : 'Back'
let next

View File

@ -23,9 +23,7 @@ type Props = {
padding?: boolean,
}
const OpenPaper = ({
classes, children, controls, padding = true,
}: Props) => (
const OpenPaper = ({ classes, children, controls, padding = true }: Props) => (
<Paper className={classes.root} elevation={1}>
<Block className={padding ? classes.padding : ''}>{children}</Block>
{controls}

View File

@ -5,10 +5,6 @@ type Props = {
children: React.Node,
}
const Step = ({ children }: Props) => (
<div>
{children}
</div>
)
const Step = ({ children }: Props) => <div>{children}</div>
export default Step

View File

@ -52,7 +52,7 @@ const GnoStepper = (props: Props) => {
const getPageProps = (pages: React.Node): PageProps => React.Children.toArray(pages)[page].props
const updateInitialProps = (newInitialProps) => {
const updateInitialProps = newInitialProps => {
setValues(newInitialProps)
}
@ -105,14 +105,12 @@ const GnoStepper = (props: Props) => {
return next(formValues)
}
const isLastPage = (pageNumber) => {
const isLastPage = pageNumber => {
const { steps } = props
return pageNumber === steps.length - 1
}
const {
steps, children, classes, disabledWhenValidating = false, testId, mutators, buttonLabels,
} = props
const { steps, children, classes, disabledWhenValidating = false, testId, mutators, buttonLabels } = props
const activePage = getActivePageFrom(children)
const lastPage = isLastPage(page)

View File

@ -57,9 +57,7 @@ class GnoTableHead extends React.PureComponent<Props> {
sortDirection={orderBy === column.id ? order : false}
>
{column.static ? (
<div style={column.style}>
{column.label}
</div>
<div style={column.style}>{column.label}</div>
) : (
<TableSortLabel
active={orderBy === column.id}

View File

@ -157,9 +157,7 @@ class GnoTable<K> extends React.Component<Props<K>, State> {
noBorder,
disableLoadingOnEmptyTable,
} = this.props
const {
order, orderBy, page, orderProp, rowsPerPage, fixed,
} = this.state
const { order, orderBy, page, orderProp, rowsPerPage, fixed } = this.state
const orderByParam = orderBy || defaultOrderBy
const orderParam = order || defaultOrder
const displayRows = rowsPerPage || defaultRowsPerPage
@ -189,10 +187,7 @@ class GnoTable<K> extends React.Component<Props<K>, State> {
</Table>
)}
{isEmpty && (
<Row
className={classes.loader}
style={this.getEmptyStyle(emptyRows + 1)}
>
<Row className={classes.loader} style={this.getEmptyStyle(emptyRows + 1)}>
<CircularProgress size={60} />
</Row>
)}

View File

@ -39,13 +39,14 @@ export const stableSort = (dataArray: List<any>, cmp: any, fixed: boolean): List
return a[1] - b[1]
})
const sortedElems: List<any> = stabilizedThis.map((el) => el[0])
const sortedElems: List<any> = stabilizedThis.map(el => el[0])
return fixedElems.concat(sortedElems)
}
export type Order = 'asc' | 'desc'
export const getSorting = (order: Order, orderBy: string, orderProp: boolean) => (order === 'desc'
? (a: any, b: any) => desc(a, b, orderBy, orderProp)
: (a: any, b: any) => -desc(a, b, orderBy, orderProp))
export const getSorting = (order: Order, orderBy: string, orderProp: boolean) =>
order === 'desc'
? (a: any, b: any) => desc(a, b, orderBy, orderProp)
: (a: any, b: any) => -desc(a, b, orderBy, orderProp)

View File

@ -3,11 +3,7 @@ import * as React from 'react'
import { Field } from 'react-final-form'
import { OnChange } from 'react-final-form-listeners'
import TextField from '~/components/forms/TextField'
import {
composeValidators,
required,
mustBeEthereumAddress,
} from '~/components/forms/validator'
import { composeValidators, required, mustBeEthereumAddress } from '~/components/forms/validator'
import { getAddressFromENS } from '~/logic/wallets/getWeb3'
type Props = {
@ -23,7 +19,7 @@ type Props = {
disabled?: boolean,
}
const isValidEnsName = (name) => /^([\w-]+\.)+(eth|test|xyz|luxe)$/.test(name)
const isValidEnsName = name => /^([\w-]+\.)+(eth|test|xyz|luxe)$/.test(name)
// an idea for second field was taken from here
// https://github.com/final-form/react-final-form-listeners/blob/master/src/OnBlur.js
@ -45,11 +41,7 @@ const AddressInput = ({
name={name}
component={TextField}
type="text"
validate={composeValidators(
required,
mustBeEthereumAddress,
...validators,
)}
validate={composeValidators(required, mustBeEthereumAddress, ...validators)}
inputAdornment={inputAdornment}
placeholder={placeholder}
text={text}
@ -59,7 +51,7 @@ const AddressInput = ({
disabled={disabled}
/>
<OnChange name={name}>
{async (value) => {
{async value => {
if (isValidEnsName(value)) {
try {
const resolverAddr = await getAddressFromENS(value)

View File

@ -5,22 +5,11 @@ import Checkbox, { type CheckoxProps } from '@material-ui/core/Checkbox'
class GnoCheckbox extends React.PureComponent<CheckoxProps> {
render() {
const {
input: {
checked, name, onChange, ...restInput
},
meta,
input: { checked, name, onChange, ...restInput },
...rest
} = this.props
return (
<Checkbox
{...rest}
name={name}
inputProps={restInput}
onChange={onChange}
checked={!!checked}
/>
)
return <Checkbox {...rest} name={name} inputProps={restInput} onChange={onChange} checked={!!checked} />
}
}

View File

@ -6,7 +6,7 @@ import { Form } from 'react-final-form'
export type OnSubmit = (
values: Object,
form: FormApi,
callback: ?(errors: ?Object) => ?Object
callback: ?(errors: ?Object) => ?Object,
) => ?Object | Promise<?Object> | void
type Props = {
@ -25,9 +25,7 @@ const stylesBasedOn = (padding: number): $Shape<CSSStyleDeclaration> => ({
flex: '1 0 auto',
})
const GnoForm = ({
onSubmit, validation, initialValues, children, padding = 0, formMutators, testId = '',
}: Props) => (
const GnoForm = ({ onSubmit, validation, initialValues, children, padding = 0, formMutators, testId = '' }: Props) => (
<Form
validate={validation}
onSubmit={onSubmit}

View File

@ -10,9 +10,7 @@ const style = {
}
const SelectInput = ({
input: {
name, value, onChange, ...restInput
},
input: { name, value, onChange, ...restInput },
meta,
label,
formControlProps,

View File

@ -21,11 +21,8 @@ const styles = () => ({
class TextField extends React.PureComponent<TextFieldProps> {
render() {
const {
input: {
name, onChange, value, ...restInput
},
input: { name, onChange, value, ...restInput },
meta,
render,
text,
inputAdornment,
classes,

View File

@ -23,13 +23,7 @@ const styles = () => ({
})
const TextareaField = ({ classes, ...props }: TextFieldProps) => (
<Field
{...props}
component={TextField}
multiline
rows="5"
className={classes.textarea}
/>
<Field {...props} component={TextField} multiline rows="5" className={classes.textarea} />
)
export default withStyles(styles)(TextareaField)

View File

@ -20,7 +20,8 @@ type Field = boolean | string | null | typeof undefined
export const required = (value: Field) => (value ? undefined : 'Required')
export const mustBeInteger = (value: string) => (!Number.isInteger(Number(value)) || value.includes('.') ? 'Must be an integer' : undefined)
export const mustBeInteger = (value: string) =>
!Number.isInteger(Number(value)) || value.includes('.') ? 'Must be an integer' : undefined
export const mustBeFloat = (value: number) => (value && Number.isNaN(Number(value)) ? 'Must be a number' : undefined)
@ -75,16 +76,19 @@ export const mustBeEthereumContractAddress = simpleMemoize(async (address: strin
: undefined
})
export const minMaxLength = (minLen: string | number, maxLen: string | number) => (value: string) => (value.length >= +minLen && value.length <= +maxLen ? undefined : `Should be ${minLen} to ${maxLen} symbols`)
export const minMaxLength = (minLen: string | number, maxLen: string | number) => (value: string) =>
value.length >= +minLen && value.length <= +maxLen ? undefined : `Should be ${minLen} to ${maxLen} symbols`
export const ADDRESS_REPEATED_ERROR = 'Address already introduced'
export const uniqueAddress = (addresses: string[] | List<string>) => simpleMemoize((value: string) => {
const addressAlreadyExists = addresses.some((address) => sameAddress(value, address))
return addressAlreadyExists ? ADDRESS_REPEATED_ERROR : undefined
})
export const uniqueAddress = (addresses: string[] | List<string>) =>
simpleMemoize((value: string) => {
const addressAlreadyExists = addresses.some(address => sameAddress(value, address))
return addressAlreadyExists ? ADDRESS_REPEATED_ERROR : undefined
})
export const composeValidators = (...validators: Function[]): FieldValidator => (value: Field) => validators.reduce((error, validator) => error || validator(value), undefined)
export const composeValidators = (...validators: Function[]): FieldValidator => (value: Field) =>
validators.reduce((error, validator) => error || validator(value), undefined)
export const inLimit = (limit: number, base: number, baseText: string, symbol: string = 'ETH') => (value: string) => {
const amount = Number(value)

View File

@ -7,10 +7,7 @@ export type Open = {
clickAway: () => void,
}
export default withStateHandlers(
() => ({ open: false }),
{
toggle: ({ open }) => () => ({ open: !open }),
clickAway: () => () => ({ open: false }),
},
)
export default withStateHandlers(() => ({ open: false }), {
toggle: ({ open }) => () => ({ open: !open }),
clickAway: () => () => ({ open: false }),
})

View File

@ -19,9 +19,7 @@ type Props = {
class Block extends PureComponent<Props> {
render() {
const {
margin, padding, justify, children, className, ...props
} = this.props
const { margin, padding, justify, children, className, ...props } = this.props
const paddingStyle = padding ? capitalize(padding, 'padding') : undefined
return (

View File

@ -14,9 +14,7 @@ const calculateStyleBased = (minWidth, minHeight) => ({
minHeight: minHeight && `${minHeight}px`,
})
const GnoButton = ({
minWidth, minHeight = 35, testId = '', style = {}, ...props
}: Props) => {
const GnoButton = ({ minWidth, minHeight = 35, testId = '', style = {}, ...props }: Props) => {
const calculatedStyle = calculateStyleBased(minWidth, minHeight)
return <Button style={{ ...calculatedStyle, ...style }} data-testid={testId} {...props} />

View File

@ -23,6 +23,8 @@ const GnoButtonLink = ({
testId = '',
className = '',
...props
}: Props) => <button type={type} className={cx(styles.btnLink, size, color, weight, className)} data-testid={testId} {...props} />
}: Props) => (
<button type={type} className={cx(styles.btnLink, size, color, weight, className)} data-testid={testId} {...props} />
)
export default GnoButtonLink

View File

@ -18,9 +18,7 @@ type Props = {
style?: Object,
}
const Hairline = ({
margin, color, style, className,
}: Props) => {
const Hairline = ({ margin, color, style, className }: Props) => {
const calculatedStyles = calculateStyleFrom(color, margin)
const mergedStyles = { ...calculatedStyles, ...(style || {}) }

View File

@ -20,9 +20,7 @@ type Props = {
}
const Heading = (props: Props) => {
const {
align, tag, truncate, margin, color, children, testId, className = '', ...rest
} = props
const { align, tag, truncate, margin, color, children, testId, className = '', ...rest } = props
const classes = cx(className, 'heading', align, tag, margin ? capitalize(margin, 'margin') : undefined, color, {
truncate,

View File

@ -14,9 +14,7 @@ type Props = {
testId?: string,
}
const Img = ({
fullwidth, alt, bordered, className, style, testId = '', ...props
}: Props) => {
const Img = ({ fullwidth, alt, bordered, className, style, testId = '', ...props }: Props) => {
const classes = cx(styles.img, { fullwidth, bordered }, className)
return <img alt={alt} style={style} className={classes} data-testid={testId} {...props} />

View File

@ -16,9 +16,7 @@ type Props = {
innerRef?: React.ElementRef<any>,
}
const GnosisLink = ({
to, children, color, className, padding, innerRef, ...props
}: Props) => {
const GnosisLink = ({ to, children, color, className, padding, innerRef, ...props }: Props) => {
const internal = /^\/(?!\/)/.test(to)
const classes = cx(styles.link, color || 'regular', padding ? capitalize(padding, 'padding') : undefined, className)
const LinkElement = internal ? Link : 'a'
@ -29,13 +27,7 @@ const GnosisLink = ({
}
return (
<LinkElement
className={classes}
href={internal ? null : to}
to={internal ? to : null}
{...refs}
{...props}
>
<LinkElement className={classes} href={internal ? null : to} to={internal ? to : null} {...refs} {...props}>
{children}
</LinkElement>
)

View File

@ -8,13 +8,11 @@ const cx = classNames.bind(styles)
type Props = {
children: React.Node,
align?: 'center',
overflow?: boolean
overflow?: boolean,
}
const Page = ({ children, align, overflow }: Props) => (
<main className={cx(styles.page, align, { overflow })}>
{children}
</main>
<main className={cx(styles.page, align, { overflow })}>{children}</main>
)
export default Page

View File

@ -78,7 +78,7 @@ const PageFrame = ({ children, classes, currentNetwork }: Props) => {
export default withStyles(notificationStyles)(
connect(
(state) => ({
state => ({
currentNetwork: networkSelector(state),
}),
null,

View File

@ -20,9 +20,7 @@ type Props = {
class Paragraph extends React.PureComponent<Props> {
render() {
const {
weight, children, color, align, size, transform, noMargin, className, dot, ...props
} = this.props
const { weight, children, color, align, size, transform, noMargin, className, dot, ...props } = this.props
return (
<p

View File

@ -15,9 +15,7 @@ type Props = {
testId?: string,
}
const Row = ({
children, className, margin, align, grow, testId = '', ...props
}: Props) => {
const Row = ({ children, className, margin, align, grow, testId = '', ...props }: Props) => {
const rowClassNames = cx(
styles.row,
margin ? capitalize(margin, 'margin') : undefined,

View File

@ -6,13 +6,11 @@ import TableCell from '@material-ui/core/TableCell'
import TableHead from '@material-ui/core/TableHead'
import TableRow from '@material-ui/core/TableRow'
export {
TableBody, TableCell, TableHead, TableRow,
}
export { TableBody, TableCell, TableHead, TableRow }
type Props = {
children: React.Node,
size?: number
size?: number,
}
const buildWidthFrom = (size: number) => ({
@ -29,9 +27,7 @@ const GnoTable = ({ size, children }: Props) => {
return (
<div style={overflowStyle}>
<Table style={style}>
{children}
</Table>
<Table style={style}>{children}</Table>
</div>
)
}

View File

@ -75,4 +75,4 @@ export const getIntercomId = () =>
export const getExchangeRatesUrl = () => 'https://api.exchangeratesapi.io/latest'
export const getSafeLastVersion = () => process.env.REACT_APP_LATEST_SAFE_VERSION
export const getSafeLastVersion = () => process.env.REACT_APP_LATEST_SAFE_VERSION || '1.1.1'

View File

@ -2,13 +2,13 @@
import type { RecordOf } from 'immutable'
export type AddressBookEntry = {
address: string;
name: string;
isOwner: boolean;
address: string,
name: string,
isOwner: boolean,
}
export type AddressBookProps = {
addressBook: Map<string, AddressBookEntry>
addressBook: Map<string, AddressBookEntry>,
}
export type AddressBook = RecordOf<AddressBookProps>

View File

@ -4,7 +4,9 @@ import type { AddressBookEntryType } from '~/logic/addressBook/model/addressBook
export const ADD_ENTRY = 'ADD_ENTRY'
export const addAddressBookEntry = createAction<string, *, *>(ADD_ENTRY, (entry: AddressBookEntryType): AddressBookEntryType => ({
entry,
}))
export const addAddressBookEntry = createAction<string, *, *>(
ADD_ENTRY,
(entry: AddressBookEntryType): AddressBookEntryType => ({
entry,
}),
)

View File

@ -4,4 +4,6 @@ import type { AddressBook } from '~/logic/addressBook/model/addressBook'
export const LOAD_ADDRESS_BOOK = 'LOAD_ADDRESS_BOOK'
export const loadAddressBook = createAction<string, *, *>(LOAD_ADDRESS_BOOK, (addressBook: AddressBook) => ({ addressBook }))
export const loadAddressBook = createAction<string, *, *>(LOAD_ADDRESS_BOOK, (addressBook: AddressBook) => ({
addressBook,
}))

View File

@ -18,7 +18,7 @@ const loadAddressBookFromStorage = () => async (dispatch: ReduxDispatch<GlobalSt
// Fetch all the current safes, in case that we don't have a safe on the adbk, we add it
const safes = safesListSelector(state)
const adbkEntries = addressBook.keySeq().toArray()
safes.forEach((safe) => {
safes.forEach(safe => {
const { address } = safe
const found = adbkEntries.includes(address)
if (!found) {

View File

@ -3,7 +3,6 @@ import { createAction } from 'redux-actions'
export const REMOVE_ENTRY = 'REMOVE_ENTRY'
export const removeAddressBookEntry = createAction<string, *, *>(REMOVE_ENTRY, (entryAddress: string): void => ({
entryAddress,
}))

View File

@ -4,7 +4,9 @@ import type { AddressBookEntry } from '~/logic/addressBook/model/addressBook'
export const UPDATE_ENTRY = 'UPDATE_ENTRY'
export const updateAddressBookEntry = createAction<string, *, *>(UPDATE_ENTRY, (entry: AddressBookEntry): AddressBookEntry => ({
entry,
}))
export const updateAddressBookEntry = createAction<string, *, *>(
UPDATE_ENTRY,
(entry: AddressBookEntry): AddressBookEntry => ({
entry,
}),
)

View File

@ -11,12 +11,7 @@ import enqueueSnackbar from '~/logic/notifications/store/actions/enqueueSnackbar
import { saveAddressBook } from '~/logic/addressBook/utils'
import type { AddressBookProps } from '~/logic/addressBook/model/addressBook'
const watchedActions = [
ADD_ENTRY,
REMOVE_ENTRY,
UPDATE_ENTRY,
]
const watchedActions = [ADD_ENTRY, REMOVE_ENTRY, UPDATE_ENTRY]
const addressBookMiddleware = (store: Store<GlobalState>) => (next: Function) => async (action: AnyAction) => {
const handledAction = next(action)

View File

@ -24,7 +24,6 @@ export const buildAddressBook = (storedAdbk: AddressBook): AddressBookProps => {
return addressBookBuilt
}
export default handleActions<State, *>(
{
[LOAD_ADDRESS_BOOK]: (state: State, action: ActionType<Function>): State => {
@ -44,23 +43,22 @@ export default handleActions<State, *>(
const { entry } = action.payload
// Adds the entry to all the safes (if it does not already exists)
const newState = state.withMutations((map) => {
const newState = state.withMutations(map => {
const adbkMap = map.get('addressBook')
if (adbkMap) {
adbkMap.keySeq()
.forEach((safeAddress) => {
const safeAddressBook = state.getIn(['addressBook', safeAddress])
adbkMap.keySeq().forEach(safeAddress => {
const safeAddressBook = state.getIn(['addressBook', safeAddress])
if (safeAddressBook) {
const adbkAddressList = getAddressesListFromAdbk(safeAddressBook)
const found = adbkAddressList.includes(entry.address)
if (!found) {
const updatedSafeAdbkList = safeAddressBook.push(entry)
map.setIn(['addressBook', safeAddress], updatedSafeAdbkList)
}
if (safeAddressBook) {
const adbkAddressList = getAddressesListFromAdbk(safeAddressBook)
const found = adbkAddressList.includes(entry.address)
if (!found) {
const updatedSafeAdbkList = safeAddressBook.push(entry)
map.setIn(['addressBook', safeAddress], updatedSafeAdbkList)
}
})
}
})
}
})
return newState
@ -69,13 +67,13 @@ export default handleActions<State, *>(
const { entry } = action.payload
// Updates the entry from all the safes
const newState = state.withMutations((map) => {
const newState = state.withMutations(map => {
map
.get('addressBook')
.keySeq()
.forEach((safeAddress) => {
.forEach(safeAddress => {
const entriesList: List<AddressBookEntry> = state.getIn(['addressBook', safeAddress])
const entryIndex = entriesList.findIndex((entryItem) => sameAddress(entryItem.address, entry.address))
const entryIndex = entriesList.findIndex(entryItem => sameAddress(entryItem.address, entry.address))
const updatedEntriesList = entriesList.set(entryIndex, entry)
map.setIn(['addressBook', safeAddress], updatedEntriesList)
})
@ -86,13 +84,13 @@ export default handleActions<State, *>(
[REMOVE_ENTRY]: (state: State, action: ActionType<Function>): State => {
const { entryAddress } = action.payload
// Removes the entry from all the safes
const newState = state.withMutations((map) => {
const newState = state.withMutations(map => {
map
.get('addressBook')
.keySeq()
.forEach((safeAddress) => {
.forEach(safeAddress => {
const entriesList = state.getIn(['addressBook', safeAddress])
const entryIndex = entriesList.findIndex((entry) => sameAddress(entry.address, entryAddress))
const entryIndex = entriesList.findIndex(entry => sameAddress(entry.address, entryAddress))
const updatedEntriesList = entriesList.remove(entryIndex)
map.setIn(['addressBook', safeAddress], updatedEntriesList)
})

View File

@ -1,3 +1,4 @@
/* eslint-disable import/named */
// @flow
import { List, Map } from 'immutable'
import { createSelector, Selector } from 'reselect'
@ -7,7 +8,8 @@ import type { GlobalState } from '~/store'
import type { AddressBook } from '~/logic/addressBook/model/addressBook'
import { safeParamAddressFromStateSelector } from '~/routes/safe/store/selectors'
export const addressBookMapSelector = (state: GlobalState): Map<string, AddressBook> => state[ADDRESS_BOOK_REDUCER_ID].get('addressBook')
export const addressBookMapSelector = (state: GlobalState): Map<string, AddressBook> =>
state[ADDRESS_BOOK_REDUCER_ID].get('addressBook')
export const getAddressBook: Selector<GlobalState, AddressBook> = createSelector(
addressBookMapSelector,
@ -38,7 +40,7 @@ export const getNameFromAddressBook = (userAddress: string): string | null => {
return null
}
const addressBook = useSelector(getAddressBook)
const result = addressBook.filter((addressBookItem) => addressBookItem.address === userAddress)
const result = addressBook.filter(addressBookItem => addressBookItem.address === userAddress)
if (result.size > 0) {
return result.get(0).name
}

View File

@ -21,10 +21,11 @@ export const saveAddressBook = async (addressBook: AddressBook) => {
}
}
export const getAddressesListFromAdbk = (addressBook: AddressBook) => Array.from(addressBook).map((entry) => entry.address)
export const getAddressesListFromAdbk = (addressBook: AddressBook) =>
Array.from(addressBook).map(entry => entry.address)
const getNameFromAdbk = (addressBook: AddressBook, userAddress: string): string | null => {
const entry = addressBook.find((addressBookItem) => addressBookItem.address === userAddress)
const entry = addressBook.find(addressBookItem => addressBookItem.address === userAddress)
if (entry) {
return entry.name
}
@ -39,12 +40,11 @@ export const getNameFromAddressBook = (userAddress: string): string | null => {
return getNameFromAdbk(addressBook, userAddress)
}
export const getOwnersWithNameFromAddressBook = (addressBook: AddressBook, ownerList: List<Owner>) => {
if (!ownerList) {
return []
}
const ownersListWithAdbkNames = ownerList.map((owner) => {
const ownersListWithAdbkNames = ownerList.map(owner => {
const ownerName = getNameFromAdbk(addressBook, owner.address)
return {
address: owner.address,

View File

@ -0,0 +1,11 @@
// @flow
// Code of the safe v1.0.0
const proxyCodeV10 =
'0x608060405273ffffffffffffffffffffffffffffffffffffffff600054163660008037600080366000845af43d6000803e6000811415603d573d6000fd5b3d6000f3fe'
// Old PayingProxyCode
const oldProxyCode =
'0x60806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680634555d5c91461008b5780635c60da1b146100b6575b73ffffffffffffffffffffffffffffffffffffffff600054163660008037600080366000845af43d6000803e6000811415610086573d6000fd5b3d6000f35b34801561009757600080fd5b506100a061010d565b6040518082815260200191505060405180910390f35b3480156100c257600080fd5b506100cb610116565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b60006002905090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050905600'
export const isProxyCode = codeToCheck =>
codeToCheck === oldProxyCode || codeToCheck === proxyCodeV10

View File

@ -1,15 +1,21 @@
// @flow
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/GnosisSafeProxyFactory.json'
import GnosisSafeSol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafe.json'
import SafeProxy from '@gnosis.pm/safe-contracts/build/contracts/Proxy.json'
import SafeProxy from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafeProxy.json'
import { ensureOnce } from '~/utils/singleton'
import { simpleMemoize } from '~/components/forms/validator'
import { getWeb3 } from '~/logic/wallets/getWeb3'
import { calculateGasOf, calculateGasPrice } from '~/logic/wallets/ethTransactions'
import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses'
import { isProxyCode } from '~/logic/contracts/historicProxyCode'
export const SENTINEL_ADDRESS = '0x0000000000000000000000000000000000000001'
export const MULTI_SEND_ADDRESS = '0xB522a9f781924eD250A11C54105E51840B138AdD'
export const SAFE_MASTER_COPY_ADDRESS = '0x34CfAC646f301356fAa8B21e94227e3583Fe3F5F'
export const DEFAULT_FALLBACK_HANDLER_ADDRESS = '0xd5D82B6aDDc9027B22dCA772Aa68D5d74cdBdF44'
export const SAFE_MASTER_COPY_ADDRESS_V10 = '0xb6029EA3B2c51D09a50B53CA8012FeEB05bDa35A'
let proxyFactoryMaster
let safeMaster
@ -66,7 +72,7 @@ export const getSafeMasterContract = async () => {
export const deploySafeContract = async (safeAccounts: string[], numConfirmations: number, userAccount: string) => {
const gnosisSafeData = await safeMaster.contract.methods
.setup(safeAccounts, numConfirmations, ZERO_ADDRESS, '0x', ZERO_ADDRESS, 0, ZERO_ADDRESS)
.setup(safeAccounts, numConfirmations, ZERO_ADDRESS, '0x', DEFAULT_FALLBACK_HANDLER_ADDRESS, ZERO_ADDRESS, 0, ZERO_ADDRESS)
.encodeABI()
const proxyFactoryData = proxyFactoryMaster.contract.methods
.createProxy(safeMaster.address, gnosisSafeData)
@ -88,7 +94,7 @@ export const estimateGasForDeployingSafe = async (
userAccount: string,
) => {
const gnosisSafeData = await safeMaster.contract.methods
.setup(safeAccounts, numConfirmations, ZERO_ADDRESS, '0x', ZERO_ADDRESS, 0, ZERO_ADDRESS)
.setup(safeAccounts, numConfirmations, ZERO_ADDRESS, '0x', DEFAULT_FALLBACK_HANDLER_ADDRESS, ZERO_ADDRESS, 0, ZERO_ADDRESS)
.encodeABI()
const proxyFactoryData = proxyFactoryMaster.contract.methods
.createProxy(safeMaster.address, gnosisSafeData)
@ -126,7 +132,49 @@ export const validateProxy = async (safeAddress: string): Promise<boolean> => {
return true
}
}
// Old PayingProxyCode
const oldProxyCode = '0x60806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680634555d5c91461008b5780635c60da1b146100b6575b73ffffffffffffffffffffffffffffffffffffffff600054163660008037600080366000845af43d6000803e6000811415610086573d6000fd5b3d6000f35b34801561009757600080fd5b506100a061010d565b6040518082815260200191505060405180910390f35b3480156100c257600080fd5b506100cb610116565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b60006002905090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050905600'
return codeWithoutMetadata === oldProxyCode
return isProxyCode(codeWithoutMetadata)
}
export type MultiSendTransactionInstanceType = {
operation: number,
to: string,
value: number,
data: string,
}
export const getEncodedMultiSendCallData = (txs: Array<MultiSendTransactionInstanceType>, web3: Object) => {
const multiSendAbi = [
{
type: 'function',
name: 'multiSend',
constant: false,
payable: false,
stateMutability: 'nonpayable',
inputs: [{ type: 'bytes', name: 'transactions' }],
outputs: [],
},
]
const multiSend = new web3.eth.Contract(multiSendAbi, MULTI_SEND_ADDRESS)
const encodeMultiSendCallData = multiSend.methods
.multiSend(
`0x${txs
.map((tx) => [
web3.eth.abi.encodeParameter('uint8', 0).slice(-2),
web3.eth.abi.encodeParameter('address', tx.to).slice(-40),
web3.eth.abi.encodeParameter('uint256', tx.value).slice(-64),
web3.eth.abi
.encodeParameter(
'uint256',
web3.utils.hexToBytes(tx.data).length,
)
.slice(-64),
tx.data.replace(/^0x/, ''),
].join(''))
.join('')}`,
)
.encodeABI()
return encodeMultiSendCallData
}

View File

@ -3,4 +3,6 @@ import { createAction } from 'redux-actions'
export const OPEN_COOKIE_BANNER = 'OPEN_COOKIE_BANNER'
export const openCookieBanner = createAction<string, *, *>(OPEN_COOKIE_BANNER, (cookieBannerOpen: boolean) => ({ cookieBannerOpen }))
export const openCookieBanner = createAction<string, *, *>(OPEN_COOKIE_BANNER, (cookieBannerOpen: boolean) => ({
cookieBannerOpen,
}))

View File

@ -3,7 +3,10 @@ import axios from 'axios'
import { getExchangeRatesUrl } from '~/config'
import { AVAILABLE_CURRENCIES } from '~/logic/currencyValues/store/model/currencyValues'
const fetchCurrenciesRates = async (baseCurrency: AVAILABLE_CURRENCIES, targetCurrencyValue: AVAILABLE_CURRENCIES): Promise<number> => {
const fetchCurrenciesRates = async (
baseCurrency: AVAILABLE_CURRENCIES,
targetCurrencyValue: AVAILABLE_CURRENCIES,
): Promise<number> => {
let rate = 0
const url = `${getExchangeRatesUrl()}?base=${baseCurrency}&symbols=${targetCurrencyValue}`

View File

@ -7,14 +7,15 @@ import fetchCurrenciesRates from '~/logic/currencyValues/api/fetchCurrenciesRate
import { currencyValuesListSelector } from '~/logic/currencyValues/store/selectors'
import { setCurrencyBalances } from '~/logic/currencyValues/store/actions/setCurrencyBalances'
// eslint-disable-next-line max-len
const fetchCurrencySelectedValue = (currencyValueSelected: AVAILABLE_CURRENCIES) => async (dispatch: ReduxDispatch<GlobalState>, getState: Function) => {
const fetchCurrencySelectedValue = (currencyValueSelected: AVAILABLE_CURRENCIES) => async (
dispatch: ReduxDispatch<GlobalState>,
getState: Function,
) => {
const state = getState()
const currencyBalancesList = currencyValuesListSelector(state)
const selectedCurrencyRateInBaseCurrency = await fetchCurrenciesRates(AVAILABLE_CURRENCIES.USD, currencyValueSelected)
const newList = []
for (const currencyValue of currencyBalancesList) {
const { balanceInBaseCurrency } = currencyValue

View File

@ -10,21 +10,24 @@ import { loadFromStorage } from '~/utils/storage'
import fetchCurrencySelectedValue from '~/logic/currencyValues/store/actions/fetchCurrencySelectedValue'
import { CURRENCY_SELECTED_KEY } from '~/logic/currencyValues/store/actions/saveCurrencySelected'
export const fetchCurrencyValues = (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalState>) => {
try {
const tokensFetched = await fetchTokenCurrenciesBalances(safeAddress)
// eslint-disable-next-line max-len
const currencyList = List(tokensFetched.data.filter((currencyBalance) => currencyBalance.balanceUsd).map((currencyBalance) => {
const { balanceUsd, tokenAddress } = currencyBalance
return makeBalanceCurrency({
currencyName: balanceUsd ? AVAILABLE_CURRENCIES.USD : null,
tokenAddress,
balanceInBaseCurrency: balanceUsd,
balanceInSelectedCurrency: balanceUsd,
})
}))
const currencyList = List(
tokensFetched.data
.filter(currencyBalance => currencyBalance.balanceUsd)
.map(currencyBalance => {
const { balanceUsd, tokenAddress } = currencyBalance
return makeBalanceCurrency({
currencyName: balanceUsd ? AVAILABLE_CURRENCIES.USD : null,
tokenAddress,
balanceInBaseCurrency: balanceUsd,
balanceInSelectedCurrency: balanceUsd,
})
}),
)
dispatch(setCurrencyBalances(currencyList))
const currencyStored = await loadFromStorage(CURRENCY_SELECTED_KEY)

View File

@ -7,10 +7,11 @@ import type { GlobalState } from '~/store'
import { saveToStorage } from '~/utils/storage'
import { setCurrencySelected } from '~/logic/currencyValues/store/actions/setCurrencySelected'
export const CURRENCY_SELECTED_KEY = 'CURRENCY_SELECTED_KEY'
const saveCurrencySelected = (currencySelected: AVAILABLE_CURRENCIES) => async (dispatch: ReduxDispatch<GlobalState>) => {
const saveCurrencySelected = (currencySelected: AVAILABLE_CURRENCIES) => async (
dispatch: ReduxDispatch<GlobalState>,
) => {
await saveToStorage(CURRENCY_SELECTED_KEY, { currencyValueSelected: currencySelected })
dispatch(setCurrencySelected(currencySelected))
}

View File

@ -5,6 +5,8 @@ import type { CurrencyValues, CurrencyValuesProps } from '~/logic/currencyValues
export const SET_CURRENCY_BALANCES = 'SET_CURRENCY_BALANCES'
// eslint-disable-next-line max-len
export const setCurrencyBalances = createAction<string, *>(SET_CURRENCY_BALANCES, (currencyBalances: Map<string, CurrencyValues>): CurrencyValuesProps => ({ currencyBalances }))
export const setCurrencyBalances = createAction<string, *>(
SET_CURRENCY_BALANCES,
(currencyBalances: Map<string, CurrencyValues>): CurrencyValuesProps => ({ currencyBalances }),
)

View File

@ -1,13 +1,12 @@
// @flow
import { createAction } from 'redux-actions'
import type {
CurrencyValuesProps,
} from '~/logic/currencyValues/store/model/currencyValues'
import type { CurrencyValuesProps } from '~/logic/currencyValues/store/model/currencyValues'
import { AVAILABLE_CURRENCIES } from '~/logic/currencyValues/store/model/currencyValues'
export const SET_CURRENT_CURRENCY = 'SET_CURRENT_CURRENCY'
// eslint-disable-next-line max-len
export const setCurrencySelected = createAction<string, *>(SET_CURRENT_CURRENCY, (currencyValueSelected: AVAILABLE_CURRENCIES): CurrencyValuesProps => ({ currencyValueSelected }))
export const setCurrencySelected = createAction<string, *>(
SET_CURRENT_CURRENCY,
(currencyValueSelected: AVAILABLE_CURRENCIES): CurrencyValuesProps => ({ currencyValueSelected }),
)

View File

@ -38,9 +38,8 @@ export const AVAILABLE_CURRENCIES = {
MYR: 'MYR',
}
export type BalanceCurrencyType = {
currencyName: AVAILABLE_CURRENCIES;
currencyName: AVAILABLE_CURRENCIES,
tokenAddress: string,
balanceInBaseCurrency: string,
balanceInSelectedCurrency: string,
@ -54,8 +53,8 @@ export const makeBalanceCurrency = Record({
})
export type CurrencyValuesProps = {
currencyValueSelected: AVAILABLE_CURRENCIES;
currencyValuesList: BalanceCurrencyType[]
currencyValueSelected: AVAILABLE_CURRENCIES,
currencyValuesList: BalanceCurrencyType[],
}
export type CurrencyValues = RecordOf<CurrencyValuesProps>

View File

@ -4,6 +4,6 @@ import { List } from 'immutable'
import { type GlobalState } from '~/store'
import { CURRENCY_VALUES_KEY } from '~/logic/currencyValues/store/reducer/currencyValues'
export const currencyValuesListSelector = (state: GlobalState) => (state[CURRENCY_VALUES_KEY].get('currencyBalances') ? state[CURRENCY_VALUES_KEY].get('currencyBalances') : List([]))
export const currencyValuesListSelector = (state: GlobalState) =>
state[CURRENCY_VALUES_KEY].get('currencyBalances') ? state[CURRENCY_VALUES_KEY].get('currencyBalances') : List([])
export const currentCurrencySelector = (state: GlobalState) => state[CURRENCY_VALUES_KEY].get('currencyValueSelected')

Some files were not shown because too many files have changed in this diff Show More