2021-02-04 19:15:23 +01:00
import { ethers } from 'ethers' ;
import log from 'loglevel' ;
import BigNumber from 'bignumber.js' ;
import { ObservableStore } from '@metamask/obs-store' ;
import { mapValues , cloneDeep } from 'lodash' ;
import abi from 'human-standard-token-abi' ;
2021-04-28 21:53:59 +02:00
import { calcTokenAmount } from '../../../ui/helpers/utils/token-util' ;
import { calcGasTotal } from '../../../ui/pages/send/send.utils' ;
2021-07-30 13:35:30 +02:00
import {
conversionUtil ,
decGWEIToHexWEI ,
addCurrencies ,
} from '../../../shared/modules/conversion.utils' ;
2020-10-06 20:28:38 +02:00
import {
DEFAULT _ERC20 _APPROVE _GAS ,
QUOTES _EXPIRED _ERROR ,
QUOTES _NOT _AVAILABLE _ERROR ,
2020-10-21 22:05:48 +02:00
SWAPS _FETCH _ORDER _CONFLICT ,
2021-03-30 20:55:14 +02:00
SWAPS _CHAINID _CONTRACT _ADDRESS _MAP ,
2021-03-15 14:21:35 +01:00
} from '../../../shared/constants/swaps' ;
2021-07-30 13:35:30 +02:00
import { GAS _ESTIMATE _TYPES } from '../../../shared/constants/gas' ;
2022-05-09 18:48:14 +02:00
import {
FALLBACK _SMART _TRANSACTIONS _REFRESH _TIME ,
FALLBACK _SMART _TRANSACTIONS _DEADLINE ,
FALLBACK _SMART _TRANSACTIONS _MAX _FEE _MULTIPLIER ,
} from '../../../shared/constants/smartTransactions' ;
2021-06-10 21:27:03 +02:00
2021-03-18 11:20:06 +01:00
import { isSwapsDefaultTokenAddress } from '../../../shared/modules/swaps.utils' ;
2020-10-06 20:28:38 +02:00
import {
fetchTradesInfo as defaultFetchTradesInfo ,
2021-09-15 15:13:18 +02:00
getBaseApi ,
2021-04-28 21:53:59 +02:00
} from '../../../ui/pages/swaps/swaps.util' ;
2021-09-15 15:13:18 +02:00
import fetchWithCache from '../../../ui/helpers/utils/fetch-with-cache' ;
2021-06-10 21:27:03 +02:00
import { MINUTE , SECOND } from '../../../shared/constants/time' ;
2022-03-07 19:54:36 +01:00
import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils' ;
2021-02-09 00:22:30 +01:00
import { NETWORK _EVENTS } from './network' ;
2020-10-06 20:28:38 +02:00
// The MAX_GAS_LIMIT is a number that is higher than the maximum gas costs we have observed on any aggregator
2021-02-04 19:15:23 +01:00
const MAX _GAS _LIMIT = 2500000 ;
2020-10-06 20:28:38 +02:00
// To ensure that our serves are not spammed if MetaMask is left idle, we limit the number of fetches for quotes that are made on timed intervals.
// 3 seems to be an appropriate balance of giving users the time they need when MetaMask is not left idle, and turning polling off when it is.
2021-02-04 19:15:23 +01:00
const POLL _COUNT _LIMIT = 3 ;
2020-10-06 20:28:38 +02:00
2020-12-15 21:24:22 +01:00
// If for any reason the MetaSwap API fails to provide a refresh time,
// provide a reasonable fallback to avoid further errors
2021-06-10 21:27:03 +02:00
const FALLBACK _QUOTE _REFRESH _TIME = MINUTE ;
2020-12-15 21:24:22 +01:00
2020-11-03 00:41:28 +01:00
function calculateGasEstimateWithRefund (
maxGas = MAX _GAS _LIMIT ,
estimatedRefund = 0 ,
estimatedGas = 0 ,
) {
2021-02-04 19:15:23 +01:00
const maxGasMinusRefund = new BigNumber ( maxGas , 10 ) . minus (
estimatedRefund ,
10 ,
) ;
2022-02-07 11:58:31 +01:00
const isMaxGasMinusRefundNegative = maxGasMinusRefund . lt ( 0 ) ;
2020-11-03 00:41:28 +01:00
2022-02-07 11:58:31 +01:00
const gasEstimateWithRefund =
! isMaxGasMinusRefundNegative && maxGasMinusRefund . lt ( estimatedGas , 16 )
2022-02-08 19:14:50 +01:00
? ` 0x ${ maxGasMinusRefund . toString ( 16 ) } `
2022-02-07 11:58:31 +01:00
: estimatedGas ;
2020-10-06 20:28:38 +02:00
2021-02-04 19:15:23 +01:00
return gasEstimateWithRefund ;
2020-10-06 20:28:38 +02:00
}
const initialState = {
swapsState : {
quotes : { } ,
2021-09-15 15:13:18 +02:00
quotesPollingLimitEnabled : false ,
2020-10-06 20:28:38 +02:00
fetchParams : null ,
tokens : null ,
tradeTxId : null ,
approveTxId : null ,
quotesLastFetched : null ,
customMaxGas : '' ,
customGasPrice : null ,
2021-07-30 13:35:30 +02:00
customMaxFeePerGas : null ,
customMaxPriorityFeePerGas : null ,
2021-08-06 21:31:30 +02:00
swapsUserFeeLevel : '' ,
2020-10-06 20:28:38 +02:00
selectedAggId : null ,
customApproveTxData : '' ,
errorKey : '' ,
topAggId : null ,
routeState : '' ,
2021-04-14 09:16:27 +02:00
swapsFeatureIsLive : true ,
2021-10-28 08:53:26 +02:00
saveFetchedQuotes : false ,
2020-12-15 21:24:22 +01:00
swapsQuoteRefreshTime : FALLBACK _QUOTE _REFRESH _TIME ,
2021-09-15 15:13:18 +02:00
swapsQuotePrefetchingRefreshTime : FALLBACK _QUOTE _REFRESH _TIME ,
2022-05-09 18:48:14 +02:00
swapsStxBatchStatusRefreshTime : FALLBACK _SMART _TRANSACTIONS _REFRESH _TIME ,
swapsStxGetTransactionsRefreshTime : FALLBACK _SMART _TRANSACTIONS _REFRESH _TIME ,
swapsStxMaxFeeMultiplier : FALLBACK _SMART _TRANSACTIONS _MAX _FEE _MULTIPLIER ,
2022-02-18 17:48:38 +01:00
swapsFeatureFlags : { } ,
2020-10-06 20:28:38 +02:00
} ,
2021-02-04 19:15:23 +01:00
} ;
2020-10-06 20:28:38 +02:00
export default class SwapsController {
2020-11-03 00:41:28 +01:00
constructor ( {
2020-10-06 20:28:38 +02:00
getBufferedGasLimit ,
2020-10-28 19:47:32 +01:00
networkController ,
2020-10-06 20:28:38 +02:00
provider ,
getProviderConfig ,
2021-11-22 13:04:31 +01:00
getTokenRatesState ,
2020-10-06 20:28:38 +02:00
fetchTradesInfo = defaultFetchTradesInfo ,
2021-03-18 11:20:06 +01:00
getCurrentChainId ,
2021-07-30 13:35:30 +02:00
getEIP1559GasFeeEstimates ,
2020-10-06 20:28:38 +02:00
} ) {
this . store = new ObservableStore ( {
swapsState : { ... initialState . swapsState } ,
2021-02-04 19:15:23 +01:00
} ) ;
2020-10-06 20:28:38 +02:00
2021-02-04 19:15:23 +01:00
this . _fetchTradesInfo = fetchTradesInfo ;
2021-03-18 11:20:06 +01:00
this . _getCurrentChainId = getCurrentChainId ;
2021-07-30 13:35:30 +02:00
this . _getEIP1559GasFeeEstimates = getEIP1559GasFeeEstimates ;
2020-10-06 20:28:38 +02:00
2021-02-04 19:15:23 +01:00
this . getBufferedGasLimit = getBufferedGasLimit ;
2021-11-22 13:04:31 +01:00
this . getTokenRatesState = getTokenRatesState ;
2020-10-06 20:28:38 +02:00
2021-02-04 19:15:23 +01:00
this . pollCount = 0 ;
this . getProviderConfig = getProviderConfig ;
2020-10-06 20:28:38 +02:00
2021-02-04 19:15:23 +01:00
this . indexOfNewestCallInFlight = 0 ;
2020-10-21 22:05:48 +02:00
2021-02-04 19:15:23 +01:00
this . ethersProvider = new ethers . providers . Web3Provider ( provider ) ;
this . _currentNetwork = networkController . store . getState ( ) . network ;
2021-02-09 00:22:30 +01:00
networkController . on ( NETWORK _EVENTS . NETWORK _DID _CHANGE , ( network ) => {
2020-10-28 19:47:32 +01:00
if ( network !== 'loading' && network !== this . _currentNetwork ) {
2021-02-04 19:15:23 +01:00
this . _currentNetwork = network ;
this . ethersProvider = new ethers . providers . Web3Provider ( provider ) ;
2020-10-28 19:47:32 +01:00
}
2021-02-04 19:15:23 +01:00
} ) ;
2020-10-06 20:28:38 +02:00
}
2022-05-09 18:48:14 +02:00
async fetchSwapsNetworkConfig ( chainId ) {
2021-09-15 15:13:18 +02:00
const response = await fetchWithCache (
2021-11-30 19:56:28 +01:00
getBaseApi ( 'network' , chainId ) ,
2021-09-15 15:13:18 +02:00
{ method : 'GET' } ,
{ cacheRefreshTime : 600000 } ,
) ;
2022-05-09 18:48:14 +02:00
const { refreshRates , parameters = { } } = response || { } ;
2021-09-15 15:13:18 +02:00
if (
! refreshRates ||
typeof refreshRates . quotes !== 'number' ||
2022-04-04 15:14:50 +02:00
typeof refreshRates . quotesPrefetching !== 'number'
2021-09-15 15:13:18 +02:00
) {
throw new Error (
` MetaMask - invalid response for refreshRates: ${ response } ` ,
) ;
}
// We presently use milliseconds in the UI.
return {
quotes : refreshRates . quotes * 1000 ,
quotesPrefetching : refreshRates . quotesPrefetching * 1000 ,
2022-02-18 17:48:38 +01:00
stxGetTransactions : refreshRates . stxGetTransactions * 1000 ,
stxBatchStatus : refreshRates . stxBatchStatus * 1000 ,
stxStatusDeadline : refreshRates . stxStatusDeadline ,
2022-05-09 18:48:14 +02:00
stxMaxFeeMultiplier : parameters . stxMaxFeeMultiplier ,
2021-09-15 15:13:18 +02:00
} ;
}
2022-05-09 18:48:14 +02:00
// Sets the network config from the MetaSwap API.
async _setSwapsNetworkConfig ( ) {
2021-03-18 11:20:06 +01:00
const chainId = this . _getCurrentChainId ( ) ;
2022-05-09 18:48:14 +02:00
let swapsNetworkConfig ;
2020-12-15 21:24:22 +01:00
try {
2022-05-09 18:48:14 +02:00
swapsNetworkConfig = await this . fetchSwapsNetworkConfig ( chainId ) ;
2020-12-15 21:24:22 +01:00
} catch ( e ) {
2022-05-09 18:48:14 +02:00
console . error ( 'Request for Swaps network config failed: ' , e ) ;
2020-12-15 21:24:22 +01:00
}
2021-07-15 20:13:53 +02:00
const { swapsState : latestSwapsState } = this . store . getState ( ) ;
2020-12-15 21:24:22 +01:00
this . store . updateState ( {
2021-09-15 15:13:18 +02:00
swapsState : {
... latestSwapsState ,
swapsQuoteRefreshTime :
2022-05-09 18:48:14 +02:00
swapsNetworkConfig ? . quotes || FALLBACK _QUOTE _REFRESH _TIME ,
2021-09-15 15:13:18 +02:00
swapsQuotePrefetchingRefreshTime :
2022-05-09 18:48:14 +02:00
swapsNetworkConfig ? . quotesPrefetching || FALLBACK _QUOTE _REFRESH _TIME ,
2022-02-18 17:48:38 +01:00
swapsStxGetTransactionsRefreshTime :
2022-05-09 18:48:14 +02:00
swapsNetworkConfig ? . stxGetTransactions ||
FALLBACK _SMART _TRANSACTIONS _REFRESH _TIME ,
2022-02-18 17:48:38 +01:00
swapsStxBatchStatusRefreshTime :
2022-05-09 18:48:14 +02:00
swapsNetworkConfig ? . stxBatchStatus ||
FALLBACK _SMART _TRANSACTIONS _REFRESH _TIME ,
2022-02-18 17:48:38 +01:00
swapsStxStatusDeadline :
2022-05-09 18:48:14 +02:00
swapsNetworkConfig ? . stxStatusDeadline ||
2022-02-18 17:48:38 +01:00
FALLBACK _SMART _TRANSACTIONS _DEADLINE ,
2022-05-09 18:48:14 +02:00
swapsStxMaxFeeMultiplier :
swapsNetworkConfig ? . stxMaxFeeMultiplier ||
FALLBACK _SMART _TRANSACTIONS _MAX _FEE _MULTIPLIER ,
2021-09-15 15:13:18 +02:00
} ,
2021-02-04 19:15:23 +01:00
} ) ;
2020-12-15 21:24:22 +01:00
}
2020-10-06 20:28:38 +02:00
// Once quotes are fetched, we poll for new ones to keep the quotes up to date. Market and aggregator contract conditions can change fast enough
2021-09-15 15:13:18 +02:00
// that quotes will no longer be available after 1 or 2 minutes. When fetchAndSetQuotes is first called, it receives fetch parameters that are stored in
2020-10-06 20:28:38 +02:00
// state. These stored parameters are used on subsequent calls made during polling.
// Note: we stop polling after 3 requests, until new quotes are explicitly asked for. The logic that enforces that maximum is in the body of fetchAndSetQuotes
2020-11-03 00:41:28 +01:00
pollForNewQuotes ( ) {
2020-12-15 21:24:22 +01:00
const {
2021-09-15 15:13:18 +02:00
swapsState : {
swapsQuoteRefreshTime ,
swapsQuotePrefetchingRefreshTime ,
quotesPollingLimitEnabled ,
} ,
2021-02-04 19:15:23 +01:00
} = this . store . getState ( ) ;
2021-09-15 15:13:18 +02:00
// swapsQuoteRefreshTime is used on the View Quote page, swapsQuotePrefetchingRefreshTime is used on the Build Quote page.
const quotesRefreshRateInMs = quotesPollingLimitEnabled
? swapsQuoteRefreshTime
: swapsQuotePrefetchingRefreshTime ;
2020-10-06 20:28:38 +02:00
this . pollingTimeout = setTimeout ( ( ) => {
2021-02-04 19:15:23 +01:00
const { swapsState } = this . store . getState ( ) ;
2020-11-03 00:41:28 +01:00
this . fetchAndSetQuotes (
swapsState . fetchParams ,
swapsState . fetchParams ? . metaData ,
true ,
2021-02-04 19:15:23 +01:00
) ;
2021-09-15 15:13:18 +02:00
} , quotesRefreshRateInMs ) ;
2020-10-06 20:28:38 +02:00
}
2020-11-03 00:41:28 +01:00
stopPollingForQuotes ( ) {
2021-09-15 15:13:18 +02:00
if ( this . pollingTimeout ) {
clearTimeout ( this . pollingTimeout ) ;
}
2020-10-06 20:28:38 +02:00
}
2020-11-03 00:41:28 +01:00
async fetchAndSetQuotes (
fetchParams ,
fetchParamsMetaData = { } ,
isPolledRequest ,
) {
2021-03-18 11:20:06 +01:00
const { chainId } = fetchParamsMetaData ;
2021-07-09 17:24:00 +02:00
const {
2021-11-30 19:56:28 +01:00
swapsState : { quotesPollingLimitEnabled , saveFetchedQuotes } ,
2021-07-09 17:24:00 +02:00
} = this . store . getState ( ) ;
2021-03-18 11:20:06 +01:00
2020-10-06 20:28:38 +02:00
if ( ! fetchParams ) {
2021-02-04 19:15:23 +01:00
return null ;
2020-10-06 20:28:38 +02:00
}
// Every time we get a new request that is not from the polling, we reset the poll count so we can poll for up to three more sets of quotes with these new params.
if ( ! isPolledRequest ) {
2021-02-04 19:15:23 +01:00
this . pollCount = 0 ;
2020-10-06 20:28:38 +02:00
}
// If there are any pending poll requests, clear them so that they don't get call while this new fetch is in process
2021-02-04 19:15:23 +01:00
clearTimeout ( this . pollingTimeout ) ;
2020-10-06 20:28:38 +02:00
if ( ! isPolledRequest ) {
2021-02-04 19:15:23 +01:00
this . setSwapsErrorKey ( '' ) ;
2020-10-06 20:28:38 +02:00
}
2020-10-21 22:05:48 +02:00
2021-02-04 19:15:23 +01:00
const indexOfCurrentCall = this . indexOfNewestCallInFlight + 1 ;
this . indexOfNewestCallInFlight = indexOfCurrentCall ;
2020-10-21 22:05:48 +02:00
2021-10-28 08:53:26 +02:00
if ( ! saveFetchedQuotes ) {
this . setSaveFetchedQuotes ( true ) ;
}
2021-09-29 18:13:34 +02:00
2020-12-15 21:24:22 +01:00
let [ newQuotes ] = await Promise . all ( [
2021-07-09 17:24:00 +02:00
this . _fetchTradesInfo ( fetchParams , {
... fetchParamsMetaData ,
} ) ,
2022-05-09 18:48:14 +02:00
this . _setSwapsNetworkConfig ( ) ,
2021-02-04 19:15:23 +01:00
] ) ;
2020-10-06 20:28:38 +02:00
2021-09-29 18:13:34 +02:00
const {
2021-10-28 08:53:26 +02:00
swapsState : { saveFetchedQuotes : saveFetchedQuotesAfterResponse } ,
2021-09-29 18:13:34 +02:00
} = this . store . getState ( ) ;
2021-10-28 08:53:26 +02:00
// If saveFetchedQuotesAfterResponse is false, it means a user left Swaps (we cleaned the state)
2021-09-29 18:13:34 +02:00
// and we don't want to set any API response with quotes into state.
2021-10-28 08:53:26 +02:00
if ( ! saveFetchedQuotesAfterResponse ) {
2021-09-29 18:13:34 +02:00
return [
{ } , // quotes
null , // selectedAggId
] ;
}
2020-10-06 20:28:38 +02:00
newQuotes = mapValues ( newQuotes , ( quote ) => ( {
... quote ,
sourceTokenInfo : fetchParamsMetaData . sourceTokenInfo ,
destinationTokenInfo : fetchParamsMetaData . destinationTokenInfo ,
2021-02-04 19:15:23 +01:00
} ) ) ;
2020-10-06 20:28:38 +02:00
2021-02-04 19:15:23 +01:00
const quotesLastFetched = Date . now ( ) ;
2020-10-06 20:28:38 +02:00
2021-02-04 19:15:23 +01:00
let approvalRequired = false ;
2020-11-03 00:41:28 +01:00
if (
2021-03-18 11:20:06 +01:00
! isSwapsDefaultTokenAddress ( fetchParams . sourceToken , chainId ) &&
2020-11-03 00:41:28 +01:00
Object . values ( newQuotes ) . length
) {
2020-10-06 20:28:38 +02:00
const allowance = await this . _getERC20Allowance (
fetchParams . sourceToken ,
fetchParams . fromAddress ,
2021-03-30 20:55:14 +02:00
chainId ,
2021-02-04 19:15:23 +01:00
) ;
2022-02-23 22:10:28 +01:00
const [ firstQuote ] = Object . values ( newQuotes ) ;
2020-10-06 20:28:38 +02:00
// For a user to be able to swap a token, they need to have approved the MetaSwap contract to withdraw that token.
// _getERC20Allowance() returns the amount of the token they have approved for withdrawal. If that amount is greater
2021-11-12 15:40:50 +01:00
// than 0, it means that approval has already occurred and is not needed. Otherwise, for tokens to be swapped, a new
2020-10-06 20:28:38 +02:00
// call of the ERC-20 approve method is required.
2021-10-20 13:07:34 +02:00
approvalRequired =
2022-02-23 22:10:28 +01:00
firstQuote . approvalNeeded &&
2021-10-20 13:07:34 +02:00
allowance . eq ( 0 ) &&
2022-02-23 22:10:28 +01:00
firstQuote . aggregator !== 'wrappedNative' ;
2020-10-06 20:28:38 +02:00
if ( ! approvalRequired ) {
newQuotes = mapValues ( newQuotes , ( quote ) => ( {
... quote ,
approvalNeeded : null ,
2021-02-04 19:15:23 +01:00
} ) ) ;
2020-10-06 20:28:38 +02:00
} else if ( ! isPolledRequest ) {
2020-11-03 00:41:28 +01:00
const { gasLimit : approvalGas } = await this . timedoutGasReturn (
2022-02-23 22:10:28 +01:00
firstQuote . approvalNeeded ,
2021-02-04 19:15:23 +01:00
) ;
2020-10-06 20:28:38 +02:00
newQuotes = mapValues ( newQuotes , ( quote ) => ( {
... quote ,
approvalNeeded : {
... quote . approvalNeeded ,
gas : approvalGas || DEFAULT _ERC20 _APPROVE _GAS ,
} ,
2021-02-04 19:15:23 +01:00
} ) ) ;
2020-10-06 20:28:38 +02:00
}
}
2021-02-04 19:15:23 +01:00
let topAggId = null ;
2020-10-06 20:28:38 +02:00
// We can reduce time on the loading screen by only doing this after the
// loading screen and best quote have rendered.
if ( ! approvalRequired && ! fetchParams ? . balanceError ) {
2021-02-04 19:15:23 +01:00
newQuotes = await this . getAllQuotesWithGasEstimates ( newQuotes ) ;
2020-10-09 20:00:20 +02:00
}
2020-10-06 20:28:38 +02:00
2020-10-09 20:00:20 +02:00
if ( Object . values ( newQuotes ) . length === 0 ) {
2021-02-04 19:15:23 +01:00
this . setSwapsErrorKey ( QUOTES _NOT _AVAILABLE _ERROR ) ;
2020-10-09 19:14:10 +02:00
} else {
2020-11-09 18:09:38 +01:00
const [
_topAggId ,
quotesWithSavingsAndFeeData ,
2021-02-04 19:15:23 +01:00
] = await this . _findTopQuoteAndCalculateSavings ( newQuotes ) ;
topAggId = _topAggId ;
newQuotes = quotesWithSavingsAndFeeData ;
2020-10-06 20:28:38 +02:00
}
2020-10-21 22:05:48 +02:00
// If a newer call has been made, don't update state with old information
// Prevents timing conflicts between fetches
if ( this . indexOfNewestCallInFlight !== indexOfCurrentCall ) {
2021-02-04 19:15:23 +01:00
throw new Error ( SWAPS _FETCH _ORDER _CONFLICT ) ;
2020-10-21 22:05:48 +02:00
}
2021-02-04 19:15:23 +01:00
const { swapsState } = this . store . getState ( ) ;
let { selectedAggId } = swapsState ;
2020-10-06 20:28:38 +02:00
if ( ! newQuotes [ selectedAggId ] ) {
2021-02-04 19:15:23 +01:00
selectedAggId = null ;
2020-10-06 20:28:38 +02:00
}
2020-10-21 22:05:48 +02:00
2020-10-06 20:28:38 +02:00
this . store . updateState ( {
swapsState : {
... swapsState ,
quotes : newQuotes ,
fetchParams : { ... fetchParams , metaData : fetchParamsMetaData } ,
quotesLastFetched ,
selectedAggId ,
topAggId ,
} ,
2021-02-04 19:15:23 +01:00
} ) ;
2020-10-06 20:28:38 +02:00
2021-09-15 15:13:18 +02:00
if ( quotesPollingLimitEnabled ) {
// We only want to do up to a maximum of three requests from polling if polling limit is enabled.
// Otherwise we won't increase pollCount, so polling will run without a limit.
this . pollCount += 1 ;
}
if ( ! quotesPollingLimitEnabled || this . pollCount < POLL _COUNT _LIMIT + 1 ) {
2021-02-04 19:15:23 +01:00
this . pollForNewQuotes ( ) ;
2020-10-06 20:28:38 +02:00
} else {
2021-02-04 19:15:23 +01:00
this . resetPostFetchState ( ) ;
this . setSwapsErrorKey ( QUOTES _EXPIRED _ERROR ) ;
return null ;
2020-10-06 20:28:38 +02:00
}
2021-02-04 19:15:23 +01:00
return [ newQuotes , topAggId ] ;
2020-10-06 20:28:38 +02:00
}
2020-11-03 00:41:28 +01:00
safeRefetchQuotes ( ) {
2021-02-04 19:15:23 +01:00
const { swapsState } = this . store . getState ( ) ;
2020-10-06 20:28:38 +02:00
if ( ! this . pollingTimeout && swapsState . fetchParams ) {
2021-02-04 19:15:23 +01:00
this . fetchAndSetQuotes ( swapsState . fetchParams ) ;
2020-10-06 20:28:38 +02:00
}
}
2020-11-03 00:41:28 +01:00
setSelectedQuoteAggId ( selectedAggId ) {
2021-02-04 19:15:23 +01:00
const { swapsState } = this . store . getState ( ) ;
this . store . updateState ( { swapsState : { ... swapsState , selectedAggId } } ) ;
2020-10-06 20:28:38 +02:00
}
2020-11-03 00:41:28 +01:00
setSwapsTokens ( tokens ) {
2021-02-04 19:15:23 +01:00
const { swapsState } = this . store . getState ( ) ;
this . store . updateState ( { swapsState : { ... swapsState , tokens } } ) ;
2020-10-06 20:28:38 +02:00
}
2021-09-15 15:13:18 +02:00
clearSwapsQuotes ( ) {
const { swapsState } = this . store . getState ( ) ;
this . store . updateState ( { swapsState : { ... swapsState , quotes : { } } } ) ;
}
2020-11-03 00:41:28 +01:00
setSwapsErrorKey ( errorKey ) {
2021-02-04 19:15:23 +01:00
const { swapsState } = this . store . getState ( ) ;
this . store . updateState ( { swapsState : { ... swapsState , errorKey } } ) ;
2020-10-06 20:28:38 +02:00
}
2020-11-03 00:41:28 +01:00
async getAllQuotesWithGasEstimates ( quotes ) {
2020-10-06 20:28:38 +02:00
const quoteGasData = await Promise . all (
Object . values ( quotes ) . map ( async ( quote ) => {
2020-11-03 00:41:28 +01:00
const { gasLimit , simulationFails } = await this . timedoutGasReturn (
quote . trade ,
2021-02-04 19:15:23 +01:00
) ;
return [ gasLimit , simulationFails , quote . aggregator ] ;
2020-10-06 20:28:38 +02:00
} ) ,
2021-02-04 19:15:23 +01:00
) ;
2020-10-06 20:28:38 +02:00
2021-02-04 19:15:23 +01:00
const newQuotes = { } ;
2020-10-06 20:28:38 +02:00
quoteGasData . forEach ( ( [ gasLimit , simulationFails , aggId ] ) => {
if ( gasLimit && ! simulationFails ) {
2020-11-03 00:41:28 +01:00
const gasEstimateWithRefund = calculateGasEstimateWithRefund (
quotes [ aggId ] . maxGas ,
quotes [ aggId ] . estimatedRefund ,
gasLimit ,
2021-02-04 19:15:23 +01:00
) ;
2020-10-06 20:28:38 +02:00
newQuotes [ aggId ] = {
... quotes [ aggId ] ,
gasEstimate : gasLimit ,
gasEstimateWithRefund ,
2021-02-04 19:15:23 +01:00
} ;
2020-10-06 20:28:38 +02:00
} else if ( quotes [ aggId ] . approvalNeeded ) {
// If gas estimation fails, but an ERC-20 approve is needed, then we do not add any estimate property to the quote object
// Such quotes will rely on the maxGas and averageGas properties from the api
2021-02-04 19:15:23 +01:00
newQuotes [ aggId ] = quotes [ aggId ] ;
2020-10-06 20:28:38 +02:00
}
// If gas estimation fails and no approval is needed, then we filter that quote out, so that it is not shown to the user
2021-02-04 19:15:23 +01:00
} ) ;
return newQuotes ;
2020-10-06 20:28:38 +02:00
}
2020-11-03 00:41:28 +01:00
timedoutGasReturn ( tradeTxParams ) {
2020-10-06 20:28:38 +02:00
return new Promise ( ( resolve ) => {
2021-02-04 19:15:23 +01:00
let gasTimedOut = false ;
2020-10-06 20:28:38 +02:00
const gasTimeout = setTimeout ( ( ) => {
2021-02-04 19:15:23 +01:00
gasTimedOut = true ;
resolve ( { gasLimit : null , simulationFails : true } ) ;
2021-06-10 21:27:03 +02:00
} , SECOND * 5 ) ;
2020-10-06 20:28:38 +02:00
2020-10-07 21:00:17 +02:00
// Remove gas from params that will be passed to the `estimateGas` call
// Including it can cause the estimate to fail if the actual gas needed
// exceeds the passed gas
const tradeTxParamsForGasEstimate = {
data : tradeTxParams . data ,
from : tradeTxParams . from ,
to : tradeTxParams . to ,
value : tradeTxParams . value ,
2021-02-04 19:15:23 +01:00
} ;
2020-10-07 21:00:17 +02:00
this . getBufferedGasLimit ( { txParams : tradeTxParamsForGasEstimate } , 1 )
2020-10-06 20:28:38 +02:00
. then ( ( { gasLimit , simulationFails } ) => {
if ( ! gasTimedOut ) {
2021-02-04 19:15:23 +01:00
clearTimeout ( gasTimeout ) ;
resolve ( { gasLimit , simulationFails } ) ;
2020-10-06 20:28:38 +02:00
}
} )
. catch ( ( e ) => {
2021-02-04 19:15:23 +01:00
log . error ( e ) ;
2020-10-06 20:28:38 +02:00
if ( ! gasTimedOut ) {
2021-02-04 19:15:23 +01:00
clearTimeout ( gasTimeout ) ;
resolve ( { gasLimit : null , simulationFails : true } ) ;
2020-10-06 20:28:38 +02:00
}
2021-02-04 19:15:23 +01:00
} ) ;
} ) ;
2020-10-06 20:28:38 +02:00
}
2020-11-03 00:41:28 +01:00
async setInitialGasEstimate ( initialAggId ) {
2021-02-04 19:15:23 +01:00
const { swapsState } = this . store . getState ( ) ;
2020-10-06 20:28:38 +02:00
2021-02-04 19:15:23 +01:00
const quoteToUpdate = { ... swapsState . quotes [ initialAggId ] } ;
2020-10-06 20:28:38 +02:00
const {
gasLimit : newGasEstimate ,
simulationFails ,
2021-02-04 19:15:23 +01:00
} = await this . timedoutGasReturn ( quoteToUpdate . trade ) ;
2020-10-06 20:28:38 +02:00
if ( newGasEstimate && ! simulationFails ) {
2020-11-03 00:41:28 +01:00
const gasEstimateWithRefund = calculateGasEstimateWithRefund (
quoteToUpdate . maxGas ,
quoteToUpdate . estimatedRefund ,
newGasEstimate ,
2021-02-04 19:15:23 +01:00
) ;
2020-10-06 20:28:38 +02:00
2021-02-04 19:15:23 +01:00
quoteToUpdate . gasEstimate = newGasEstimate ;
quoteToUpdate . gasEstimateWithRefund = gasEstimateWithRefund ;
2020-10-06 20:28:38 +02:00
}
this . store . updateState ( {
2020-11-03 00:41:28 +01:00
swapsState : {
... swapsState ,
quotes : { ... swapsState . quotes , [ initialAggId ] : quoteToUpdate } ,
} ,
2021-02-04 19:15:23 +01:00
} ) ;
2020-10-06 20:28:38 +02:00
}
2020-11-03 00:41:28 +01:00
setApproveTxId ( approveTxId ) {
2021-02-04 19:15:23 +01:00
const { swapsState } = this . store . getState ( ) ;
this . store . updateState ( { swapsState : { ... swapsState , approveTxId } } ) ;
2020-10-06 20:28:38 +02:00
}
2020-11-03 00:41:28 +01:00
setTradeTxId ( tradeTxId ) {
2021-02-04 19:15:23 +01:00
const { swapsState } = this . store . getState ( ) ;
this . store . updateState ( { swapsState : { ... swapsState , tradeTxId } } ) ;
2020-10-06 20:28:38 +02:00
}
2020-11-03 00:41:28 +01:00
setQuotesLastFetched ( quotesLastFetched ) {
2021-02-04 19:15:23 +01:00
const { swapsState } = this . store . getState ( ) ;
this . store . updateState ( {
swapsState : { ... swapsState , quotesLastFetched } ,
} ) ;
2020-10-06 20:28:38 +02:00
}
2020-11-03 00:41:28 +01:00
setSwapsTxGasPrice ( gasPrice ) {
2021-02-04 19:15:23 +01:00
const { swapsState } = this . store . getState ( ) ;
2020-10-06 20:28:38 +02:00
this . store . updateState ( {
swapsState : { ... swapsState , customGasPrice : gasPrice } ,
2021-02-04 19:15:23 +01:00
} ) ;
2020-10-06 20:28:38 +02:00
}
2021-07-30 13:35:30 +02:00
setSwapsTxMaxFeePerGas ( maxFeePerGas ) {
const { swapsState } = this . store . getState ( ) ;
this . store . updateState ( {
swapsState : { ... swapsState , customMaxFeePerGas : maxFeePerGas } ,
} ) ;
}
2021-08-06 21:31:30 +02:00
setSwapsUserFeeLevel ( swapsUserFeeLevel ) {
const { swapsState } = this . store . getState ( ) ;
this . store . updateState ( {
swapsState : { ... swapsState , swapsUserFeeLevel } ,
} ) ;
}
2021-09-15 15:13:18 +02:00
setSwapsQuotesPollingLimitEnabled ( quotesPollingLimitEnabled ) {
const { swapsState } = this . store . getState ( ) ;
this . store . updateState ( {
swapsState : { ... swapsState , quotesPollingLimitEnabled } ,
} ) ;
}
2021-07-30 13:35:30 +02:00
setSwapsTxMaxFeePriorityPerGas ( maxPriorityFeePerGas ) {
const { swapsState } = this . store . getState ( ) ;
this . store . updateState ( {
swapsState : {
... swapsState ,
customMaxPriorityFeePerGas : maxPriorityFeePerGas ,
} ,
} ) ;
}
2020-11-03 00:41:28 +01:00
setSwapsTxGasLimit ( gasLimit ) {
2021-02-04 19:15:23 +01:00
const { swapsState } = this . store . getState ( ) ;
2020-10-06 20:28:38 +02:00
this . store . updateState ( {
swapsState : { ... swapsState , customMaxGas : gasLimit } ,
2021-02-04 19:15:23 +01:00
} ) ;
2020-10-06 20:28:38 +02:00
}
2020-11-03 00:41:28 +01:00
setCustomApproveTxData ( data ) {
2021-02-04 19:15:23 +01:00
const { swapsState } = this . store . getState ( ) ;
2020-10-06 20:28:38 +02:00
this . store . updateState ( {
swapsState : { ... swapsState , customApproveTxData : data } ,
2021-02-04 19:15:23 +01:00
} ) ;
2020-10-06 20:28:38 +02:00
}
2020-11-03 00:41:28 +01:00
setBackgroundSwapRouteState ( routeState ) {
2021-02-04 19:15:23 +01:00
const { swapsState } = this . store . getState ( ) ;
this . store . updateState ( { swapsState : { ... swapsState , routeState } } ) ;
2020-10-06 20:28:38 +02:00
}
2021-10-28 08:53:26 +02:00
setSaveFetchedQuotes ( status ) {
2021-09-29 18:13:34 +02:00
const { swapsState } = this . store . getState ( ) ;
this . store . updateState ( {
2021-10-28 08:53:26 +02:00
swapsState : { ... swapsState , saveFetchedQuotes : status } ,
2021-09-29 18:13:34 +02:00
} ) ;
}
2021-07-09 17:24:00 +02:00
setSwapsLiveness ( swapsLiveness ) {
2021-02-04 19:15:23 +01:00
const { swapsState } = this . store . getState ( ) ;
2021-11-30 19:56:28 +01:00
const { swapsFeatureIsLive } = swapsLiveness ;
2020-11-03 00:41:28 +01:00
this . store . updateState ( {
2021-11-30 19:56:28 +01:00
swapsState : { ... swapsState , swapsFeatureIsLive } ,
2021-02-04 19:15:23 +01:00
} ) ;
2020-10-06 20:28:38 +02:00
}
2022-02-18 17:48:38 +01:00
setSwapsFeatureFlags ( swapsFeatureFlags ) {
const { swapsState } = this . store . getState ( ) ;
this . store . updateState ( {
swapsState : { ... swapsState , swapsFeatureFlags } ,
} ) ;
}
2020-11-03 00:41:28 +01:00
resetPostFetchState ( ) {
2021-02-04 19:15:23 +01:00
const { swapsState } = this . store . getState ( ) ;
2020-10-06 20:28:38 +02:00
this . store . updateState ( {
swapsState : {
... initialState . swapsState ,
tokens : swapsState . tokens ,
fetchParams : swapsState . fetchParams ,
swapsFeatureIsLive : swapsState . swapsFeatureIsLive ,
2020-12-15 21:24:22 +01:00
swapsQuoteRefreshTime : swapsState . swapsQuoteRefreshTime ,
2021-09-15 15:13:18 +02:00
swapsQuotePrefetchingRefreshTime :
swapsState . swapsQuotePrefetchingRefreshTime ,
2022-02-18 17:48:38 +01:00
swapsFeatureFlags : swapsState . swapsFeatureFlags ,
2020-10-06 20:28:38 +02:00
} ,
2021-02-04 19:15:23 +01:00
} ) ;
clearTimeout ( this . pollingTimeout ) ;
2020-10-06 20:28:38 +02:00
}
2020-11-03 00:41:28 +01:00
resetSwapsState ( ) {
2021-02-04 19:15:23 +01:00
const { swapsState } = this . store . getState ( ) ;
2020-10-06 20:28:38 +02:00
this . store . updateState ( {
2020-11-03 00:41:28 +01:00
swapsState : {
... initialState . swapsState ,
2020-12-15 21:24:22 +01:00
swapsQuoteRefreshTime : swapsState . swapsQuoteRefreshTime ,
2021-09-15 15:13:18 +02:00
swapsQuotePrefetchingRefreshTime :
swapsState . swapsQuotePrefetchingRefreshTime ,
2020-11-03 00:41:28 +01:00
} ,
2021-02-04 19:15:23 +01:00
} ) ;
clearTimeout ( this . pollingTimeout ) ;
2020-10-06 20:28:38 +02:00
}
2020-11-03 00:41:28 +01:00
async _findTopQuoteAndCalculateSavings ( quotes = { } ) {
2021-11-22 13:04:31 +01:00
const {
contractExchangeRates : tokenConversionRates ,
} = this . getTokenRatesState ( ) ;
2020-10-06 20:28:38 +02:00
const {
2021-07-30 13:35:30 +02:00
swapsState : { customGasPrice , customMaxPriorityFeePerGas } ,
2021-02-04 19:15:23 +01:00
} = this . store . getState ( ) ;
2021-03-18 11:20:06 +01:00
const chainId = this . _getCurrentChainId ( ) ;
2020-10-06 20:28:38 +02:00
2021-02-04 19:15:23 +01:00
const numQuotes = Object . keys ( quotes ) . length ;
2020-10-19 23:52:47 +02:00
if ( ! numQuotes ) {
2021-02-04 19:15:23 +01:00
return { } ;
2020-10-06 20:28:38 +02:00
}
2021-02-04 19:15:23 +01:00
const newQuotes = cloneDeep ( quotes ) ;
2020-11-09 18:09:38 +01:00
2021-07-30 13:35:30 +02:00
const {
gasFeeEstimates ,
gasEstimateType ,
} = await this . _getEIP1559GasFeeEstimates ( ) ;
let usedGasPrice = '0x0' ;
if ( gasEstimateType === GAS _ESTIMATE _TYPES . FEE _MARKET ) {
const {
high : { suggestedMaxPriorityFeePerGas } ,
estimatedBaseFee ,
} = gasFeeEstimates ;
usedGasPrice = addCurrencies (
customMaxPriorityFeePerGas || // Is already in hex WEI.
decGWEIToHexWEI ( suggestedMaxPriorityFeePerGas ) ,
decGWEIToHexWEI ( estimatedBaseFee ) ,
{
aBase : 16 ,
bBase : 16 ,
toNumericBase : 'hex' ,
numberOfDecimals : 6 ,
} ,
) ;
} else if ( gasEstimateType === GAS _ESTIMATE _TYPES . LEGACY ) {
usedGasPrice = customGasPrice || decGWEIToHexWEI ( gasFeeEstimates . high ) ;
} else if ( gasEstimateType === GAS _ESTIMATE _TYPES . ETH _GASPRICE ) {
usedGasPrice =
customGasPrice || decGWEIToHexWEI ( gasFeeEstimates . gasPrice ) ;
}
2020-10-06 20:28:38 +02:00
2021-02-04 19:15:23 +01:00
let topAggId = null ;
let overallValueOfBestQuoteForSorting = null ;
2020-10-06 20:28:38 +02:00
2020-11-09 18:09:38 +01:00
Object . values ( newQuotes ) . forEach ( ( quote ) => {
2020-10-06 20:28:38 +02:00
const {
2020-10-19 23:52:47 +02:00
aggregator ,
approvalNeeded ,
averageGas ,
2020-10-06 20:28:38 +02:00
destinationAmount = 0 ,
destinationToken ,
destinationTokenInfo ,
gasEstimate ,
2020-10-19 23:52:47 +02:00
sourceAmount ,
sourceToken ,
trade ,
2020-11-09 18:09:38 +01:00
fee : metaMaskFee ,
2021-02-04 19:15:23 +01:00
} = quote ;
2020-10-19 23:52:47 +02:00
2020-10-06 20:28:38 +02:00
const tradeGasLimitForCalculation = gasEstimate
? new BigNumber ( gasEstimate , 16 )
2021-02-04 19:15:23 +01:00
: new BigNumber ( averageGas || MAX _GAS _LIMIT , 10 ) ;
2020-10-19 23:52:47 +02:00
2020-10-06 20:28:38 +02:00
const totalGasLimitForCalculation = tradeGasLimitForCalculation
. plus ( approvalNeeded ? . gas || '0x0' , 16 )
2021-02-04 19:15:23 +01:00
. toString ( 16 ) ;
2020-10-19 23:52:47 +02:00
2020-10-06 20:28:38 +02:00
const gasTotalInWeiHex = calcGasTotal (
totalGasLimitForCalculation ,
usedGasPrice ,
2021-02-04 19:15:23 +01:00
) ;
2020-10-19 23:52:47 +02:00
// trade.value is a sum of different values depending on the transaction.
// It always includes any external fees charged by the quote source. In
2021-03-18 11:20:06 +01:00
// addition, if the source asset is the selected chain's default token, trade.value
// includes the amount of that token.
2020-11-03 00:41:28 +01:00
const totalWeiCost = new BigNumber ( gasTotalInWeiHex , 16 ) . plus (
trade . value ,
16 ,
2021-02-04 19:15:23 +01:00
) ;
2020-10-19 23:52:47 +02:00
const totalEthCost = conversionUtil ( totalWeiCost , {
2020-10-06 20:28:38 +02:00
fromCurrency : 'ETH' ,
fromDenomination : 'WEI' ,
toDenomination : 'ETH' ,
fromNumericBase : 'BN' ,
numberOfDecimals : 6 ,
2021-02-04 19:15:23 +01:00
} ) ;
2020-10-06 20:28:38 +02:00
2020-10-19 23:52:47 +02:00
// The total fee is aggregator/exchange fees plus gas fees.
2021-03-18 11:20:06 +01:00
// If the swap is from the selected chain's default token, subtract
// the sourceAmount from the total cost. Otherwise, the total fee
// is simply trade.value plus gas fees.
const ethFee = isSwapsDefaultTokenAddress ( sourceToken , chainId )
? conversionUtil (
totalWeiCost . minus ( sourceAmount , 10 ) , // sourceAmount is in wei
{
fromCurrency : 'ETH' ,
fromDenomination : 'WEI' ,
toDenomination : 'ETH' ,
fromNumericBase : 'BN' ,
numberOfDecimals : 6 ,
} ,
)
: totalEthCost ;
2020-10-19 23:52:47 +02:00
2020-11-09 18:09:38 +01:00
const decimalAdjustedDestinationAmount = calcTokenAmount (
destinationAmount ,
destinationTokenInfo . decimals ,
2021-02-04 19:15:23 +01:00
) ;
2020-11-09 18:09:38 +01:00
const tokenPercentageOfPreFeeDestAmount = new BigNumber ( 100 , 10 )
. minus ( metaMaskFee , 10 )
2021-02-04 19:15:23 +01:00
. div ( 100 ) ;
2020-11-09 18:09:38 +01:00
const destinationAmountBeforeMetaMaskFee = decimalAdjustedDestinationAmount . div (
tokenPercentageOfPreFeeDestAmount ,
2021-02-04 19:15:23 +01:00
) ;
2020-11-09 18:09:38 +01:00
const metaMaskFeeInTokens = destinationAmountBeforeMetaMaskFee . minus (
decimalAdjustedDestinationAmount ,
2021-02-04 19:15:23 +01:00
) ;
2020-11-09 18:09:38 +01:00
2021-11-22 13:04:31 +01:00
const tokenConversionRate =
tokenConversionRates [
Object . keys ( tokenConversionRates ) . find ( ( tokenAddress ) =>
isEqualCaseInsensitive ( tokenAddress , destinationToken ) ,
)
] ;
2021-02-04 19:15:23 +01:00
const conversionRateForSorting = tokenConversionRate || 1 ;
2020-11-09 18:09:38 +01:00
const ethValueOfTokens = decimalAdjustedDestinationAmount . times (
2021-11-23 17:33:32 +01:00
conversionRateForSorting . toString ( 10 ) ,
2020-11-09 18:09:38 +01:00
10 ,
2021-02-04 19:15:23 +01:00
) ;
2020-11-09 18:09:38 +01:00
2021-03-18 11:20:06 +01:00
const conversionRateForCalculations = isSwapsDefaultTokenAddress (
destinationToken ,
chainId ,
)
? 1
: tokenConversionRate ;
2020-11-09 18:09:38 +01:00
const overallValueOfQuoteForSorting =
conversionRateForCalculations === undefined
? ethValueOfTokens
2021-02-04 19:15:23 +01:00
: ethValueOfTokens . minus ( ethFee , 10 ) ;
2020-11-09 18:09:38 +01:00
2021-02-04 19:15:23 +01:00
quote . ethFee = ethFee . toString ( 10 ) ;
2020-11-09 18:09:38 +01:00
if ( conversionRateForCalculations !== undefined ) {
2021-02-04 19:15:23 +01:00
quote . ethValueOfTokens = ethValueOfTokens . toString ( 10 ) ;
quote . overallValueOfQuote = overallValueOfQuoteForSorting . toString ( 10 ) ;
2020-11-09 18:09:38 +01:00
quote . metaMaskFeeInEth = metaMaskFeeInTokens
2021-11-23 17:33:32 +01:00
. times ( conversionRateForCalculations . toString ( 10 ) )
2021-02-04 19:15:23 +01:00
. toString ( 10 ) ;
2020-11-09 18:09:38 +01:00
}
2020-10-06 20:28:38 +02:00
if (
2020-11-09 18:09:38 +01:00
overallValueOfBestQuoteForSorting === null ||
overallValueOfQuoteForSorting . gt ( overallValueOfBestQuoteForSorting )
2020-10-06 20:28:38 +02:00
) {
2021-02-04 19:15:23 +01:00
topAggId = aggregator ;
overallValueOfBestQuoteForSorting = overallValueOfQuoteForSorting ;
2020-10-06 20:28:38 +02:00
}
2021-02-04 19:15:23 +01:00
} ) ;
2020-10-06 20:28:38 +02:00
const isBest =
2021-03-18 11:20:06 +01:00
isSwapsDefaultTokenAddress (
newQuotes [ topAggId ] . destinationToken ,
chainId ,
2021-11-22 13:04:31 +01:00
) ||
Boolean (
tokenConversionRates [
Object . keys ( tokenConversionRates ) . find ( ( tokenAddress ) =>
isEqualCaseInsensitive (
tokenAddress ,
newQuotes [ topAggId ] ? . destinationToken ,
) ,
)
] ,
) ;
2020-10-06 20:28:38 +02:00
2021-02-04 19:15:23 +01:00
let savings = null ;
2020-10-19 23:52:47 +02:00
if ( isBest ) {
2021-02-04 19:15:23 +01:00
const bestQuote = newQuotes [ topAggId ] ;
2020-11-09 18:09:38 +01:00
2021-02-04 19:15:23 +01:00
savings = { } ;
2020-11-09 18:09:38 +01:00
const {
ethFee : medianEthFee ,
metaMaskFeeInEth : medianMetaMaskFee ,
ethValueOfTokens : medianEthValueOfTokens ,
2021-02-04 19:15:23 +01:00
} = getMedianEthValueQuote ( Object . values ( newQuotes ) ) ;
2020-11-09 18:09:38 +01:00
2020-10-19 23:52:47 +02:00
// Performance savings are calculated as:
2020-11-09 18:09:38 +01:00
// (ethValueOfTokens for the best trade) - (ethValueOfTokens for the media trade)
savings . performance = new BigNumber ( bestQuote . ethValueOfTokens , 10 ) . minus (
medianEthValueOfTokens ,
2020-10-19 23:52:47 +02:00
10 ,
2021-02-04 19:15:23 +01:00
) ;
2020-10-19 23:52:47 +02:00
2020-11-09 18:09:38 +01:00
// Fee savings are calculated as:
// (fee for the median trade) - (fee for the best trade)
2021-02-04 19:15:23 +01:00
savings . fee = new BigNumber ( medianEthFee ) . minus ( bestQuote . ethFee , 10 ) ;
2020-11-09 18:09:38 +01:00
2021-02-04 19:15:23 +01:00
savings . metaMaskFee = bestQuote . metaMaskFeeInEth ;
2020-10-19 23:52:47 +02:00
2020-11-09 18:09:38 +01:00
// Total savings are calculated as:
// performance savings + fee savings - metamask fee
savings . total = savings . performance
. plus ( savings . fee )
. minus ( savings . metaMaskFee )
2021-02-04 19:15:23 +01:00
. toString ( 10 ) ;
savings . performance = savings . performance . toString ( 10 ) ;
savings . fee = savings . fee . toString ( 10 ) ;
savings . medianMetaMaskFee = medianMetaMaskFee ;
2020-11-09 18:09:38 +01:00
2021-02-04 19:15:23 +01:00
newQuotes [ topAggId ] . isBestQuote = true ;
newQuotes [ topAggId ] . savings = savings ;
2020-10-19 23:52:47 +02:00
}
2021-02-04 19:15:23 +01:00
return [ topAggId , newQuotes ] ;
2020-10-06 20:28:38 +02:00
}
2021-03-30 20:55:14 +02:00
async _getERC20Allowance ( contractAddress , walletAddress , chainId ) {
2020-10-06 20:28:38 +02:00
const contract = new ethers . Contract (
2020-11-03 00:41:28 +01:00
contractAddress ,
abi ,
this . ethersProvider ,
2021-02-04 19:15:23 +01:00
) ;
2021-03-30 20:55:14 +02:00
return await contract . allowance (
walletAddress ,
SWAPS _CHAINID _CONTRACT _ADDRESS _MAP [ chainId ] ,
) ;
2020-10-06 20:28:38 +02:00
}
2020-10-19 23:52:47 +02:00
}
/ * *
2020-11-09 18:09:38 +01:00
* Calculates the median overallValueOfQuote of a sample of quotes .
2020-10-19 23:52:47 +02:00
*
2022-01-07 16:57:33 +01:00
* @ param { Array } _quotes - A sample of quote objects with overallValueOfQuote , ethFee , metaMaskFeeInEth , and ethValueOfTokens properties
2020-11-09 18:09:38 +01:00
* @ returns { Object } An object with the ethValueOfTokens , ethFee , and metaMaskFeeInEth of the quote with the median overallValueOfQuote
2020-10-19 23:52:47 +02:00
* /
2020-11-09 18:09:38 +01:00
function getMedianEthValueQuote ( _quotes ) {
if ( ! Array . isArray ( _quotes ) || _quotes . length === 0 ) {
2021-02-04 19:15:23 +01:00
throw new Error ( 'Expected non-empty array param.' ) ;
2020-10-19 23:52:47 +02:00
}
2021-02-04 19:15:23 +01:00
const quotes = [ ... _quotes ] ;
2020-11-09 18:09:38 +01:00
quotes . sort ( ( quoteA , quoteB ) => {
2021-02-04 19:15:23 +01:00
const overallValueOfQuoteA = new BigNumber ( quoteA . overallValueOfQuote , 10 ) ;
const overallValueOfQuoteB = new BigNumber ( quoteB . overallValueOfQuote , 10 ) ;
2020-11-09 18:09:38 +01:00
if ( overallValueOfQuoteA . equals ( overallValueOfQuoteB ) ) {
2021-02-04 19:15:23 +01:00
return 0 ;
2020-10-19 23:52:47 +02:00
}
2021-02-04 19:15:23 +01:00
return overallValueOfQuoteA . lessThan ( overallValueOfQuoteB ) ? - 1 : 1 ;
} ) ;
2020-10-19 23:52:47 +02:00
2020-11-09 18:09:38 +01:00
if ( quotes . length % 2 === 1 ) {
// return middle values
const medianOverallValue =
2021-02-04 19:15:23 +01:00
quotes [ ( quotes . length - 1 ) / 2 ] . overallValueOfQuote ;
2020-11-09 18:09:38 +01:00
const quotesMatchingMedianQuoteValue = quotes . filter (
( quote ) => medianOverallValue === quote . overallValueOfQuote ,
2021-02-04 19:15:23 +01:00
) ;
return meansOfQuotesFeesAndValue ( quotesMatchingMedianQuoteValue ) ;
2020-10-19 23:52:47 +02:00
}
// return mean of middle two values
2021-02-04 19:15:23 +01:00
const upperIndex = quotes . length / 2 ;
const lowerIndex = upperIndex - 1 ;
2020-11-09 18:09:38 +01:00
2021-02-04 19:15:23 +01:00
const overallValueAtUpperIndex = quotes [ upperIndex ] . overallValueOfQuote ;
const overallValueAtLowerIndex = quotes [ lowerIndex ] . overallValueOfQuote ;
2020-11-09 18:09:38 +01:00
const quotesMatchingUpperIndexValue = quotes . filter (
( quote ) => overallValueAtUpperIndex === quote . overallValueOfQuote ,
2021-02-04 19:15:23 +01:00
) ;
2020-11-09 18:09:38 +01:00
const quotesMatchingLowerIndexValue = quotes . filter (
( quote ) => overallValueAtLowerIndex === quote . overallValueOfQuote ,
2021-02-04 19:15:23 +01:00
) ;
2020-11-09 18:09:38 +01:00
const feesAndValueAtUpperIndex = meansOfQuotesFeesAndValue (
quotesMatchingUpperIndexValue ,
2021-02-04 19:15:23 +01:00
) ;
2020-11-09 18:09:38 +01:00
const feesAndValueAtLowerIndex = meansOfQuotesFeesAndValue (
quotesMatchingLowerIndexValue ,
2021-02-04 19:15:23 +01:00
) ;
2020-11-09 18:09:38 +01:00
return {
ethFee : new BigNumber ( feesAndValueAtUpperIndex . ethFee , 10 )
. plus ( feesAndValueAtLowerIndex . ethFee , 10 )
. dividedBy ( 2 )
. toString ( 10 ) ,
metaMaskFeeInEth : new BigNumber (
feesAndValueAtUpperIndex . metaMaskFeeInEth ,
10 ,
)
. plus ( feesAndValueAtLowerIndex . metaMaskFeeInEth , 10 )
. dividedBy ( 2 )
. toString ( 10 ) ,
ethValueOfTokens : new BigNumber (
feesAndValueAtUpperIndex . ethValueOfTokens ,
10 ,
)
. plus ( feesAndValueAtLowerIndex . ethValueOfTokens , 10 )
. dividedBy ( 2 )
. toString ( 10 ) ,
2021-02-04 19:15:23 +01:00
} ;
2020-11-09 18:09:38 +01:00
}
/ * *
* Calculates the arithmetic mean for each of three properties - ethFee , metaMaskFeeInEth and ethValueOfTokens - across
* an array of objects containing those properties .
*
* @ param { Array } quotes - A sample of quote objects with overallValueOfQuote , ethFee , metaMaskFeeInEth and
* ethValueOfTokens properties
* @ returns { Object } An object with the arithmetic mean each of the ethFee , metaMaskFeeInEth and ethValueOfTokens of
* the passed quote objects
* /
function meansOfQuotesFeesAndValue ( quotes ) {
const feeAndValueSumsAsBigNumbers = quotes . reduce (
( feeAndValueSums , quote ) => ( {
ethFee : feeAndValueSums . ethFee . plus ( quote . ethFee , 10 ) ,
metaMaskFeeInEth : feeAndValueSums . metaMaskFeeInEth . plus (
quote . metaMaskFeeInEth ,
10 ,
) ,
ethValueOfTokens : feeAndValueSums . ethValueOfTokens . plus (
quote . ethValueOfTokens ,
10 ,
) ,
} ) ,
{
ethFee : new BigNumber ( 0 , 10 ) ,
metaMaskFeeInEth : new BigNumber ( 0 , 10 ) ,
ethValueOfTokens : new BigNumber ( 0 , 10 ) ,
} ,
2021-02-04 19:15:23 +01:00
) ;
2020-11-09 18:09:38 +01:00
return {
ethFee : feeAndValueSumsAsBigNumbers . ethFee
. div ( quotes . length , 10 )
. toString ( 10 ) ,
metaMaskFeeInEth : feeAndValueSumsAsBigNumbers . metaMaskFeeInEth
. div ( quotes . length , 10 )
. toString ( 10 ) ,
ethValueOfTokens : feeAndValueSumsAsBigNumbers . ethValueOfTokens
. div ( quotes . length , 10 )
. toString ( 10 ) ,
2021-02-04 19:15:23 +01:00
} ;
2020-10-19 23:52:47 +02:00
}
2020-10-06 20:28:38 +02:00
2020-10-19 23:52:47 +02:00
export const utils = {
2020-11-09 18:09:38 +01:00
getMedianEthValueQuote ,
meansOfQuotesFeesAndValue ,
2021-02-04 19:15:23 +01:00
} ;