fetch transactions refactoring wip
This commit is contained in:
parent
83877aecd2
commit
b5da092ea5
|
@ -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))
|
||||||
|
|
|
@ -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) })
|
||||||
|
}
|
|
@ -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')),
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
|
import { buildTransactionFrom } from '../fetchTransactions'
|
||||||
|
|
||||||
// type TxServiceModel = {
|
// type TxServiceModel = {
|
||||||
// blockNumber: ?number,
|
// blockNumber: ?number,
|
||||||
// safeTxHash: string,
|
// safeTxHash: string,
|
||||||
|
|
Loading…
Reference in New Issue