fetch transactions refactoring wip

This commit is contained in:
Mikhail Mikheev 2020-04-28 15:31:11 +04:00
parent 83877aecd2
commit b5da092ea5
4 changed files with 173 additions and 108 deletions

View File

@ -1,7 +1,6 @@
// @flow // @flow
import ERC20Detailed from '@openzeppelin/contracts/build/contracts/ERC20Detailed.json' import ERC20Detailed from '@openzeppelin/contracts/build/contracts/ERC20Detailed.json'
import axios from 'axios' import axios from 'axios'
import bn from 'bignumber.js'
import { List, Map, type RecordInstance } from 'immutable' import { List, Map, type RecordInstance } from 'immutable'
import { batch } from 'react-redux' import { batch } from 'react-redux'
import type { Dispatch as ReduxDispatch } from 'redux' import type { Dispatch as ReduxDispatch } from 'redux'
@ -11,10 +10,8 @@ import { addTransactions } from './addTransactions'
import generateBatchRequests from '~/logic/contracts/generateBatchRequests' import generateBatchRequests from '~/logic/contracts/generateBatchRequests'
import { decodeParamsFromSafeMethod } from '~/logic/contracts/methodIds' import { decodeParamsFromSafeMethod } from '~/logic/contracts/methodIds'
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 { TOKEN_REDUCER_ID } from '~/logic/tokens/store/reducer/tokens' import { TOKEN_REDUCER_ID } from '~/logic/tokens/store/reducer/tokens'
import { ALTERNATIVE_TOKEN_ABI } from '~/logic/tokens/utils/alternativeAbi'
import { import {
SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH, SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH,
isMultisendTransaction, isMultisendTransaction,
@ -63,15 +60,6 @@ type TxServiceModel = {
creationTx?: boolean, creationTx?: boolean,
} }
type IncomingTxServiceModel = {
blockNumber: number,
transactionHash: string,
to: string,
value: number,
tokenAddress: string,
from: string,
}
export const buildTransactionFrom = async ( export const buildTransactionFrom = async (
safeAddress: string, safeAddress: string,
tx: TxServiceModel, tx: TxServiceModel,
@ -213,83 +201,31 @@ const addMockSafeCreationTx = (safeAddress): Array<TxServiceModel> => [
}, },
] ]
const batchRequestTxsData = (txs: any[]) => { const batchTxTokenRequest = (txs: any[]) => {
const web3Batch = new web3.BatchRequest() const batch = new web3.BatchRequest()
const whenTxsValues = txs.map((tx) => { const whenTxsValues = txs.map((tx) => {
const methods = ['decimals', { method: 'getCode', type: 'eth', args: [tx.to] }, 'symbol', 'name'] const methods = ['decimals', { method: 'getCode', type: 'eth', args: [tx.to] }, 'symbol', 'name']
return generateBatchRequests({ return generateBatchRequests({
abi: ERC20Detailed.abi, abi: ERC20Detailed.abi,
address: tx.to, address: tx.to,
batch: web3Batch, batch,
context: tx, context: tx,
methods, methods,
}) })
}) })
web3Batch.execute() batch.execute()
return Promise.all(whenTxsValues) return Promise.all(whenTxsValues)
} }
const batchRequestIncomingTxsData = (txs: IncomingTxServiceModel[]) => {
const web3Batch = new web3.BatchRequest()
const whenTxsValues = txs.map((tx) => {
const methods = ['symbol', 'decimals', { method: 'getTransaction', args: [tx.transactionHash], type: 'eth' }]
return generateBatchRequests({
abi: ALTERNATIVE_TOKEN_ABI,
address: tx.tokenAddress,
batch: web3Batch,
context: tx,
methods,
})
})
web3Batch.execute()
return Promise.all(whenTxsValues).then((txsValues) =>
txsValues.map(([tx, symbol, decimals, { gas, gasPrice }]) => [
tx,
symbol === null ? 'ETH' : symbol,
decimals === null ? '18' : decimals,
bn(gas).div(gasPrice).toFixed(),
]),
)
}
export const buildIncomingTransactionFrom = ([tx, symbol, decimals, fee]: [
IncomingTxServiceModel,
string,
string,
string,
]) => {
// this is a particular treatment for the DCD token, as it seems to lack of symbol and decimal methods
if (tx.tokenAddress && tx.tokenAddress.toLowerCase() === '0xe0b7927c4af23765cb51314a0e0521a9645f0e2a') {
symbol = 'DCD'
decimals = '9'
}
const { transactionHash, ...incomingTx } = tx
return makeIncomingTransaction({
...incomingTx,
symbol,
decimals,
fee,
executionTxHash: transactionHash,
safeTxHash: transactionHash,
})
}
export type SafeTransactionsType = { export type SafeTransactionsType = {
outgoing: Map<string, List<TransactionProps>>, outgoing: Map<string, List<TransactionProps>>,
cancel: Map<string, List<TransactionProps>>, cancel: Map<string, List<TransactionProps>>,
} }
let etagSafeTransactions = null let etagSafeTransactions = null
let etagCachedSafeIncommingTransactions = null
export const loadSafeTransactions = async (safeAddress: string, getState: GetState): Promise<SafeTransactionsType> => { export const loadSafeTransactions = async (safeAddress: string, getState: GetState): Promise<SafeTransactionsType> => {
let transactions: TxServiceModel[] = addMockSafeCreationTx(safeAddress) let transactions: TxServiceModel[] = addMockSafeCreationTx(safeAddress)
@ -306,7 +242,7 @@ export const loadSafeTransactions = async (safeAddress: string, getState: GetSta
const response = await axios.get(url, config) const response = await axios.get(url, config)
if (response.data.count > 0) { if (response.data.count > 0) {
if (etagSafeTransactions === response.headers.etag) { if (etagSafeTransactions === response.headers.etag) {
// The txs are the same, we can return the cached ones // The txs are the same as we currently have, we don't have to proceed
return return
} }
transactions = transactions.concat(response.data.results) transactions = transactions.concat(response.data.results)
@ -324,7 +260,7 @@ export const loadSafeTransactions = async (safeAddress: string, getState: GetSta
const state = getState() const state = getState()
const knownTokens = state[TOKEN_REDUCER_ID] const knownTokens = state[TOKEN_REDUCER_ID]
const txsWithData = await batchRequestTxsData(transactions) const txsWithData = await batchTxTokenRequest(transactions)
// In case that the etags don't match, we parse the new transactions and save them to the cache // 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(
txsWithData.map(([tx: TxServiceModel, decimals, code, symbol, name]) => txsWithData.map(([tx: TxServiceModel, decimals, code, symbol, name]) =>
@ -340,44 +276,10 @@ export const loadSafeTransactions = async (safeAddress: string, getState: GetSta
} }
} }
export const loadSafeIncomingTransactions = async (safeAddress: string) => {
let incomingTransactions: IncomingTxServiceModel[] = []
try {
const config = etagCachedSafeIncommingTransactions
? {
headers: {
'If-None-Match': etagCachedSafeIncommingTransactions,
},
}
: undefined
const url = buildIncomingTxServiceUrl(safeAddress)
const response = await axios.get(url, config)
if (response.data.count > 0) {
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) {
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 incomingTxsWithData = await batchRequestIncomingTxsData(incomingTransactions)
const incomingTxsRecord = incomingTxsWithData.map(buildIncomingTransactionFrom)
return Map().set(safeAddress, List(incomingTxsRecord))
}
export default (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalState>, getState: GetState) => { export default (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalState>, getState: GetState) => {
web3 = await getWeb3() web3 = await getWeb3()
const transactions: SafeTransactionsType | undefined = await loadSafeTransactions(safeAddress, getState) const transactions: SafeTransactionsType | typeof undefined = await loadSafeTransactions(safeAddress, getState)
if (transactions) { if (transactions) {
const { cancel, outgoing } = transactions const { cancel, outgoing } = transactions
@ -387,9 +289,9 @@ export default (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalSta
}) })
} }
const incomingTransactions: Map<string, List<IncomingTransaction>> | undefined = await loadSafeIncomingTransactions( const incomingTransactions:
safeAddress, | Map<string, List<IncomingTransaction>>
) | typeof undefined = await loadSafeIncomingTransactions(safeAddress)
if (incomingTransactions) { if (incomingTransactions) {
dispatch(addIncomingTransactions(incomingTransactions)) dispatch(addIncomingTransactions(incomingTransactions))

View File

@ -0,0 +1,106 @@
// @flow
import axios from 'axios'
import bn from 'bignumber.js'
import { List, Map, type RecordInstance } from 'immutable'
import generateBatchRequests from '~/logic/contracts/generateBatchRequests'
import { buildIncomingTxServiceUrl } from '~/logic/safe/transactions/incomingTxHistory'
import { ALTERNATIVE_TOKEN_ABI } from '~/logic/tokens/utils/alternativeAbi'
import { web3ReadOnly } from '~/logic/wallets/getWeb3'
import { makeIncomingTransaction } from '~/routes/safe/store/models/incomingTransaction'
type IncomingTxServiceModel = {
blockNumber: number,
transactionHash: string,
to: string,
value: number,
tokenAddress: string,
from: string,
}
const buildIncomingTransactionFrom = ([tx, symbol, decimals, fee]: [
IncomingTxServiceModel,
string,
string,
string,
]) => {
// this is a particular treatment for the DCD token, as it seems to lack of symbol and decimal methods
if (tx.tokenAddress && tx.tokenAddress.toLowerCase() === '0xe0b7927c4af23765cb51314a0e0521a9645f0e2a') {
symbol = 'DCD'
decimals = '9'
}
const { transactionHash, ...incomingTx } = tx
return makeIncomingTransaction({
...incomingTx,
symbol,
decimals,
fee,
executionTxHash: transactionHash,
safeTxHash: transactionHash,
})
}
const batchIncomingTxsTokenDataRequest = (txs: IncomingTxServiceModel[]) => {
const batch = new web3ReadOnly.BatchRequest()
const whenTxsValues = txs.map((tx) => {
const methods = ['symbol', 'decimals', { method: 'getTransaction', args: [tx.transactionHash], type: 'eth' }]
return generateBatchRequests({
abi: ALTERNATIVE_TOKEN_ABI,
address: tx.tokenAddress,
batch,
context: tx,
methods,
})
})
batch.execute()
return Promise.all(whenTxsValues).then((txsValues) =>
txsValues.map(([tx, symbol, decimals, { gas, gasPrice }]) => [
tx,
symbol === null ? 'ETH' : symbol,
decimals === null ? '18' : decimals,
bn(gas).div(gasPrice).toFixed(),
]),
)
}
let prevIncomingTxsEtag = null
export const loadSafeIncomingTransactions = async (safeAddress: string) => {
let incomingTransactions: IncomingTxServiceModel[] = []
try {
const config = prevIncomingTxsEtag
? {
headers: {
'If-None-Match': prevIncomingTxsEtag,
},
}
: undefined
const url = buildIncomingTxServiceUrl(safeAddress)
const response = await axios.get(url, config)
if (response.data.count > 0) {
incomingTransactions = response.data.results
if (prevIncomingTxsEtag === response.headers.etag) {
// The txs are the same as we currently have, we don't have to proceed
return
}
prevIncomingTxsEtag = response.headers.etag
}
} catch (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 incomingTxsWithData = await batchIncomingTxsTokenDataRequest(incomingTransactions)
const incomingTxsRecord = incomingTxsWithData.map(buildIncomingTransactionFrom)
return Map({ safeAddress: List(incomingTxsRecord) })
}

View File

@ -0,0 +1,54 @@
// @flow
let prevSaveTransactionsEtag = null
export const loadOutgoingTransactions = async (
safeAddress: string,
getState: GetState,
): Promise<SafeTransactionsType> => {
let transactions: TxServiceModel[] = addMockSafeCreationTx(safeAddress)
try {
const config = prevSaveTransactionsEtag
? {
headers: {
'If-None-Match': prevSaveTransactionsEtag,
},
}
: undefined
const url = buildTxServiceUrl(safeAddress)
const response = await axios.get(url, config)
if (response.data.count > 0) {
if (prevSaveTransactionsEtag === response.headers.etag) {
// The txs are the same as we currently have, we don't have to proceed
return
}
transactions = transactions.concat(response.data.results)
prevSaveTransactionsEtag = response.headers.etag
}
} catch (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)
}
}
const state = getState()
const knownTokens = state[TOKEN_REDUCER_ID]
const txsWithData = await batchTxTokenRequest(transactions)
// 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(
txsWithData.map(([tx: TxServiceModel, decimals, code, symbol, name]) =>
buildTransactionFrom(safeAddress, tx, knownTokens, decimals, symbol, name, code),
),
)
const groupedTxs = List(txsRecord).groupBy((tx) => (tx.get('cancellationTx') ? 'cancel' : 'outgoing'))
return {
outgoing: Map().set(safeAddress, groupedTxs.get('outgoing')),
cancel: Map().set(safeAddress, groupedTxs.get('cancel')),
}
}

View File

@ -1,4 +1,7 @@
// @flow // @flow
import { buildTransactionFrom } from '../fetchTransactions'
// type TxServiceModel = { // type TxServiceModel = {
// blockNumber: ?number, // blockNumber: ?number,
// safeTxHash: string, // safeTxHash: string,