mirror of
https://github.com/status-im/safe-react.git
synced 2025-01-12 02:54:09 +00:00
* Generates a cache to avoid multiples getHumanFriendlyToken() for the same token address * Adds etags implementation for transactions * Caches outgoing and incoming safe transactions based on etag value * Removes cachedSafeTransactions, cachedSafeIncommingTransactions * Refactors getTokenInstance * Avoid recreating tokens on fetchTokens() once we have them in redux * Fixs error on catch * Batch request tokens balances * Fixs missing token names Changes the tokens limit from 300 to 3000 * fix: failed to instantiate non-standard ERC-20 tokens For the batchRequest of balances, we're just using the `balanceOf` method call. So having a simple ABI with that only method prevents errors with non-standard ERC-20 Tokens. * Removes unnecessary action updateSafeThreshold Removes unnecessary action fetchEtherBalance * Updated comments in code Replaces constant with directly dispatching action * BatchRequest done right * fix: invalid action name `savedToken` -> `saveToken` * Renames getTokenInstance to getTokenInfos Fixs first load of transactions are empty * Move fetchTokenBalances to `Balances` and `SendModal` components * fix: Incoming transaction type Backend now changed the type from 'incoming' to one of: `'ERC721_TRANSFER', 'ERC20_TRANSFER', 'ETHER_TRANSFER'` * fix: tokenInstance `symbol` and `decimal` extraction * Fix property name `decimals` instead of `tokenDecimals` * Standardize non-standard ERC20 tokens discovery * fix: isStandardERC20 * Revert "Move fetchTokenBalances to `Balances` and `SendModal` components" This reverts commit ed84bd92 * Fixs Typo INCOMING_TX_TYPES Renames tokenInstance with localToken * Renames getBatchBalances to getTokenBalances Returns saved tokens instead of tokenInstance in getTokenInfos * Remove promise returns Co-authored-by: fernandomg <fernando.greco@gmail.com>
This commit is contained in:
parent
58130760c4
commit
c73dafe3ce
@ -9,7 +9,7 @@ const fetchTokenBalanceList = (safeAddress: string) => {
|
|||||||
|
|
||||||
return axios.get(url, {
|
return axios.get(url, {
|
||||||
params: {
|
params: {
|
||||||
limit: 300,
|
limit: 3000,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ const fetchTokenList = () => {
|
|||||||
|
|
||||||
return axios.get(url, {
|
return axios.get(url, {
|
||||||
params: {
|
params: {
|
||||||
limit: 300,
|
limit: 3000,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,10 @@ import saveTokens from './saveTokens'
|
|||||||
|
|
||||||
import { fetchTokenList } from '~/logic/tokens/api'
|
import { fetchTokenList } from '~/logic/tokens/api'
|
||||||
import { type TokenProps, makeToken } from '~/logic/tokens/store/model/token'
|
import { type TokenProps, makeToken } from '~/logic/tokens/store/model/token'
|
||||||
|
import { tokensSelector } from '~/logic/tokens/store/selectors'
|
||||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||||
import { type GlobalState } from '~/store'
|
import { type GlobalState } from '~/store'
|
||||||
|
import { store } from '~/store/index'
|
||||||
import { ensureOnce } from '~/utils/singleton'
|
import { ensureOnce } from '~/utils/singleton'
|
||||||
|
|
||||||
const createStandardTokenContract = async () => {
|
const createStandardTokenContract = async () => {
|
||||||
@ -37,12 +39,67 @@ const createERC721TokenContract = async () => {
|
|||||||
return erc721Token
|
return erc721Token
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const OnlyBalanceToken = {
|
||||||
|
contractName: 'OnlyBalanceToken',
|
||||||
|
abi: [
|
||||||
|
{
|
||||||
|
constant: true,
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: 'owner',
|
||||||
|
type: 'address',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: 'balanceOf',
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
name: '',
|
||||||
|
type: 'uint256',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
payable: false,
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
constant: true,
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: 'owner',
|
||||||
|
type: 'address',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: 'balances',
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
name: '',
|
||||||
|
type: 'uint256',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
payable: false,
|
||||||
|
stateMutability: 'view',
|
||||||
|
type: 'function',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
// For the `batchRequest` of balances, we're just using the `balanceOf` method call.
|
||||||
|
// So having a simple ABI only with `balanceOf` prevents errors
|
||||||
|
// when instantiating non-standard ERC-20 Tokens.
|
||||||
|
const createOnlyBalanceToken = () => {
|
||||||
|
const web3 = getWeb3()
|
||||||
|
const contract = new web3.eth.Contract(OnlyBalanceToken.abi)
|
||||||
|
return contract
|
||||||
|
}
|
||||||
|
|
||||||
export const getHumanFriendlyToken = ensureOnce(createHumanFriendlyTokenContract)
|
export const getHumanFriendlyToken = ensureOnce(createHumanFriendlyTokenContract)
|
||||||
|
|
||||||
export const getStandardTokenContract = ensureOnce(createStandardTokenContract)
|
export const getStandardTokenContract = ensureOnce(createStandardTokenContract)
|
||||||
|
|
||||||
export const getERC721TokenContract = ensureOnce(createERC721TokenContract)
|
export const getERC721TokenContract = ensureOnce(createERC721TokenContract)
|
||||||
|
|
||||||
|
export const getOnlyBalanceToken = ensureOnce(createOnlyBalanceToken)
|
||||||
|
|
||||||
export const containsMethodByHash = async (contractAddress: string, methodHash: string) => {
|
export const containsMethodByHash = async (contractAddress: string, methodHash: string) => {
|
||||||
const web3 = getWeb3()
|
const web3 = getWeb3()
|
||||||
const byteCode = await web3.eth.getCode(contractAddress)
|
const byteCode = await web3.eth.getCode(contractAddress)
|
||||||
@ -50,19 +107,54 @@ export const containsMethodByHash = async (contractAddress: string, methodHash:
|
|||||||
return byteCode.indexOf(methodHash.replace('0x', '')) !== -1
|
return byteCode.indexOf(methodHash.replace('0x', '')) !== -1
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchTokens = () => async (dispatch: ReduxDispatch<GlobalState>) => {
|
export const getTokenInfos = async (tokenAddress: string) => {
|
||||||
|
if (!tokenAddress) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const { tokens } = store.getState()
|
||||||
|
const localToken = tokens.get(tokenAddress)
|
||||||
|
// If the token is inside the store we return the store token
|
||||||
|
if (localToken) {
|
||||||
|
return localToken
|
||||||
|
}
|
||||||
|
// Otherwise we fetch it, save it to the store and return it
|
||||||
|
const tokenContract = await getHumanFriendlyToken()
|
||||||
|
const tokenInstance = await tokenContract.at(tokenAddress)
|
||||||
|
const [tokenSymbol, tokenDecimals, name] = await Promise.all([
|
||||||
|
tokenInstance.symbol(),
|
||||||
|
tokenInstance.decimals(),
|
||||||
|
tokenInstance.name(),
|
||||||
|
])
|
||||||
|
const savedToken = makeToken({
|
||||||
|
address: tokenAddress,
|
||||||
|
name: name ? name : tokenSymbol,
|
||||||
|
symbol: tokenSymbol,
|
||||||
|
decimals: tokenDecimals,
|
||||||
|
logoUri: '',
|
||||||
|
})
|
||||||
|
const newTokens = tokens.set(tokenAddress, savedToken)
|
||||||
|
store.dispatch(saveTokens(newTokens))
|
||||||
|
|
||||||
|
return savedToken
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchTokens = () => async (dispatch: ReduxDispatch<GlobalState>, getState: Function) => {
|
||||||
try {
|
try {
|
||||||
|
const currentSavedTokens = tokensSelector(getState())
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: { results: tokenList },
|
data: { results: tokenList },
|
||||||
} = await fetchTokenList()
|
} = await fetchTokenList()
|
||||||
|
|
||||||
|
if (currentSavedTokens && currentSavedTokens.size === tokenList.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const tokens = List(tokenList.map((token: TokenProps) => makeToken(token)))
|
const tokens = List(tokenList.map((token: TokenProps) => makeToken(token)))
|
||||||
|
|
||||||
dispatch(saveTokens(tokens))
|
dispatch(saveTokens(tokens))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error fetching token list', err)
|
console.error('Error fetching token list', err)
|
||||||
|
|
||||||
return Promise.resolve()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ import Paragraph from '~/components/layout/Paragraph'
|
|||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
import Span from '~/components/layout/Span'
|
import Span from '~/components/layout/Span'
|
||||||
import IncomingTxDescription from '~/routes/safe/components/Transactions/TxsTable/ExpandedTx/IncomingTxDescription'
|
import IncomingTxDescription from '~/routes/safe/components/Transactions/TxsTable/ExpandedTx/IncomingTxDescription'
|
||||||
import { INCOMING_TX_TYPE } from '~/routes/safe/store/models/incomingTransaction'
|
import { INCOMING_TX_TYPES } from '~/routes/safe/store/models/incomingTransaction'
|
||||||
import { type Owner } from '~/routes/safe/store/models/owner'
|
import { type Owner } from '~/routes/safe/store/models/owner'
|
||||||
import { type Transaction } from '~/routes/safe/store/models/transaction'
|
import { type Transaction } from '~/routes/safe/store/models/transaction'
|
||||||
|
|
||||||
@ -58,8 +58,8 @@ const ExpandedTx = ({
|
|||||||
const [openModal, setOpenModal] = useState<OpenModal>(null)
|
const [openModal, setOpenModal] = useState<OpenModal>(null)
|
||||||
const openApproveModal = () => setOpenModal('approveTx')
|
const openApproveModal = () => setOpenModal('approveTx')
|
||||||
const closeModal = () => setOpenModal(null)
|
const closeModal = () => setOpenModal(null)
|
||||||
const thresholdReached = tx.type !== INCOMING_TX_TYPE && threshold <= tx.confirmations.size
|
const thresholdReached = !INCOMING_TX_TYPES.includes(tx.type) && threshold <= tx.confirmations.size
|
||||||
const canExecute = tx.type !== INCOMING_TX_TYPE && nonce === tx.nonce
|
const canExecute = !INCOMING_TX_TYPES.includes(tx.type) && nonce === tx.nonce
|
||||||
const cancelThresholdReached = !!cancelTx && threshold <= cancelTx.confirmations.size
|
const cancelThresholdReached = !!cancelTx && threshold <= cancelTx.confirmations.size
|
||||||
const canExecuteCancel = nonce === tx.nonce
|
const canExecuteCancel = nonce === tx.nonce
|
||||||
|
|
||||||
@ -76,7 +76,9 @@ const ExpandedTx = ({
|
|||||||
<Block className={classes.expandedTxBlock}>
|
<Block className={classes.expandedTxBlock}>
|
||||||
<Row>
|
<Row>
|
||||||
<Col layout="column" xs={6}>
|
<Col layout="column" xs={6}>
|
||||||
<Block className={cn(classes.txDataContainer, tx.type === INCOMING_TX_TYPE && classes.incomingTxBlock)}>
|
<Block
|
||||||
|
className={cn(classes.txDataContainer, INCOMING_TX_TYPES.includes(tx.type) && classes.incomingTxBlock)}
|
||||||
|
>
|
||||||
<Block align="left" className={classes.txData}>
|
<Block align="left" className={classes.txData}>
|
||||||
<Bold className={classes.txHash}>Hash:</Bold>
|
<Bold className={classes.txHash}>Hash:</Bold>
|
||||||
{tx.executionTxHash ? <EtherScanLink cut={8} type="tx" value={tx.executionTxHash} /> : 'n/a'}
|
{tx.executionTxHash ? <EtherScanLink cut={8} type="tx" value={tx.executionTxHash} /> : 'n/a'}
|
||||||
@ -89,7 +91,7 @@ const ExpandedTx = ({
|
|||||||
<Bold>Fee: </Bold>
|
<Bold>Fee: </Bold>
|
||||||
{tx.fee ? tx.fee : 'n/a'}
|
{tx.fee ? tx.fee : 'n/a'}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
{tx.type === INCOMING_TX_TYPE ? (
|
{INCOMING_TX_TYPES.includes(tx.type) ? (
|
||||||
<>
|
<>
|
||||||
<Paragraph noMargin>
|
<Paragraph noMargin>
|
||||||
<Bold>Created: </Bold>
|
<Bold>Created: </Bold>
|
||||||
@ -128,9 +130,9 @@ const ExpandedTx = ({
|
|||||||
)}
|
)}
|
||||||
</Block>
|
</Block>
|
||||||
<Hairline />
|
<Hairline />
|
||||||
{tx.type === INCOMING_TX_TYPE ? <IncomingTxDescription tx={tx} /> : <TxDescription tx={tx} />}
|
{INCOMING_TX_TYPES.includes(tx.type) ? <IncomingTxDescription tx={tx} /> : <TxDescription tx={tx} />}
|
||||||
</Col>
|
</Col>
|
||||||
{tx.type !== INCOMING_TX_TYPE && (
|
{!INCOMING_TX_TYPES.includes(tx.type) && (
|
||||||
<OwnersColumn
|
<OwnersColumn
|
||||||
cancelThresholdReached={cancelThresholdReached}
|
cancelThresholdReached={cancelThresholdReached}
|
||||||
cancelTx={cancelTx}
|
cancelTx={cancelTx}
|
||||||
|
@ -9,7 +9,7 @@ import TxType from './TxType'
|
|||||||
import { type Column } from '~/components/Table/TableHead'
|
import { type Column } from '~/components/Table/TableHead'
|
||||||
import { type SortRow, buildOrderFieldFrom } from '~/components/Table/sorting'
|
import { type SortRow, buildOrderFieldFrom } from '~/components/Table/sorting'
|
||||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||||
import { INCOMING_TX_TYPE, type IncomingTransaction } from '~/routes/safe/store/models/incomingTransaction'
|
import { INCOMING_TX_TYPES, type IncomingTransaction } from '~/routes/safe/store/models/incomingTransaction'
|
||||||
import { type Transaction } from '~/routes/safe/store/models/transaction'
|
import { type Transaction } from '~/routes/safe/store/models/transaction'
|
||||||
|
|
||||||
export const TX_TABLE_ID = 'id'
|
export const TX_TABLE_ID = 'id'
|
||||||
@ -101,7 +101,7 @@ export const getTxTableData = (
|
|||||||
const cancelTxsByNonce = cancelTxs.reduce((acc, tx) => acc.set(tx.nonce, tx), Map())
|
const cancelTxsByNonce = cancelTxs.reduce((acc, tx) => acc.set(tx.nonce, tx), Map())
|
||||||
|
|
||||||
return transactions.map(tx => {
|
return transactions.map(tx => {
|
||||||
if (tx.type === INCOMING_TX_TYPE) {
|
if (INCOMING_TX_TYPES.includes(tx.type)) {
|
||||||
return getIncomingTxTableData(tx)
|
return getIncomingTxTableData(tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,11 +125,12 @@ class SafeView extends React.Component<Props, State> {
|
|||||||
fetchEtherBalance,
|
fetchEtherBalance,
|
||||||
fetchTokenBalances,
|
fetchTokenBalances,
|
||||||
fetchTransactions,
|
fetchTransactions,
|
||||||
|
safe,
|
||||||
safeUrl,
|
safeUrl,
|
||||||
} = this.props
|
} = this.props
|
||||||
checkAndUpdateSafeOwners(safeUrl)
|
checkAndUpdateSafeOwners(safeUrl)
|
||||||
fetchTokenBalances(safeUrl, activeTokens)
|
fetchTokenBalances(safeUrl, activeTokens)
|
||||||
fetchEtherBalance(safeUrl)
|
fetchEtherBalance(safe)
|
||||||
fetchTransactions(safeUrl)
|
fetchTransactions(safeUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,13 +3,17 @@ import type { Dispatch as ReduxDispatch } from 'redux'
|
|||||||
|
|
||||||
import { getBalanceInEtherOf } from '~/logic/wallets/getWeb3'
|
import { getBalanceInEtherOf } from '~/logic/wallets/getWeb3'
|
||||||
import updateSafe from '~/routes/safe/store/actions/updateSafe'
|
import updateSafe from '~/routes/safe/store/actions/updateSafe'
|
||||||
|
import type { Safe } from '~/routes/safe/store/models/safe'
|
||||||
import { type GlobalState } from '~/store/index'
|
import { type GlobalState } from '~/store/index'
|
||||||
|
|
||||||
const fetchEtherBalance = (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalState>) => {
|
const fetchEtherBalance = (safe: Safe) => async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||||
try {
|
try {
|
||||||
const ethBalance = await getBalanceInEtherOf(safeAddress)
|
const { address, ethBalance } = safe
|
||||||
|
const newEthBalance = await getBalanceInEtherOf(address)
|
||||||
|
|
||||||
dispatch(updateSafe({ address: safeAddress, ethBalance }))
|
if (newEthBalance !== ethBalance) {
|
||||||
|
dispatch(updateSafe({ address, newEthBalance }))
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
console.error('Error when fetching Ether balance:', err)
|
console.error('Error when fetching Ether balance:', err)
|
||||||
|
@ -70,12 +70,16 @@ export const checkAndUpdateSafe = (safeAdd: string) => async (dispatch: ReduxDis
|
|||||||
const remoteOwners = await gnosisSafe.getOwners()
|
const remoteOwners = await gnosisSafe.getOwners()
|
||||||
// Converts from [ { address, ownerName} ] to address array
|
// Converts from [ { address, ownerName} ] to address array
|
||||||
const localOwners = localSafe.owners.map(localOwner => localOwner.address)
|
const localOwners = localSafe.owners.map(localOwner => localOwner.address)
|
||||||
|
const localThreshold = localSafe.threshold
|
||||||
|
|
||||||
// Updates threshold values
|
// Updates threshold values
|
||||||
const threshold = await gnosisSafe.getThreshold()
|
const remoteThreshold = await gnosisSafe.getThreshold()
|
||||||
localSafe.threshold = threshold.toNumber()
|
localSafe.threshold = remoteThreshold.toNumber()
|
||||||
|
|
||||||
|
if (localThreshold !== remoteThreshold.toNumber()) {
|
||||||
|
dispatch(updateSafeThreshold({ safeAddress, threshold: remoteThreshold.toNumber() }))
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(updateSafeThreshold({ safeAddress, threshold: threshold.toNumber() }))
|
|
||||||
// If the remote owners does not contain a local address, we remove that local owner
|
// If the remote owners does not contain a local address, we remove that local owner
|
||||||
localOwners.forEach(localAddress => {
|
localOwners.forEach(localAddress => {
|
||||||
const remoteOwnerIndex = remoteOwners.findIndex(remoteAddress => sameAddress(remoteAddress, localAddress))
|
const remoteOwnerIndex = remoteOwners.findIndex(remoteAddress => sameAddress(remoteAddress, localAddress))
|
||||||
|
@ -5,10 +5,61 @@ import type { Dispatch as ReduxDispatch } from 'redux'
|
|||||||
|
|
||||||
import updateSafe from './updateSafe'
|
import updateSafe from './updateSafe'
|
||||||
|
|
||||||
import { getStandardTokenContract } from '~/logic/tokens/store/actions/fetchTokens'
|
import { getOnlyBalanceToken, getStandardTokenContract } from '~/logic/tokens/store/actions/fetchTokens'
|
||||||
import { type Token } from '~/logic/tokens/store/model/token'
|
import { type Token } from '~/logic/tokens/store/model/token'
|
||||||
import { ETH_ADDRESS } from '~/logic/tokens/utils/tokenHelpers'
|
import { ETH_ADDRESS } from '~/logic/tokens/utils/tokenHelpers'
|
||||||
|
import { sameAddress } from '~/logic/wallets/ethAddresses'
|
||||||
|
import { ETHEREUM_NETWORK, getWeb3 } from '~/logic/wallets/getWeb3'
|
||||||
import { type GlobalState } from '~/store/index'
|
import { type GlobalState } from '~/store/index'
|
||||||
|
import { NETWORK } from '~/utils/constants'
|
||||||
|
|
||||||
|
// List of all the non-standard ERC20 tokens
|
||||||
|
const nonStandardERC20 = [
|
||||||
|
// DATAcoin
|
||||||
|
{ network: ETHEREUM_NETWORK.RINKEBY, address: '0x0cf0ee63788a0849fe5297f3407f701e122cc023' },
|
||||||
|
]
|
||||||
|
|
||||||
|
// This is done due to an issues with DATAcoin contract in Rinkeby
|
||||||
|
// https://rinkeby.etherscan.io/address/0x0cf0ee63788a0849fe5297f3407f701e122cc023#readContract
|
||||||
|
// It doesn't have a `balanceOf` method implemented.
|
||||||
|
const isStandardERC20 = (address: string): boolean => {
|
||||||
|
return !nonStandardERC20.find(token => sameAddress(address, token.address) && sameAddress(NETWORK, token.network))
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTokenBalances = (tokens: List<Token>, safeAddress: string) => {
|
||||||
|
const web3 = getWeb3()
|
||||||
|
const batch = new web3.BatchRequest()
|
||||||
|
|
||||||
|
const safeTokens = tokens.toJS().filter(({ address }) => address !== ETH_ADDRESS)
|
||||||
|
const safeTokensBalances = safeTokens.map(({ address, decimals }: any) => {
|
||||||
|
const onlyBalanceToken = getOnlyBalanceToken()
|
||||||
|
onlyBalanceToken.options.address = address
|
||||||
|
|
||||||
|
// As a fallback, we're using `balances`
|
||||||
|
const method = isStandardERC20(address) ? 'balanceOf' : 'balances'
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const request = onlyBalanceToken.methods[method](safeAddress).call.request((error, balance) => {
|
||||||
|
if (error) {
|
||||||
|
// if there's no balance, we log the error, but `resolve` with a default '0'
|
||||||
|
console.error('No balance method found', error)
|
||||||
|
resolve('0')
|
||||||
|
} else {
|
||||||
|
resolve({
|
||||||
|
address,
|
||||||
|
balance: new BigNumber(balance).div(`1e${decimals}`).toFixed(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
batch.add(request)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
batch.execute()
|
||||||
|
|
||||||
|
return Promise.all(safeTokensBalances)
|
||||||
|
}
|
||||||
|
|
||||||
export const calculateBalanceOf = async (tokenAddress: string, safeAddress: string, decimals: number = 18) => {
|
export const calculateBalanceOf = async (tokenAddress: string, safeAddress: string, decimals: number = 18) => {
|
||||||
if (tokenAddress === ETH_ADDRESS) {
|
if (tokenAddress === ETH_ADDRESS) {
|
||||||
@ -34,15 +85,7 @@ const fetchTokenBalances = (safeAddress: string, tokens: List<Token>) => async (
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const withBalances = await Promise.all(
|
const withBalances = await getTokenBalances(tokens, safeAddress)
|
||||||
tokens.map(async token => {
|
|
||||||
const balance = await calculateBalanceOf(token.address, safeAddress, token.decimals)
|
|
||||||
return {
|
|
||||||
address: token.address,
|
|
||||||
balance,
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
const balances = Map().withMutations(map => {
|
const balances = Map().withMutations(map => {
|
||||||
withBalances.forEach(({ address, balance }) => {
|
withBalances.forEach(({ address, balance }) => {
|
||||||
|
@ -11,7 +11,7 @@ import { decodeParamsFromSafeMethod } from '~/logic/contracts/methodIds'
|
|||||||
import { buildIncomingTxServiceUrl } from '~/logic/safe/transactions/incomingTxHistory'
|
import { buildIncomingTxServiceUrl } from '~/logic/safe/transactions/incomingTxHistory'
|
||||||
import { type TxServiceType, buildTxServiceUrl } from '~/logic/safe/transactions/txHistory'
|
import { type TxServiceType, buildTxServiceUrl } from '~/logic/safe/transactions/txHistory'
|
||||||
import { getLocalSafe } from '~/logic/safe/utils'
|
import { getLocalSafe } from '~/logic/safe/utils'
|
||||||
import { getHumanFriendlyToken } from '~/logic/tokens/store/actions/fetchTokens'
|
import { getTokenInfos } from '~/logic/tokens/store/actions/fetchTokens'
|
||||||
import { ALTERNATIVE_TOKEN_ABI } from '~/logic/tokens/utils/alternativeAbi'
|
import { ALTERNATIVE_TOKEN_ABI } from '~/logic/tokens/utils/alternativeAbi'
|
||||||
import {
|
import {
|
||||||
DECIMALS_METHOD_HASH,
|
DECIMALS_METHOD_HASH,
|
||||||
@ -111,9 +111,9 @@ export const buildTransactionFrom = async (safeAddress: string, tx: TxServiceMod
|
|||||||
let refundSymbol = 'ETH'
|
let refundSymbol = 'ETH'
|
||||||
let decimals = 18
|
let decimals = 18
|
||||||
if (tx.gasToken !== ZERO_ADDRESS) {
|
if (tx.gasToken !== ZERO_ADDRESS) {
|
||||||
const gasToken = await (await getHumanFriendlyToken()).at(tx.gasToken)
|
const gasToken = await getTokenInfos(tx.gasToken)
|
||||||
refundSymbol = await gasToken.symbol()
|
refundSymbol = gasToken.symbol
|
||||||
decimals = await gasToken.decimals()
|
decimals = gasToken.decimals
|
||||||
}
|
}
|
||||||
|
|
||||||
const feeString = (tx.gasPrice * (tx.baseGas + tx.safeTxGas)).toString().padStart(decimals, 0)
|
const feeString = (tx.gasPrice * (tx.baseGas + tx.safeTxGas)).toString().padStart(decimals, 0)
|
||||||
@ -131,10 +131,10 @@ export const buildTransactionFrom = async (safeAddress: string, tx: TxServiceMod
|
|||||||
let decimals = 18
|
let decimals = 18
|
||||||
let decodedParams
|
let decodedParams
|
||||||
if (isSendTokenTx) {
|
if (isSendTokenTx) {
|
||||||
const tokenContract = await getHumanFriendlyToken()
|
const tokenInstance = await getTokenInfos(tx.to)
|
||||||
const tokenInstance = await tokenContract.at(tx.to)
|
|
||||||
try {
|
try {
|
||||||
;[symbol, decimals] = await Promise.all([tokenInstance.symbol(), tokenInstance.decimals()])
|
symbol = tokenInstance.symbol
|
||||||
|
decimals = tokenInstance.decimals
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const alternativeTokenInstance = new web3.eth.Contract(ALTERNATIVE_TOKEN_ABI, tx.to)
|
const alternativeTokenInstance = new web3.eth.Contract(ALTERNATIVE_TOKEN_ABI, tx.to)
|
||||||
const [tokenSymbol, tokenDecimals] = await Promise.all([
|
const [tokenSymbol, tokenDecimals] = await Promise.all([
|
||||||
@ -230,11 +230,9 @@ export const buildIncomingTransactionFrom = async (tx: IncomingTxServiceModel) =
|
|||||||
|
|
||||||
if (tx.tokenAddress) {
|
if (tx.tokenAddress) {
|
||||||
try {
|
try {
|
||||||
const tokenContract = await getHumanFriendlyToken()
|
const tokenInstance = await getTokenInfos(tx.tokenAddress)
|
||||||
const tokenInstance = await tokenContract.at(tx.tokenAddress)
|
symbol = tokenInstance.symbol
|
||||||
const [tokenSymbol, tokenDecimals] = await Promise.all([tokenInstance.symbol(), tokenInstance.decimals()])
|
decimals = tokenInstance.decimals
|
||||||
symbol = tokenSymbol
|
|
||||||
decimals = tokenDecimals
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
try {
|
try {
|
||||||
const { methods } = new web3.eth.Contract(ALTERNATIVE_TOKEN_ABI, tx.tokenAddress)
|
const { methods } = new web3.eth.Contract(ALTERNATIVE_TOKEN_ABI, tx.tokenAddress)
|
||||||
@ -269,19 +267,41 @@ export type SafeTransactionsType = {
|
|||||||
cancel: Map<string, List<TransactionProps>>,
|
cancel: Map<string, List<TransactionProps>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let etagSafeTransactions = null
|
||||||
|
let etagCachedSafeIncommingTransactions = null
|
||||||
export const loadSafeTransactions = async (safeAddress: string): Promise<SafeTransactionsType> => {
|
export const loadSafeTransactions = async (safeAddress: string): Promise<SafeTransactionsType> => {
|
||||||
let transactions: TxServiceModel[] = addMockSafeCreationTx(safeAddress)
|
let transactions: TxServiceModel[] = addMockSafeCreationTx(safeAddress)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const config = etagSafeTransactions
|
||||||
|
? {
|
||||||
|
headers: {
|
||||||
|
'If-None-Match': etagSafeTransactions,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
|
||||||
const url = buildTxServiceUrl(safeAddress)
|
const url = buildTxServiceUrl(safeAddress)
|
||||||
const response = await axios.get(url)
|
const response = await axios.get(url, config)
|
||||||
if (response.data.count > 0) {
|
if (response.data.count > 0) {
|
||||||
transactions = transactions.concat(response.data.results)
|
transactions = transactions.concat(response.data.results)
|
||||||
|
if (etagSafeTransactions === response.headers.etag) {
|
||||||
|
// The txs are the same, we can return the cached ones
|
||||||
|
return
|
||||||
|
}
|
||||||
|
etagSafeTransactions = response.headers.etag
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Requests for outgoing transactions for ${safeAddress} failed with 404`, err)
|
if (err && err.response && err.response.status === 304) {
|
||||||
|
// NOTE: this is the expected implementation, currently the backend is not returning 304.
|
||||||
|
// So I check if the returned etag is the same instead (see above)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
console.error(`Requests for outgoing transactions for ${safeAddress} failed with 404`, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In case that the etags don't match, we parse the new transactions and save them to the cache
|
||||||
const txsRecord: Array<RecordInstance<TransactionProps>> = await Promise.all(
|
const txsRecord: Array<RecordInstance<TransactionProps>> = await Promise.all(
|
||||||
transactions.map((tx: TxServiceModel) => buildTransactionFrom(safeAddress, tx)),
|
transactions.map((tx: TxServiceModel) => buildTransactionFrom(safeAddress, tx)),
|
||||||
)
|
)
|
||||||
@ -297,26 +317,50 @@ export const loadSafeTransactions = async (safeAddress: string): Promise<SafeTra
|
|||||||
export const loadSafeIncomingTransactions = async (safeAddress: string) => {
|
export const loadSafeIncomingTransactions = async (safeAddress: string) => {
|
||||||
let incomingTransactions: IncomingTxServiceModel[] = []
|
let incomingTransactions: IncomingTxServiceModel[] = []
|
||||||
try {
|
try {
|
||||||
|
const config = etagCachedSafeIncommingTransactions
|
||||||
|
? {
|
||||||
|
headers: {
|
||||||
|
'If-None-Match': etagCachedSafeIncommingTransactions,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
const url = buildIncomingTxServiceUrl(safeAddress)
|
const url = buildIncomingTxServiceUrl(safeAddress)
|
||||||
const response = await axios.get(url)
|
const response = await axios.get(url, config)
|
||||||
if (response.data.count > 0) {
|
if (response.data.count > 0) {
|
||||||
incomingTransactions = response.data.results
|
incomingTransactions = response.data.results
|
||||||
|
if (etagCachedSafeIncommingTransactions === response.headers.etag) {
|
||||||
|
// The txs are the same, we can return the cached ones
|
||||||
|
return
|
||||||
|
}
|
||||||
|
etagCachedSafeIncommingTransactions = response.headers.etag
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Requests for incoming transactions for ${safeAddress} failed with 404`, err)
|
if (err && err.response && err.response.status === 304) {
|
||||||
|
// We return cached transactions
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
console.error(`Requests for incoming transactions for ${safeAddress} failed with 404`, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const incomingTxsRecord = await Promise.all(incomingTransactions.map(buildIncomingTransactionFrom))
|
const incomingTxsRecord = await Promise.all(incomingTransactions.map(buildIncomingTransactionFrom))
|
||||||
|
|
||||||
return Map().set(safeAddress, List(incomingTxsRecord))
|
return Map().set(safeAddress, List(incomingTxsRecord))
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalState>) => {
|
export default (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||||
web3 = await getWeb3()
|
web3 = await getWeb3()
|
||||||
|
|
||||||
const { cancel, outgoing }: SafeTransactionsType = await loadSafeTransactions(safeAddress)
|
const transactions: SafeTransactionsType | undefined = await loadSafeTransactions(safeAddress)
|
||||||
const incomingTransactions: Map<string, List<IncomingTransaction>> = await loadSafeIncomingTransactions(safeAddress)
|
if (transactions) {
|
||||||
dispatch(addCancellationTransactions(cancel))
|
const { cancel, outgoing } = transactions
|
||||||
dispatch(addTransactions(outgoing))
|
dispatch(addCancellationTransactions(cancel))
|
||||||
dispatch(addIncomingTransactions(incomingTransactions))
|
dispatch(addTransactions(outgoing))
|
||||||
|
}
|
||||||
|
const incomingTransactions: Map<string, List<IncomingTransaction>> | undefined = await loadSafeIncomingTransactions(
|
||||||
|
safeAddress,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (incomingTransactions) {
|
||||||
|
dispatch(addIncomingTransactions(incomingTransactions))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import { Record } from 'immutable'
|
import { Record } from 'immutable'
|
||||||
import type { RecordFactory, RecordOf } from 'immutable'
|
import type { RecordFactory, RecordOf } from 'immutable'
|
||||||
|
|
||||||
export const INCOMING_TX_TYPE = 'incoming'
|
export const INCOMING_TX_TYPES = ['ERC721_TRANSFER', 'ERC20_TRANSFER', 'ETHER_TRANSFER']
|
||||||
|
|
||||||
export type IncomingTransactionProps = {
|
export type IncomingTransactionProps = {
|
||||||
blockNumber: number,
|
blockNumber: number,
|
||||||
@ -53,7 +53,7 @@ export const makeIncomingTransaction: RecordFactory<IncomingTransactionProps> =
|
|||||||
decimals: 18,
|
decimals: 18,
|
||||||
fee: '',
|
fee: '',
|
||||||
executionDate: '',
|
executionDate: '',
|
||||||
type: INCOMING_TX_TYPE,
|
type: INCOMING_TX_TYPES,
|
||||||
status: 'success',
|
status: 'success',
|
||||||
nonce: null,
|
nonce: null,
|
||||||
confirmations: null,
|
confirmations: null,
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
// @flow
|
// @flow
|
||||||
export const NETWORK = process.env.REACT_APP_NETWORK
|
import { ETHEREUM_NETWORK } from '~/logic/wallets/getWeb3'
|
||||||
|
|
||||||
|
export const NETWORK = process.env.REACT_APP_NETWORK || ETHEREUM_NETWORK.RINKEBY
|
||||||
export const GOOGLE_ANALYTICS_ID_RINKEBY = process.env.REACT_APP_GOOGLE_ANALYTICS_ID_RINKEBY
|
export const GOOGLE_ANALYTICS_ID_RINKEBY = process.env.REACT_APP_GOOGLE_ANALYTICS_ID_RINKEBY
|
||||||
export const GOOGLE_ANALYTICS_ID_MAINNET = process.env.REACT_APP_GOOGLE_ANALYTICS_ID_MAINNET
|
export const GOOGLE_ANALYTICS_ID_MAINNET = process.env.REACT_APP_GOOGLE_ANALYTICS_ID_MAINNET
|
||||||
export const INTERCOM_ID = process.env.REACT_APP_INTERCOM_ID
|
export const INTERCOM_ID = process.env.REACT_APP_INTERCOM_ID
|
||||||
|
Loading…
x
Reference in New Issue
Block a user