Tech debt 1265: Enable strictNullChecks TS compiler option (#1301)
* dep bump, enable strictNullChecks ts compiler option * working on errors wip * remove unused imports * nullchecks errors wip * fixing errors * fixing errors * fixing errors * fix clipboard func type errors * error fixes * error fixes * error fixes * error fixes * error fixes * error fixes * error fixing * error fixing * error fixing * error fixing * error fixing * error fixing * error fixing * error fixing * error fixing * error fixing * error fixing * error fixing * error fixing * error fixing * error fixing * Finally made it * eslint fixes * eslint fixes 2 * fix send funds validation * Update style load in ThresholdSettings * Fix isValidAddress default state for SendCollectible * fix OwnersColumn return type * fix extractUsefulMethods typing Co-authored-by: Daniel Sanchez <daniel.sanchez@gnosis.pm>
This commit is contained in:
parent
c5bafa6569
commit
bfed9679f7
18
package.json
18
package.json
|
@ -178,7 +178,7 @@
|
||||||
"bignumber.js": "9.0.0",
|
"bignumber.js": "9.0.0",
|
||||||
"bnc-onboard": "1.12.0",
|
"bnc-onboard": "1.12.0",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"concurrently": "^5.2.0",
|
"concurrently": "^5.3.0",
|
||||||
"connected-react-router": "6.8.0",
|
"connected-react-router": "6.8.0",
|
||||||
"coveralls": "^3.1.0",
|
"coveralls": "^3.1.0",
|
||||||
"currency-flags": "2.1.2",
|
"currency-flags": "2.1.2",
|
||||||
|
@ -190,19 +190,19 @@
|
||||||
"eth-sig-util": "^2.5.3",
|
"eth-sig-util": "^2.5.3",
|
||||||
"ethereum-blockies-base64": "^1.0.2",
|
"ethereum-blockies-base64": "^1.0.2",
|
||||||
"ethereumjs-abi": "0.6.8",
|
"ethereumjs-abi": "0.6.8",
|
||||||
"exponential-backoff": "^3.0.1",
|
"exponential-backoff": "^3.1.0",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"final-form": "^4.20.1",
|
"final-form": "^4.20.1",
|
||||||
"final-form-calculate": "^1.3.1",
|
"final-form-calculate": "^1.3.1",
|
||||||
"history": "4.10.1",
|
"history": "4.10.1",
|
||||||
"immortal-db": "^1.0.3",
|
"immortal-db": "^1.1.0",
|
||||||
"immutable": "^4.0.0-rc.12",
|
"immutable": "^4.0.0-rc.12",
|
||||||
"js-cookie": "^2.2.1",
|
"js-cookie": "^2.2.1",
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"lodash.memoize": "^4.1.2",
|
"lodash.memoize": "^4.1.2",
|
||||||
"material-ui-search-bar": "^1.0.0-beta.13",
|
"material-ui-search-bar": "^1.0.0",
|
||||||
"notistack": "https://github.com/gnosis/notistack.git#v0.9.4",
|
"notistack": "https://github.com/gnosis/notistack.git#v0.9.4",
|
||||||
"open": "^7.1.0",
|
"open": "^7.2.0",
|
||||||
"polished": "3.6.6",
|
"polished": "3.6.6",
|
||||||
"qrcode.react": "1.0.0",
|
"qrcode.react": "1.0.0",
|
||||||
"query-string": "6.13.1",
|
"query-string": "6.13.1",
|
||||||
|
@ -215,7 +215,7 @@
|
||||||
"react-qr-reader": "^2.2.1",
|
"react-qr-reader": "^2.2.1",
|
||||||
"react-redux": "7.2.1",
|
"react-redux": "7.2.1",
|
||||||
"react-router-dom": "5.2.0",
|
"react-router-dom": "5.2.0",
|
||||||
"react-scripts": "^3.4.1",
|
"react-scripts": "^3.4.3",
|
||||||
"react-window": "^1.8.5",
|
"react-window": "^1.8.5",
|
||||||
"recompose": "^0.30.0",
|
"recompose": "^0.30.0",
|
||||||
"redux": "4.0.5",
|
"redux": "4.0.5",
|
||||||
|
@ -263,7 +263,7 @@
|
||||||
"eslint-plugin-import": "2.22.0",
|
"eslint-plugin-import": "2.22.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.3.1",
|
"eslint-plugin-jsx-a11y": "^6.3.1",
|
||||||
"eslint-plugin-prettier": "^3.1.4",
|
"eslint-plugin-prettier": "^3.1.4",
|
||||||
"eslint-plugin-react": "^7.20.5",
|
"eslint-plugin-react": "^7.20.6",
|
||||||
"eslint-plugin-sort-destructure-keys": "1.3.5",
|
"eslint-plugin-sort-destructure-keys": "1.3.5",
|
||||||
"ethereumjs-abi": "0.6.8",
|
"ethereumjs-abi": "0.6.8",
|
||||||
"husky": "^4.2.5",
|
"husky": "^4.2.5",
|
||||||
|
@ -272,9 +272,9 @@
|
||||||
"prettier": "2.1.1",
|
"prettier": "2.1.1",
|
||||||
"react-app-rewired": "^2.1.6",
|
"react-app-rewired": "^2.1.6",
|
||||||
"react-docgen-typescript-loader": "^3.7.2",
|
"react-docgen-typescript-loader": "^3.7.2",
|
||||||
"truffle": "5.1.36",
|
"truffle": "5.1.41",
|
||||||
"typechain": "^2.0.0",
|
"typechain": "^2.0.0",
|
||||||
"typescript": "3.9.7",
|
"typescript": "3.9.7",
|
||||||
"wait-on": "5.1.0"
|
"wait-on": "5.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import IconButton from '@material-ui/core/IconButton'
|
import IconButton from '@material-ui/core/IconButton'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { createStyles, makeStyles } from '@material-ui/core/styles'
|
||||||
import Close from '@material-ui/icons/Close'
|
import Close from '@material-ui/icons/Close'
|
||||||
import QRCode from 'qrcode.react'
|
import QRCode from 'qrcode.react'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
|
|
||||||
import CopyBtn from 'src/components/CopyBtn'
|
import CopyBtn from 'src/components/CopyBtn'
|
||||||
import EtherscanBtn from 'src/components/EtherscanBtn'
|
import EtherscanBtn from 'src/components/EtherscanBtn'
|
||||||
|
@ -14,72 +13,79 @@ import Col from 'src/components/layout/Col'
|
||||||
import Hairline from 'src/components/layout/Hairline'
|
import Hairline from 'src/components/layout/Hairline'
|
||||||
import Paragraph from 'src/components/layout/Paragraph'
|
import Paragraph from 'src/components/layout/Paragraph'
|
||||||
import Row from 'src/components/layout/Row'
|
import Row from 'src/components/layout/Row'
|
||||||
import { safeNameSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors'
|
|
||||||
import { lg, md, screenSm, secondaryText, sm } from 'src/theme/variables'
|
import { lg, md, screenSm, secondaryText, sm } from 'src/theme/variables'
|
||||||
import { copyToClipboard } from 'src/utils/clipboard'
|
import { copyToClipboard } from 'src/utils/clipboard'
|
||||||
|
|
||||||
const styles = () => ({
|
const useStyles = makeStyles(
|
||||||
heading: {
|
createStyles({
|
||||||
padding: `${md} ${lg}`,
|
heading: {
|
||||||
justifyContent: 'space-between',
|
padding: `${md} ${lg}`,
|
||||||
maxHeight: '75px',
|
justifyContent: 'space-between',
|
||||||
boxSizing: 'border-box',
|
maxHeight: '75px',
|
||||||
},
|
boxSizing: 'border-box',
|
||||||
close: {
|
|
||||||
height: lg,
|
|
||||||
width: lg,
|
|
||||||
fill: secondaryText,
|
|
||||||
},
|
|
||||||
qrContainer: {
|
|
||||||
backgroundColor: '#fff',
|
|
||||||
padding: md,
|
|
||||||
borderRadius: '6px',
|
|
||||||
border: `1px solid ${secondaryText}`,
|
|
||||||
},
|
|
||||||
annotation: {
|
|
||||||
margin: lg,
|
|
||||||
marginBottom: 0,
|
|
||||||
},
|
|
||||||
safeName: {
|
|
||||||
margin: `${md} 0`,
|
|
||||||
},
|
|
||||||
buttonRow: {
|
|
||||||
height: '84px',
|
|
||||||
justifyContent: 'center',
|
|
||||||
'& > button': {
|
|
||||||
fontFamily: 'Averta',
|
|
||||||
fontSize: md,
|
|
||||||
boxShadow: '1px 2px 10px 0 rgba(212, 212, 211, 0.59)',
|
|
||||||
},
|
},
|
||||||
},
|
close: {
|
||||||
addressContainer: {
|
height: lg,
|
||||||
flexDirection: 'column',
|
width: lg,
|
||||||
justifyContent: 'center',
|
fill: secondaryText,
|
||||||
margin: `${lg} 0`,
|
|
||||||
|
|
||||||
[`@media (min-width: ${screenSm}px)`]: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
},
|
},
|
||||||
},
|
qrContainer: {
|
||||||
address: {
|
backgroundColor: '#fff',
|
||||||
marginLeft: sm,
|
padding: md,
|
||||||
marginRight: sm,
|
borderRadius: '6px',
|
||||||
maxWidth: '70%',
|
border: `1px solid ${secondaryText}`,
|
||||||
overflowWrap: 'break-word',
|
|
||||||
|
|
||||||
[`@media (min-width: ${screenSm}px)`]: {
|
|
||||||
maxWidth: 'none',
|
|
||||||
},
|
},
|
||||||
},
|
annotation: {
|
||||||
})
|
margin: lg,
|
||||||
|
marginBottom: 0,
|
||||||
|
},
|
||||||
|
safeName: {
|
||||||
|
margin: `${md} 0`,
|
||||||
|
},
|
||||||
|
buttonRow: {
|
||||||
|
height: '84px',
|
||||||
|
justifyContent: 'center',
|
||||||
|
'& > button': {
|
||||||
|
fontFamily: 'Averta',
|
||||||
|
fontSize: md,
|
||||||
|
boxShadow: '1px 2px 10px 0 rgba(212, 212, 211, 0.59)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
addressContainer: {
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
margin: `${lg} 0`,
|
||||||
|
|
||||||
|
[`@media (min-width: ${screenSm}px)`]: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
address: {
|
||||||
|
marginLeft: sm,
|
||||||
|
marginRight: sm,
|
||||||
|
maxWidth: '70%',
|
||||||
|
overflowWrap: 'break-word',
|
||||||
|
|
||||||
|
[`@media (min-width: ${screenSm}px)`]: {
|
||||||
|
maxWidth: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onClose: () => void
|
||||||
|
safeAddress: string
|
||||||
|
safeName: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReceiveModal = ({ onClose, safeAddress, safeName }: Props) => {
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
const Receive = ({ classes, onClose }) => {
|
|
||||||
const safeAddress = useSelector(safeParamAddressFromStateSelector)
|
|
||||||
const safeName = useSelector(safeNameSelector)
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Row align="center" className={classes.heading} grow>
|
<Row align="center" className={classes.heading} grow>
|
||||||
<Paragraph className={classes.manage} noMargin size="xl" weight="bolder">
|
<Paragraph noMargin size="xl" weight="bolder">
|
||||||
Receive funds
|
Receive funds
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<IconButton disableRipple onClick={onClose}>
|
<IconButton disableRipple onClick={onClose}>
|
||||||
|
@ -122,4 +128,4 @@ const Receive = ({ classes, onClose }) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles as any)(Receive)
|
export default ReceiveModal
|
|
@ -30,7 +30,7 @@ import { currentCurrencySelector, safeFiatBalancesTotalSelector } from 'src/logi
|
||||||
import { formatAmountInUsFormat } from 'src/logic/tokens/utils/formatAmount'
|
import { formatAmountInUsFormat } from 'src/logic/tokens/utils/formatAmount'
|
||||||
import { grantedSelector } from 'src/routes/safe/container/selector'
|
import { grantedSelector } from 'src/routes/safe/container/selector'
|
||||||
|
|
||||||
import Receive from './ModalReceive'
|
import Receive from './ReceiveModal'
|
||||||
import { useSidebarItems } from 'src/components/AppLayout/Sidebar/useSidebarItems'
|
import { useSidebarItems } from 'src/components/AppLayout/Sidebar/useSidebarItems'
|
||||||
|
|
||||||
const notificationStyles = {
|
const notificationStyles = {
|
||||||
|
@ -79,7 +79,8 @@ const App: React.FC = ({ children }) => {
|
||||||
|
|
||||||
const sendFunds = safeActionsState.sendFunds as { isOpen: boolean; selectedToken: string }
|
const sendFunds = safeActionsState.sendFunds as { isOpen: boolean; selectedToken: string }
|
||||||
const formattedTotalBalance = currentSafeBalance ? formatAmountInUsFormat(currentSafeBalance) : ''
|
const formattedTotalBalance = currentSafeBalance ? formatAmountInUsFormat(currentSafeBalance) : ''
|
||||||
const balance = !!formattedTotalBalance && !!currentCurrency ? `${formattedTotalBalance} ${currentCurrency}` : null
|
const balance =
|
||||||
|
!!formattedTotalBalance && !!currentCurrency ? `${formattedTotalBalance} ${currentCurrency}` : undefined
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (matchSafe?.isExact) {
|
if (matchSafe?.isExact) {
|
||||||
|
@ -133,14 +134,16 @@ const App: React.FC = ({ children }) => {
|
||||||
selectedToken={sendFunds.selectedToken}
|
selectedToken={sendFunds.selectedToken}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Modal
|
{safeAddress && safeName && (
|
||||||
description="Receive Tokens Form"
|
<Modal
|
||||||
handleClose={onReceiveHide}
|
description="Receive Tokens Form"
|
||||||
open={safeActionsState.showReceive as boolean}
|
handleClose={onReceiveHide}
|
||||||
title="Receive Tokens"
|
open={safeActionsState.showReceive as boolean}
|
||||||
>
|
title="Receive Tokens"
|
||||||
<Receive onClose={onReceiveHide} />
|
>
|
||||||
</Modal>
|
<Receive onClose={onReceiveHide} safeAddress={safeAddress} safeName={safeName} />
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
</SnackbarProvider>
|
</SnackbarProvider>
|
||||||
<CookiesBanner />
|
<CookiesBanner />
|
||||||
|
|
|
@ -50,7 +50,7 @@ export const Base = (): React.ReactElement => {
|
||||||
safeAddress="0xEE63624cC4Dd2355B16b35eFaadF3F7450A9438B"
|
safeAddress="0xEE63624cC4Dd2355B16b35eFaadF3F7450A9438B"
|
||||||
safeName="someName"
|
safeName="someName"
|
||||||
granted={true}
|
granted={true}
|
||||||
balance={null}
|
balance={undefined}
|
||||||
onToggleSafeList={() => console.log}
|
onToggleSafeList={() => console.log}
|
||||||
onReceiveClick={() => console.log}
|
onReceiveClick={() => console.log}
|
||||||
onNewTransactionClick={() => console.log}
|
onNewTransactionClick={() => console.log}
|
||||||
|
|
|
@ -50,7 +50,7 @@ const HeaderComponent = (): React.ReactElement => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const getProviderInfoBased = () => {
|
const getProviderInfoBased = () => {
|
||||||
if (!loaded) {
|
if (!loaded || !provider) {
|
||||||
return <ProviderDisconnected />
|
return <ProviderDisconnected />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,10 +79,10 @@ const UnStyledButton = styled.button`
|
||||||
`
|
`
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
address: string | null
|
address: string | undefined
|
||||||
safeName: string
|
safeName: string | undefined
|
||||||
granted: boolean
|
granted: boolean
|
||||||
balance: string | null
|
balance: string | undefined
|
||||||
onToggleSafeList: () => void
|
onToggleSafeList: () => void
|
||||||
onReceiveClick: () => void
|
onReceiveClick: () => void
|
||||||
onNewTransactionClick: () => void
|
onNewTransactionClick: () => void
|
||||||
|
|
|
@ -38,9 +38,9 @@ const HelpCenterLink = styled.a`
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
type Props = {
|
type Props = {
|
||||||
safeAddress: string | null
|
safeAddress?: string
|
||||||
safeName: string | null
|
safeName?: string
|
||||||
balance: string | null
|
balance?: string
|
||||||
granted: boolean
|
granted: boolean
|
||||||
onToggleSafeList: () => void
|
onToggleSafeList: () => void
|
||||||
onReceiveClick: () => void
|
onReceiveClick: () => void
|
||||||
|
@ -57,34 +57,32 @@ const Sidebar = ({
|
||||||
onToggleSafeList,
|
onToggleSafeList,
|
||||||
onReceiveClick,
|
onReceiveClick,
|
||||||
onNewTransactionClick,
|
onNewTransactionClick,
|
||||||
}: Props): React.ReactElement => {
|
}: Props): React.ReactElement => (
|
||||||
return (
|
<>
|
||||||
<>
|
<SafeHeader
|
||||||
<SafeHeader
|
address={safeAddress}
|
||||||
address={safeAddress}
|
safeName={safeName}
|
||||||
safeName={safeName}
|
granted={granted}
|
||||||
granted={granted}
|
balance={balance}
|
||||||
balance={balance}
|
onToggleSafeList={onToggleSafeList}
|
||||||
onToggleSafeList={onToggleSafeList}
|
onReceiveClick={onReceiveClick}
|
||||||
onReceiveClick={onReceiveClick}
|
onNewTransactionClick={onNewTransactionClick}
|
||||||
onNewTransactionClick={onNewTransactionClick}
|
/>
|
||||||
/>
|
|
||||||
|
|
||||||
{items.length ? (
|
{items.length ? (
|
||||||
<>
|
<>
|
||||||
<StyledDivider />
|
|
||||||
<List items={items} />
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<HelpContainer>
|
|
||||||
<StyledDivider />
|
<StyledDivider />
|
||||||
<HelpCenterLink href="https://help.gnosis-safe.io/en/" target="_blank" title="Help Center of Gnosis Safe">
|
<List items={items} />
|
||||||
<IconText text="HELP CENTER" iconSize="md" textSize="md" color="placeHolder" iconType="question" />
|
</>
|
||||||
</HelpCenterLink>
|
) : null}
|
||||||
</HelpContainer>
|
|
||||||
</>
|
<HelpContainer>
|
||||||
)
|
<StyledDivider />
|
||||||
}
|
<HelpCenterLink href="https://help.gnosis-safe.io/en/" target="_blank" title="Help Center of Gnosis Safe">
|
||||||
|
<IconText text="HELP CENTER" iconSize="md" textSize="md" color="placeHolder" iconType="question" />
|
||||||
|
</HelpCenterLink>
|
||||||
|
</HelpContainer>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
export default Sidebar
|
export default Sidebar
|
||||||
|
|
|
@ -60,9 +60,9 @@ export const FooterWrapper = styled.footer`
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
sidebarItems: ListItemType[]
|
sidebarItems: ListItemType[]
|
||||||
safeAddress: string | null
|
safeAddress: string | undefined
|
||||||
safeName: string | null
|
safeName: string | undefined
|
||||||
balance: string | null
|
balance: string | undefined
|
||||||
granted: boolean
|
granted: boolean
|
||||||
onToggleSafeList: () => void
|
onToggleSafeList: () => void
|
||||||
onReceiveClick: () => void
|
onReceiveClick: () => void
|
||||||
|
|
|
@ -82,7 +82,7 @@ const useStyles = makeStyles({
|
||||||
})
|
})
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
currentSafe: string | null
|
currentSafe: string | undefined
|
||||||
defaultSafe: DefaultSafe
|
defaultSafe: DefaultSafe
|
||||||
safes: SafeRecord[]
|
safes: SafeRecord[]
|
||||||
onSafeClick: () => void
|
onSafeClick: () => void
|
||||||
|
|
|
@ -8,7 +8,7 @@ interface CellWidth {
|
||||||
maxWidth: string
|
maxWidth: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const cellWidth = (width: string | number): CellWidth | undefined => {
|
export const cellWidth = (width?: string | number): CellWidth | undefined => {
|
||||||
if (!width) {
|
if (!width) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { createSelector } from 'reselect'
|
||||||
|
|
||||||
import { ADDRESS_BOOK_REDUCER_ID } from 'src/logic/addressBook/store/reducer/addressBook'
|
import { ADDRESS_BOOK_REDUCER_ID } from 'src/logic/addressBook/store/reducer/addressBook'
|
||||||
import { AddressBookMap } from 'src/logic/addressBook/store/reducer/types/addressBook.d'
|
import { AddressBookMap } from 'src/logic/addressBook/store/reducer/types/addressBook.d'
|
||||||
|
import { AddressBookEntryRecord } from 'src/logic/addressBook/model/addressBook'
|
||||||
import { safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors'
|
import { safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors'
|
||||||
|
|
||||||
export const addressBookMapSelector = (state: AppReduxState): AddressBookMap =>
|
export const addressBookMapSelector = (state: AppReduxState): AddressBookMap =>
|
||||||
|
@ -13,8 +14,8 @@ export const getAddressBook = createSelector(
|
||||||
addressBookMapSelector,
|
addressBookMapSelector,
|
||||||
safeParamAddressFromStateSelector,
|
safeParamAddressFromStateSelector,
|
||||||
(addressBook, safeAddress) => {
|
(addressBook, safeAddress) => {
|
||||||
let result = List([])
|
let result: List<AddressBookEntryRecord> = List([])
|
||||||
if (addressBook) {
|
if (addressBook && safeAddress) {
|
||||||
result = addressBook.get(safeAddress, List())
|
result = addressBook.get(safeAddress, List())
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -19,7 +19,7 @@ export const saveAddressBook = async (addressBook) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getAddressesListFromAdbk = (addressBook) => Array.from(addressBook).map((entry: any) => entry.address)
|
export const getAddressesListFromAdbk = (addressBook: List<any>) => addressBook.map((entry: any) => entry.address)
|
||||||
|
|
||||||
export const getNameFromAdbk = (addressBook, userAddress) => {
|
export const getNameFromAdbk = (addressBook, userAddress) => {
|
||||||
const entry = addressBook.find((addressBookItem) => addressBookItem.address === userAddress)
|
const entry = addressBook.find((addressBookItem) => addressBookItem.address === userAddress)
|
||||||
|
|
|
@ -2,14 +2,19 @@ import { AbiItem } from 'web3-utils'
|
||||||
|
|
||||||
import { web3ReadOnly as web3 } from 'src/logic/wallets/getWeb3'
|
import { web3ReadOnly as web3 } from 'src/logic/wallets/getWeb3'
|
||||||
|
|
||||||
export interface AbiItemExtended extends AbiItem {
|
export interface AllowedAbiItem extends AbiItem {
|
||||||
|
name: string
|
||||||
|
type: 'function'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AbiItemExtended extends AllowedAbiItem {
|
||||||
action: string
|
action: string
|
||||||
methodSignature: string
|
methodSignature: string
|
||||||
signatureHash: string
|
signatureHash: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getMethodSignature = ({ inputs, name }: AbiItem): string => {
|
export const getMethodSignature = ({ inputs, name }: AbiItem): string => {
|
||||||
const params = inputs.map((x) => x.type).join(',')
|
const params = inputs?.map((x) => x.type).join(',')
|
||||||
return `${name}(${params})`
|
return `${name}(${params})`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,12 +40,17 @@ export const isAllowedMethod = ({ name, type }: AbiItem): boolean => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getMethodAction = ({ stateMutability }: AbiItem): 'read' | 'write' => {
|
export const getMethodAction = ({ stateMutability }: AbiItem): 'read' | 'write' => {
|
||||||
|
if (!stateMutability) {
|
||||||
|
return 'write'
|
||||||
|
}
|
||||||
|
|
||||||
return ['view', 'pure'].includes(stateMutability) ? 'read' : 'write'
|
return ['view', 'pure'].includes(stateMutability) ? 'read' : 'write'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const extractUsefulMethods = (abi: AbiItem[]): AbiItemExtended[] => {
|
export const extractUsefulMethods = (abi: AbiItem[]): AbiItemExtended[] => {
|
||||||
return abi
|
const allowedAbiItems = abi.filter(isAllowedMethod) as AllowedAbiItem[]
|
||||||
.filter(isAllowedMethod)
|
|
||||||
|
return allowedAbiItems
|
||||||
.map(
|
.map(
|
||||||
(method): AbiItemExtended => ({
|
(method): AbiItemExtended => ({
|
||||||
action: getMethodAction(method),
|
action: getMethodAction(method),
|
||||||
|
@ -48,9 +58,11 @@ export const extractUsefulMethods = (abi: AbiItem[]): AbiItemExtended[] => {
|
||||||
...method,
|
...method,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.sort(({ name: a }, { name: b }) => (a.toLowerCase() > b.toLowerCase() ? 1 : -1))
|
.sort(({ name: a }, { name: b }) => {
|
||||||
|
return a.toLowerCase() > b.toLowerCase() ? 1 : -1
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isPayable = (method: AbiItem | AbiItemExtended): boolean => {
|
export const isPayable = (method: AbiItem | AbiItemExtended): boolean => {
|
||||||
return method.payable
|
return !!method?.payable
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,8 @@ import { web3ReadOnly as web3 } from 'src/logic/wallets/getWeb3'
|
||||||
* @param {array<{ args: [any], method: string, type: 'eth'|undefined } | string>} args.methods - methods to be called
|
* @param {array<{ args: [any], method: string, type: 'eth'|undefined } | string>} args.methods - methods to be called
|
||||||
* @returns {Promise<[*]>}
|
* @returns {Promise<[*]>}
|
||||||
*/
|
*/
|
||||||
const generateBatchRequests = ({ abi, address, batch, context, methods }: any): any => {
|
const generateBatchRequests = ({ abi, address, batch = new web3.BatchRequest() , context, methods }: any): any => {
|
||||||
const contractInstance: any = new web3.eth.Contract(abi, address)
|
const contractInstance: any = new web3.eth.Contract(abi, address)
|
||||||
const localBatch = batch ? null : new web3.BatchRequest()
|
|
||||||
|
|
||||||
const values = methods.map((methodObject) => {
|
const values = methods.map((methodObject) => {
|
||||||
let method, type, args = []
|
let method, type, args = []
|
||||||
|
@ -39,14 +38,15 @@ const generateBatchRequests = ({ abi, address, batch, context, methods }: any):
|
||||||
} else {
|
} else {
|
||||||
request = contractInstance.methods[method](...args).call.request(resolver)
|
request = contractInstance.methods[method](...args).call.request(resolver)
|
||||||
}
|
}
|
||||||
batch ? batch.add(request) : localBatch.add(request)
|
|
||||||
|
batch.add(request)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
resolve(null)
|
resolve(null)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
localBatch && localBatch.execute()
|
batch.execute()
|
||||||
|
|
||||||
const returnValues = context ? [context, ...values] : values
|
const returnValues = context ? [context, ...values] : values
|
||||||
|
|
||||||
|
|
|
@ -6,15 +6,12 @@ import { TokenProps } from 'src/logic/tokens/store/model/token'
|
||||||
export type BalanceEndpoint = {
|
export type BalanceEndpoint = {
|
||||||
balance: string
|
balance: string
|
||||||
balanceUsd: string
|
balanceUsd: string
|
||||||
tokenAddress?: string
|
tokenAddress: string
|
||||||
token?: TokenProps
|
token?: TokenProps
|
||||||
usdConversion: string
|
usdConversion: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchTokenCurrenciesBalances = (safeAddress?: string): Promise<AxiosResponse<BalanceEndpoint[]>> => {
|
const fetchTokenCurrenciesBalances = (safeAddress: string): Promise<AxiosResponse<BalanceEndpoint[]>> => {
|
||||||
if (!safeAddress) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
const apiUrl = getTxServiceHost()
|
const apiUrl = getTxServiceHost()
|
||||||
const url = `${apiUrl}safes/${safeAddress}/balances/usd/`
|
const url = `${apiUrl}safes/${safeAddress}/balances/usd/`
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ export const fetchCurrencyValues = (safeAddress: string) => async (
|
||||||
dispatch: Dispatch<typeof setCurrencyBalances | typeof setSelectedCurrency | typeof setCurrencyRate>,
|
dispatch: Dispatch<typeof setCurrencyBalances | typeof setSelectedCurrency | typeof setCurrencyRate>,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const storedCurrencies: Map<string, CurrencyRateValue> | unknown = await loadCurrencyValues()
|
const storedCurrencies = await loadCurrencyValues()
|
||||||
const storedCurrency = storedCurrencies[safeAddress]
|
const storedCurrency = storedCurrencies[safeAddress]
|
||||||
if (!storedCurrency) {
|
if (!storedCurrency) {
|
||||||
return batch(() => {
|
return batch(() => {
|
||||||
|
|
|
@ -1,18 +1,11 @@
|
||||||
import fetchCurrencyRate from 'src/logic/currencyValues/store/actions/fetchCurrencyRate'
|
import fetchCurrencyRate from 'src/logic/currencyValues/store/actions/fetchCurrencyRate'
|
||||||
import { SET_CURRENCY_BALANCES } from 'src/logic/currencyValues/store/actions/setCurrencyBalances'
|
|
||||||
import { SET_CURRENCY_RATE } from 'src/logic/currencyValues/store/actions/setCurrencyRate'
|
|
||||||
import { SET_CURRENT_CURRENCY } from 'src/logic/currencyValues/store/actions/setSelectedCurrency'
|
import { SET_CURRENT_CURRENCY } from 'src/logic/currencyValues/store/actions/setSelectedCurrency'
|
||||||
import { currencyValuesSelector } from 'src/logic/currencyValues/store/selectors'
|
|
||||||
import { saveCurrencyValues } from 'src/logic/currencyValues/store/utils/currencyValuesStorage'
|
|
||||||
import { AVAILABLE_CURRENCIES, CurrencyRateValue } from '../model/currencyValues'
|
|
||||||
import { Map } from 'immutable'
|
|
||||||
|
|
||||||
const watchedActions = [SET_CURRENT_CURRENCY, SET_CURRENCY_RATE, SET_CURRENCY_BALANCES]
|
const watchedActions = [SET_CURRENT_CURRENCY]
|
||||||
|
|
||||||
const currencyValuesStorageMiddleware = (store) => (next) => async (action) => {
|
const currencyValuesStorageMiddleware = (store) => (next) => async (action) => {
|
||||||
const handledAction = next(action)
|
const handledAction = next(action)
|
||||||
if (watchedActions.includes(action.type)) {
|
if (watchedActions.includes(action.type)) {
|
||||||
const state = store.getState()
|
|
||||||
const { dispatch } = store
|
const { dispatch } = store
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case SET_CURRENT_CURRENCY: {
|
case SET_CURRENT_CURRENCY: {
|
||||||
|
@ -20,22 +13,6 @@ const currencyValuesStorageMiddleware = (store) => (next) => async (action) => {
|
||||||
dispatch(fetchCurrencyRate(safeAddress, selectedCurrency))
|
dispatch(fetchCurrencyRate(safeAddress, selectedCurrency))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case SET_CURRENCY_RATE:
|
|
||||||
case SET_CURRENCY_BALANCES: {
|
|
||||||
const currencyValues = currencyValuesSelector(state)
|
|
||||||
|
|
||||||
const currencyValuesWithoutBalances: Map<string, CurrencyRateValue> = currencyValues.map((currencyValue) => {
|
|
||||||
const currencyRate: number = currencyValue.get('currencyRate')
|
|
||||||
const selectedCurrency: AVAILABLE_CURRENCIES = currencyValue.get('selectedCurrency')
|
|
||||||
return {
|
|
||||||
currencyRate,
|
|
||||||
selectedCurrency,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
await saveCurrencyValues(currencyValuesWithoutBalances)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
|
|
|
@ -16,7 +16,7 @@ export const safeFiatBalancesSelector = createSelector(
|
||||||
currencyValuesSelector,
|
currencyValuesSelector,
|
||||||
safeParamAddressFromStateSelector,
|
safeParamAddressFromStateSelector,
|
||||||
(currencyValues, safeAddress): CurrencyReducerMap | undefined => {
|
(currencyValues, safeAddress): CurrencyReducerMap | undefined => {
|
||||||
if (!currencyValues) return
|
if (!currencyValues || !safeAddress) return
|
||||||
return currencyValues.get(safeAddress)
|
return currencyValues.get(safeAddress)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -11,6 +11,6 @@ export const saveCurrencyValues = async (currencyValues: Map<string, CurrencyRat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const loadCurrencyValues = async (): Promise<Map<string, CurrencyRateValue> | unknown> => {
|
export const loadCurrencyValues = async (): Promise<Record<string, CurrencyRateValue>> => {
|
||||||
return (await loadFromStorage(CURRENCY_VALUES_STORAGE_KEY)) || {}
|
return (await loadFromStorage(CURRENCY_VALUES_STORAGE_KEY)) || {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,9 @@ import { getCurrentSessionFromStorage } from 'src/logic/currentSession/utils'
|
||||||
const loadCurrentSessionFromStorage = () => async (dispatch) => {
|
const loadCurrentSessionFromStorage = () => async (dispatch) => {
|
||||||
const currentSession = await getCurrentSessionFromStorage()
|
const currentSession = await getCurrentSessionFromStorage()
|
||||||
|
|
||||||
dispatch(loadCurrentSession(makeCurrentSession(currentSession ? currentSession : {})))
|
if (currentSession) {
|
||||||
|
dispatch(loadCurrentSession(makeCurrentSession(currentSession)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default loadCurrentSessionFromStorage
|
export default loadCurrentSessionFromStorage
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
import { Record } from 'immutable'
|
import { Record } from 'immutable'
|
||||||
|
|
||||||
export const makeCurrentSession = Record({
|
type SessionProps = {
|
||||||
|
viewedSafes: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const makeCurrentSession = Record<SessionProps>({
|
||||||
viewedSafes: [],
|
viewedSafes: [],
|
||||||
})
|
})
|
||||||
|
|
|
@ -7,6 +7,10 @@ import { saveCurrentSessionToStorage } from 'src/logic/currentSession/utils'
|
||||||
|
|
||||||
export const CURRENT_SESSION_REDUCER_ID = 'currentSession'
|
export const CURRENT_SESSION_REDUCER_ID = 'currentSession'
|
||||||
|
|
||||||
|
export type SerializedSessionState = {
|
||||||
|
viewedSafes: string[]
|
||||||
|
}
|
||||||
|
|
||||||
export default handleActions(
|
export default handleActions(
|
||||||
{
|
{
|
||||||
[LOAD_CURRENT_SESSION]: (state, action) => state.merge(Map(action.payload)),
|
[LOAD_CURRENT_SESSION]: (state, action) => state.merge(Map(action.payload)),
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import { loadFromStorage, saveToStorage } from 'src/utils/storage'
|
import { loadFromStorage, saveToStorage } from 'src/utils/storage'
|
||||||
|
import { SerializedSessionState } from 'src/logic/currentSession/store/reducer/currentSession'
|
||||||
|
|
||||||
const CURRENT_SESSION_STORAGE_KEY = 'CURRENT_SESSION'
|
const CURRENT_SESSION_STORAGE_KEY = 'CURRENT_SESSION'
|
||||||
|
|
||||||
export const getCurrentSessionFromStorage = async () => loadFromStorage(CURRENT_SESSION_STORAGE_KEY)
|
export const getCurrentSessionFromStorage = async (): Promise<SerializedSessionState | undefined> =>
|
||||||
|
loadFromStorage(CURRENT_SESSION_STORAGE_KEY)
|
||||||
|
|
||||||
export const saveCurrentSessionToStorage = async (currentSession) => {
|
export const saveCurrentSessionToStorage = async (currentSession) => {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -16,7 +16,7 @@ interface DebounceOptions {
|
||||||
export const useDebouncedCallback = <T extends (...args: unknown[]) => unknown>(
|
export const useDebouncedCallback = <T extends (...args: unknown[]) => unknown>(
|
||||||
callback: T,
|
callback: T,
|
||||||
delay = 0,
|
delay = 0,
|
||||||
options: DebounceOptions,
|
options?: DebounceOptions,
|
||||||
): T & { cancel: () => void } => useCallback(debounce(callback, delay, options), [callback, delay, options])
|
): T & { cancel: () => void } => useCallback(debounce(callback, delay, options), [callback, delay, options])
|
||||||
|
|
||||||
export const useDebounce = <T extends unknown>(value: T, delay = 0, options?: DebounceOptions): T => {
|
export const useDebounce = <T extends unknown>(value: T, delay = 0, options?: DebounceOptions): T => {
|
||||||
|
|
|
@ -15,7 +15,7 @@ const setNotificationOrigin = (notification: Notification, origin: string): Noti
|
||||||
}
|
}
|
||||||
|
|
||||||
const appInfo = getAppInfoFromOrigin(origin)
|
const appInfo = getAppInfoFromOrigin(origin)
|
||||||
return { ...notification, message: `${appInfo.name}: ${notification.message}` }
|
return { ...notification, message: `${appInfo ? appInfo.name : 'Unknown origin'}: ${notification.message}` }
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStandardTxNotificationsQueue = (
|
const getStandardTxNotificationsQueue = (
|
||||||
|
|
|
@ -10,7 +10,7 @@ import fetchTransactions from 'src/logic/safe/store/actions/transactions/fetchTr
|
||||||
import fetchSafeCreationTx from 'src/logic/safe/store/actions/fetchSafeCreationTx'
|
import fetchSafeCreationTx from 'src/logic/safe/store/actions/fetchSafeCreationTx'
|
||||||
import { Dispatch } from 'src/logic/safe/store/actions/types.d'
|
import { Dispatch } from 'src/logic/safe/store/actions/types.d'
|
||||||
|
|
||||||
export const useLoadSafe = (safeAddress: string): void => {
|
export const useLoadSafe = (safeAddress?: string): void => {
|
||||||
const dispatch = useDispatch<Dispatch>()
|
const dispatch = useDispatch<Dispatch>()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -8,9 +8,9 @@ import { checkAndUpdateSafe } from 'src/logic/safe/store/actions/fetchSafe'
|
||||||
import fetchTransactions from 'src/logic/safe/store/actions/transactions/fetchTransactions'
|
import fetchTransactions from 'src/logic/safe/store/actions/transactions/fetchTransactions'
|
||||||
import { TIMEOUT } from 'src/utils/constants'
|
import { TIMEOUT } from 'src/utils/constants'
|
||||||
|
|
||||||
export const useSafeScheduledUpdates = (safeAddress: string): void => {
|
export const useSafeScheduledUpdates = (safeAddress?: string): void => {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const timer = useRef<number>(null)
|
const timer = useRef<number>()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// using this variable to prevent setting a timeout when the component is already unmounted or the effect
|
// using this variable to prevent setting a timeout when the component is already unmounted or the effect
|
||||||
|
@ -29,7 +29,7 @@ export const useSafeScheduledUpdates = (safeAddress: string): void => {
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
timer.current = setTimeout(() => {
|
timer.current = setTimeout(() => {
|
||||||
fetchSafeData(safeAddress)
|
fetchSafeData(address)
|
||||||
}, TIMEOUT * 3)
|
}, TIMEOUT * 3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,8 +30,8 @@ const getAllTransactionsUri = (safeAddress: string): string => {
|
||||||
|
|
||||||
const fetchAllTransactions = async (
|
const fetchAllTransactions = async (
|
||||||
urlParams: ServiceUriParams,
|
urlParams: ServiceUriParams,
|
||||||
eTag: string | null,
|
eTag?: string,
|
||||||
): Promise<{ responseEtag: string; results: Transaction[]; count?: number }> => {
|
): Promise<{ responseEtag?: string; results: Transaction[]; count?: number }> => {
|
||||||
const { safeAddress, limit, offset, orderBy, queued, trusted } = urlParams
|
const { safeAddress, limit, offset, orderBy, queued, trusted } = urlParams
|
||||||
try {
|
try {
|
||||||
const url = getAllTransactionsUri(safeAddress)
|
const url = getAllTransactionsUri(safeAddress)
|
||||||
|
|
|
@ -100,7 +100,7 @@ interface CreateTransactionArgs {
|
||||||
navigateToTransactionsTab?: boolean
|
navigateToTransactionsTab?: boolean
|
||||||
notifiedTransaction: string
|
notifiedTransaction: string
|
||||||
operation?: number
|
operation?: number
|
||||||
origin?: string
|
origin?: string | null
|
||||||
safeAddress: string
|
safeAddress: string
|
||||||
to: string
|
to: string
|
||||||
txData?: string
|
txData?: string
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { Action, Dispatch } from 'redux'
|
||||||
import { SENTINEL_ADDRESS } from 'src/logic/contracts/safeContracts'
|
import { SENTINEL_ADDRESS } from 'src/logic/contracts/safeContracts'
|
||||||
import { AppReduxState } from 'src/store'
|
import { AppReduxState } from 'src/store'
|
||||||
|
|
||||||
const buildOwnersFrom = (safeOwners: string[], localSafe: SafeRecordProps): List<SafeOwner> => {
|
const buildOwnersFrom = (safeOwners: string[], localSafe?: SafeRecordProps): List<SafeOwner> => {
|
||||||
const ownersList = safeOwners.map((ownerAddress) => {
|
const ownersList = safeOwners.map((ownerAddress) => {
|
||||||
const convertedAdd = checksumAddress(ownerAddress)
|
const convertedAdd = checksumAddress(ownerAddress)
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ export const buildSafe = async (
|
||||||
needsUpdate,
|
needsUpdate,
|
||||||
featuresEnabled,
|
featuresEnabled,
|
||||||
balances: Map(),
|
balances: Map(),
|
||||||
latestIncomingTxBlock: null,
|
latestIncomingTxBlock: 0,
|
||||||
activeAssets: Set(),
|
activeAssets: Set(),
|
||||||
activeTokens: Set(),
|
activeTokens: Set(),
|
||||||
blacklistedAssets: Set(),
|
blacklistedAssets: Set(),
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import { addSafe } from './addSafe'
|
import { Dispatch } from 'redux'
|
||||||
|
|
||||||
import { SAFES_KEY } from 'src/logic/safe/utils'
|
import { SAFES_KEY } from 'src/logic/safe/utils'
|
||||||
|
import { SafeRecordProps } from 'src/logic/safe/store/models/safe'
|
||||||
import { buildSafe } from 'src/logic/safe/store/reducer/safe'
|
import { buildSafe } from 'src/logic/safe/store/reducer/safe'
|
||||||
|
|
||||||
import { loadFromStorage } from 'src/utils/storage'
|
import { loadFromStorage } from 'src/utils/storage'
|
||||||
import { Dispatch } from 'redux'
|
|
||||||
|
import { addSafe } from './addSafe'
|
||||||
|
|
||||||
const loadSafesFromStorage = () => async (dispatch: Dispatch): Promise<void> => {
|
const loadSafesFromStorage = () => async (dispatch: Dispatch): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const safes = await loadFromStorage(SAFES_KEY)
|
const safes = await loadFromStorage<Record<string, SafeRecordProps>>(SAFES_KEY)
|
||||||
|
|
||||||
if (safes) {
|
if (safes) {
|
||||||
Object.values(safes).forEach((safeProps) => {
|
Object.values(safes).forEach((safeProps) => {
|
||||||
|
|
|
@ -34,7 +34,7 @@ const processTransaction = ({ approveAndExecute, notifiedTransaction, safeAddres
|
||||||
const safeInstance = await getGnosisSafeInstanceAt(safeAddress)
|
const safeInstance = await getGnosisSafeInstanceAt(safeAddress)
|
||||||
|
|
||||||
const lastTx = await getLastTx(safeAddress)
|
const lastTx = await getLastTx(safeAddress)
|
||||||
const nonce = await getNewTxNonce(null, lastTx, safeInstance)
|
const nonce = await getNewTxNonce(undefined, lastTx, safeInstance)
|
||||||
const isExecution = approveAndExecute || (await shouldExecuteTransaction(safeInstance, nonce, lastTx))
|
const isExecution = approveAndExecute || (await shouldExecuteTransaction(safeInstance, nonce, lastTx))
|
||||||
const safeVersion = await getCurrentSafeVersion(safeInstance)
|
const safeVersion = await getCurrentSafeVersion(safeInstance)
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ async function fetchTransactions(
|
||||||
txType: TransactionTypes.INCOMING | TransactionTypes.OUTGOING,
|
txType: TransactionTypes.INCOMING | TransactionTypes.OUTGOING,
|
||||||
safeAddress: string,
|
safeAddress: string,
|
||||||
eTag: string | null,
|
eTag: string | null,
|
||||||
): Promise<{ eTag: string; results: TxServiceModel[] | IncomingTxServiceModel[] }> {
|
): Promise<{ eTag: string | null; results: TxServiceModel[] | IncomingTxServiceModel[] }> {
|
||||||
try {
|
try {
|
||||||
const url = getServiceUrl(txType, safeAddress)
|
const url = getServiceUrl(txType, safeAddress)
|
||||||
const response = await axios.get(url, eTag ? { headers: { 'If-None-Match': eTag } } : undefined)
|
const response = await axios.get(url, eTag ? { headers: { 'If-None-Match': eTag } } : undefined)
|
||||||
|
|
|
@ -39,8 +39,9 @@ export default (safeAddress: string): ThunkAction<Promise<void>, AppReduxState,
|
||||||
}
|
}
|
||||||
|
|
||||||
const incomingTransactions = await loadIncomingTransactions(safeAddress)
|
const incomingTransactions = await loadIncomingTransactions(safeAddress)
|
||||||
|
const safeIncomingTxs = incomingTransactions.get(safeAddress)
|
||||||
|
|
||||||
if (incomingTransactions.get(safeAddress).size) {
|
if (safeIncomingTxs?.size) {
|
||||||
dispatch(addIncomingTransactions(incomingTransactions))
|
dispatch(addIncomingTransactions(incomingTransactions))
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -68,8 +68,8 @@ const batchIncomingTxsTokenDataRequest = (txs: IncomingTxServiceModel[]) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let previousETag = null
|
let previousETag: string | null = null
|
||||||
export const loadIncomingTransactions = async (safeAddress: string) => {
|
export const loadIncomingTransactions = async (safeAddress: string): Promise<Map<string, List<any>>> => {
|
||||||
const { eTag, results } = await fetchTransactions(TransactionTypes.INCOMING, safeAddress, previousETag)
|
const { eTag, results } = await fetchTransactions(TransactionTypes.INCOMING, safeAddress, previousETag)
|
||||||
previousETag = eTag
|
previousETag = eTag
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,7 @@ export type TxServiceModel = {
|
||||||
blockNumber?: number | null
|
blockNumber?: number | null
|
||||||
confirmations: ConfirmationServiceModel[]
|
confirmations: ConfirmationServiceModel[]
|
||||||
confirmationsRequired: number
|
confirmationsRequired: number
|
||||||
creationTx?: boolean | null
|
data: string | null
|
||||||
data?: string | null
|
|
||||||
dataDecoded?: DataDecoded
|
dataDecoded?: DataDecoded
|
||||||
ethGasPrice: string
|
ethGasPrice: string
|
||||||
executionDate?: string | null
|
executionDate?: string | null
|
||||||
|
@ -40,15 +39,15 @@ export type TxServiceModel = {
|
||||||
isExecuted: boolean
|
isExecuted: boolean
|
||||||
isSuccessful: boolean
|
isSuccessful: boolean
|
||||||
modified: string
|
modified: string
|
||||||
nonce?: number | null
|
nonce: number
|
||||||
operation: number
|
operation: number
|
||||||
origin?: string | null
|
origin: string | null
|
||||||
refundReceiver: string
|
refundReceiver: string
|
||||||
safe: string
|
safe: string
|
||||||
safeTxGas: number
|
safeTxGas: number
|
||||||
safeTxHash: string
|
safeTxHash: string
|
||||||
signatures: string
|
signatures: string
|
||||||
submissionDate?: string | null
|
submissionDate: string | null
|
||||||
to: string
|
to: string
|
||||||
transactionHash?: string | null
|
transactionHash?: string | null
|
||||||
value: string
|
value: string
|
||||||
|
@ -78,7 +77,7 @@ export type BatchProcessTxsProps = OutgoingTxs & {
|
||||||
*/
|
*/
|
||||||
const extractCancelAndOutgoingTxs = (safeAddress: string, outgoingTxs: TxServiceModel[]): OutgoingTxs => {
|
const extractCancelAndOutgoingTxs = (safeAddress: string, outgoingTxs: TxServiceModel[]): OutgoingTxs => {
|
||||||
return outgoingTxs.reduce(
|
return outgoingTxs.reduce(
|
||||||
(acc, transaction) => {
|
(acc: { cancellationTxs: Record<number, TxServiceModel>; outgoingTxs: TxServiceModel[] }, transaction) => {
|
||||||
if (
|
if (
|
||||||
isCancelTransaction(transaction, safeAddress) &&
|
isCancelTransaction(transaction, safeAddress) &&
|
||||||
outgoingTxs.find((tx) => tx.nonce === transaction.nonce && !isCancelTransaction(tx, safeAddress))
|
outgoingTxs.find((tx) => tx.nonce === transaction.nonce && !isCancelTransaction(tx, safeAddress))
|
||||||
|
@ -164,7 +163,7 @@ const batchProcessOutgoingTransactions = async ({
|
||||||
// outgoing transactions
|
// outgoing transactions
|
||||||
const outgoingTxsWithData = outgoingTxs.length ? await batchRequestContractCode(outgoingTxs) : []
|
const outgoingTxsWithData = outgoingTxs.length ? await batchRequestContractCode(outgoingTxs) : []
|
||||||
|
|
||||||
const outgoing = []
|
const outgoing: Transaction[] = []
|
||||||
for (const [tx, txCode] of outgoingTxsWithData) {
|
for (const [tx, txCode] of outgoingTxsWithData) {
|
||||||
outgoing.push(
|
outgoing.push(
|
||||||
await buildTx({
|
await buildTx({
|
||||||
|
@ -182,7 +181,7 @@ const batchProcessOutgoingTransactions = async ({
|
||||||
return { cancel, outgoing }
|
return { cancel, outgoing }
|
||||||
}
|
}
|
||||||
|
|
||||||
let previousETag = null
|
let previousETag: string | null = null
|
||||||
export const loadOutgoingTransactions = async (safeAddress: string): Promise<SafeTransactionsType> => {
|
export const loadOutgoingTransactions = async (safeAddress: string): Promise<SafeTransactionsType> => {
|
||||||
const defaultResponse = {
|
const defaultResponse = {
|
||||||
cancel: Map(),
|
cancel: Map(),
|
||||||
|
|
|
@ -19,6 +19,7 @@ import {
|
||||||
TransactionTypes,
|
TransactionTypes,
|
||||||
TransactionTypeValues,
|
TransactionTypeValues,
|
||||||
TxArgs,
|
TxArgs,
|
||||||
|
RefundParams,
|
||||||
} from 'src/logic/safe/store/models/types/transaction'
|
} from 'src/logic/safe/store/models/types/transaction'
|
||||||
import { CANCELLATION_TRANSACTIONS_REDUCER_ID } from 'src/logic/safe/store/reducer/cancellationTransactions'
|
import { CANCELLATION_TRANSACTIONS_REDUCER_ID } from 'src/logic/safe/store/reducer/cancellationTransactions'
|
||||||
import { SAFE_REDUCER_ID } from 'src/logic/safe/store/reducer/safe'
|
import { SAFE_REDUCER_ID } from 'src/logic/safe/store/reducer/safe'
|
||||||
|
@ -65,15 +66,15 @@ export const isModifySettingsTransaction = (tx: TxServiceModel, safeAddress: str
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isMultiSendTransaction = (tx: TxServiceModel): boolean => {
|
export const isMultiSendTransaction = (tx: TxServiceModel): boolean => {
|
||||||
return !isEmptyData(tx.data) && tx.data.substring(0, 10) === '0x8d80ff0a' && Number(tx.value) === 0
|
return !isEmptyData(tx.data) && tx.data?.substring(0, 10) === '0x8d80ff0a' && Number(tx.value) === 0
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isUpgradeTransaction = (tx: TxServiceModel): boolean => {
|
export const isUpgradeTransaction = (tx: TxServiceModel): boolean => {
|
||||||
return (
|
return (
|
||||||
!isEmptyData(tx.data) &&
|
!isEmptyData(tx.data) &&
|
||||||
isMultiSendTransaction(tx) &&
|
isMultiSendTransaction(tx) &&
|
||||||
tx.data.substr(308, 8) === '7de7edef' && // 7de7edef - changeMasterCopy (308, 8)
|
tx.data?.substr(308, 8) === '7de7edef' && // 7de7edef - changeMasterCopy (308, 8)
|
||||||
tx.data.substr(550, 8) === 'f08a0323' // f08a0323 - setFallbackHandler (550, 8)
|
tx.data?.substr(550, 8) === 'f08a0323' // f08a0323 - setFallbackHandler (550, 8)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +84,7 @@ export const isOutgoingTransaction = (tx: TxServiceModel, safeAddress: string):
|
||||||
|
|
||||||
export const isCustomTransaction = async (
|
export const isCustomTransaction = async (
|
||||||
tx: TxServiceModel,
|
tx: TxServiceModel,
|
||||||
txCode: string,
|
txCode: string | null,
|
||||||
safeAddress: string,
|
safeAddress: string,
|
||||||
knownTokens: Map<string, Token>,
|
knownTokens: Map<string, Token>,
|
||||||
): Promise<boolean> => {
|
): Promise<boolean> => {
|
||||||
|
@ -98,9 +99,9 @@ export const isCustomTransaction = async (
|
||||||
export const getRefundParams = async (
|
export const getRefundParams = async (
|
||||||
tx: TxServiceModel,
|
tx: TxServiceModel,
|
||||||
tokenInfo: (string) => Promise<{ decimals: number; symbol: string } | null>,
|
tokenInfo: (string) => Promise<{ decimals: number; symbol: string } | null>,
|
||||||
): Promise<any> => {
|
): Promise<RefundParams | null> => {
|
||||||
const txGasPrice = Number(tx.gasPrice)
|
const txGasPrice = Number(tx.gasPrice)
|
||||||
let refundParams = null
|
let refundParams: RefundParams | null = null
|
||||||
|
|
||||||
if (txGasPrice > 0) {
|
if (txGasPrice > 0) {
|
||||||
let refundSymbol = 'ETH'
|
let refundSymbol = 'ETH'
|
||||||
|
@ -273,7 +274,6 @@ export const buildTx = async ({
|
||||||
blockNumber: tx.blockNumber,
|
blockNumber: tx.blockNumber,
|
||||||
cancelled: isTxCancelled,
|
cancelled: isTxCancelled,
|
||||||
confirmations,
|
confirmations,
|
||||||
creationTx: tx.creationTx,
|
|
||||||
customTx: isCustomTx,
|
customTx: isCustomTx,
|
||||||
data: tx.data ? tx.data : EMPTY_DATA,
|
data: tx.data ? tx.data : EMPTY_DATA,
|
||||||
dataDecoded: tx.dataDecoded,
|
dataDecoded: tx.dataDecoded,
|
||||||
|
@ -325,7 +325,7 @@ export const mockTransaction = (tx: TxToMock, safeAddress: string, state: AppRed
|
||||||
|
|
||||||
return buildTx({
|
return buildTx({
|
||||||
cancellationTxs,
|
cancellationTxs,
|
||||||
currentUser: null,
|
currentUser: undefined,
|
||||||
knownTokens,
|
knownTokens,
|
||||||
outgoingTxs,
|
outgoingTxs,
|
||||||
safe,
|
safe,
|
||||||
|
@ -344,7 +344,7 @@ export const updateStoredTransactionsStatus = (dispatch: (any) => void, walletRe
|
||||||
dispatch(
|
dispatch(
|
||||||
addOrUpdateTransactions({
|
addOrUpdateTransactions({
|
||||||
safeAddress,
|
safeAddress,
|
||||||
transactions: transactions.withMutations((list) =>
|
transactions: transactions.withMutations((list: any[]) =>
|
||||||
list.map((tx) => tx.set('status', calculateTransactionStatus(tx, safe, walletRecord.account))),
|
list.map((tx) => tx.set('status', calculateTransactionStatus(tx, safe, walletRecord.account))),
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -4,7 +4,7 @@ import axios from 'axios'
|
||||||
|
|
||||||
import { buildTxServiceUrl } from 'src/logic/safe/transactions/txHistory'
|
import { buildTxServiceUrl } from 'src/logic/safe/transactions/txHistory'
|
||||||
|
|
||||||
export const getLastTx = async (safeAddress: string): Promise<TxServiceModel> => {
|
export const getLastTx = async (safeAddress: string): Promise<TxServiceModel | null> => {
|
||||||
try {
|
try {
|
||||||
const url = buildTxServiceUrl(safeAddress)
|
const url = buildTxServiceUrl(safeAddress)
|
||||||
const response = await axios.get(url, { params: { limit: 1 } })
|
const response = await axios.get(url, { params: { limit: 1 } })
|
||||||
|
@ -17,23 +17,24 @@ export const getLastTx = async (safeAddress: string): Promise<TxServiceModel> =>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getNewTxNonce = async (
|
export const getNewTxNonce = async (
|
||||||
txNonce: string | null,
|
txNonce: string | undefined,
|
||||||
lastTx: TxServiceModel,
|
lastTx: TxServiceModel | null,
|
||||||
safeInstance: GnosisSafe,
|
safeInstance: GnosisSafe,
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
if (!Number.isInteger(Number.parseInt(txNonce, 10))) {
|
if (typeof txNonce === 'string' && !Number.isInteger(Number.parseInt(txNonce, 10))) {
|
||||||
return lastTx === null
|
return lastTx === null
|
||||||
? // use current's safe nonce as fallback
|
? // use current's safe nonce as fallback
|
||||||
(await safeInstance.methods.nonce().call()).toString()
|
(await safeInstance.methods.nonce().call()).toString()
|
||||||
: `${lastTx.nonce + 1}`
|
: `${lastTx.nonce + 1}`
|
||||||
}
|
}
|
||||||
return txNonce
|
|
||||||
|
return txNonce as string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const shouldExecuteTransaction = async (
|
export const shouldExecuteTransaction = async (
|
||||||
safeInstance: GnosisSafe,
|
safeInstance: GnosisSafe,
|
||||||
nonce: string,
|
nonce: string,
|
||||||
lastTx: TxServiceModel,
|
lastTx: TxServiceModel | null,
|
||||||
): Promise<boolean> => {
|
): Promise<boolean> => {
|
||||||
const threshold = await safeInstance.methods.getThreshold().call()
|
const threshold = await safeInstance.methods.getThreshold().call()
|
||||||
|
|
||||||
|
@ -45,7 +46,7 @@ export const shouldExecuteTransaction = async (
|
||||||
// by the user using the exec button.
|
// by the user using the exec button.
|
||||||
const canExecuteCurrentTransaction = lastTx && lastTx.isExecuted
|
const canExecuteCurrentTransaction = lastTx && lastTx.isExecuted
|
||||||
|
|
||||||
return isFirstTransaction || canExecuteCurrentTransaction
|
return isFirstTransaction || !!canExecuteCurrentTransaction
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -85,7 +85,7 @@ const notificationsMiddleware = (store) => (next) => async (action) => {
|
||||||
const safes = safesMapSelector(state)
|
const safes = safesMapSelector(state)
|
||||||
const currentSafe = safes.get(safeAddress)
|
const currentSafe = safes.get(safeAddress)
|
||||||
|
|
||||||
if (!isUserAnOwner(currentSafe, userAddress) || awaitingTransactions.size === 0) {
|
if (!currentSafe || !isUserAnOwner(currentSafe, userAddress) || awaitingTransactions.size === 0) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ export enum PendingActionType {
|
||||||
REJECT = 'reject',
|
REJECT = 'reject',
|
||||||
}
|
}
|
||||||
export type PendingActionValues = PendingActionType[keyof PendingActionType]
|
export type PendingActionValues = PendingActionType[keyof PendingActionType]
|
||||||
|
export type RefundParams = { fee: string; symbol: string }
|
||||||
|
|
||||||
export type TransactionProps = {
|
export type TransactionProps = {
|
||||||
baseGas: number
|
baseGas: number
|
||||||
|
@ -43,7 +44,7 @@ export type TransactionProps = {
|
||||||
creator: string
|
creator: string
|
||||||
creationTx: boolean
|
creationTx: boolean
|
||||||
customTx: boolean
|
customTx: boolean
|
||||||
data?: string | null
|
data: string | null
|
||||||
dataDecoded: DataDecoded | null
|
dataDecoded: DataDecoded | null
|
||||||
decimals?: (number | string) | null
|
decimals?: (number | string) | null
|
||||||
decodedParams: DecodedParams | null
|
decodedParams: DecodedParams | null
|
||||||
|
@ -51,7 +52,7 @@ export type TransactionProps = {
|
||||||
executionTxHash?: string | null
|
executionTxHash?: string | null
|
||||||
executor: string
|
executor: string
|
||||||
factoryAddress: string
|
factoryAddress: string
|
||||||
fee?: string // It will be replace with the new TXs types.
|
fee: string | null // It will be replace with the new TXs types.
|
||||||
gasPrice: string
|
gasPrice: string
|
||||||
gasToken: string
|
gasToken: string
|
||||||
isCancellationTx: boolean
|
isCancellationTx: boolean
|
||||||
|
@ -63,18 +64,18 @@ export type TransactionProps = {
|
||||||
masterCopy: string
|
masterCopy: string
|
||||||
modifySettingsTx: boolean
|
modifySettingsTx: boolean
|
||||||
multiSendTx: boolean
|
multiSendTx: boolean
|
||||||
nonce?: number | null
|
nonce: number
|
||||||
operation: number
|
operation: number
|
||||||
origin: string | null
|
origin: string | null
|
||||||
ownersWithPendingActions: Map<PendingActionValues, List<any>>
|
ownersWithPendingActions: Map<PendingActionValues, List<any>>
|
||||||
recipient: string
|
recipient: string
|
||||||
refundParams: any
|
refundParams: RefundParams | null
|
||||||
refundReceiver: string
|
refundReceiver: string
|
||||||
safeTxGas: number
|
safeTxGas: number
|
||||||
safeTxHash: string
|
safeTxHash: string
|
||||||
setupData: string
|
setupData: string
|
||||||
status?: TransactionStatus
|
status: TransactionStatus
|
||||||
submissionDate?: string | null
|
submissionDate: string | null
|
||||||
symbol?: string | null
|
symbol?: string | null
|
||||||
transactionHash: string | null
|
transactionHash: string | null
|
||||||
transfers?: Transfer[]
|
transfers?: Transfer[]
|
||||||
|
@ -87,7 +88,7 @@ export type Transaction = RecordOf<TransactionProps>
|
||||||
|
|
||||||
export type TxArgs = {
|
export type TxArgs = {
|
||||||
baseGas: number
|
baseGas: number
|
||||||
data?: string | null
|
data: string
|
||||||
gasPrice: string
|
gasPrice: string
|
||||||
gasToken: string
|
gasToken: string
|
||||||
nonce: number
|
nonce: number
|
||||||
|
|
|
@ -37,7 +37,7 @@ export const buildSafe = (storedSafe: SafeRecordProps): SafeRecordProps => {
|
||||||
blacklistedTokens,
|
blacklistedTokens,
|
||||||
activeAssets,
|
activeAssets,
|
||||||
blacklistedAssets,
|
blacklistedAssets,
|
||||||
latestIncomingTxBlock: null,
|
latestIncomingTxBlock: 0,
|
||||||
modules: null,
|
modules: null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,11 +12,11 @@ export const allTransactionsSelector = createSelector(getTransactionsStateSelect
|
||||||
export const safeAllTransactionsSelector = createSelector(
|
export const safeAllTransactionsSelector = createSelector(
|
||||||
safeParamAddressFromStateSelector,
|
safeParamAddressFromStateSelector,
|
||||||
allTransactionsSelector,
|
allTransactionsSelector,
|
||||||
(safeAddress, transactions) => transactions[safeAddress]?.transactions || [],
|
(safeAddress, transactions) => (safeAddress ? transactions[safeAddress]?.transactions : []),
|
||||||
)
|
)
|
||||||
|
|
||||||
export const safeTotalTransactionsAmountSelector = createSelector(
|
export const safeTotalTransactionsAmountSelector = createSelector(
|
||||||
safeParamAddressFromStateSelector,
|
safeParamAddressFromStateSelector,
|
||||||
allTransactionsSelector,
|
allTransactionsSelector,
|
||||||
(safeAddress, transactions) => transactions[safeAddress]?.totalTransactionsCount || 0,
|
(safeAddress, transactions) => (safeAddress ? transactions[safeAddress]?.totalTransactionsCount : 0),
|
||||||
)
|
)
|
||||||
|
|
|
@ -36,7 +36,7 @@ const cancellationTransactionsSelector = (state: AppReduxState) => state[CANCELL
|
||||||
|
|
||||||
const incomingTransactionsSelector = (state: AppReduxState) => state[INCOMING_TRANSACTIONS_REDUCER_ID]
|
const incomingTransactionsSelector = (state: AppReduxState) => state[INCOMING_TRANSACTIONS_REDUCER_ID]
|
||||||
|
|
||||||
export const safeParamAddressFromStateSelector = (state: AppReduxState): string | null => {
|
export const safeParamAddressFromStateSelector = (state: AppReduxState): string | undefined => {
|
||||||
const match = matchPath<{ safeAddress: string }>(state.router.location.pathname, {
|
const match = matchPath<{ safeAddress: string }>(state.router.location.pathname, {
|
||||||
path: `${SAFELIST_ADDRESS}/:safeAddress`,
|
path: `${SAFELIST_ADDRESS}/:safeAddress`,
|
||||||
})
|
})
|
||||||
|
@ -45,7 +45,7 @@ export const safeParamAddressFromStateSelector = (state: AppReduxState): string
|
||||||
return checksumAddress(match.params.safeAddress)
|
return checksumAddress(match.params.safeAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export const safeParamAddressSelector = (
|
export const safeParamAddressSelector = (
|
||||||
|
@ -177,16 +177,16 @@ export const safeBlacklistedAssetsSelector = createSelector(
|
||||||
)
|
)
|
||||||
|
|
||||||
export const safeActiveAssetsSelectorBySafe = (safeAddress: string, safes: SafesMap): Set<string> =>
|
export const safeActiveAssetsSelectorBySafe = (safeAddress: string, safes: SafesMap): Set<string> =>
|
||||||
safes.get(safeAddress).get('activeAssets')
|
safes.get(safeAddress)?.get('activeAssets') || Set()
|
||||||
|
|
||||||
export const safeBlacklistedAssetsSelectorBySafe = (safeAddress: string, safes: SafesMap): Set<string> =>
|
export const safeBlacklistedAssetsSelectorBySafe = (safeAddress: string, safes: SafesMap): Set<string> =>
|
||||||
safes.get(safeAddress).get('blacklistedAssets')
|
safes.get(safeAddress)?.get('blacklistedAssets') || Set()
|
||||||
|
|
||||||
const baseSafe = makeSafe()
|
const baseSafe = makeSafe()
|
||||||
|
|
||||||
export const safeFieldSelector = <K extends keyof SafeRecordProps>(field: K) => (
|
export const safeFieldSelector = <K extends keyof SafeRecordProps>(field: K) => (
|
||||||
safe: SafeRecord,
|
safe: SafeRecord,
|
||||||
): SafeRecordProps[K] | null => (safe ? safe.get(field, baseSafe.get(field)) : null)
|
): SafeRecordProps[K] | undefined => (safe ? safe.get(field, baseSafe.get(field)) : undefined)
|
||||||
|
|
||||||
export const safeNameSelector = createSelector(safeSelector, safeFieldSelector('name'))
|
export const safeNameSelector = createSelector(safeSelector, safeFieldSelector('name'))
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
import { List } from 'immutable'
|
import { List } from 'immutable'
|
||||||
|
|
||||||
import { isPendingTransaction } from 'src/logic/safe/store/actions/transactions/utils/transactionHelpers'
|
import { isPendingTransaction } from 'src/logic/safe/store/actions/transactions/utils/transactionHelpers'
|
||||||
|
import { Transaction } from 'src/logic/safe/store/models/types/transaction'
|
||||||
|
|
||||||
export const getAwaitingTransactions = (allTransactions = List([]), cancellationTxs, userAccount: string) => {
|
export const getAwaitingTransactions = (
|
||||||
|
allTransactions: List<Transaction>,
|
||||||
|
cancellationTxs,
|
||||||
|
userAccount: string,
|
||||||
|
): List<Transaction> => {
|
||||||
return allTransactions.filter((tx) => {
|
return allTransactions.filter((tx) => {
|
||||||
const cancelTx = !!tx.nonce && !isNaN(Number(tx.nonce)) ? cancellationTxs.get(`${tx.nonce}`) : null
|
const cancelTx = !!tx.nonce && !isNaN(Number(tx.nonce)) ? cancellationTxs.get(`${tx.nonce}`) : null
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ const estimateDataGasCosts = (data: string): number => {
|
||||||
return accumulator + 16
|
return accumulator + 16
|
||||||
}
|
}
|
||||||
|
|
||||||
return data.match(/.{2}/g).reduce(reducer, 0)
|
return data.match(/.{2}/g)?.reduce(reducer, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const estimateTxGasCosts = async (
|
export const estimateTxGasCosts = async (
|
||||||
|
@ -38,6 +38,11 @@ export const estimateTxGasCosts = async (
|
||||||
try {
|
try {
|
||||||
const web3 = getWeb3()
|
const web3 = getWeb3()
|
||||||
const from = await getAccountFrom(web3)
|
const from = await getAccountFrom(web3)
|
||||||
|
|
||||||
|
if (!from) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
const safeInstance = (new web3.eth.Contract(GnosisSafeSol.abi as AbiItem[], safeAddress) as unknown) as GnosisSafe
|
const safeInstance = (new web3.eth.Contract(GnosisSafeSol.abi as AbiItem[], safeAddress) as unknown) as GnosisSafe
|
||||||
const nonce = await safeInstance.methods.nonce().call()
|
const nonce = await safeInstance.methods.nonce().call()
|
||||||
const threshold = await safeInstance.methods.getThreshold().call()
|
const threshold = await safeInstance.methods.getThreshold().call()
|
||||||
|
|
|
@ -39,7 +39,7 @@ export const ethSigner = async ({
|
||||||
return reject(err)
|
return reject(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (signature.result == null) {
|
if (signature?.result == null) {
|
||||||
reject(new Error(ETH_SIGN_NOT_SUPPORTED_ERROR_MSG))
|
reject(new Error(ETH_SIGN_NOT_SUPPORTED_ERROR_MSG))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,18 @@ import { loadFromStorage, saveToStorage } from 'src/utils/storage'
|
||||||
import { SafeRecordProps } from 'src/logic/safe/store/models/safe'
|
import { SafeRecordProps } from 'src/logic/safe/store/models/safe'
|
||||||
|
|
||||||
export const SAFES_KEY = 'SAFES'
|
export const SAFES_KEY = 'SAFES'
|
||||||
export const TX_KEY = 'TX'
|
|
||||||
export const DEFAULT_SAFE_KEY = 'DEFAULT_SAFE'
|
export const DEFAULT_SAFE_KEY = 'DEFAULT_SAFE'
|
||||||
|
|
||||||
|
type StoredSafes = Record<string, SafeRecordProps>
|
||||||
|
|
||||||
|
export const loadStoredSafes = async (): Promise<StoredSafes | undefined> => {
|
||||||
|
const safes = await loadFromStorage<StoredSafes>(SAFES_KEY)
|
||||||
|
|
||||||
|
return safes
|
||||||
|
}
|
||||||
|
|
||||||
export const getSafeName = async (safeAddress: string): Promise<string | undefined> => {
|
export const getSafeName = async (safeAddress: string): Promise<string | undefined> => {
|
||||||
const safes = await loadFromStorage(SAFES_KEY)
|
const safes = await loadStoredSafes()
|
||||||
if (!safes) {
|
if (!safes) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
@ -23,9 +30,9 @@ export const saveSafes = async (safes) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getLocalSafe = async (safeAddress: string): Promise<SafeRecordProps | null> => {
|
export const getLocalSafe = async (safeAddress: string): Promise<SafeRecordProps | undefined> => {
|
||||||
const storedSafes = (await loadFromStorage(SAFES_KEY)) || {}
|
const storedSafes = await loadStoredSafes()
|
||||||
return storedSafes[safeAddress] || null
|
return storedSafes?.[safeAddress]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getDefaultSafe = async (): Promise<string> => {
|
export const getDefaultSafe = async (): Promise<string> => {
|
||||||
|
|
|
@ -11,13 +11,15 @@ export const FEATURES = [
|
||||||
{ name: 'ERC1155', validVersion: '>=1.1.1' },
|
{ name: 'ERC1155', validVersion: '>=1.1.1' },
|
||||||
]
|
]
|
||||||
|
|
||||||
export const safeNeedsUpdate = (currentVersion: string, latestVersion: string): boolean => {
|
type Feature = typeof FEATURES[number]
|
||||||
|
|
||||||
|
export const safeNeedsUpdate = (currentVersion?: string, latestVersion?: string): boolean => {
|
||||||
if (!currentVersion || !latestVersion) {
|
if (!currentVersion || !latestVersion) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const current = semverValid(currentVersion)
|
const current = semverValid(currentVersion) as string
|
||||||
const latest = semverValid(latestVersion)
|
const latest = semverValid(latestVersion) as string
|
||||||
|
|
||||||
return latest ? semverLessThan(current, latest) : false
|
return latest ? semverLessThan(current, latest) : false
|
||||||
}
|
}
|
||||||
|
@ -26,7 +28,7 @@ export const getCurrentSafeVersion = (gnosisSafeInstance: GnosisSafe): Promise<s
|
||||||
gnosisSafeInstance.methods.VERSION().call()
|
gnosisSafeInstance.methods.VERSION().call()
|
||||||
|
|
||||||
export const enabledFeatures = (version: string): string[] =>
|
export const enabledFeatures = (version: string): string[] =>
|
||||||
FEATURES.reduce((acc, feature) => {
|
FEATURES.reduce((acc: string[], feature: Feature) => {
|
||||||
if (semverSatisfies(version, feature.validVersion)) {
|
if (semverSatisfies(version, feature.validVersion)) {
|
||||||
acc.push(feature.name)
|
acc.push(feature.name)
|
||||||
}
|
}
|
||||||
|
@ -44,11 +46,11 @@ export const checkIfSafeNeedsUpdate = async (
|
||||||
lastSafeVersion: string,
|
lastSafeVersion: string,
|
||||||
): Promise<SafeVersionInfo> => {
|
): Promise<SafeVersionInfo> => {
|
||||||
if (!gnosisSafeInstance || !lastSafeVersion) {
|
if (!gnosisSafeInstance || !lastSafeVersion) {
|
||||||
return null
|
throw new Error('checkIfSafeNeedsUpdate: No Safe Instance or version provided')
|
||||||
}
|
}
|
||||||
const safeMasterVersion = await getCurrentSafeVersion(gnosisSafeInstance)
|
const safeMasterVersion = await getCurrentSafeVersion(gnosisSafeInstance)
|
||||||
const current = semverValid(safeMasterVersion)
|
const current = semverValid(safeMasterVersion) as string
|
||||||
const latest = semverValid(lastSafeVersion)
|
const latest = semverValid(lastSafeVersion) as string
|
||||||
const needUpdate = safeNeedsUpdate(safeMasterVersion, lastSafeVersion)
|
const needUpdate = safeNeedsUpdate(safeMasterVersion, lastSafeVersion)
|
||||||
|
|
||||||
return { current, latest, needUpdate }
|
return { current, latest, needUpdate }
|
||||||
|
|
|
@ -48,7 +48,7 @@ const extractDataFromResult = (currentTokens: TokenState) => (
|
||||||
if (tokenAddress === null) {
|
if (tokenAddress === null) {
|
||||||
acc.ethBalance = humanReadableValue(balance, 18)
|
acc.ethBalance = humanReadableValue(balance, 18)
|
||||||
} else {
|
} else {
|
||||||
acc.balances = acc.balances.merge({ [tokenAddress]: humanReadableValue(balance, Number(token.decimals)) })
|
acc.balances = acc.balances.merge({ [tokenAddress]: humanReadableValue(balance, Number(token?.decimals)) })
|
||||||
|
|
||||||
if (currentTokens && !currentTokens.get(tokenAddress)) {
|
if (currentTokens && !currentTokens.get(tokenAddress)) {
|
||||||
acc.tokens = acc.tokens.push(makeToken({ address: tokenAddress, ...token }))
|
acc.tokens = acc.tokens.push(makeToken({ address: tokenAddress, ...token }))
|
||||||
|
@ -57,7 +57,7 @@ const extractDataFromResult = (currentTokens: TokenState) => (
|
||||||
|
|
||||||
acc.currencyList = acc.currencyList.push(
|
acc.currencyList = acc.currencyList.push(
|
||||||
makeBalanceCurrency({
|
makeBalanceCurrency({
|
||||||
currencyName: balanceUsd ? AVAILABLE_CURRENCIES.USD : null,
|
currencyName: balanceUsd ? AVAILABLE_CURRENCIES.USD : undefined,
|
||||||
tokenAddress,
|
tokenAddress,
|
||||||
balanceInBaseCurrency: balanceUsd,
|
balanceInBaseCurrency: balanceUsd,
|
||||||
balanceInSelectedCurrency: balanceUsd,
|
balanceInSelectedCurrency: balanceUsd,
|
||||||
|
|
|
@ -57,11 +57,7 @@ const getTokenValues = (tokenAddress) =>
|
||||||
methods: ['decimals', 'name', 'symbol'],
|
methods: ['decimals', 'name', 'symbol'],
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getTokenInfos = async (tokenAddress: string): Promise<Token> => {
|
export const getTokenInfos = async (tokenAddress: string): Promise<Token | undefined> => {
|
||||||
if (!tokenAddress) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const { tokens } = store.getState()
|
const { tokens } = store.getState()
|
||||||
const localToken = tokens.get(tokenAddress)
|
const localToken = tokens.get(tokenAddress)
|
||||||
|
|
||||||
|
@ -74,7 +70,7 @@ export const getTokenInfos = async (tokenAddress: string): Promise<Token> => {
|
||||||
const [tokenDecimals, tokenName, tokenSymbol] = await getTokenValues(tokenAddress)
|
const [tokenDecimals, tokenName, tokenSymbol] = await getTokenValues(tokenAddress)
|
||||||
|
|
||||||
if (tokenDecimals === null) {
|
if (tokenDecimals === null) {
|
||||||
return null
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = makeToken({
|
const token = makeToken({
|
||||||
|
|
|
@ -5,7 +5,7 @@ export type TokenProps = {
|
||||||
name: string
|
name: string
|
||||||
symbol: string
|
symbol: string
|
||||||
decimals: number | string
|
decimals: number | string
|
||||||
logoUri?: string | null
|
logoUri: string
|
||||||
balance?: number | string
|
balance?: number | string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,18 +35,18 @@ export const isAddressAToken = async (tokenAddress: string): Promise<boolean> =>
|
||||||
// } catch {
|
// } catch {
|
||||||
// return 'Not a token address'
|
// return 'Not a token address'
|
||||||
// }
|
// }
|
||||||
const call = await web3.eth.call({ to: tokenAddress, data: web3.utils.sha3('totalSupply()') })
|
const call = await web3.eth.call({ to: tokenAddress, data: web3.utils.sha3('totalSupply()') as string })
|
||||||
|
|
||||||
return call !== '0x'
|
return call !== '0x'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isTokenTransfer = (tx: TxServiceModel): boolean => {
|
export const isTokenTransfer = (tx: TxServiceModel): boolean => {
|
||||||
return !isEmptyData(tx.data) && tx.data.substring(0, 10) === '0xa9059cbb' && Number(tx.value) === 0
|
return !isEmptyData(tx.data) && tx.data?.substring(0, 10) === '0xa9059cbb' && Number(tx.value) === 0
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isSendERC721Transaction = (
|
export const isSendERC721Transaction = (
|
||||||
tx: TxServiceModel,
|
tx: TxServiceModel,
|
||||||
txCode: string,
|
txCode: string | null,
|
||||||
knownTokens: Map<string, Token>,
|
knownTokens: Map<string, Token>,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
// "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85" - ens token contract, includes safeTransferFrom
|
// "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85" - ens token contract, includes safeTransferFrom
|
||||||
|
@ -78,7 +78,7 @@ export const getERC20DecimalsAndSymbol = async (
|
||||||
try {
|
try {
|
||||||
const storedTokenInfo = await getTokenInfos(tokenAddress)
|
const storedTokenInfo = await getTokenInfos(tokenAddress)
|
||||||
|
|
||||||
if (storedTokenInfo === null) {
|
if (!storedTokenInfo) {
|
||||||
const [tokenDecimals, tokenSymbol] = await generateBatchRequests({
|
const [tokenDecimals, tokenSymbol] = await generateBatchRequests({
|
||||||
abi: ALTERNATIVE_TOKEN_ABI,
|
abi: ALTERNATIVE_TOKEN_ABI,
|
||||||
address: tokenAddress,
|
address: tokenAddress,
|
||||||
|
@ -96,7 +96,7 @@ export const getERC20DecimalsAndSymbol = async (
|
||||||
|
|
||||||
export const isSendERC20Transaction = async (
|
export const isSendERC20Transaction = async (
|
||||||
tx: TxServiceModel,
|
tx: TxServiceModel,
|
||||||
txCode: string,
|
txCode: string | null,
|
||||||
knownTokens: Map<string, Token>,
|
knownTokens: Map<string, Token>,
|
||||||
): Promise<boolean> => {
|
): Promise<boolean> => {
|
||||||
let isSendTokenTx = !isSendERC721Transaction(tx, txCode, knownTokens) && isTokenTransfer(tx)
|
let isSendTokenTx = !isSendERC721Transaction(tx, txCode, knownTokens) && isTokenTransfer(tx)
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { List } from 'immutable'
|
||||||
import { SafeRecord } from 'src/logic/safe/store/models/safe'
|
import { SafeRecord } from 'src/logic/safe/store/models/safe'
|
||||||
export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
|
export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
|
||||||
|
|
||||||
export const sameAddress = (firstAddress: string, secondAddress: string): boolean => {
|
export const sameAddress = (firstAddress: string | undefined, secondAddress: string | undefined): boolean => {
|
||||||
if (!firstAddress) {
|
if (!firstAddress) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,7 +96,7 @@ const isSmartContractWallet = async (web3Provider: Web3, account: string): Promi
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getProviderInfo = async (web3Instance: Web3, providerName = 'Wallet'): Promise<ProviderProps> => {
|
export const getProviderInfo = async (web3Instance: Web3, providerName = 'Wallet'): Promise<ProviderProps> => {
|
||||||
const account = await getAccountFrom(web3Instance)
|
const account = (await getAccountFrom(web3Instance)) || ''
|
||||||
const network = await getNetworkIdFrom(web3Instance)
|
const network = await getNetworkIdFrom(web3Instance)
|
||||||
const smartContractWallet = await isSmartContractWallet(web3Instance, account)
|
const smartContractWallet = await isSmartContractWallet(web3Instance, account)
|
||||||
const hardwareWallet = isHardwareWallet(providerName)
|
const hardwareWallet = isHardwareWallet(providerName)
|
||||||
|
|
|
@ -16,8 +16,7 @@ export const loadLastUsedProvider = async (): Promise<string | undefined> => {
|
||||||
return lastUsedProvider
|
return lastUsedProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
let watcherInterval = null
|
let watcherInterval
|
||||||
|
|
||||||
const providerWatcherMware = (store) => (next) => async (action) => {
|
const providerWatcherMware = (store) => (next) => async (action) => {
|
||||||
const handledAction = next(action)
|
const handledAction = next(action)
|
||||||
|
|
||||||
|
|
|
@ -120,6 +120,8 @@ const DetailsForm = ({ errors, form }: DetailsFormProps): React.ReactElement =>
|
||||||
fieldMutator={(val) => {
|
fieldMutator={(val) => {
|
||||||
form.mutators.setValue(FIELD_LOAD_ADDRESS, val)
|
form.mutators.setValue(FIELD_LOAD_ADDRESS, val)
|
||||||
}}
|
}}
|
||||||
|
// eslint-disable-next-line
|
||||||
|
// @ts-ignore
|
||||||
inputAdornment={
|
inputAdornment={
|
||||||
noErrorsOn(FIELD_LOAD_ADDRESS, errors) && {
|
noErrorsOn(FIELD_LOAD_ADDRESS, errors) && {
|
||||||
endAdornment: (
|
endAdornment: (
|
||||||
|
@ -156,12 +158,15 @@ const DetailsForm = ({ errors, form }: DetailsFormProps): React.ReactElement =>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const DetailsPage = () => (controls: React.ReactNode, { errors, form }: StepperPageFormProps): React.ReactElement => (
|
const DetailsPage = () =>
|
||||||
<>
|
function LoadSafeDetails(controls: React.ReactNode, { errors, form }: StepperPageFormProps): React.ReactElement {
|
||||||
<OpenPaper controls={controls}>
|
return (
|
||||||
<DetailsForm errors={errors} form={form} />
|
<>
|
||||||
</OpenPaper>
|
<OpenPaper controls={controls}>
|
||||||
</>
|
<DetailsForm errors={errors} form={form} />
|
||||||
)
|
</OpenPaper>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default DetailsPage
|
export default DetailsPage
|
||||||
|
|
|
@ -79,7 +79,7 @@ const calculateSafeValues = (owners, threshold, values) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const OwnerListComponent = (props) => {
|
const OwnerListComponent = (props) => {
|
||||||
const [owners, setOwners] = useState([])
|
const [owners, setOwners] = useState<string[]>([])
|
||||||
const { classes, updateInitialProps, values } = props
|
const { classes, updateInitialProps, values } = props
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -156,12 +156,15 @@ const OwnerListComponent = (props) => {
|
||||||
|
|
||||||
const OwnerListPage = withStyles(styles as any)(OwnerListComponent)
|
const OwnerListPage = withStyles(styles as any)(OwnerListComponent)
|
||||||
|
|
||||||
const OwnerList = ({ updateInitialProps }, network) => (controls, { values }) => (
|
const OwnerList = ({ updateInitialProps }, network) =>
|
||||||
<>
|
function LoadSafeOwnerList(controls, { values }): React.ReactElement {
|
||||||
<OpenPaper controls={controls} padding={false}>
|
return (
|
||||||
<OwnerListPage network={network} updateInitialProps={updateInitialProps} values={values} />
|
<>
|
||||||
</OpenPaper>
|
<OpenPaper controls={controls} padding={false}>
|
||||||
</>
|
<OwnerListPage network={network} updateInitialProps={updateInitialProps} values={values} />
|
||||||
)
|
</OpenPaper>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default OwnerList
|
export default OwnerList
|
||||||
|
|
|
@ -6,12 +6,11 @@ import { FIELD_LOAD_ADDRESS, FIELD_LOAD_NAME } from '../components/fields'
|
||||||
|
|
||||||
import Page from 'src/components/layout/Page'
|
import Page from 'src/components/layout/Page'
|
||||||
import { getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts'
|
import { getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts'
|
||||||
import { SAFES_KEY, saveSafes } from 'src/logic/safe/utils'
|
import { saveSafes, loadStoredSafes } from 'src/logic/safe/utils'
|
||||||
import { getNamesFrom, getOwnersFrom } from 'src/routes/open/utils/safeDataExtractor'
|
import { getNamesFrom, getOwnersFrom } from 'src/routes/open/utils/safeDataExtractor'
|
||||||
import { SAFELIST_ADDRESS } from 'src/routes/routes'
|
import { SAFELIST_ADDRESS } from 'src/routes/routes'
|
||||||
import { buildSafe } from 'src/logic/safe/store/actions/fetchSafe'
|
import { buildSafe } from 'src/logic/safe/store/actions/fetchSafe'
|
||||||
import { history } from 'src/store'
|
import { history } from 'src/store'
|
||||||
import { loadFromStorage } from 'src/utils/storage'
|
|
||||||
import { SafeOwner, SafeRecordProps } from 'src/logic/safe/store/models/safe'
|
import { SafeOwner, SafeRecordProps } from 'src/logic/safe/store/models/safe'
|
||||||
import { List } from 'immutable'
|
import { List } from 'immutable'
|
||||||
import { checksumAddress } from 'src/utils/checksumAddress'
|
import { checksumAddress } from 'src/utils/checksumAddress'
|
||||||
|
@ -27,7 +26,7 @@ export const loadSafe = async (
|
||||||
const safeProps = await buildSafe(safeAddress, safeName)
|
const safeProps = await buildSafe(safeAddress, safeName)
|
||||||
safeProps.owners = owners
|
safeProps.owners = owners
|
||||||
|
|
||||||
const storedSafes = (await loadFromStorage(SAFES_KEY)) || {}
|
const storedSafes = (await loadStoredSafes()) || {}
|
||||||
|
|
||||||
storedSafes[safeAddress] = safeProps
|
storedSafes[safeAddress] = safeProps
|
||||||
|
|
||||||
|
|
|
@ -138,6 +138,8 @@ const SafeOwners = (props) => {
|
||||||
fieldMutator={(val) => {
|
fieldMutator={(val) => {
|
||||||
form.mutators.setValue(addressName, val)
|
form.mutators.setValue(addressName, val)
|
||||||
}}
|
}}
|
||||||
|
// eslint-disable-next-line
|
||||||
|
// @ts-ignore
|
||||||
inputAdornment={
|
inputAdornment={
|
||||||
noErrorsOn(addressName, errors) && {
|
noErrorsOn(addressName, errors) && {
|
||||||
endAdornment: (
|
endAdornment: (
|
||||||
|
@ -217,18 +219,21 @@ const SafeOwners = (props) => {
|
||||||
|
|
||||||
const SafeOwnersForm = withStyles(styles as any)(withRouter(SafeOwners))
|
const SafeOwnersForm = withStyles(styles as any)(withRouter(SafeOwners))
|
||||||
|
|
||||||
const SafeOwnersPage = ({ updateInitialProps }) => (controls, { errors, form, values }) => (
|
const SafeOwnersPage = ({ updateInitialProps }) =>
|
||||||
<>
|
function OpenSafeOwnersPage(controls, { errors, form, values }) {
|
||||||
<OpenPaper controls={controls} padding={false}>
|
return (
|
||||||
<SafeOwnersForm
|
<>
|
||||||
errors={errors}
|
<OpenPaper controls={controls} padding={false}>
|
||||||
form={form}
|
<SafeOwnersForm
|
||||||
otherAccounts={getAccountsFrom(values)}
|
errors={errors}
|
||||||
updateInitialProps={updateInitialProps}
|
form={form}
|
||||||
values={values}
|
otherAccounts={getAccountsFrom(values)}
|
||||||
/>
|
updateInitialProps={updateInitialProps}
|
||||||
</OpenPaper>
|
values={values}
|
||||||
</>
|
/>
|
||||||
)
|
</OpenPaper>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default SafeOwnersPage
|
export default SafeOwnersPage
|
||||||
|
|
|
@ -161,7 +161,7 @@ const Open = ({ addSafe, network, provider, userAccount }: OwnProps): React.Reac
|
||||||
pathname: `${SAFELIST_ADDRESS}/${safeProps.address}/balances`,
|
pathname: `${SAFELIST_ADDRESS}/${safeProps.address}/balances`,
|
||||||
state: {
|
state: {
|
||||||
name,
|
name,
|
||||||
tx: pendingCreation.txHash,
|
tx: pendingCreation?.txHash,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,7 +177,7 @@ const Open = ({ addSafe, network, provider, userAccount }: OwnProps): React.Reac
|
||||||
|
|
||||||
const onRetry = async () => {
|
const onRetry = async () => {
|
||||||
const values = await loadFromStorage<{ txHash: string }>(SAFE_PENDING_CREATION_STORAGE_KEY)
|
const values = await loadFromStorage<{ txHash: string }>(SAFE_PENDING_CREATION_STORAGE_KEY)
|
||||||
delete values.txHash
|
delete values?.txHash
|
||||||
await saveToStorage(SAFE_PENDING_CREATION_STORAGE_KEY, values)
|
await saveToStorage(SAFE_PENDING_CREATION_STORAGE_KEY, values)
|
||||||
setSafeCreationPendingInfo(values)
|
setSafeCreationPendingInfo(values)
|
||||||
createSafeProxy()
|
createSafeProxy()
|
||||||
|
|
|
@ -105,7 +105,7 @@ const BackButton = styled(Button)`
|
||||||
const SafeDeployment = ({ creationTxHash, onCancel, onRetry, onSuccess, provider, submittedPromise }: any) => {
|
const SafeDeployment = ({ creationTxHash, onCancel, onRetry, onSuccess, provider, submittedPromise }: any) => {
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [stepIndex, setStepIndex] = useState(0)
|
const [stepIndex, setStepIndex] = useState(0)
|
||||||
const [safeCreationTxHash, setSafeCreationTxHash] = useState()
|
const [safeCreationTxHash, setSafeCreationTxHash] = useState('')
|
||||||
const [createdSafeAddress, setCreatedSafeAddress] = useState()
|
const [createdSafeAddress, setCreatedSafeAddress] = useState()
|
||||||
|
|
||||||
const [error, setError] = useState(false)
|
const [error, setError] = useState(false)
|
||||||
|
@ -242,7 +242,7 @@ const SafeDeployment = ({ creationTxHash, onCancel, onRetry, onSuccess, provider
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let interval
|
let interval
|
||||||
|
|
||||||
const awaitUntilSafeIsDeployed = async () => {
|
const awaitUntilSafeIsDeployed = async (safeCreationTxHash: string) => {
|
||||||
try {
|
try {
|
||||||
const web3 = getWeb3()
|
const web3 = getWeb3()
|
||||||
const receipt = await web3.eth.getTransactionReceipt(safeCreationTxHash)
|
const receipt = await web3.eth.getTransactionReceipt(safeCreationTxHash)
|
||||||
|
@ -283,7 +283,9 @@ const SafeDeployment = ({ creationTxHash, onCancel, onRetry, onSuccess, provider
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
awaitUntilSafeIsDeployed()
|
if (typeof safeCreationTxHash === 'string') {
|
||||||
|
awaitUntilSafeIsDeployed(safeCreationTxHash)
|
||||||
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(interval)
|
clearInterval(interval)
|
||||||
|
@ -294,7 +296,7 @@ const SafeDeployment = ({ creationTxHash, onCancel, onRetry, onSuccess, provider
|
||||||
return <Loader size="sm" />
|
return <Loader size="sm" />
|
||||||
}
|
}
|
||||||
|
|
||||||
let FooterComponent = null
|
let FooterComponent
|
||||||
if (error) {
|
if (error) {
|
||||||
FooterComponent = ErrorFooter
|
FooterComponent = ErrorFooter
|
||||||
} else if (steps[stepIndex].footerComponent) {
|
} else if (steps[stepIndex].footerComponent) {
|
||||||
|
|
|
@ -50,7 +50,7 @@ const AddressBookTable = ({ classes }) => {
|
||||||
const safesList = useSelector(safesListSelector)
|
const safesList = useSelector(safesListSelector)
|
||||||
const entryAddressToEditOrCreateNew = useSelector(addressBookQueryParamsSelector)
|
const entryAddressToEditOrCreateNew = useSelector(addressBookQueryParamsSelector)
|
||||||
const addressBook = useSelector(getAddressBook)
|
const addressBook = useSelector(getAddressBook)
|
||||||
const [selectedEntry, setSelectedEntry] = useState(null)
|
const [selectedEntry, setSelectedEntry] = useState<any>(null)
|
||||||
const [editCreateEntryModalOpen, setEditCreateEntryModalOpen] = useState(false)
|
const [editCreateEntryModalOpen, setEditCreateEntryModalOpen] = useState(false)
|
||||||
const [deleteEntryModalOpen, setDeleteEntryModalOpen] = useState(false)
|
const [deleteEntryModalOpen, setDeleteEntryModalOpen] = useState(false)
|
||||||
const [sendFundsModalOpen, setSendFundsModalOpen] = useState(false)
|
const [sendFundsModalOpen, setSendFundsModalOpen] = useState(false)
|
||||||
|
@ -70,7 +70,7 @@ const AddressBookTable = ({ classes }) => {
|
||||||
if (entryAddressToEditOrCreateNew) {
|
if (entryAddressToEditOrCreateNew) {
|
||||||
const checksumEntryAdd = checksumAddress(entryAddressToEditOrCreateNew)
|
const checksumEntryAdd = checksumAddress(entryAddressToEditOrCreateNew)
|
||||||
const key = addressBook.findKey((entry) => entry.address === checksumEntryAdd)
|
const key = addressBook.findKey((entry) => entry.address === checksumEntryAdd)
|
||||||
if (key >= 0) {
|
if (key && key >= 0) {
|
||||||
// Edit old entry
|
// Edit old entry
|
||||||
const value = addressBook.get(key)
|
const value = addressBook.get(key)
|
||||||
setSelectedEntry({ entry: value, index: key })
|
setSelectedEntry({ entry: value, index: key })
|
||||||
|
|
|
@ -38,7 +38,7 @@ const Transactions = (): React.ReactElement => {
|
||||||
{transactionsByPage.map((tx: Transaction, index) => {
|
{transactionsByPage.map((tx: Transaction, index) => {
|
||||||
let txHash = ''
|
let txHash = ''
|
||||||
if ('transactionHash' in tx) {
|
if ('transactionHash' in tx) {
|
||||||
txHash = tx.transactionHash
|
txHash = tx.transactionHash as string
|
||||||
}
|
}
|
||||||
if ('txHash' in tx) {
|
if ('txHash' in tx) {
|
||||||
txHash = tx.txHash
|
txHash = tx.txHash
|
||||||
|
|
|
@ -14,7 +14,7 @@ const AppAgreement = (): React.ReactElement => {
|
||||||
const { visited } = useFormState({ subscription: { visited: true } })
|
const { visited } = useFormState({ subscription: { visited: true } })
|
||||||
|
|
||||||
// trick to prevent having the field validated by default. Not sure why this happens in this form
|
// trick to prevent having the field validated by default. Not sure why this happens in this form
|
||||||
const validate = !visited.agreementAccepted ? undefined : required
|
const validate = !visited?.agreementAccepted ? undefined : required
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Field
|
<Field
|
||||||
|
|
|
@ -28,7 +28,7 @@ export const appUrlResolver = createDecorator({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const AppInfoUpdater = ({ onAppInfo }: { onAppInfo: (appInfo: SafeApp) => void }): React.ReactElement => {
|
export const AppInfoUpdater = ({ onAppInfo }: { onAppInfo: (appInfo: SafeApp) => void }): null => {
|
||||||
const {
|
const {
|
||||||
input: { value: appUrl },
|
input: { value: appUrl },
|
||||||
} = useField('appUrl', { subscription: { value: true } })
|
} = useField('appUrl', { subscription: { value: true } })
|
||||||
|
@ -52,7 +52,7 @@ const AppUrl = ({ appList }: { appList: SafeApp[] }): React.ReactElement => {
|
||||||
const { visited } = useFormState({ subscription: { visited: true } })
|
const { visited } = useFormState({ subscription: { visited: true } })
|
||||||
|
|
||||||
// trick to prevent having the field validated by default. Not sure why this happens in this form
|
// trick to prevent having the field validated by default. Not sure why this happens in this form
|
||||||
const validate = !visited.appUrl ? undefined : composeValidators(required, validateUrl, uniqueApp(appList))
|
const validate = !visited?.appUrl ? undefined : composeValidators(required, validateUrl, uniqueApp(appList))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Field label="App URL" name="appUrl" placeholder="App URL" type="text" component={TextField} validate={validate} />
|
<Field label="App URL" name="appUrl" placeholder="App URL" type="text" component={TextField} validate={validate} />
|
||||||
|
|
|
@ -9,14 +9,14 @@ interface SubmitButtonStatusProps {
|
||||||
onSubmitButtonStatusChange: (disabled: boolean) => void
|
onSubmitButtonStatusChange: (disabled: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const SubmitButtonStatus = ({ appInfo, onSubmitButtonStatusChange }: SubmitButtonStatusProps): React.ReactElement => {
|
const SubmitButtonStatus = ({ appInfo, onSubmitButtonStatusChange }: SubmitButtonStatusProps): null => {
|
||||||
const { valid, validating, visited } = useFormState({
|
const { valid, validating, visited } = useFormState({
|
||||||
subscription: { valid: true, validating: true, visited: true },
|
subscription: { valid: true, validating: true, visited: true },
|
||||||
})
|
})
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
// if non visited, fields were not evaluated yet. Then, the default value is considered invalid
|
// if non visited, fields were not evaluated yet. Then, the default value is considered invalid
|
||||||
const fieldsVisited = visited.agreementAccepted && visited.appUrl
|
const fieldsVisited = visited?.agreementAccepted && visited.appUrl
|
||||||
|
|
||||||
onSubmitButtonStatusChange(validating || !valid || !fieldsVisited || !isAppManifestValid(appInfo))
|
onSubmitButtonStatusChange(validating || !valid || !fieldsVisited || !isAppManifestValid(appInfo))
|
||||||
}, [validating, valid, visited, onSubmitButtonStatusChange, appInfo])
|
}, [validating, valid, visited, onSubmitButtonStatusChange, appInfo])
|
||||||
|
|
|
@ -40,7 +40,7 @@ const INITIAL_VALUES: AddAppFormValues = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const APP_INFO: SafeApp = {
|
const APP_INFO: SafeApp = {
|
||||||
id: undefined,
|
id: '',
|
||||||
url: '',
|
url: '',
|
||||||
name: '',
|
name: '',
|
||||||
iconUrl: appsIconSvg,
|
iconUrl: appsIconSvg,
|
||||||
|
|
|
@ -54,7 +54,7 @@ const AppFrame = forwardRef<HTMLIFrameElement, AppFrameProps>(function AppFrameC
|
||||||
const redirectToBalance = () => history.push(`${SAFELIST_ADDRESS}/${safeAddress}/balances`)
|
const redirectToBalance = () => history.push(`${SAFELIST_ADDRESS}/${safeAddress}/balances`)
|
||||||
|
|
||||||
if (!selectedApp) {
|
if (!selectedApp) {
|
||||||
return null
|
return <div />
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!consentReceived) {
|
if (!consentReceived) {
|
||||||
|
|
|
@ -31,7 +31,7 @@ const isTxValid = (t: Transaction): boolean => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const isAddressValid = mustBeEthereumAddress(t.to) === undefined
|
const isAddressValid = mustBeEthereumAddress(t.to) === undefined
|
||||||
return isAddressValid && t.data && typeof t.data === 'string'
|
return isAddressValid && !!t.data && typeof t.data === 'string'
|
||||||
}
|
}
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
|
@ -84,7 +84,7 @@ const ConfirmTransactionModal = ({
|
||||||
onCancel,
|
onCancel,
|
||||||
onUserConfirm,
|
onUserConfirm,
|
||||||
onClose,
|
onClose,
|
||||||
}: OwnProps): React.ReactElement => {
|
}: OwnProps): React.ReactElement | null => {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return null
|
return null
|
||||||
|
|
|
@ -14,6 +14,8 @@ type Props = {
|
||||||
onAppRemoved: (appId: string) => void
|
onAppRemoved: (appId: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AppListItem = SafeApp & { checked: boolean }
|
||||||
|
|
||||||
const ManageApps = ({ appList, onAppAdded, onAppToggle, onAppRemoved }: Props): React.ReactElement => {
|
const ManageApps = ({ appList, onAppAdded, onAppToggle, onAppRemoved }: Props): React.ReactElement => {
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
const [isSubmitDisabled, setIsSubmitDisabled] = useState(true)
|
const [isSubmitDisabled, setIsSubmitDisabled] = useState(true)
|
||||||
|
@ -28,7 +30,7 @@ const ManageApps = ({ appList, onAppAdded, onAppToggle, onAppRemoved }: Props):
|
||||||
|
|
||||||
const closeModal = () => setIsOpen(false)
|
const closeModal = () => setIsOpen(false)
|
||||||
|
|
||||||
const getItemList = () =>
|
const getItemList = (): AppListItem[] =>
|
||||||
appList.map((a) => {
|
appList.map((a) => {
|
||||||
return { ...a, checked: !a.disabled }
|
return { ...a, checked: !a.disabled }
|
||||||
})
|
})
|
||||||
|
|
|
@ -42,7 +42,7 @@ const useAppList = (): UseAppListReturnType => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let apps = []
|
let apps: SafeApp[] = []
|
||||||
// using the appURL to recover app info
|
// using the appURL to recover app info
|
||||||
for (let index = 0; index < list.length; index++) {
|
for (let index = 0; index < list.length; index++) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -44,7 +44,7 @@ const useIframeMessageHandler = (
|
||||||
selectedApp: SafeApp | undefined,
|
selectedApp: SafeApp | undefined,
|
||||||
openConfirmationModal: (txs: Transaction[], requestId: RequestId) => void,
|
openConfirmationModal: (txs: Transaction[], requestId: RequestId) => void,
|
||||||
closeModal: () => void,
|
closeModal: () => void,
|
||||||
iframeRef: MutableRefObject<HTMLIFrameElement>,
|
iframeRef: MutableRefObject<HTMLIFrameElement | null>,
|
||||||
): ReturnType => {
|
): ReturnType => {
|
||||||
const { enqueueSnackbar, closeSnackbar } = useSnackbar()
|
const { enqueueSnackbar, closeSnackbar } = useSnackbar()
|
||||||
const safeName = useSelector(safeNameSelector)
|
const safeName = useSelector(safeNameSelector)
|
||||||
|
@ -60,8 +60,8 @@ const useIframeMessageHandler = (
|
||||||
requestId: requestId || Math.trunc(window.performance.now()),
|
requestId: requestId || Math.trunc(window.performance.now()),
|
||||||
}
|
}
|
||||||
|
|
||||||
if (iframeRef?.current && selectedApp) {
|
if (iframeRef && selectedApp) {
|
||||||
iframeRef.current.contentWindow.postMessage(requestWithMessage, selectedApp.url)
|
iframeRef.current?.contentWindow?.postMessage(requestWithMessage, selectedApp.url)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[iframeRef, selectedApp],
|
[iframeRef, selectedApp],
|
||||||
|
@ -77,7 +77,9 @@ const useIframeMessageHandler = (
|
||||||
|
|
||||||
switch (msg.data.messageId) {
|
switch (msg.data.messageId) {
|
||||||
case SDK_MESSAGES.SEND_TRANSACTIONS: {
|
case SDK_MESSAGES.SEND_TRANSACTIONS: {
|
||||||
openConfirmationModal(msg.data.data, requestId)
|
if (msg.data.data) {
|
||||||
|
openConfirmationModal(msg.data.data, requestId)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,9 +87,9 @@ const useIframeMessageHandler = (
|
||||||
const message = {
|
const message = {
|
||||||
messageId: INTERFACE_MESSAGES.ON_SAFE_INFO,
|
messageId: INTERFACE_MESSAGES.ON_SAFE_INFO,
|
||||||
data: {
|
data: {
|
||||||
safeAddress,
|
safeAddress: safeAddress as string,
|
||||||
network: network,
|
network,
|
||||||
ethBalance,
|
ethBalance: ethBalance as string,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +106,7 @@ const useIframeMessageHandler = (
|
||||||
if (message.origin === window.origin) {
|
if (message.origin === window.origin) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!selectedApp.url.includes(message.origin)) {
|
if (!selectedApp?.url.includes(message.origin)) {
|
||||||
console.error(`ThirdPartyApp: A message was received from an unknown origin ${message.origin}`)
|
console.error(`ThirdPartyApp: A message was received from an unknown origin ${message.origin}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import styled, { css } from 'styled-components'
|
||||||
import ManageApps from './components/ManageApps'
|
import ManageApps from './components/ManageApps'
|
||||||
import AppFrame from './components/AppFrame'
|
import AppFrame from './components/AppFrame'
|
||||||
import { useAppList } from './hooks/useAppList'
|
import { useAppList } from './hooks/useAppList'
|
||||||
|
import { SafeApp } from './types.d'
|
||||||
|
|
||||||
import LCL from 'src/components/ListContentLayout'
|
import LCL from 'src/components/ListContentLayout'
|
||||||
import { networkSelector } from 'src/logic/wallets/store/selectors'
|
import { networkSelector } from 'src/logic/wallets/store/selectors'
|
||||||
|
@ -63,7 +64,7 @@ const Apps = (): React.ReactElement => {
|
||||||
const [confirmTransactionModal, setConfirmTransactionModal] = useState<ConfirmTransactionModalState>(
|
const [confirmTransactionModal, setConfirmTransactionModal] = useState<ConfirmTransactionModalState>(
|
||||||
INITIAL_CONFIRM_TX_MODAL_STATE,
|
INITIAL_CONFIRM_TX_MODAL_STATE,
|
||||||
)
|
)
|
||||||
const iframeRef = useRef<HTMLIFrameElement>()
|
const iframeRef = useRef<HTMLIFrameElement>(null)
|
||||||
|
|
||||||
const { trackEvent } = useAnalytics()
|
const { trackEvent } = useAnalytics()
|
||||||
const granted = useSelector(grantedSelector)
|
const granted = useSelector(grantedSelector)
|
||||||
|
@ -146,14 +147,14 @@ const Apps = (): React.ReactElement => {
|
||||||
sendMessageToIframe({
|
sendMessageToIframe({
|
||||||
messageId: INTERFACE_MESSAGES.ON_SAFE_INFO,
|
messageId: INTERFACE_MESSAGES.ON_SAFE_INFO,
|
||||||
data: {
|
data: {
|
||||||
safeAddress,
|
safeAddress: safeAddress as string,
|
||||||
network,
|
network,
|
||||||
ethBalance,
|
ethBalance: ethBalance as string,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}, [ethBalance, network, safeAddress, selectedApp, sendMessageToIframe])
|
}, [ethBalance, network, safeAddress, selectedApp, sendMessageToIframe])
|
||||||
|
|
||||||
if (loadingAppList || !appList.length) {
|
if (loadingAppList || !appList.length || !safeAddress) {
|
||||||
return (
|
return (
|
||||||
<LoadingContainer>
|
<LoadingContainer>
|
||||||
<Loader size="md" />
|
<Loader size="md" />
|
||||||
|
@ -199,10 +200,10 @@ const Apps = (): React.ReactElement => {
|
||||||
</CenteredMT>
|
</CenteredMT>
|
||||||
<ConfirmTransactionModal
|
<ConfirmTransactionModal
|
||||||
isOpen={confirmTransactionModal.isOpen}
|
isOpen={confirmTransactionModal.isOpen}
|
||||||
app={selectedApp}
|
app={selectedApp as SafeApp}
|
||||||
safeAddress={safeAddress}
|
safeAddress={safeAddress}
|
||||||
ethBalance={ethBalance}
|
ethBalance={ethBalance as string}
|
||||||
safeName={safeName}
|
safeName={safeName as string}
|
||||||
txs={confirmTransactionModal.txs}
|
txs={confirmTransactionModal.txs}
|
||||||
onCancel={closeConfirmationModal}
|
onCancel={closeConfirmationModal}
|
||||||
onClose={closeConfirmationModal}
|
onClose={closeConfirmationModal}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export type SafeApp = {
|
export type SafeApp = {
|
||||||
id: string | undefined
|
id: string
|
||||||
url: string
|
url: string
|
||||||
name: string
|
name: string
|
||||||
iconUrl: string
|
iconUrl: string
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import memoize from 'lodash.memoize'
|
import memoize from 'lodash.memoize'
|
||||||
|
|
||||||
import { SafeApp } from './types'
|
import { SafeApp } from './types.d'
|
||||||
|
|
||||||
import { getGnosisSafeAppsUrl } from 'src/config/index'
|
import { getGnosisSafeAppsUrl } from 'src/config/index'
|
||||||
import { getContentFromENS } from 'src/logic/wallets/getWeb3'
|
import { getContentFromENS } from 'src/logic/wallets/getWeb3'
|
||||||
|
@ -62,8 +62,8 @@ export const isAppManifestValid = (appInfo: SafeApp): boolean =>
|
||||||
!appInfo.error
|
!appInfo.error
|
||||||
|
|
||||||
export const getAppInfoFromUrl = memoize(
|
export const getAppInfoFromUrl = memoize(
|
||||||
async (appUrl?: string): Promise<SafeApp> => {
|
async (appUrl: string): Promise<SafeApp> => {
|
||||||
let res = { id: undefined, url: appUrl, name: 'unknown', iconUrl: appsIconSvg, error: true, description: '' }
|
let res = { id: '', url: appUrl, name: 'unknown', iconUrl: appsIconSvg, error: true, description: '' }
|
||||||
|
|
||||||
if (!appUrl?.length) {
|
if (!appUrl?.length) {
|
||||||
return res
|
return res
|
||||||
|
|
|
@ -79,7 +79,7 @@ const Collectibles = (): React.ReactElement => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const [selectedToken, setSelectedToken] = React.useState({})
|
const [selectedToken, setSelectedToken] = React.useState({})
|
||||||
const [sendNFTsModalOpen, setSendNFTsModalOpen] = React.useState(false)
|
const [sendNFTsModalOpen, setSendNFTsModalOpen] = React.useState(false)
|
||||||
const { address, ethBalance, name } = useSelector(safeSelector)
|
const { address, ethBalance, name } = useSelector(safeSelector) || {}
|
||||||
const nftTokens = useSelector(nftTokensSelector)
|
const nftTokens = useSelector(nftTokensSelector)
|
||||||
const activeAssetsList = useSelector(activeNftAssetsListSelector)
|
const activeAssetsList = useSelector(activeNftAssetsListSelector)
|
||||||
const { trackEvent } = useAnalytics()
|
const { trackEvent } = useAnalytics()
|
||||||
|
|
|
@ -5,7 +5,7 @@ import AddressInfo from 'src/components/AddressInfo'
|
||||||
import { safeSelector } from 'src/logic/safe/store/selectors'
|
import { safeSelector } from 'src/logic/safe/store/selectors'
|
||||||
|
|
||||||
const SafeInfo = () => {
|
const SafeInfo = () => {
|
||||||
const { address: safeAddress = '', ethBalance, name: safeName } = useSelector(safeSelector)
|
const { address: safeAddress = '', ethBalance, name: safeName } = useSelector(safeSelector) || {}
|
||||||
return <AddressInfo ethBalance={ethBalance} safeAddress={safeAddress} safeName={safeName} />
|
return <AddressInfo ethBalance={ethBalance} safeAddress={safeAddress} safeName={safeName} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,9 +22,9 @@ export interface AddressBookProps {
|
||||||
pristine: boolean
|
pristine: boolean
|
||||||
recipientAddress?: string
|
recipientAddress?: string
|
||||||
setSelectedEntry: (
|
setSelectedEntry: (
|
||||||
entry: { address?: string; name?: string } | React.SetStateAction<{ address: string; name: string }>,
|
entry: { address?: string; name?: string } | React.SetStateAction<{ address: string; name: string }> | null,
|
||||||
) => void
|
) => void
|
||||||
setIsValidAddress: (valid?: boolean) => void
|
setIsValidAddress: (valid: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
@ -157,7 +157,7 @@ const AddressBookInput = ({
|
||||||
optionsArray.filter((item) => {
|
optionsArray.filter((item) => {
|
||||||
const inputLowerCase = inputValue.toLowerCase()
|
const inputLowerCase = inputValue.toLowerCase()
|
||||||
const foundName = item.name.toLowerCase().includes(inputLowerCase)
|
const foundName = item.name.toLowerCase().includes(inputLowerCase)
|
||||||
const foundAddress = item.address.toLowerCase().includes(inputLowerCase)
|
const foundAddress = item.address?.toLowerCase().includes(inputLowerCase)
|
||||||
return foundName || foundAddress
|
return foundName || foundAddress
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -212,6 +212,11 @@ const AddressBookInput = ({
|
||||||
)}
|
)}
|
||||||
renderOption={(adbkEntry) => {
|
renderOption={(adbkEntry) => {
|
||||||
const { address, name } = adbkEntry
|
const { address, name } = adbkEntry
|
||||||
|
|
||||||
|
if (!address) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.itemOptionList}>
|
<div className={classes.itemOptionList}>
|
||||||
<div className={classes.identicon}>
|
<div className={classes.identicon}>
|
||||||
|
|
|
@ -62,8 +62,8 @@ const useStyles = makeStyles({
|
||||||
|
|
||||||
const ChooseTxType = ({ onClose, recipientAddress, setActiveScreen }) => {
|
const ChooseTxType = ({ onClose, recipientAddress, setActiveScreen }) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const { featuresEnabled } = useSelector(safeSelector)
|
const { featuresEnabled } = useSelector(safeSelector) || {}
|
||||||
const erc721Enabled = featuresEnabled.includes('ERC721')
|
const erc721Enabled = featuresEnabled?.includes('ERC721')
|
||||||
const [disableContractInteraction, setDisableContractInteraction] = React.useState(!!recipientAddress)
|
const [disableContractInteraction, setDisableContractInteraction] = React.useState(!!recipientAddress)
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
mustBeEthereumAddress,
|
mustBeEthereumAddress,
|
||||||
mustBeEthereumContractAddress,
|
mustBeEthereumContractAddress,
|
||||||
required,
|
required,
|
||||||
|
Validator,
|
||||||
} from 'src/components/forms/validator'
|
} from 'src/components/forms/validator'
|
||||||
import Col from 'src/components/layout/Col'
|
import Col from 'src/components/layout/Col'
|
||||||
import Row from 'src/components/layout/Row'
|
import Row from 'src/components/layout/Row'
|
||||||
|
@ -34,8 +35,12 @@ const EthAddressInput = ({
|
||||||
text,
|
text,
|
||||||
}: EthAddressInputProps): React.ReactElement => {
|
}: EthAddressInputProps): React.ReactElement => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const validatorsList = [isRequired && required, mustBeEthereumAddress, isContract && mustBeEthereumContractAddress]
|
const validatorsList = [
|
||||||
const validate = composeValidators(...validatorsList.filter((_) => _))
|
isRequired && required,
|
||||||
|
mustBeEthereumAddress,
|
||||||
|
isContract && mustBeEthereumContractAddress,
|
||||||
|
] as Validator[]
|
||||||
|
const validate = composeValidators(...validatorsList.filter((validator) => validator))
|
||||||
const { pristine } = useFormState({ subscription: { pristine: true } })
|
const { pristine } = useFormState({ subscription: { pristine: true } })
|
||||||
const {
|
const {
|
||||||
input: { value },
|
input: { value },
|
||||||
|
|
|
@ -20,14 +20,18 @@ const useStyles = makeStyles(styles)
|
||||||
interface EthValueProps {
|
interface EthValueProps {
|
||||||
onSetMax: (ethBalance: string) => void
|
onSetMax: (ethBalance: string) => void
|
||||||
}
|
}
|
||||||
const EthValue = ({ onSetMax }: EthValueProps) => {
|
const EthValue = ({ onSetMax }: EthValueProps): React.ReactElement | null => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const { ethBalance } = useSelector(safeSelector)
|
const { ethBalance } = useSelector(safeSelector) || {}
|
||||||
const {
|
const {
|
||||||
input: { value: method },
|
input: { value: method },
|
||||||
} = useField('selectedMethod', { subscription: { value: true } })
|
} = useField('selectedMethod', { subscription: { value: true } })
|
||||||
const disabled = !isPayable(method)
|
const disabled = !isPayable(method)
|
||||||
|
|
||||||
|
if (!ethBalance) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return disabled ? null : (
|
return disabled ? null : (
|
||||||
<>
|
<>
|
||||||
<Row className={classes.fullWidth} margin="xs">
|
<Row className={classes.fullWidth} margin="xs">
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { NO_CONTRACT } from 'src/routes/safe/components/Balances/SendModal/scree
|
||||||
import CheckIcon from 'src/routes/safe/components/CurrencyDropdown/img/check.svg'
|
import CheckIcon from 'src/routes/safe/components/CurrencyDropdown/img/check.svg'
|
||||||
import { useDropdownStyles } from 'src/routes/safe/components/CurrencyDropdown/style'
|
import { useDropdownStyles } from 'src/routes/safe/components/CurrencyDropdown/style'
|
||||||
import { DropdownListTheme } from 'src/theme/mui'
|
import { DropdownListTheme } from 'src/theme/mui'
|
||||||
import { extractUsefulMethods } from 'src/logic/contractInteraction/sources/ABIService'
|
import { extractUsefulMethods, AbiItemExtended } from 'src/logic/contractInteraction/sources/ABIService'
|
||||||
|
|
||||||
const MENU_WIDTH = '452px'
|
const MENU_WIDTH = '452px'
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ interface MethodsDropdownProps {
|
||||||
onChange: (method: AbiItem) => void
|
onChange: (method: AbiItem) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const MethodsDropdown = ({ onChange }: MethodsDropdownProps) => {
|
const MethodsDropdown = ({ onChange }: MethodsDropdownProps): React.ReactElement | null => {
|
||||||
const classes = useDropdownStyles({ buttonWidth: MENU_WIDTH })
|
const classes = useDropdownStyles({ buttonWidth: MENU_WIDTH })
|
||||||
const {
|
const {
|
||||||
input: { value: abi },
|
input: { value: abi },
|
||||||
|
@ -34,8 +34,8 @@ const MethodsDropdown = ({ onChange }: MethodsDropdownProps) => {
|
||||||
initialValues: { selectedMethod: selectedMethodByDefault },
|
initialValues: { selectedMethod: selectedMethodByDefault },
|
||||||
} = useFormState({ subscription: { initialValues: true } })
|
} = useFormState({ subscription: { initialValues: true } })
|
||||||
const [selectedMethod, setSelectedMethod] = React.useState(selectedMethodByDefault ? selectedMethodByDefault : {})
|
const [selectedMethod, setSelectedMethod] = React.useState(selectedMethodByDefault ? selectedMethodByDefault : {})
|
||||||
const [methodsList, setMethodsList] = React.useState([])
|
const [methodsList, setMethodsList] = React.useState<AbiItemExtended[]>([])
|
||||||
const [methodsListFiltered, setMethodsListFiltered] = React.useState([])
|
const [methodsListFiltered, setMethodsListFiltered] = React.useState<AbiItemExtended[]>([])
|
||||||
const [anchorEl, setAnchorEl] = React.useState(null)
|
const [anchorEl, setAnchorEl] = React.useState(null)
|
||||||
const [searchParams, setSearchParams] = React.useState('')
|
const [searchParams, setSearchParams] = React.useState('')
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ const MethodsDropdown = ({ onChange }: MethodsDropdownProps) => {
|
||||||
}, [abi])
|
}, [abi])
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
setMethodsListFiltered(methodsList.filter(({ name }) => name.toLowerCase().includes(searchParams.toLowerCase())))
|
setMethodsListFiltered(methodsList.filter(({ name }) => name?.toLowerCase().includes(searchParams.toLowerCase())))
|
||||||
}, [methodsList, searchParams])
|
}, [methodsList, searchParams])
|
||||||
|
|
||||||
const handleClick = (event) => {
|
const handleClick = (event) => {
|
||||||
|
|
|
@ -15,7 +15,7 @@ type Props = {
|
||||||
placeholder: string
|
placeholder: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const InputComponent = ({ type, keyValue, placeholder }: Props): React.ReactElement => {
|
const InputComponent = ({ type, keyValue, placeholder }: Props): React.ReactElement | null => {
|
||||||
if (!type) {
|
if (!type) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,18 +7,18 @@ import InputComponent from './InputComponent'
|
||||||
import { generateFormFieldKey } from '../utils'
|
import { generateFormFieldKey } from '../utils'
|
||||||
import { AbiItemExtended } from 'src/logic/contractInteraction/sources/ABIService'
|
import { AbiItemExtended } from 'src/logic/contractInteraction/sources/ABIService'
|
||||||
|
|
||||||
const RenderInputParams = (): React.ReactElement => {
|
const RenderInputParams = (): React.ReactElement | null => {
|
||||||
const {
|
const {
|
||||||
meta: { valid: validABI },
|
meta: { valid: validABI },
|
||||||
} = useField('abi', { subscription: { valid: true, value: true } })
|
} = useField('abi', { subscription: { valid: true, value: true } })
|
||||||
const {
|
const {
|
||||||
input: { value: method },
|
input: { value: method },
|
||||||
}: { input: { value: AbiItemExtended } } = useField('selectedMethod', { subscription: { value: true } })
|
}: { input: { value: AbiItemExtended } } = useField('selectedMethod', { subscription: { value: true } })
|
||||||
const renderInputs = validABI && !!method && method.inputs.length
|
const renderInputs = validABI && !!method && method.inputs?.length
|
||||||
|
|
||||||
return !renderInputs ? null : (
|
return !renderInputs ? null : (
|
||||||
<>
|
<>
|
||||||
{method.inputs.map(({ name, type }, index) => {
|
{method.inputs?.map(({ name, type }, index) => {
|
||||||
const placeholder = name ? `${name} (${type})` : type
|
const placeholder = name ? `${name} (${type})` : type
|
||||||
const key = generateFormFieldKey(type, method.signatureHash, index)
|
const key = generateFormFieldKey(type, method.signatureHash, index)
|
||||||
|
|
||||||
|
|
|
@ -40,11 +40,11 @@ type Props = {
|
||||||
tx: TransactionReviewType
|
tx: TransactionReviewType
|
||||||
}
|
}
|
||||||
|
|
||||||
const ContractInteractionReview = ({ onClose, onPrev, tx }: Props) => {
|
const ContractInteractionReview = ({ onClose, onPrev, tx }: Props): React.ReactElement => {
|
||||||
const { enqueueSnackbar, closeSnackbar } = useSnackbar()
|
const { enqueueSnackbar, closeSnackbar } = useSnackbar()
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const { address: safeAddress } = useSelector(safeSelector)
|
const { address: safeAddress } = useSelector(safeSelector) || {}
|
||||||
const [gasCosts, setGasCosts] = useState('< 0.001')
|
const [gasCosts, setGasCosts] = useState('< 0.001')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -54,7 +54,7 @@ const ContractInteractionReview = ({ onClose, onPrev, tx }: Props) => {
|
||||||
const { fromWei, toBN } = getWeb3().utils
|
const { fromWei, toBN } = getWeb3().utils
|
||||||
const txData = tx.data ? tx.data.trim() : ''
|
const txData = tx.data ? tx.data.trim() : ''
|
||||||
|
|
||||||
const estimatedGasCosts = await estimateTxGasCosts(safeAddress, tx.contractAddress, txData)
|
const estimatedGasCosts = await estimateTxGasCosts(safeAddress as string, tx.contractAddress as string, txData)
|
||||||
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
|
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
|
||||||
const formattedGasCosts = formatAmount(gasCostsAsEth)
|
const formattedGasCosts = formatAmount(gasCostsAsEth)
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ const ContractInteractionReview = ({ onClose, onPrev, tx }: Props) => {
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Row>
|
</Row>
|
||||||
<Row align="center" margin="md">
|
<Row align="center" margin="md">
|
||||||
<AddressInfo safeAddress={tx.contractAddress} />
|
<AddressInfo safeAddress={tx.contractAddress as string} />
|
||||||
</Row>
|
</Row>
|
||||||
<Row margin="xs">
|
<Row margin="xs">
|
||||||
<Paragraph color="disabled" noMargin size="md" style={{ letterSpacing: '-0.5px' }}>
|
<Paragraph color="disabled" noMargin size="md" style={{ letterSpacing: '-0.5px' }}>
|
||||||
|
@ -129,11 +129,11 @@ const ContractInteractionReview = ({ onClose, onPrev, tx }: Props) => {
|
||||||
</Row>
|
</Row>
|
||||||
<Row align="center" margin="md">
|
<Row align="center" margin="md">
|
||||||
<Paragraph className={classes.value} size="md" style={{ margin: 0 }}>
|
<Paragraph className={classes.value} size="md" style={{ margin: 0 }}>
|
||||||
{tx.selectedMethod.name}
|
{tx.selectedMethod?.name}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Row>
|
</Row>
|
||||||
{tx.selectedMethod.inputs.map(({ name, type }, index) => {
|
{tx.selectedMethod?.inputs?.map(({ name, type }, index) => {
|
||||||
const key = generateFormFieldKey(type, tx.selectedMethod.signatureHash, index)
|
const key = generateFormFieldKey(type, tx.selectedMethod?.signatureHash || '', index)
|
||||||
const value: string = getValueFromTxInputs(key, type, tx)
|
const value: string = getValueFromTxInputs(key, type, tx)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import IconButton from '@material-ui/core/IconButton'
|
import IconButton from '@material-ui/core/IconButton'
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import Close from '@material-ui/icons/Close'
|
import Close from '@material-ui/icons/Close'
|
||||||
import { useSnackbar } from 'notistack'
|
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
|
||||||
|
@ -39,10 +38,9 @@ type Props = {
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const ReviewCustomTx = ({ onClose, onPrev, tx }: Props): React.ReactElement => {
|
const ReviewCustomTx = ({ onClose, onPrev, tx }: Props): React.ReactElement => {
|
||||||
const { enqueueSnackbar, closeSnackbar } = useSnackbar()
|
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const { address: safeAddress } = useSelector(safeSelector)
|
const { address: safeAddress } = useSelector(safeSelector) || {}
|
||||||
const [gasCosts, setGasCosts] = useState<string>('< 0.001')
|
const [gasCosts, setGasCosts] = useState<string>('< 0.001')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -52,7 +50,7 @@ const ReviewCustomTx = ({ onClose, onPrev, tx }: Props): React.ReactElement => {
|
||||||
const { fromWei, toBN } = getWeb3().utils
|
const { fromWei, toBN } = getWeb3().utils
|
||||||
const txData = tx.data ? tx.data.trim() : ''
|
const txData = tx.data ? tx.data.trim() : ''
|
||||||
|
|
||||||
const estimatedGasCosts = await estimateTxGasCosts(safeAddress, tx.contractAddress, txData)
|
const estimatedGasCosts = await estimateTxGasCosts(safeAddress as string, tx.contractAddress as string, txData)
|
||||||
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
|
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
|
||||||
const formattedGasCosts = formatAmount(gasCostsAsEth)
|
const formattedGasCosts = formatAmount(gasCostsAsEth)
|
||||||
|
|
||||||
|
@ -76,14 +74,12 @@ const ReviewCustomTx = ({ onClose, onPrev, tx }: Props): React.ReactElement => {
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
createTransaction({
|
createTransaction({
|
||||||
safeAddress,
|
safeAddress: safeAddress as string,
|
||||||
to: txRecipient,
|
to: txRecipient as string,
|
||||||
valueInWei: txValue,
|
valueInWei: txValue,
|
||||||
txData,
|
txData,
|
||||||
notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX,
|
notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX,
|
||||||
enqueueSnackbar,
|
}),
|
||||||
closeSnackbar,
|
|
||||||
} as any),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
onClose()
|
onClose()
|
||||||
|
@ -118,15 +114,15 @@ const ReviewCustomTx = ({ onClose, onPrev, tx }: Props): React.ReactElement => {
|
||||||
</Row>
|
</Row>
|
||||||
<Row align="center" margin="md">
|
<Row align="center" margin="md">
|
||||||
<Col xs={1}>
|
<Col xs={1}>
|
||||||
<Identicon address={tx.contractAddress} diameter={32} />
|
<Identicon address={tx.contractAddress as string} diameter={32} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col layout="column" xs={11}>
|
<Col layout="column" xs={11}>
|
||||||
<Block justify="left">
|
<Block justify="left">
|
||||||
<Paragraph noMargin weight="bolder">
|
<Paragraph noMargin weight="bolder">
|
||||||
{tx.contractAddress}
|
{tx.contractAddress}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<CopyBtn content={tx.contractAddress} />
|
<CopyBtn content={tx.contractAddress as string} />
|
||||||
<EtherscanBtn type="address" value={tx.contractAddress} />
|
<EtherscanBtn type="address" value={tx.contractAddress as string} />
|
||||||
</Block>
|
</Block>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
@ -52,7 +52,7 @@ const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const SendCustomTx: React.FC<Props> = ({ initialValues, onClose, onNext, contractAddress, switchMethod, isABI }) => {
|
const SendCustomTx: React.FC<Props> = ({ initialValues, onClose, onNext, contractAddress, switchMethod, isABI }) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const { ethBalance } = useSelector(safeSelector)
|
const { ethBalance } = useSelector(safeSelector) || {}
|
||||||
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
|
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
|
||||||
const [selectedEntry, setSelectedEntry] = useState<{ address?: string; name?: string } | null>({
|
const [selectedEntry, setSelectedEntry] = useState<{ address?: string; name?: string } | null>({
|
||||||
address: contractAddress || initialValues.contractAddress,
|
address: contractAddress || initialValues.contractAddress,
|
||||||
|
@ -230,7 +230,7 @@ const SendCustomTx: React.FC<Props> = ({ initialValues, onClose, onNext, contrac
|
||||||
placeholder="Value*"
|
placeholder="Value*"
|
||||||
text="Value*"
|
text="Value*"
|
||||||
type="text"
|
type="text"
|
||||||
validate={composeValidators(mustBeFloat, maxValue(ethBalance), minValue(0))}
|
validate={composeValidators(mustBeFloat, maxValue(ethBalance || '0'), minValue(0))}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
@ -49,7 +49,7 @@ const ContractInteraction: React.FC<ContractInteractionProps> = ({
|
||||||
isABI,
|
isABI,
|
||||||
}) => {
|
}) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const { address: safeAddress = '' } = useSelector(safeSelector)
|
const { address: safeAddress = '' } = useSelector(safeSelector) || {}
|
||||||
let setCallResults
|
let setCallResults
|
||||||
|
|
||||||
React.useMemo(() => {
|
React.useMemo(() => {
|
||||||
|
|
|
@ -59,7 +59,7 @@ export const formMutators: Record<string, Mutator<{ selectedMethod: { name: stri
|
||||||
},
|
},
|
||||||
setSelectedMethod: (args, state, utils) => {
|
setSelectedMethod: (args, state, utils) => {
|
||||||
const modified =
|
const modified =
|
||||||
state.lastFormState.values.selectedMethod && state.lastFormState.values.selectedMethod.name !== args[0].name
|
state.lastFormState?.values.selectedMethod && state.lastFormState.values.selectedMethod.name !== args[0].name
|
||||||
|
|
||||||
if (modified) {
|
if (modified) {
|
||||||
utils.changeValue(state, 'callResults', () => '')
|
utils.changeValue(state, 'callResults', () => '')
|
||||||
|
@ -115,8 +115,8 @@ export const createTxObject = (
|
||||||
): ContractSendMethod => {
|
): ContractSendMethod => {
|
||||||
const web3 = getWeb3()
|
const web3 = getWeb3()
|
||||||
const contract: any = new web3.eth.Contract([method], contractAddress)
|
const contract: any = new web3.eth.Contract([method], contractAddress)
|
||||||
const { inputs, name, signatureHash } = method
|
const { inputs, name = '', signatureHash } = method
|
||||||
const args = inputs.map(extractMethodArgs(signatureHash, values))
|
const args = inputs?.map(extractMethodArgs(signatureHash, values)) || []
|
||||||
|
|
||||||
return contract.methods[name](...args)
|
return contract.methods[name](...args)
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ const ReviewCollectible = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const shortener = textShortener()
|
const shortener = textShortener()
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const { address: safeAddress } = useSelector(safeSelector)
|
const { address: safeAddress } = useSelector(safeSelector) || {}
|
||||||
const nftTokens = useSelector(nftTokensSelector)
|
const nftTokens = useSelector(nftTokensSelector)
|
||||||
const [gasCosts, setGasCosts] = useState('< 0.001')
|
const [gasCosts, setGasCosts] = useState('< 0.001')
|
||||||
const txToken = nftTokens.find(
|
const txToken = nftTokens.find(
|
||||||
|
@ -66,7 +66,7 @@ const ReviewCollectible = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx
|
||||||
const tokenInstance = await ERC721Token.at(tx.assetAddress)
|
const tokenInstance = await ERC721Token.at(tx.assetAddress)
|
||||||
const txData = tokenInstance.contract.methods[methodToCall](...params).encodeABI()
|
const txData = tokenInstance.contract.methods[methodToCall](...params).encodeABI()
|
||||||
|
|
||||||
const estimatedGasCosts = await estimateTxGasCosts(safeAddress, tx.recipientAddress, txData)
|
const estimatedGasCosts = await estimateTxGasCosts(safeAddress as string, tx.recipientAddress, txData)
|
||||||
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
|
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
|
||||||
const formattedGasCosts = formatAmount(gasCostsAsEth)
|
const formattedGasCosts = formatAmount(gasCostsAsEth)
|
||||||
|
|
||||||
|
@ -148,7 +148,7 @@ const ReviewCollectible = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx
|
||||||
<Row align="center" margin="md">
|
<Row align="center" margin="md">
|
||||||
<Img alt={txToken.name} height={28} onError={setImageToPlaceholder} src={txToken.image} />
|
<Img alt={txToken.name} height={28} onError={setImageToPlaceholder} src={txToken.image} />
|
||||||
<Paragraph className={classes.amount} noMargin size="md">
|
<Paragraph className={classes.amount} noMargin size="md">
|
||||||
{shortener(txToken.name)} (Token ID: {shortener(txToken.tokenId)})
|
{shortener(txToken.name)} (Token ID: {shortener(txToken.tokenId as string)})
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { makeStyles } from '@material-ui/core/styles'
|
||||||
import Close from '@material-ui/icons/Close'
|
import Close from '@material-ui/icons/Close'
|
||||||
import { BigNumber } from 'bignumber.js'
|
import { BigNumber } from 'bignumber.js'
|
||||||
import { withSnackbar } from 'notistack'
|
import { withSnackbar } from 'notistack'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState, useMemo } from 'react'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
|
||||||
import ArrowDown from '../assets/arrow-down.svg'
|
import ArrowDown from '../assets/arrow-down.svg'
|
||||||
|
@ -39,14 +39,14 @@ const useStyles = makeStyles(styles as any)
|
||||||
const ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => {
|
const ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const { address: safeAddress } = useSelector(safeSelector)
|
const { address: safeAddress } = useSelector(safeSelector) || {}
|
||||||
const tokens = useSelector(extendedSafeTokensSelector)
|
const tokens = useSelector(extendedSafeTokensSelector)
|
||||||
const [gasCosts, setGasCosts] = useState('< 0.001')
|
const [gasCosts, setGasCosts] = useState('< 0.001')
|
||||||
const [data, setData] = useState('')
|
const [data, setData] = useState('')
|
||||||
|
|
||||||
const txToken = tokens.find((token) => token.address === tx.token)
|
const txToken = useMemo(() => tokens.find((token) => token.address === tx.token), [tokens, tx.token])
|
||||||
const isSendingETH = txToken.address === ETH_ADDRESS
|
const isSendingETH = txToken?.address === ETH_ADDRESS
|
||||||
const txRecipient = isSendingETH ? tx.recipientAddress : txToken.address
|
const txRecipient = isSendingETH ? tx.recipientAddress : txToken?.address
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isCurrent = true
|
let isCurrent = true
|
||||||
|
@ -54,18 +54,22 @@ const ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => {
|
||||||
const estimateGas = async () => {
|
const estimateGas = async () => {
|
||||||
const { fromWei, toBN } = getWeb3().utils
|
const { fromWei, toBN } = getWeb3().utils
|
||||||
|
|
||||||
|
if (!txToken) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let txData = EMPTY_DATA
|
let txData = EMPTY_DATA
|
||||||
|
|
||||||
if (!isSendingETH) {
|
if (!isSendingETH) {
|
||||||
const StandardToken = await getHumanFriendlyToken()
|
const StandardToken = await getHumanFriendlyToken()
|
||||||
const tokenInstance = await StandardToken.at(txToken.address)
|
const tokenInstance = await StandardToken.at(txToken.address as string)
|
||||||
const decimals = await tokenInstance.decimals()
|
const decimals = await tokenInstance.decimals()
|
||||||
const txAmount = new BigNumber(tx.amount).times(10 ** decimals.toNumber()).toString()
|
const txAmount = new BigNumber(tx.amount).times(10 ** decimals.toNumber()).toString()
|
||||||
|
|
||||||
txData = tokenInstance.contract.methods.transfer(tx.recipientAddress, txAmount).encodeABI()
|
txData = tokenInstance.contract.methods.transfer(tx.recipientAddress, txAmount).encodeABI()
|
||||||
}
|
}
|
||||||
|
|
||||||
const estimatedGasCosts = await estimateTxGasCosts(safeAddress, txRecipient, txData)
|
const estimatedGasCosts = await estimateTxGasCosts(safeAddress as string, txRecipient, txData)
|
||||||
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
|
const gasCostsAsEth = fromWei(toBN(estimatedGasCosts), 'ether')
|
||||||
const formattedGasCosts = formatAmount(gasCostsAsEth)
|
const formattedGasCosts = formatAmount(gasCostsAsEth)
|
||||||
|
|
||||||
|
@ -80,7 +84,7 @@ const ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => {
|
||||||
return () => {
|
return () => {
|
||||||
isCurrent = false
|
isCurrent = false
|
||||||
}
|
}
|
||||||
}, [isSendingETH, safeAddress, tx.amount, tx.recipientAddress, txRecipient, txToken.address])
|
}, [isSendingETH, safeAddress, tx.amount, tx.recipientAddress, txRecipient, txToken])
|
||||||
|
|
||||||
const submitTx = async () => {
|
const submitTx = async () => {
|
||||||
const web3 = getWeb3()
|
const web3 = getWeb3()
|
||||||
|
@ -155,9 +159,14 @@ const ReviewTx = ({ closeSnackbar, enqueueSnackbar, onClose, onPrev, tx }) => {
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Row>
|
</Row>
|
||||||
<Row align="center" margin="md">
|
<Row align="center" margin="md">
|
||||||
<Img alt={txToken.name} height={28} onError={setImageToPlaceholder} src={txToken.logoUri} />
|
<Img alt={txToken?.name as string} height={28} onError={setImageToPlaceholder} src={txToken?.logoUri} />
|
||||||
<Paragraph className={classes.amount} noMargin size="md" data-testid={`amount-${txToken.symbol}-review-step`}>
|
<Paragraph
|
||||||
{tx.amount} {txToken.symbol}
|
className={classes.amount}
|
||||||
|
noMargin
|
||||||
|
size="md"
|
||||||
|
data-testid={`amount-${txToken?.symbol as string}-review-step`}
|
||||||
|
>
|
||||||
|
{tx.amount} {txToken?.symbol}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
|
|
|
@ -53,7 +53,7 @@ const SendCollectible = ({ initialValues, onClose, onNext, recipientAddress, sel
|
||||||
name: '',
|
name: '',
|
||||||
})
|
})
|
||||||
const [pristine, setPristine] = useState(true)
|
const [pristine, setPristine] = useState(true)
|
||||||
const [isValidAddress, setIsValidAddress] = useState(true)
|
const [isValidAddress, setIsValidAddress] = useState(false)
|
||||||
|
|
||||||
React.useMemo(() => {
|
React.useMemo(() => {
|
||||||
if (selectedEntry === null && pristine) {
|
if (selectedEntry === null && pristine) {
|
||||||
|
@ -129,7 +129,7 @@ const SendCollectible = ({ initialValues, onClose, onNext, recipientAddress, sel
|
||||||
<div
|
<div
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.keyCode !== 9) {
|
if (e.keyCode !== 9) {
|
||||||
setSelectedEntry(null)
|
setSelectedEntry({ address: '', name: 'string' })
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
role="listbox"
|
role="listbox"
|
||||||
|
@ -150,7 +150,7 @@ const SendCollectible = ({ initialValues, onClose, onNext, recipientAddress, sel
|
||||||
<Paragraph
|
<Paragraph
|
||||||
className={classes.selectAddress}
|
className={classes.selectAddress}
|
||||||
noMargin
|
noMargin
|
||||||
onClick={() => setSelectedEntry(null)}
|
onClick={() => setSelectedEntry({ address: '', name: 'string' })}
|
||||||
weight="bolder"
|
weight="bolder"
|
||||||
>
|
>
|
||||||
{selectedEntry.name}
|
{selectedEntry.name}
|
||||||
|
@ -158,7 +158,7 @@ const SendCollectible = ({ initialValues, onClose, onNext, recipientAddress, sel
|
||||||
<Paragraph
|
<Paragraph
|
||||||
className={classes.selectAddress}
|
className={classes.selectAddress}
|
||||||
noMargin
|
noMargin
|
||||||
onClick={() => setSelectedEntry(null)}
|
onClick={() => setSelectedEntry({ address: '', name: 'string' })}
|
||||||
weight="bolder"
|
weight="bolder"
|
||||||
>
|
>
|
||||||
{selectedEntry.address}
|
{selectedEntry.address}
|
||||||
|
|
|
@ -58,7 +58,7 @@ const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedT
|
||||||
})
|
})
|
||||||
|
|
||||||
const [pristine, setPristine] = useState(true)
|
const [pristine, setPristine] = useState(true)
|
||||||
const [isValidAddress, setIsValidAddress] = useState(true)
|
const [isValidAddress, setIsValidAddress] = useState(false)
|
||||||
|
|
||||||
React.useMemo(() => {
|
React.useMemo(() => {
|
||||||
if (selectedEntry === null && pristine) {
|
if (selectedEntry === null && pristine) {
|
||||||
|
@ -130,7 +130,7 @@ const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedT
|
||||||
<div
|
<div
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.keyCode !== 9) {
|
if (e.keyCode !== 9) {
|
||||||
setSelectedEntry(null)
|
setSelectedEntry({ address: '', name: 'string' })
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
role="listbox"
|
role="listbox"
|
||||||
|
@ -151,7 +151,7 @@ const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedT
|
||||||
<Paragraph
|
<Paragraph
|
||||||
className={classes.selectAddress}
|
className={classes.selectAddress}
|
||||||
noMargin
|
noMargin
|
||||||
onClick={() => setSelectedEntry(null)}
|
onClick={() => setSelectedEntry({ address: '', name: 'string' })}
|
||||||
weight="bolder"
|
weight="bolder"
|
||||||
>
|
>
|
||||||
{selectedEntry.name}
|
{selectedEntry.name}
|
||||||
|
@ -159,7 +159,7 @@ const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedT
|
||||||
<Paragraph
|
<Paragraph
|
||||||
className={classes.selectAddress}
|
className={classes.selectAddress}
|
||||||
noMargin
|
noMargin
|
||||||
onClick={() => setSelectedEntry(null)}
|
onClick={() => setSelectedEntry({ address: '', name: 'string' })}
|
||||||
weight="bolder"
|
weight="bolder"
|
||||||
>
|
>
|
||||||
{selectedEntry.address}
|
{selectedEntry.address}
|
||||||
|
@ -204,7 +204,7 @@ const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedT
|
||||||
Amount
|
Amount
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<ButtonLink
|
<ButtonLink
|
||||||
onClick={() => mutators.setMax(selectedTokenRecord.balance)}
|
onClick={() => mutators.setMax(selectedTokenRecord?.balance)}
|
||||||
weight="bold"
|
weight="bold"
|
||||||
testId="send-max-btn"
|
testId="send-max-btn"
|
||||||
>
|
>
|
||||||
|
@ -230,7 +230,7 @@ const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedT
|
||||||
required,
|
required,
|
||||||
mustBeFloat,
|
mustBeFloat,
|
||||||
minValue(0, false),
|
minValue(0, false),
|
||||||
maxValue(selectedTokenRecord?.balance),
|
maxValue(selectedTokenRecord?.balance || 0),
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<OnChange name="token">
|
<OnChange name="token">
|
||||||
|
|
|
@ -61,7 +61,7 @@ export const getBalanceData = (
|
||||||
symbol: token.symbol,
|
symbol: token.symbol,
|
||||||
},
|
},
|
||||||
assetOrder: token.name,
|
assetOrder: token.name,
|
||||||
[BALANCE_TABLE_BALANCE_ID]: `${formatAmountInUsFormat(token.balance.toString())} ${token.symbol}`,
|
[BALANCE_TABLE_BALANCE_ID]: `${formatAmountInUsFormat(token.balance?.toString() || '0')} ${token.symbol}`,
|
||||||
balanceOrder: Number(token.balance),
|
balanceOrder: Number(token.balance),
|
||||||
[FIXED]: token.symbol === 'ETH',
|
[FIXED]: token.symbol === 'ETH',
|
||||||
[BALANCE_TABLE_VALUE_ID]: getTokenPriceInCurrency(token, currencySelected, currencyValues, currencyRate),
|
[BALANCE_TABLE_VALUE_ID]: getTokenPriceInCurrency(token, currencySelected, currencyValues, currencyRate),
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { makeStyles } from '@material-ui/core/styles'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
import Receive from 'src/components/App/ModalReceive'
|
import Receive from 'src/components/App/ReceiveModal'
|
||||||
import Tokens from './Tokens'
|
import Tokens from './Tokens'
|
||||||
import { styles } from './style'
|
import { styles } from './style'
|
||||||
|
|
||||||
|
@ -15,7 +15,11 @@ import Row from 'src/components/layout/Row'
|
||||||
import { SAFELIST_ADDRESS } from 'src/routes/routes'
|
import { SAFELIST_ADDRESS } from 'src/routes/routes'
|
||||||
import SendModal from 'src/routes/safe/components/Balances/SendModal'
|
import SendModal from 'src/routes/safe/components/Balances/SendModal'
|
||||||
import CurrencyDropdown from 'src/routes/safe/components/CurrencyDropdown'
|
import CurrencyDropdown from 'src/routes/safe/components/CurrencyDropdown'
|
||||||
import { safeFeaturesEnabledSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors'
|
import {
|
||||||
|
safeFeaturesEnabledSelector,
|
||||||
|
safeParamAddressFromStateSelector,
|
||||||
|
safeNameSelector,
|
||||||
|
} from 'src/logic/safe/store/selectors'
|
||||||
|
|
||||||
import { wrapInSuspense } from 'src/utils/wrapInSuspense'
|
import { wrapInSuspense } from 'src/utils/wrapInSuspense'
|
||||||
import { useFetchTokens } from 'src/logic/safe/hooks/useFetchTokens'
|
import { useFetchTokens } from 'src/logic/safe/hooks/useFetchTokens'
|
||||||
|
@ -33,7 +37,7 @@ const INITIAL_STATE = {
|
||||||
showManageCollectibleModal: false,
|
showManageCollectibleModal: false,
|
||||||
sendFunds: {
|
sendFunds: {
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
selectedToken: undefined,
|
selectedToken: '',
|
||||||
},
|
},
|
||||||
showReceive: false,
|
showReceive: false,
|
||||||
}
|
}
|
||||||
|
@ -49,11 +53,12 @@ const Balances = (): React.ReactElement => {
|
||||||
|
|
||||||
const address = useSelector(safeParamAddressFromStateSelector)
|
const address = useSelector(safeParamAddressFromStateSelector)
|
||||||
const featuresEnabled = useSelector(safeFeaturesEnabledSelector)
|
const featuresEnabled = useSelector(safeFeaturesEnabledSelector)
|
||||||
|
const safeName = useSelector(safeNameSelector)
|
||||||
|
|
||||||
useFetchTokens(address)
|
useFetchTokens(address as string)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const erc721Enabled = featuresEnabled && featuresEnabled.includes('ERC721')
|
const erc721Enabled = Boolean(featuresEnabled?.includes('ERC721'))
|
||||||
|
|
||||||
setState((prevState) => ({
|
setState((prevState) => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
|
@ -84,7 +89,7 @@ const Balances = (): React.ReactElement => {
|
||||||
...prevState,
|
...prevState,
|
||||||
sendFunds: {
|
sendFunds: {
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
selectedToken: undefined,
|
selectedToken: '',
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -224,7 +229,7 @@ const Balances = (): React.ReactElement => {
|
||||||
paperClassName={receiveModal}
|
paperClassName={receiveModal}
|
||||||
title="Receive Tokens"
|
title="Receive Tokens"
|
||||||
>
|
>
|
||||||
<Receive onClose={() => onHide('Receive')} />
|
<Receive safeAddress={address as string} safeName={safeName as string} onClose={() => onHide('Receive')} />
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -22,7 +22,7 @@ import { setImageToPlaceholder } from '../Balances/utils'
|
||||||
import Img from 'src/components/layout/Img/index'
|
import Img from 'src/components/layout/Img/index'
|
||||||
import etherIcon from 'src/assets/icons/icon_etherTokens.svg'
|
import etherIcon from 'src/assets/icons/icon_etherTokens.svg'
|
||||||
|
|
||||||
const CurrencyDropdown = (): React.ReactElement => {
|
const CurrencyDropdown = (): React.ReactElement | null => {
|
||||||
const currenciesList = Object.values(AVAILABLE_CURRENCIES)
|
const currenciesList = Object.values(AVAILABLE_CURRENCIES)
|
||||||
const safeAddress = useSelector(safeParamAddressFromStateSelector)
|
const safeAddress = useSelector(safeParamAddressFromStateSelector)
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
@ -48,7 +48,11 @@ const CurrencyDropdown = (): React.ReactElement => {
|
||||||
handleClose()
|
handleClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
return !selectedCurrency ? null : (
|
if (!selectedCurrency) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<MuiThemeProvider theme={DropdownListTheme}>
|
<MuiThemeProvider theme={DropdownListTheme}>
|
||||||
<>
|
<>
|
||||||
<button className={classes.button} onClick={handleClick} type="button">
|
<button className={classes.button} onClick={handleClick} type="button">
|
||||||
|
|
|
@ -50,7 +50,7 @@ const ModulesTable = ({ moduleData }: ModulesTableProps): React.ReactElement =>
|
||||||
const [viewRemoveModuleModal, setViewRemoveModuleModal] = React.useState(false)
|
const [viewRemoveModuleModal, setViewRemoveModuleModal] = React.useState(false)
|
||||||
const hideRemoveModuleModal = () => setViewRemoveModuleModal(false)
|
const hideRemoveModuleModal = () => setViewRemoveModuleModal(false)
|
||||||
|
|
||||||
const [selectedModule, setSelectedModule] = React.useState(null)
|
const [selectedModule, setSelectedModule] = React.useState<ModulePair>()
|
||||||
const triggerRemoveSelectedModule = (module: ModulePair): void => {
|
const triggerRemoveSelectedModule = (module: ModulePair): void => {
|
||||||
setSelectedModule(module)
|
setSelectedModule(module)
|
||||||
setViewRemoveModuleModal(true)
|
setViewRemoveModuleModal(true)
|
||||||
|
@ -67,7 +67,7 @@ const ModulesTable = ({ moduleData }: ModulesTableProps): React.ReactElement =>
|
||||||
disablePagination
|
disablePagination
|
||||||
label="Modules"
|
label="Modules"
|
||||||
noBorder
|
noBorder
|
||||||
size={moduleData.length}
|
size={moduleData?.length}
|
||||||
>
|
>
|
||||||
{(sortedData) =>
|
{(sortedData) =>
|
||||||
sortedData.map((row, index) => (
|
sortedData.map((row, index) => (
|
||||||
|
@ -117,7 +117,9 @@ const ModulesTable = ({ moduleData }: ModulesTableProps): React.ReactElement =>
|
||||||
}
|
}
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
{viewRemoveModuleModal && <RemoveModuleModal onClose={hideRemoveModuleModal} selectedModule={selectedModule} />}
|
{viewRemoveModuleModal && selectedModule && (
|
||||||
|
<RemoveModuleModal onClose={hideRemoveModuleModal} selectedModule={selectedModule} />
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ interface RemoveModuleModal {
|
||||||
const RemoveModuleModal = ({ onClose, selectedModule }: RemoveModuleModal): React.ReactElement => {
|
const RemoveModuleModal = ({ onClose, selectedModule }: RemoveModuleModal): React.ReactElement => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
const safeAddress = useSelector(safeParamAddressFromStateSelector)
|
const safeAddress = useSelector(safeParamAddressFromStateSelector) as string
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
const removeSelectedModule = async (): Promise<void> => {
|
const removeSelectedModule = async (): Promise<void> => {
|
||||||
|
|
|
@ -42,7 +42,7 @@ const Advanced = (): React.ReactElement => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const nonce = useSelector(safeNonceSelector)
|
const nonce = useSelector(safeNonceSelector)
|
||||||
const modules = useSelector(safeModulesSelector)
|
const modules = useSelector(safeModulesSelector)
|
||||||
const moduleData = getModuleData(modules) ?? null
|
const moduleData = modules ? getModuleData(modules) ?? null : null
|
||||||
const { trackEvent } = useAnalytics()
|
const { trackEvent } = useAnalytics()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -35,7 +35,7 @@ const OwnerForm = ({ classes, onClose, onSubmit }) => {
|
||||||
onSubmit(values)
|
onSubmit(values)
|
||||||
}
|
}
|
||||||
const owners = useSelector(safeOwnersSelector)
|
const owners = useSelector(safeOwnersSelector)
|
||||||
const ownerDoesntExist = uniqueAddress(owners.map((o) => o.address))
|
const ownerDoesntExist = uniqueAddress(owners?.map((o) => o.address) || [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue