diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 0a0d720d5..0818fc894 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -41,6 +41,7 @@ import { PRIORITY_LEVELS, } from '../../../../shared/constants/gas'; import { decGWEIToHexWEI } from '../../../../shared/modules/conversion.utils'; +import { isSwapsDefaultTokenAddress } from '../../../../shared/modules/swaps.utils'; import { EVENT } from '../../../../shared/constants/metametrics'; import { HARDFORKS, @@ -60,6 +61,7 @@ import PendingTransactionTracker from './pending-tx-tracker'; import * as txUtils from './lib/util'; const MAX_MEMSTORE_TX_LIST_SIZE = 100; // Number of transactions (by unique nonces) to keep in memory +const UPDATE_POST_TX_BALANCE_TIMEOUT = 5000; const SWAP_TRANSACTION_TYPES = [ TRANSACTION_TYPES.SWAP, @@ -1465,6 +1467,39 @@ export default class TransactionController extends EventEmitter { this._trackTransactionMetricsEvent(txMeta, TRANSACTION_EVENTS.SUBMITTED); } + async updatePostTxBalance({ txMeta, txId, numberOfAttempts = 6 }) { + const postTxBalance = await this.query.getBalance(txMeta.txParams.from); + const latestTxMeta = this.txStateManager.getTransaction(txId); + const approvalTxMeta = latestTxMeta.approvalTxId + ? this.txStateManager.getTransaction(latestTxMeta.approvalTxId) + : null; + latestTxMeta.postTxBalance = postTxBalance.toString(16); + const isDefaultTokenAddress = isSwapsDefaultTokenAddress( + txMeta.destinationTokenAddress, + txMeta.chainId, + ); + if ( + isDefaultTokenAddress && + txMeta.preTxBalance === latestTxMeta.postTxBalance && + numberOfAttempts > 0 + ) { + setTimeout(() => { + // If postTxBalance is the same as preTxBalance, try it again. + this.updatePostTxBalance({ + txMeta, + txId, + numberOfAttempts: numberOfAttempts - 1, + }); + }, UPDATE_POST_TX_BALANCE_TIMEOUT); + } else { + this.txStateManager.updateTransaction( + latestTxMeta, + 'transactions#confirmTransaction - add postTxBalance', + ); + this._trackSwapsMetrics(latestTxMeta, approvalTxMeta); + } + } + /** * Sets the status of the transaction to confirmed and sets the status of nonce duplicates as * dropped if the txParams have data it will fetch the txReceipt @@ -1528,21 +1563,10 @@ export default class TransactionController extends EventEmitter { ); if (txMeta.type === TRANSACTION_TYPES.SWAP) { - const postTxBalance = await this.query.getBalance(txMeta.txParams.from); - const latestTxMeta = this.txStateManager.getTransaction(txId); - - const approvalTxMeta = latestTxMeta.approvalTxId - ? this.txStateManager.getTransaction(latestTxMeta.approvalTxId) - : null; - - latestTxMeta.postTxBalance = postTxBalance.toString(16); - - this.txStateManager.updateTransaction( - latestTxMeta, - 'transactions#confirmTransaction - add postTxBalance', - ); - - this._trackSwapsMetrics(latestTxMeta, approvalTxMeta); + await this.updatePostTxBalance({ + txMeta, + txId, + }); } } catch (err) { log.error(err); @@ -1600,21 +1624,10 @@ export default class TransactionController extends EventEmitter { ); if (txMeta.type === TRANSACTION_TYPES.SWAP) { - const postTxBalance = await this.query.getBalance(txMeta.txParams.from); - const latestTxMeta = this.txStateManager.getTransaction(txId); - - const approvalTxMeta = latestTxMeta.approvalTxId - ? this.txStateManager.getTransaction(latestTxMeta.approvalTxId) - : null; - - latestTxMeta.postTxBalance = postTxBalance.toString(16); - - this.txStateManager.updateTransaction( - latestTxMeta, - 'transactions#confirmTransaction - add postTxBalance', - ); - - this._trackSwapsMetrics(latestTxMeta, approvalTxMeta); + await this.updatePostTxBalance({ + txMeta, + txId, + }); } } catch (err) { log.error(err); diff --git a/ui/pages/swaps/swaps.util.js b/ui/pages/swaps/swaps.util.js index 446e6eff5..ec12cc6f9 100644 --- a/ui/pages/swaps/swaps.util.js +++ b/ui/pages/swaps/swaps.util.js @@ -758,6 +758,12 @@ export function getSwapsTokensReceivedFromTxMeta( return null; } + if (txMeta.swapMetaData && txMeta.preTxBalance === txMeta.postTxBalance) { + // If preTxBalance and postTxBalance are equal, postTxBalance hasn't been updated on time + // because of the RPC provider delay, so we return an estimated receiving amount instead. + return txMeta.swapMetaData.token_to_amount; + } + let approvalTxGasCost = '0x0'; if (approvalTxMeta && approvalTxMeta.txReceipt) { approvalTxGasCost = calcGasTotal( diff --git a/ui/pages/swaps/swaps.util.test.js b/ui/pages/swaps/swaps.util.test.js index 0aaa2345f..8abff8171 100644 --- a/ui/pages/swaps/swaps.util.test.js +++ b/ui/pages/swaps/swaps.util.test.js @@ -8,6 +8,7 @@ import { RINKEBY_CHAIN_ID, KOVAN_CHAIN_ID, AVALANCHE_CHAIN_ID, + ETH_SYMBOL, } from '../../../shared/constants/network'; import { SWAPS_CHAINID_CONTRACT_ADDRESS_MAP, @@ -39,6 +40,7 @@ import { countDecimals, shouldEnableDirectWrapping, showRemainingTimeInMinAndSec, + getSwapsTokensReceivedFromTxMeta, } from './swaps.util'; jest.mock('../../helpers/utils/storage-helpers.js', () => ({ @@ -567,4 +569,106 @@ describe('Swaps Util', () => { expect(true).toBe(true); }); }); + + describe('getSwapsTokensReceivedFromTxMeta', () => { + const createProps = () => { + return { + tokenSymbol: ETH_SYMBOL, + txMeta: { + swapMetaData: { + token_to_amount: 5, + }, + txReceipt: { + status: '0x0', + }, + preTxBalance: '8b11', + postTxBalance: '8b11', + }, + tokenAddress: '0x881d40237659c251811cec9c364ef91234567890', + accountAddress: '0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f', + tokenDecimals: 6, + approvalTxMeta: null, + chainId: MAINNET_CHAIN_ID, + }; + }; + + it('returns an estimated amount if preTxBalance and postTxBalance are the same for ETH', () => { + const props = createProps(); + expect( + getSwapsTokensReceivedFromTxMeta( + props.tokenSymbol, + props.txMeta, + props.tokenAddress, + props.accountAddress, + props.tokenDecimals, + props.approvalTxMeta, + props.chainId, + ), + ).toBe(props.txMeta.swapMetaData.token_to_amount); + }); + + it('returns null if there is no txMeta', () => { + const props = createProps(); + props.txMeta = undefined; + expect( + getSwapsTokensReceivedFromTxMeta( + props.tokenSymbol, + props.txMeta, + props.tokenAddress, + props.accountAddress, + props.tokenDecimals, + props.approvalTxMeta, + props.chainId, + ), + ).toBeNull(); + }); + + it('returns null if there is no txMeta.txReceipt', () => { + const props = createProps(); + props.txMeta.txReceipt = undefined; + expect( + getSwapsTokensReceivedFromTxMeta( + props.tokenSymbol, + props.txMeta, + props.tokenAddress, + props.accountAddress, + props.tokenDecimals, + props.approvalTxMeta, + props.chainId, + ), + ).toBeNull(); + }); + + it('returns null if there is no txMeta.postTxBalance', () => { + const props = createProps(); + props.txMeta.postTxBalance = undefined; + expect( + getSwapsTokensReceivedFromTxMeta( + props.tokenSymbol, + props.txMeta, + props.tokenAddress, + props.accountAddress, + props.tokenDecimals, + props.approvalTxMeta, + props.chainId, + ), + ).toBeNull(); + }); + + it('returns null if there is no txMeta.preTxBalance', () => { + const props = createProps(); + props.txMeta.preTxBalance = undefined; + expect( + getSwapsTokensReceivedFromTxMeta( + props.tokenSymbol, + props.txMeta, + props.tokenAddress, + props.accountAddress, + props.tokenDecimals, + props.approvalTxMeta, + props.chainId, + ), + ).toBeNull(); + }); + }); });