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