Merge branch 'development' of https://github.com/gnosis/safe-react into coveralls

This commit is contained in:
Agustin Pane 2020-07-01 08:50:14 -03:00
commit 98ec85d454
27 changed files with 155 additions and 144 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "safe-react", "name": "safe-react",
"version": "2.3.1", "version": "2.4.0",
"description": "Allowing crypto users manage funds in a safer way", "description": "Allowing crypto users manage funds in a safer way",
"website": "https://github.com/gnosis/safe-react#readme", "website": "https://github.com/gnosis/safe-react#readme",
"bugs": { "bugs": {
@ -170,7 +170,7 @@
"async-sema": "^3.1.0", "async-sema": "^3.1.0",
"axios": "0.19.2", "axios": "0.19.2",
"bignumber.js": "9.0.0", "bignumber.js": "9.0.0",
"bnc-onboard": "1.9.4", "bnc-onboard": "1.10.0",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"concurrently": "^5.2.0", "concurrently": "^5.2.0",
"connected-react-router": "6.8.0", "connected-react-router": "6.8.0",

View File

@ -9,8 +9,13 @@ const Box = styled.p`
border: solid 2px ${border}; border: solid 2px ${border};
` `
const TextBox = ({ children }: any) => { type Props = {
return <Box>{children}</Box> children: React.ReactNode
className?: string
}
const TextBox = ({ children, ...rest }: Props): React.ReactElement => {
return <Box {...rest}>{children}</Box>
} }
export default TextBox export default TextBox

View File

@ -1,6 +1,12 @@
import { Record } from 'immutable' import { Record } from 'immutable'
export const makeAddressBookEntry = Record({ export interface AddressBookEntryProps {
address: string
name: string
isOwner: boolean
}
export const makeAddressBookEntry = Record<AddressBookEntryProps>({
address: '', address: '',
name: '', name: '',
isOwner: false, isOwner: false,

View File

@ -1,4 +1,4 @@
import { List, Map } from 'immutable' import { List } from 'immutable'
import { loadAddressBook } from 'src/logic/addressBook/store/actions/loadAddressBook' import { loadAddressBook } from 'src/logic/addressBook/store/actions/loadAddressBook'
import { buildAddressBook } from 'src/logic/addressBook/store/reducer/addressBook' import { buildAddressBook } from 'src/logic/addressBook/store/reducer/addressBook'
@ -8,11 +8,12 @@ import { safesListSelector } from 'src/routes/safe/store/selectors'
const loadAddressBookFromStorage = () => async (dispatch, getState) => { const loadAddressBookFromStorage = () => async (dispatch, getState) => {
try { try {
const state = getState() const state = getState()
let addressBook = await getAddressBookFromStorage() let storedAdBk = await getAddressBookFromStorage()
if (!addressBook) { if (!storedAdBk) {
addressBook = Map([]) storedAdBk = []
} }
addressBook = buildAddressBook(addressBook)
let addressBook = buildAddressBook(storedAdBk)
// Fetch all the current safes, in case that we don't have a safe on the adbk, we add it // Fetch all the current safes, in case that we don't have a safe on the adbk, we add it
const safes = safesListSelector(state) const safes = safesListSelector(state)
const adbkEntries = addressBook.keySeq().toArray() const adbkEntries = addressBook.keySeq().toArray()

View File

@ -1,11 +1,12 @@
import { loadFromStorage, saveToStorage } from 'src/utils/storage' import { loadFromStorage, saveToStorage } from 'src/utils/storage'
import { AddressBookEntryProps } from './../model/addressBook'
const ADDRESS_BOOK_STORAGE_KEY = 'ADDRESS_BOOK_STORAGE_KEY' const ADDRESS_BOOK_STORAGE_KEY = 'ADDRESS_BOOK_STORAGE_KEY'
export const getAddressBookFromStorage = async () => { export const getAddressBookFromStorage = async (): Promise<Array<AddressBookEntryProps> | undefined> => {
const data = await loadFromStorage(ADDRESS_BOOK_STORAGE_KEY) const data = await loadFromStorage<Array<AddressBookEntryProps>>(ADDRESS_BOOK_STORAGE_KEY)
return data || [] return data
} }
export const saveAddressBook = async (addressBook) => { export const saveAddressBook = async (addressBook) => {

View File

@ -27,8 +27,8 @@ export const getLocalSafe = async (safeAddress) => {
return storedSafes[safeAddress] return storedSafes[safeAddress]
} }
export const getDefaultSafe = async () => { export const getDefaultSafe = async (): Promise<string> => {
const defaultSafe = await loadFromStorage(DEFAULT_SAFE_KEY) const defaultSafe = await loadFromStorage<string>(DEFAULT_SAFE_KEY)
return defaultSafe || '' return defaultSafe || ''
} }

View File

@ -7,7 +7,7 @@ import { getActiveTokens } from 'src/logic/tokens/utils/tokensStorage'
const loadActiveTokens = () => async (dispatch) => { const loadActiveTokens = () => async (dispatch) => {
try { try {
const tokens = await getActiveTokens() const tokens = (await getActiveTokens()) || {}
// The filter of strings was made because of the issue #751. Please see: https://github.com/gnosis/safe-react/pull/755#issuecomment-612969340 // The filter of strings was made because of the issue #751. Please see: https://github.com/gnosis/safe-react/pull/755#issuecomment-612969340
const tokenRecordsList = List( const tokenRecordsList = List(
Object.values(tokens) Object.values(tokens)

View File

@ -1,19 +0,0 @@
import { createAction } from 'redux-actions'
import { removeFromActiveTokens, removeTokenFromStorage } from 'src/logic/tokens/utils/tokensStorage'
export const REMOVE_TOKEN = 'REMOVE_TOKEN'
export const removeToken = createAction(REMOVE_TOKEN, (safeAddress, token) => ({
safeAddress,
token,
}))
const deleteToken = (safeAddress, token) => async (dispatch) => {
dispatch(removeToken(safeAddress, token))
await removeFromActiveTokens(safeAddress, token)
await removeTokenFromStorage(safeAddress, token)
}
export default deleteToken

View File

@ -2,7 +2,6 @@ import { Map } from 'immutable'
import { handleActions } from 'redux-actions' import { handleActions } from 'redux-actions'
import { ADD_TOKEN } from 'src/logic/tokens/store/actions/addToken' import { ADD_TOKEN } from 'src/logic/tokens/store/actions/addToken'
import { REMOVE_TOKEN } from 'src/logic/tokens/store/actions/removeToken'
import { ADD_TOKENS } from 'src/logic/tokens/store/actions/saveTokens' import { ADD_TOKENS } from 'src/logic/tokens/store/actions/saveTokens'
import { makeToken } from 'src/logic/tokens/store/model/token' import { makeToken } from 'src/logic/tokens/store/model/token'
@ -27,12 +26,6 @@ export default handleActions(
return state.set(tokenAddress, makeToken(token)) return state.set(tokenAddress, makeToken(token))
}, },
[REMOVE_TOKEN]: (state, action) => {
const { token } = action.payload
const { address: tokenAddress } = token
return state.remove(tokenAddress)
},
}, },
Map(), Map(),
) )

View File

@ -1,4 +1,3 @@
import memoize from 'lodash.memoize'
import logo from 'src/assets/icons/icon_etherTokens.svg' import logo from 'src/assets/icons/icon_etherTokens.svg'
import generateBatchRequests from 'src/logic/contracts/generateBatchRequests' import generateBatchRequests from 'src/logic/contracts/generateBatchRequests'
import { import {
@ -26,7 +25,7 @@ export const getEthAsToken = (balance: string): Token => {
}) })
} }
export const isAddressAToken = async (tokenAddress): Promise<boolean> => { export const isAddressAToken = async (tokenAddress: string): Promise<boolean> => {
// SECOND APPROACH: // SECOND APPROACH:
// They both seem to work the same // They both seem to work the same
// const tokenContract = await getStandardTokenContract() // const tokenContract = await getStandardTokenContract()
@ -45,26 +44,36 @@ export const isTokenTransfer = (tx: any): boolean => {
} }
export const isSendERC721Transaction = (tx: any, txCode: string, knownTokens: any) => { export const isSendERC721Transaction = (tx: any, txCode: string, knownTokens: any) => {
// "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85" - ens token contract, includes safeTransferFrom
// but no proper ERC721 standard implemented
return ( return (
(txCode && txCode.includes(SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH)) || (txCode &&
txCode.includes(SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH) &&
tx.to !== '0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85') ||
(isTokenTransfer(tx) && !knownTokens.get(tx.to)) (isTokenTransfer(tx) && !knownTokens.get(tx.to))
) )
} }
export const getERC721Symbol = memoize( export const getERC721Symbol = async (contractAddress: string): Promise<string> => {
async (contractAddress: string): Promise<string> => { let tokenSymbol = 'UNKNOWN'
try {
const ERC721token = await getERC721TokenContract() const ERC721token = await getERC721TokenContract()
const tokenInstance = await ERC721token.at(contractAddress) const tokenInstance = await ERC721token.at(contractAddress)
return tokenInstance.symbol() tokenSymbol = tokenInstance.symbol()
}, } catch (err) {
) console.error(`Failed to retrieve token symbol for ERC721 token ${contractAddress}`)
}
return tokenSymbol
}
export const getERC20DecimalsAndSymbol = async ( export const getERC20DecimalsAndSymbol = async (
tokenAddress: string, tokenAddress: string,
): Promise<{ decimals: number; symbol: string }> => { ): Promise<{ decimals: number; symbol: string }> => {
const tokenInfos = await getTokenInfos(tokenAddress) const tokenInfo = { decimals: 18, symbol: 'UNKNOWN' }
try {
const storedTokenInfo = await getTokenInfos(tokenAddress)
if (tokenInfos === null) { if (storedTokenInfo === null) {
const [tokenDecimals, tokenSymbol] = await generateBatchRequests({ const [tokenDecimals, tokenSymbol] = await generateBatchRequests({
abi: ALTERNATIVE_TOKEN_ABI, abi: ALTERNATIVE_TOKEN_ABI,
address: tokenAddress, address: tokenAddress,
@ -73,8 +82,11 @@ export const getERC20DecimalsAndSymbol = async (
return { decimals: Number(tokenDecimals), symbol: tokenSymbol } return { decimals: Number(tokenDecimals), symbol: tokenSymbol }
} }
} catch (err) {
console.error(`Failed to retrieve token info for ERC20 token ${tokenAddress}`)
}
return { decimals: Number(tokenInfos.decimals), symbol: tokenInfos.symbol } return tokenInfo
} }
export const isSendERC20Transaction = async ( export const isSendERC20Transaction = async (

View File

@ -1,6 +1,7 @@
import { List } from 'immutable' import { Map } from 'immutable'
import { loadFromStorage, saveToStorage } from 'src/utils/storage' import { loadFromStorage, saveToStorage } from 'src/utils/storage'
import { TokenProps, Token } from './../store/model/token'
export const ACTIVE_TOKENS_KEY = 'ACTIVE_TOKENS' export const ACTIVE_TOKENS_KEY = 'ACTIVE_TOKENS'
export const CUSTOM_TOKENS_KEY = 'CUSTOM_TOKENS' export const CUSTOM_TOKENS_KEY = 'CUSTOM_TOKENS'
@ -9,42 +10,16 @@ export const CUSTOM_TOKENS_KEY = 'CUSTOM_TOKENS'
// to avoid iterating a large amount of data of tokens from the backend // to avoid iterating a large amount of data of tokens from the backend
// Custom tokens should be saved too unless they're deleted (marking them as inactive doesn't count) // Custom tokens should be saved too unless they're deleted (marking them as inactive doesn't count)
export const saveActiveTokens = async (tokens) => { export const saveActiveTokens = async (tokens: Map<string, Token>): Promise<void> => {
try { try {
await saveToStorage(ACTIVE_TOKENS_KEY, tokens.toJS()) await saveToStorage(ACTIVE_TOKENS_KEY, tokens.toJS() as Record<string, TokenProps>)
} catch (err) { } catch (err) {
console.error('Error storing tokens in localstorage', err) console.error('Error storing tokens in localstorage', err)
} }
} }
export const getActiveTokens = async () => { export const getActiveTokens = async (): Promise<Record<string, TokenProps> | undefined> => {
const data = await loadFromStorage(ACTIVE_TOKENS_KEY) const data = await loadFromStorage<Record<string, TokenProps>>(ACTIVE_TOKENS_KEY)
return data || {} return data
}
export const getCustomTokens = async () => {
const data = await loadFromStorage(CUSTOM_TOKENS_KEY)
return data ? List(data) : List()
}
export const removeTokenFromStorage = async (safeAddress, token) => {
const data = await getCustomTokens()
try {
const index = data.indexOf(token)
await saveToStorage(CUSTOM_TOKENS_KEY, data.remove(index))
} catch (err) {
console.error('Error removing token in localstorage', err)
}
}
export const removeFromActiveTokens = async (safeAddress, token) => {
const activeTokens = await getActiveTokens()
const index = activeTokens.findIndex((activeToken) => activeToken.name === token.name)
if (index !== -1) {
await saveActiveTokens(safeAddress)
}
} }

View File

@ -10,10 +10,10 @@ const watchedActions = [ADD_PROVIDER, REMOVE_PROVIDER]
const LAST_USED_PROVIDER_KEY = 'LAST_USED_PROVIDER' const LAST_USED_PROVIDER_KEY = 'LAST_USED_PROVIDER'
export const loadLastUsedProvider = async () => { export const loadLastUsedProvider = async (): Promise<string | undefined> => {
const lastUsedProvider = await loadFromStorage(LAST_USED_PROVIDER_KEY) const lastUsedProvider = await loadFromStorage<string>(LAST_USED_PROVIDER_KEY)
return lastUsedProvider || '' return lastUsedProvider
} }
let watcherInterval = null let watcherInterval = null

View File

@ -7,6 +7,7 @@ import { LOAD_ADDRESS, OPEN_ADDRESS, SAFELIST_ADDRESS, SAFE_PARAM_ADDRESS, WELCO
import Loader from 'src/components/Loader' import Loader from 'src/components/Loader'
import { defaultSafeSelector } from 'src/routes/safe/store/selectors' import { defaultSafeSelector } from 'src/routes/safe/store/selectors'
import { useAnalytics } from 'src/utils/googleAnalytics' import { useAnalytics } from 'src/utils/googleAnalytics'
import { DEFAULT_SAFE_INITIAL_STATE } from 'src/routes/safe/store/reducer/safe'
const Welcome = React.lazy(() => import('./welcome/container')) const Welcome = React.lazy(() => import('./welcome/container'))
@ -44,7 +45,7 @@ const Routes = ({ location }) => {
return <Redirect to={WELCOME_ADDRESS} /> return <Redirect to={WELCOME_ADDRESS} />
} }
if (typeof defaultSafe === 'undefined') { if (defaultSafe === DEFAULT_SAFE_INITIAL_STATE) {
return <Loader /> return <Loader />
} }

View File

@ -16,17 +16,25 @@ import { SAFELIST_ADDRESS } from 'src/routes/routes'
import { buildSafe } from 'src/routes/safe/store/actions/fetchSafe' import { buildSafe } from 'src/routes/safe/store/actions/fetchSafe'
import { history } from 'src/store' import { history } from 'src/store'
import { loadFromStorage } from 'src/utils/storage' import { loadFromStorage } from 'src/utils/storage'
import { Dispatch } from 'redux'
import { SafeOwner } from '../../safe/store/models/safe'
import { List } from 'immutable'
export const loadSafe = async (safeName, safeAddress, owners, addSafe) => { export const loadSafe = async (
safeName: string,
safeAddress: string,
owners: List<SafeOwner>,
addSafe: Dispatch<any>,
): Promise<void> => {
const safeProps = await buildSafe(safeAddress, safeName) const safeProps = await buildSafe(safeAddress, safeName)
safeProps.owners = owners safeProps.owners = owners
await addSafe(safeProps)
const storedSafes = (await loadFromStorage(SAFES_KEY)) || {} const storedSafes = (await loadFromStorage(SAFES_KEY)) || {}
storedSafes[safeAddress] = safeProps storedSafes[safeAddress] = safeProps
saveSafes(storedSafes) await saveSafes(storedSafes)
await addSafe(safeProps)
} }
class Load extends React.Component<any> { class Load extends React.Component<any> {

View File

@ -103,7 +103,7 @@ const Open = ({ addSafe, network, provider, userAccount }) => {
// check if there is a safe being created // check if there is a safe being created
useEffect(() => { useEffect(() => {
const load = async () => { const load = async () => {
const pendingCreation = await loadFromStorage(SAFE_PENDING_CREATION_STORAGE_KEY) const pendingCreation = await loadFromStorage<{ txHash: string }>(SAFE_PENDING_CREATION_STORAGE_KEY)
if (pendingCreation && pendingCreation.txHash) { if (pendingCreation && pendingCreation.txHash) {
setSafeCreationPendingInfo(pendingCreation) setSafeCreationPendingInfo(pendingCreation)
setShowProgress(true) setShowProgress(true)
@ -133,7 +133,7 @@ const Open = ({ addSafe, network, provider, userAccount }) => {
} }
const onSafeCreated = async (safeAddress) => { const onSafeCreated = async (safeAddress) => {
const pendingCreation = await loadFromStorage(SAFE_PENDING_CREATION_STORAGE_KEY) const pendingCreation = await loadFromStorage<{ txHash: string }>(SAFE_PENDING_CREATION_STORAGE_KEY)
const name = getSafeNameFrom(pendingCreation) const name = getSafeNameFrom(pendingCreation)
const ownersNames = getNamesFrom(pendingCreation) const ownersNames = getNamesFrom(pendingCreation)
@ -167,7 +167,7 @@ const Open = ({ addSafe, network, provider, userAccount }) => {
} }
const onRetry = async () => { const onRetry = async () => {
const values = await loadFromStorage(SAFE_PENDING_CREATION_STORAGE_KEY) const values = await loadFromStorage<{ txHash: string }>(SAFE_PENDING_CREATION_STORAGE_KEY)
delete values.txHash delete values.txHash
await saveToStorage(SAFE_PENDING_CREATION_STORAGE_KEY, values) await saveToStorage(SAFE_PENDING_CREATION_STORAGE_KEY, values)
setSafeCreationPendingInfo(values) setSafeCreationPendingInfo(values)

View File

@ -1,6 +1,7 @@
import { List } from 'immutable' import { List } from 'immutable'
import { makeOwner } from 'src/routes/safe/store/models/owner' import { makeOwner } from 'src/routes/safe/store/models/owner'
import { SafeOwner } from '../../safe/store/models/safe'
export const getAccountsFrom = (values) => { export const getAccountsFrom = (values) => {
const accounts = Object.keys(values) const accounts = Object.keys(values)
@ -18,7 +19,7 @@ export const getNamesFrom = (values) => {
return accounts.map((account) => values[account]).slice(0, values.owners) return accounts.map((account) => values[account]).slice(0, values.owners)
} }
export const getOwnersFrom = (names, addresses) => { export const getOwnersFrom = (names, addresses): List<SafeOwner> => {
const owners = names.map((name, index) => makeOwner({ name, address: addresses[index] })) const owners = names.map((name, index) => makeOwner({ name, address: addresses[index] }))
return List(owners) return List(owners)

View File

@ -54,6 +54,10 @@ const IconText = styled.div`
margin-right: 4px; margin-right: 4px;
} }
` `
const StyledTextBox = styled(TextBox)`
max-width: 444px;
`
const isTxValid = (t: SafeAppTx): boolean => { const isTxValid = (t: SafeAppTx): boolean => {
if (!['string', 'number'].includes(typeof t.value)) { if (!['string', 'number'].includes(typeof t.value)) {
return false return false
@ -111,7 +115,7 @@ const confirmTransactions = (
</div> </div>
<div className="section"> <div className="section">
<Heading tag="h3">Data (hex encoded)*</Heading> <Heading tag="h3">Data (hex encoded)*</Heading>
<TextBox>{tx.data}</TextBox> <StyledTextBox>{tx.data}</StyledTextBox>
</div> </div>
</CollapseContent> </CollapseContent>
</Collapse> </Collapse>

View File

@ -21,7 +21,7 @@ import {
} from 'src/routes/safe/store/selectors' } from 'src/routes/safe/store/selectors'
import { loadFromStorage, saveToStorage } from 'src/utils/storage' import { loadFromStorage, saveToStorage } from 'src/utils/storage'
import { isSameHref } from 'src/utils/url' import { isSameHref } from 'src/utils/url'
import { SafeApp } from './types' import { SafeApp, StoredSafeApp } from './types'
const APPS_STORAGE_KEY = 'APPS_STORAGE_KEY' const APPS_STORAGE_KEY = 'APPS_STORAGE_KEY'
const APPS_LEGAL_DISCLAIMER_STORAGE_KEY = 'APPS_LEGAL_DISCLAIMER_STORAGE_KEY' const APPS_LEGAL_DISCLAIMER_STORAGE_KEY = 'APPS_LEGAL_DISCLAIMER_STORAGE_KEY'
@ -195,7 +195,7 @@ function Apps({ closeModal, closeSnackbar, enqueueSnackbar, openModal }) {
setAppList(copyAppList) setAppList(copyAppList)
// update storage list // update storage list
const persistedAppList = (await loadFromStorage(APPS_STORAGE_KEY)) || [] const persistedAppList = (await loadFromStorage<StoredSafeApp[]>(APPS_STORAGE_KEY)) || []
let storageApp = persistedAppList.find((a) => a.url === app.url) let storageApp = persistedAppList.find((a) => a.url === app.url)
if (!storageApp) { if (!storageApp) {
@ -303,7 +303,7 @@ function Apps({ closeModal, closeSnackbar, enqueueSnackbar, openModal }) {
// recover apps from storage: // recover apps from storage:
// * third-party apps added by the user // * third-party apps added by the user
// * disabled status for both static and third-party apps // * disabled status for both static and third-party apps
const persistedAppList = (await loadFromStorage(APPS_STORAGE_KEY)) || [] const persistedAppList = (await loadFromStorage<StoredSafeApp[]>(APPS_STORAGE_KEY)) || []
const list = [...persistedAppList] const list = [...persistedAppList]
staticAppsList.forEach((staticApp) => { staticAppsList.forEach((staticApp) => {

View File

@ -6,3 +6,8 @@ export type SafeApp = {
disabled?: boolean disabled?: boolean
error: boolean error: boolean
} }
export type StoredSafeApp = {
url: string
disabled?: boolean
}

View File

@ -18,12 +18,9 @@ export const staticAppsList: Array<{ url: string; disabled: boolean }> = [
// request // request
{ url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmQapdJP6zERqpDKKPECNeMDDgwmGUqbKk1PjHpYj8gfDJ`, disabled: false }, { url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmQapdJP6zERqpDKKPECNeMDDgwmGUqbKk1PjHpYj8gfDJ`, disabled: false },
// Aave // Aave
// { url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmUfgEqdJ5kVjWTQofnDmvxdhDLBAaejiHkhQhfw6aYvBg`, disabled: false }, { url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmfHFQHCSyaSL8Aq4eWZeB3buy4neiUCPchob2pYdV9gJT`, disabled: false },
{ url: `${gnosisAppsUrl}/compound`, disabled: false }, { url: `${gnosisAppsUrl}/compound`, disabled: false },
{ url: `${gnosisAppsUrl}/tx-builder`, disabled: false }, { url: `${gnosisAppsUrl}/tx-builder`, disabled: false },
{ url: `${gnosisAppsUrl}/pool-together`, disabled: false },
{ url: `${gnosisAppsUrl}/open-zeppelin`, disabled: false },
{ url: `${gnosisAppsUrl}/synthetix`, disabled: false },
] ]
export const getAppInfoFromOrigin = (origin) => { export const getAppInfoFromOrigin = (origin) => {

View File

@ -13,6 +13,7 @@ import updateSafe from 'src/routes/safe/store/actions/updateSafe'
import { makeOwner } from 'src/routes/safe/store/models/owner' import { makeOwner } from 'src/routes/safe/store/models/owner'
import { checksumAddress } from 'src/utils/checksumAddress' import { checksumAddress } from 'src/utils/checksumAddress'
import { SafeOwner } from '../models/safe'
const buildOwnersFrom = ( const buildOwnersFrom = (
safeOwners, safeOwners,
@ -51,7 +52,7 @@ export const buildSafe = async (safeAdd, safeName, latestMasterContractVersion?:
const threshold = Number(thresholdStr) const threshold = Number(thresholdStr)
const nonce = Number(nonceStr) const nonce = Number(nonceStr)
const owners = List(buildOwnersFrom(remoteOwners, localSafe)) const owners = List<SafeOwner>(buildOwnersFrom(remoteOwners, localSafe))
const needsUpdate = safeNeedsUpdate(currentVersion, latestMasterContractVersion) const needsUpdate = safeNeedsUpdate(currentVersion, latestMasterContractVersion)
const featuresEnabled = enabledFeatures(currentVersion) const featuresEnabled = enabledFeatures(currentVersion)

View File

@ -253,7 +253,20 @@ export const buildTx = async ({
const refundParams = await getRefundParams(tx, getERC20DecimalsAndSymbol) const refundParams = await getRefundParams(tx, getERC20DecimalsAndSymbol)
const decodedParams = getDecodedParams(tx) const decodedParams = getDecodedParams(tx)
const confirmations = getConfirmations(tx) const confirmations = getConfirmations(tx)
const { decimals = 18, symbol = 'ETH' } = isSendERC20Tx ? await getERC20DecimalsAndSymbol(tx.to) : {}
let tokenDecimals = 18
let tokenSymbol = 'ETH'
try {
if (isSendERC20Tx) {
const { decimals, symbol } = await getERC20DecimalsAndSymbol(tx.to)
tokenDecimals = decimals
tokenSymbol = symbol
} else if (isSendERC721Tx) {
tokenSymbol = await getERC721Symbol(tx.to)
}
} catch (err) {
console.log(`Failed to retrieve token data from ${tx.to}`)
}
const txToStore = makeTransaction({ const txToStore = makeTransaction({
baseGas: tx.baseGas, baseGas: tx.baseGas,
@ -263,7 +276,7 @@ export const buildTx = async ({
creationTx: tx.creationTx, creationTx: tx.creationTx,
customTx: isCustomTx, customTx: isCustomTx,
data: tx.data ? tx.data : EMPTY_DATA, data: tx.data ? tx.data : EMPTY_DATA,
decimals, decimals: tokenDecimals,
decodedParams, decodedParams,
executionDate: tx.executionDate, executionDate: tx.executionDate,
executionTxHash: tx.transactionHash, executionTxHash: tx.transactionHash,
@ -286,7 +299,7 @@ export const buildTx = async ({
safeTxGas: tx.safeTxGas, safeTxGas: tx.safeTxGas,
safeTxHash: tx.safeTxHash, safeTxHash: tx.safeTxHash,
submissionDate: tx.submissionDate, submissionDate: tx.submissionDate,
symbol: isSendERC721Tx ? await getERC721Symbol(tx.to) : symbol, symbol: tokenSymbol,
upgradeTx: isUpgradeTx, upgradeTx: isUpgradeTx,
value: tx.value.toString(), value: tx.value.toString(),
}) })

View File

@ -38,7 +38,7 @@ const sendAwaitingTransactionNotification = async (
return return
} }
let lastTimeUserLoggedInForSafes = (await loadFromStorage(LAST_TIME_USED_LOGGED_IN_ID)) || [] let lastTimeUserLoggedInForSafes = (await loadFromStorage<Record<string, string>>(LAST_TIME_USED_LOGGED_IN_ID)) || {}
const lastTimeUserLoggedIn = const lastTimeUserLoggedIn =
lastTimeUserLoggedInForSafes && lastTimeUserLoggedInForSafes[safeAddress] lastTimeUserLoggedInForSafes && lastTimeUserLoggedInForSafes[safeAddress]
? lastTimeUserLoggedInForSafes[safeAddress] ? lastTimeUserLoggedInForSafes[safeAddress]

View File

@ -1,10 +1,15 @@
import { List, Map, Record, RecordOf, Set } from 'immutable' import { List, Map, Record, RecordOf, Set } from 'immutable'
export type SafeOwner = {
name: string
address: string
}
export type SafeRecordProps = { export type SafeRecordProps = {
name: string name: string
address: string address: string
threshold: number threshold: number
ethBalance: number ethBalance: string
owners: List<{ name: string; address: string }> owners: List<{ name: string; address: string }>
activeTokens: Set<string> activeTokens: Set<string>
activeAssets: Set<string> activeAssets: Set<string>
@ -23,7 +28,7 @@ const makeSafe = Record<SafeRecordProps>({
name: '', name: '',
address: '', address: '',
threshold: 0, threshold: 0,
ethBalance: 0, ethBalance: '0',
owners: List([]), owners: List([]),
activeTokens: Set(), activeTokens: Set(),
activeAssets: Set(), activeAssets: Set(),

View File

@ -16,6 +16,7 @@ import makeSafe from 'src/routes/safe/store/models/safe'
import { checksumAddress } from 'src/utils/checksumAddress' import { checksumAddress } from 'src/utils/checksumAddress'
export const SAFE_REDUCER_ID = 'safes' export const SAFE_REDUCER_ID = 'safes'
export const DEFAULT_SAFE_INITIAL_STATE = 'NOT_ASKED'
export const buildSafe = (storedSafe) => { export const buildSafe = (storedSafe) => {
const names = storedSafe.owners.map((owner) => owner.name) const names = storedSafe.owners.map((owner) => owner.name)
@ -125,10 +126,8 @@ export default handleActions(
[SET_LATEST_MASTER_CONTRACT_VERSION]: (state, action) => state.set('latestMasterContractVersion', action.payload), [SET_LATEST_MASTER_CONTRACT_VERSION]: (state, action) => state.set('latestMasterContractVersion', action.payload),
}, },
Map({ Map({
// $FlowFixMe defaultSafe: DEFAULT_SAFE_INITIAL_STATE,
defaultSafe: undefined,
safes: Map(), safes: Map(),
// $FlowFixMe
latestMasterContractVersion: '', latestMasterContractVersion: '',
}), }),
) )

View File

@ -10,7 +10,7 @@ export const storage = new ImmortalStorage(stores)
const PREFIX = `v2_${getNetwork()}` const PREFIX = `v2_${getNetwork()}`
export const loadFromStorage = async (key) => { export const loadFromStorage = async <T = unknown>(key: string): Promise<T | undefined> => {
try { try {
const stringifiedValue = await storage.get(`${PREFIX}__${key}`) const stringifiedValue = await storage.get(`${PREFIX}__${key}`)
if (stringifiedValue === null || stringifiedValue === undefined) { if (stringifiedValue === null || stringifiedValue === undefined) {
@ -24,7 +24,10 @@ export const loadFromStorage = async (key) => {
} }
} }
export const saveToStorage = async (key, value) => { export const saveToStorage = async (
key: string,
value: Record<string, unknown> | boolean | string | number | Array<unknown>,
): Promise<void> => {
try { try {
const stringifiedValue = JSON.stringify(value) const stringifiedValue = JSON.stringify(value)
await storage.set(`${PREFIX}__${key}`, stringifiedValue) await storage.set(`${PREFIX}__${key}`, stringifiedValue)
@ -33,7 +36,7 @@ export const saveToStorage = async (key, value) => {
} }
} }
export const removeFromStorage = async (key) => { export const removeFromStorage = async (key: string): Promise<void> => {
try { try {
await storage.remove(`${PREFIX}__${key}`) await storage.remove(`${PREFIX}__${key}`)
} catch (err) { } catch (err) {

View File

@ -2396,7 +2396,7 @@
"@restless/sanitizers" "^0.2.4" "@restless/sanitizers" "^0.2.4"
reactive-properties "^0.1.11" reactive-properties "^0.1.11"
"@walletconnect/client@^1.0.11": "@walletconnect/client@^1.0.5":
version "1.0.11" version "1.0.11"
resolved "https://registry.yarnpkg.com/@walletconnect/client/-/client-1.0.11.tgz#ee58d2662e433cb67c5d2157c1b4525f864ab6ec" resolved "https://registry.yarnpkg.com/@walletconnect/client/-/client-1.0.11.tgz#ee58d2662e433cb67c5d2157c1b4525f864ab6ec"
integrity sha512-NVMDRUuLMqRPmzR7xjVfRcuXegvrCTtzuVyU8iYEnriZ3fFZcFj3PWVzN44RvLYQ4yUxWzeUkXIVRmmecOLMbQ== integrity sha512-NVMDRUuLMqRPmzR7xjVfRcuXegvrCTtzuVyU8iYEnriZ3fFZcFj3PWVzN44RvLYQ4yUxWzeUkXIVRmmecOLMbQ==
@ -2415,7 +2415,7 @@
"@walletconnect/types" "^1.0.11" "@walletconnect/types" "^1.0.11"
"@walletconnect/utils" "^1.0.11" "@walletconnect/utils" "^1.0.11"
"@walletconnect/http-connection@^1.0.11": "@walletconnect/http-connection@^1.0.5":
version "1.0.11" version "1.0.11"
resolved "https://registry.yarnpkg.com/@walletconnect/http-connection/-/http-connection-1.0.11.tgz#3c00ab02b4e6f4ffa1aa346b19569e585609dfa2" resolved "https://registry.yarnpkg.com/@walletconnect/http-connection/-/http-connection-1.0.11.tgz#3c00ab02b4e6f4ffa1aa346b19569e585609dfa2"
integrity sha512-kT9tKfp0KfKO+WkufSEi2Ppcgni2LB1Qly66uV3xZEwqouY+8Fs7Rf/BQ9o8KmosnP9WxBjgO+S4OMDWNLHCdA== integrity sha512-kT9tKfp0KfKO+WkufSEi2Ppcgni2LB1Qly66uV3xZEwqouY+8Fs7Rf/BQ9o8KmosnP9WxBjgO+S4OMDWNLHCdA==
@ -2438,7 +2438,7 @@
resolved "https://registry.yarnpkg.com/@walletconnect/mobile-registry/-/mobile-registry-1.0.11.tgz#55a060fb113524e75ed675fce1ab50bb6c5aa1ce" resolved "https://registry.yarnpkg.com/@walletconnect/mobile-registry/-/mobile-registry-1.0.11.tgz#55a060fb113524e75ed675fce1ab50bb6c5aa1ce"
integrity sha512-E78BfSr4RNSUPl/4Qpfg4bPO+QynMqUj55X20S41z1aGIYhXNM33sUVWGkbxO5rHuHYLB9Z5O/ob0sENKCXAfA== integrity sha512-E78BfSr4RNSUPl/4Qpfg4bPO+QynMqUj55X20S41z1aGIYhXNM33sUVWGkbxO5rHuHYLB9Z5O/ob0sENKCXAfA==
"@walletconnect/qrcode-modal@^1.0.11": "@walletconnect/qrcode-modal@^1.0.5":
version "1.0.11" version "1.0.11"
resolved "https://registry.yarnpkg.com/@walletconnect/qrcode-modal/-/qrcode-modal-1.0.11.tgz#5b34d583c034aed74307350797c66ddce6193744" resolved "https://registry.yarnpkg.com/@walletconnect/qrcode-modal/-/qrcode-modal-1.0.11.tgz#5b34d583c034aed74307350797c66ddce6193744"
integrity sha512-GsSQ/E3ixBEiQz3EOFypW2FCFIS6G37crpJunkLhefi9w2/CMeQ5bk4SIFKyGsAv6uEtwAcPVh7tNkoiGEsb2A== integrity sha512-GsSQ/E3ixBEiQz3EOFypW2FCFIS6G37crpJunkLhefi9w2/CMeQ5bk4SIFKyGsAv6uEtwAcPVh7tNkoiGEsb2A==
@ -2457,7 +2457,7 @@
"@walletconnect/types" "^1.0.11" "@walletconnect/types" "^1.0.11"
ws "7.3.0" ws "7.3.0"
"@walletconnect/types@^1.0.11": "@walletconnect/types@^1.0.11", "@walletconnect/types@^1.0.5":
version "1.0.11" version "1.0.11"
resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-1.0.11.tgz#6dd23eb3a8dd2824f76cc2c54217ecca8229d0a2" resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-1.0.11.tgz#6dd23eb3a8dd2824f76cc2c54217ecca8229d0a2"
integrity sha512-ysIQI6DsMELQAAt5zk2ZMKKyOwgk+XP4KeOhmDk+sIQskBugFl0ARl5iQZzGz9pcrHdlg1Fi7ucGw3UaExqIVA== integrity sha512-ysIQI6DsMELQAAt5zk2ZMKKyOwgk+XP4KeOhmDk+sIQskBugFl0ARl5iQZzGz9pcrHdlg1Fi7ucGw3UaExqIVA==
@ -2472,16 +2472,15 @@
enc-utils "2.1.0" enc-utils "2.1.0"
js-sha3 "0.8.0" js-sha3 "0.8.0"
"@walletconnect/web3-provider@^1.0.0-beta.47": "@walletconnect/web3-provider@^1.0.5":
version "1.0.11" version "1.0.5"
resolved "https://registry.yarnpkg.com/@walletconnect/web3-provider/-/web3-provider-1.0.11.tgz#462d84808379f5ec8747e46de217286d61837620" resolved "https://registry.yarnpkg.com/@walletconnect/web3-provider/-/web3-provider-1.0.5.tgz#c7e3d1a1449b3f1fd6125e3fabd361394dd1ecdd"
integrity sha512-ppzYwfrVtl5j8zMOPl07v5w+Gb0ptspQm3TGUqVVClaIdXt96uCBBPxwi5bZa4pSXVKgJvI9EbKzaqUS8kVsyQ== integrity sha512-oxrkE1kMZl7mlno7lmupLZS+slDC83nZh5UbE4B2Gr0aOTfT1lqyQMkvSXYOcl0fjR+p0x6iJ4sh0nvhV6Loug==
dependencies: dependencies:
"@walletconnect/client" "^1.0.11" "@walletconnect/client" "^1.0.5"
"@walletconnect/http-connection" "^1.0.11" "@walletconnect/http-connection" "^1.0.5"
"@walletconnect/qrcode-modal" "^1.0.11" "@walletconnect/qrcode-modal" "^1.0.5"
"@walletconnect/types" "^1.0.11" "@walletconnect/types" "^1.0.5"
"@walletconnect/utils" "^1.0.11"
web3-provider-engine "15.0.7" web3-provider-engine "15.0.7"
"@web3-js/scrypt-shim@^0.1.0": "@web3-js/scrypt-shim@^0.1.0":
@ -4083,20 +4082,20 @@ bn.js@^5.1.1, bn.js@^5.1.2:
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.2.tgz#c9686902d3c9a27729f43ab10f9d79c2004da7b0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.2.tgz#c9686902d3c9a27729f43ab10f9d79c2004da7b0"
integrity sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA== integrity sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA==
bnc-onboard@1.9.4: bnc-onboard@1.10.0:
version "1.9.4" version "1.10.0"
resolved "https://registry.yarnpkg.com/bnc-onboard/-/bnc-onboard-1.9.4.tgz#c70c41652234009708e3a2334ab064a3a4c008c3" resolved "https://registry.yarnpkg.com/bnc-onboard/-/bnc-onboard-1.10.0.tgz#f1c2797b10060808a20b46fa072c57a466bb5f6a"
integrity sha512-mtecvcE/I4+6fydWXVZrrw1OD/UaiR36IojyIeXqxEg6tI/6pu0DsJhszWF5FeN83dg1qOy24eo4rTNh8WKItQ== integrity sha512-d/Xj1RxoDOL3IN7bdD0ZENjSkbMXcvn+cWLLOi/7XbI+H+elDZS/knAnYLa99j54Mu+odTSXuBy94EwvXbey0w==
dependencies: dependencies:
"@ledgerhq/hw-app-eth" "^5.7.0" "@ledgerhq/hw-app-eth" "^5.7.0"
"@ledgerhq/hw-transport-u2f" "^5.7.0" "@ledgerhq/hw-transport-u2f" "^5.7.0"
"@portis/web3" "^2.0.0-beta.42" "@portis/web3" "^2.0.0-beta.42"
"@toruslabs/torus-embed" "^1.3.0" "@toruslabs/torus-embed" "^1.3.0"
"@unilogin/provider" "^0.5.21" "@unilogin/provider" "^0.5.21"
"@walletconnect/web3-provider" "^1.0.0-beta.47" "@walletconnect/web3-provider" "^1.0.5"
authereum "^0.0.4-beta.157" authereum "^0.0.4-beta.157"
bignumber.js "^9.0.0" bignumber.js "^9.0.0"
bnc-sdk "^2.1.3" bnc-sdk "^2.1.4"
bowser "^2.5.2" bowser "^2.5.2"
ethereumjs-tx "^2.1.2" ethereumjs-tx "^2.1.2"
ethereumjs-util "^6.2.0" ethereumjs-util "^6.2.0"
@ -4109,7 +4108,7 @@ bnc-onboard@1.9.4:
walletlink "^2.0.2" walletlink "^2.0.2"
web3-provider-engine "^15.0.4" web3-provider-engine "^15.0.4"
bnc-sdk@^2.1.3: bnc-sdk@^2.1.4:
version "2.1.4" version "2.1.4"
resolved "https://registry.yarnpkg.com/bnc-sdk/-/bnc-sdk-2.1.4.tgz#23267198f5a48e800d9c2406f6d04a767cab5643" resolved "https://registry.yarnpkg.com/bnc-sdk/-/bnc-sdk-2.1.4.tgz#23267198f5a48e800d9c2406f6d04a767cab5643"
integrity sha512-aU7DYweE+6tfTvZE7NOOfQsieU2Zyrav6o/xwuLt+uKGvrkblIeg1aqBW1yAQBEg4LCHEygX6TwZk8VznDAh3g== integrity sha512-aU7DYweE+6tfTvZE7NOOfQsieU2Zyrav6o/xwuLt+uKGvrkblIeg1aqBW1yAQBEg4LCHEygX6TwZk8VznDAh3g==
@ -17648,6 +17647,7 @@ websocket@^1.0.31:
dependencies: dependencies:
debug "^2.2.0" debug "^2.2.0"
es5-ext "^0.10.50" es5-ext "^0.10.50"
gulp "^4.0.2"
nan "^2.14.0" nan "^2.14.0"
typedarray-to-buffer "^3.1.5" typedarray-to-buffer "^3.1.5"
yaeti "^0.0.6" yaeti "^0.0.6"