mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge pull request #10072 from MetaMask/Version-v8.1.9
Version v8.1.9 RC
This commit is contained in:
commit
47734b2c63
@ -195,6 +195,7 @@ module.exports = {
|
||||
'babel.config.js',
|
||||
'nyc.config.js',
|
||||
'stylelint.config.js',
|
||||
'app/scripts/runLockdown.js',
|
||||
'development/**/*.js',
|
||||
'test/e2e/**/*.js',
|
||||
'test/lib/wait-until-called.js',
|
||||
|
@ -2,6 +2,14 @@
|
||||
|
||||
## Current Develop Branch
|
||||
|
||||
## 8.1.9 Tue Dec 15 2020
|
||||
- [#10034](https://github.com/MetaMask/metamask-extension/pull/10034): Fix contentscript injection failure on Firefox 56
|
||||
- [#10045](https://github.com/MetaMask/metamask-extension/pull/10045): Fix token validation in Send flow
|
||||
- [#10048](https://github.com/MetaMask/metamask-extension/pull/10048): Display boolean values when signing typed data
|
||||
- [#10070](https://github.com/MetaMask/metamask-extension/pull/10070): Add eth_getProof
|
||||
- [#10043](https://github.com/MetaMask/metamask-extension/pull/10043): Improve swaps maximum gas estimation
|
||||
- [#10069](https://github.com/MetaMask/metamask-extension/pull/10069): Fetch swap quote refresh time from API
|
||||
|
||||
## 8.1.8 Wed Dec 09 2020
|
||||
- [#9992](https://github.com/MetaMask/metamask-extension/pull/9992): Improve transaction params validation
|
||||
- [#9991](https://github.com/MetaMask/metamask-extension/pull/9991): Don't allow more than 15% slippage
|
||||
|
@ -38,6 +38,7 @@
|
||||
{
|
||||
"matches": ["file://*/*", "http://*/*", "https://*/*"],
|
||||
"js": [
|
||||
"disable-console.js",
|
||||
"globalthis.js",
|
||||
"lockdown.js",
|
||||
"runLockdown.js",
|
||||
@ -77,6 +78,6 @@
|
||||
"notifications"
|
||||
],
|
||||
"short_name": "__MSG_appName__",
|
||||
"version": "8.1.8",
|
||||
"version": "8.1.9",
|
||||
"web_accessible_resources": ["inpage.js", "phishing.html"]
|
||||
}
|
||||
|
@ -41,8 +41,8 @@ function injectScript(content) {
|
||||
scriptTag.textContent = content
|
||||
container.insertBefore(scriptTag, container.children[0])
|
||||
container.removeChild(scriptTag)
|
||||
} catch (e) {
|
||||
console.error('MetaMask provider injection failed.', e)
|
||||
} catch (error) {
|
||||
console.error('MetaMask: Provider injection failed.', error)
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,10 +95,10 @@ async function setupStreams() {
|
||||
function forwardTrafficBetweenMuxers(channelName, muxA, muxB) {
|
||||
const channelA = muxA.createStream(channelName)
|
||||
const channelB = muxB.createStream(channelName)
|
||||
pump(channelA, channelB, channelA, (err) =>
|
||||
logStreamDisconnectWarning(
|
||||
`MetaMask muxed traffic for channel "${channelName}" failed.`,
|
||||
err,
|
||||
pump(channelA, channelB, channelA, (error) =>
|
||||
console.debug(
|
||||
`MetaMask: Muxed traffic for channel "${channelName}" failed.`,
|
||||
error,
|
||||
),
|
||||
)
|
||||
}
|
||||
@ -107,14 +107,13 @@ function forwardTrafficBetweenMuxers(channelName, muxA, muxB) {
|
||||
* Error handler for page to extension stream disconnections
|
||||
*
|
||||
* @param {string} remoteLabel - Remote stream name
|
||||
* @param {Error} err - Stream connection error
|
||||
* @param {Error} error - Stream connection error
|
||||
*/
|
||||
function logStreamDisconnectWarning(remoteLabel, err) {
|
||||
let warningMsg = `MetamaskContentscript - lost connection to ${remoteLabel}`
|
||||
if (err) {
|
||||
warningMsg += `\n${err.stack}`
|
||||
}
|
||||
console.warn(warningMsg)
|
||||
function logStreamDisconnectWarning(remoteLabel, error) {
|
||||
console.debug(
|
||||
`MetaMask: Content script lost connection to "${remoteLabel}".`,
|
||||
error,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -214,7 +213,7 @@ function blockedDomainCheck() {
|
||||
* Redirects the current page to a phishing information page
|
||||
*/
|
||||
function redirectToPhishingWarning() {
|
||||
console.log('MetaMask - routing to Phishing Warning component')
|
||||
console.debug('MetaMask: Routing to Phishing Warning component.')
|
||||
const extensionURL = extension.runtime.getURL('phishing.html')
|
||||
window.location.href = `${extensionURL}#${querystring.stringify({
|
||||
hostname: window.location.hostname,
|
||||
|
@ -32,15 +32,11 @@ export const LOG_METHOD_TYPES = {
|
||||
export const LOG_LIMIT = 100
|
||||
|
||||
export const SAFE_METHODS = [
|
||||
'web3_sha3',
|
||||
'web3_clientVersion',
|
||||
'net_listening',
|
||||
'net_peerCount',
|
||||
'net_version',
|
||||
'eth_blockNumber',
|
||||
'eth_call',
|
||||
'eth_chainId',
|
||||
'eth_coinbase',
|
||||
'eth_decrypt',
|
||||
'eth_estimateGas',
|
||||
'eth_gasPrice',
|
||||
'eth_getBalance',
|
||||
@ -49,9 +45,11 @@ export const SAFE_METHODS = [
|
||||
'eth_getBlockTransactionCountByHash',
|
||||
'eth_getBlockTransactionCountByNumber',
|
||||
'eth_getCode',
|
||||
'eth_getEncryptionPublicKey',
|
||||
'eth_getFilterChanges',
|
||||
'eth_getFilterLogs',
|
||||
'eth_getLogs',
|
||||
'eth_getProof',
|
||||
'eth_getStorageAt',
|
||||
'eth_getTransactionByBlockHashAndIndex',
|
||||
'eth_getTransactionByBlockNumberAndIndex',
|
||||
@ -72,8 +70,6 @@ export const SAFE_METHODS = [
|
||||
'eth_sendRawTransaction',
|
||||
'eth_sendTransaction',
|
||||
'eth_sign',
|
||||
'personal_sign',
|
||||
'personal_ecRecover',
|
||||
'eth_signTypedData',
|
||||
'eth_signTypedData_v1',
|
||||
'eth_signTypedData_v3',
|
||||
@ -83,7 +79,12 @@ export const SAFE_METHODS = [
|
||||
'eth_syncing',
|
||||
'eth_uninstallFilter',
|
||||
'metamask_watchAsset',
|
||||
'net_listening',
|
||||
'net_peerCount',
|
||||
'net_version',
|
||||
'personal_ecRecover',
|
||||
'personal_sign',
|
||||
'wallet_watchAsset',
|
||||
'eth_getEncryptionPublicKey',
|
||||
'eth_decrypt',
|
||||
'web3_clientVersion',
|
||||
'web3_sha3',
|
||||
]
|
||||
|
@ -17,6 +17,7 @@ import {
|
||||
import {
|
||||
fetchTradesInfo as defaultFetchTradesInfo,
|
||||
fetchSwapsFeatureLiveness as defaultFetchSwapsFeatureLiveness,
|
||||
fetchSwapsQuoteRefreshTime as defaultFetchSwapsQuoteRefreshTime,
|
||||
} from '../../../ui/app/pages/swaps/swaps.util'
|
||||
|
||||
const METASWAP_ADDRESS = '0x881d40237659c251811cec9c364ef91dc08d300c'
|
||||
@ -28,6 +29,14 @@ const MAX_GAS_LIMIT = 2500000
|
||||
// 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.
|
||||
const POLL_COUNT_LIMIT = 3
|
||||
|
||||
// If for any reason the MetaSwap API fails to provide a refresh time,
|
||||
// provide a reasonable fallback to avoid further errors
|
||||
const FALLBACK_QUOTE_REFRESH_TIME = 60000
|
||||
|
||||
// This is the amount of time to wait, after successfully fetching quotes
|
||||
// and their gas estimates, before fetching for new quotes
|
||||
const QUOTE_POLLING_DIFFERENCE_INTERVAL = 10 * 1000
|
||||
|
||||
function calculateGasEstimateWithRefund(
|
||||
maxGas = MAX_GAS_LIMIT,
|
||||
estimatedRefund = 0,
|
||||
@ -42,9 +51,6 @@ function calculateGasEstimateWithRefund(
|
||||
return gasEstimateWithRefund
|
||||
}
|
||||
|
||||
// This is the amount of time to wait, after successfully fetching quotes and their gas estimates, before fetching for new quotes
|
||||
const QUOTE_POLLING_INTERVAL = 50 * 1000
|
||||
|
||||
const initialState = {
|
||||
swapsState: {
|
||||
quotes: {},
|
||||
@ -61,6 +67,7 @@ const initialState = {
|
||||
topAggId: null,
|
||||
routeState: '',
|
||||
swapsFeatureIsLive: false,
|
||||
swapsQuoteRefreshTime: FALLBACK_QUOTE_REFRESH_TIME,
|
||||
},
|
||||
}
|
||||
|
||||
@ -73,6 +80,7 @@ export default class SwapsController {
|
||||
tokenRatesStore,
|
||||
fetchTradesInfo = defaultFetchTradesInfo,
|
||||
fetchSwapsFeatureLiveness = defaultFetchSwapsFeatureLiveness,
|
||||
fetchSwapsQuoteRefreshTime = defaultFetchSwapsQuoteRefreshTime,
|
||||
}) {
|
||||
this.store = new ObservableStore({
|
||||
swapsState: { ...initialState.swapsState },
|
||||
@ -80,6 +88,7 @@ export default class SwapsController {
|
||||
|
||||
this._fetchTradesInfo = fetchTradesInfo
|
||||
this._fetchSwapsFeatureLiveness = fetchSwapsFeatureLiveness
|
||||
this._fetchSwapsQuoteRefreshTime = fetchSwapsQuoteRefreshTime
|
||||
|
||||
this.getBufferedGasLimit = getBufferedGasLimit
|
||||
this.tokenRatesStore = tokenRatesStore
|
||||
@ -101,11 +110,31 @@ export default class SwapsController {
|
||||
this._setupSwapsLivenessFetching()
|
||||
}
|
||||
|
||||
// Sets the refresh rate for quote updates from the MetaSwap API
|
||||
async _setSwapsQuoteRefreshTime() {
|
||||
// Default to fallback time unless API returns valid response
|
||||
let swapsQuoteRefreshTime = FALLBACK_QUOTE_REFRESH_TIME
|
||||
try {
|
||||
swapsQuoteRefreshTime = await this._fetchSwapsQuoteRefreshTime()
|
||||
} catch (e) {
|
||||
console.error('Request for swaps quote refresh time failed: ', e)
|
||||
}
|
||||
|
||||
const { swapsState } = this.store.getState()
|
||||
this.store.updateState({
|
||||
swapsState: { ...swapsState, swapsQuoteRefreshTime },
|
||||
})
|
||||
}
|
||||
|
||||
// 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
|
||||
// that quotes will no longer be available after 1 or 2 minutes. When fetchAndSetQuotes is first called it, receives fetch that parameters are stored in
|
||||
// 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
|
||||
pollForNewQuotes() {
|
||||
const {
|
||||
swapsState: { swapsQuoteRefreshTime },
|
||||
} = this.store.getState()
|
||||
|
||||
this.pollingTimeout = setTimeout(() => {
|
||||
const { swapsState } = this.store.getState()
|
||||
this.fetchAndSetQuotes(
|
||||
@ -113,7 +142,7 @@ export default class SwapsController {
|
||||
swapsState.fetchParams?.metaData,
|
||||
true,
|
||||
)
|
||||
}, QUOTE_POLLING_INTERVAL)
|
||||
}, swapsQuoteRefreshTime - QUOTE_POLLING_DIFFERENCE_INTERVAL)
|
||||
}
|
||||
|
||||
stopPollingForQuotes() {
|
||||
@ -128,7 +157,6 @@ export default class SwapsController {
|
||||
if (!fetchParams) {
|
||||
return null
|
||||
}
|
||||
|
||||
// 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) {
|
||||
this.pollCount = 0
|
||||
@ -144,7 +172,10 @@ export default class SwapsController {
|
||||
const indexOfCurrentCall = this.indexOfNewestCallInFlight + 1
|
||||
this.indexOfNewestCallInFlight = indexOfCurrentCall
|
||||
|
||||
let newQuotes = await this._fetchTradesInfo(fetchParams)
|
||||
let [newQuotes] = await Promise.all([
|
||||
this._fetchTradesInfo(fetchParams),
|
||||
this._setSwapsQuoteRefreshTime(),
|
||||
])
|
||||
|
||||
newQuotes = mapValues(newQuotes, (quote) => ({
|
||||
...quote,
|
||||
@ -422,6 +453,7 @@ export default class SwapsController {
|
||||
tokens: swapsState.tokens,
|
||||
fetchParams: swapsState.fetchParams,
|
||||
swapsFeatureIsLive: swapsState.swapsFeatureIsLive,
|
||||
swapsQuoteRefreshTime: swapsState.swapsQuoteRefreshTime,
|
||||
},
|
||||
})
|
||||
clearTimeout(this.pollingTimeout)
|
||||
@ -435,6 +467,7 @@ export default class SwapsController {
|
||||
...initialState.swapsState,
|
||||
tokens: swapsState.tokens,
|
||||
swapsFeatureIsLive: swapsState.swapsFeatureIsLive,
|
||||
swapsQuoteRefreshTime: swapsState.swapsQuoteRefreshTime,
|
||||
},
|
||||
})
|
||||
clearTimeout(this.pollingTimeout)
|
||||
|
9
app/scripts/disable-console.js
Normal file
9
app/scripts/disable-console.js
Normal file
@ -0,0 +1,9 @@
|
||||
// Disable console.log in contentscript to prevent SES/lockdown logging to external page
|
||||
// eslint-disable-next-line import/unambiguous
|
||||
if (
|
||||
!(typeof process !== 'undefined' && process.env.METAMASK_DEBUG) &&
|
||||
typeof console !== undefined
|
||||
) {
|
||||
console.log = () => undefined
|
||||
console.info = () => undefined
|
||||
}
|
@ -1,8 +1,19 @@
|
||||
// Freezes all intrinsics
|
||||
// eslint-disable-next-line no-undef,import/unambiguous
|
||||
lockdown({
|
||||
consoleTaming: 'unsafe',
|
||||
errorTaming: 'unsafe',
|
||||
mathTaming: 'unsafe',
|
||||
dateTaming: 'unsafe',
|
||||
})
|
||||
try {
|
||||
// eslint-disable-next-line no-undef,import/unambiguous
|
||||
lockdown({
|
||||
consoleTaming: 'unsafe',
|
||||
errorTaming: 'unsafe',
|
||||
mathTaming: 'unsafe',
|
||||
dateTaming: 'unsafe',
|
||||
})
|
||||
} catch (error) {
|
||||
// If the `lockdown` call throws an exception, it interferes with the
|
||||
// contentscript injection on some versions of Firefox. The error is
|
||||
// caught and logged here so that the contentscript still gets injected.
|
||||
// This affects Firefox v56 and Waterfox Classic
|
||||
console.error('Lockdown failed:', error)
|
||||
if (window.sentry && window.sentry.captureException) {
|
||||
window.sentry.captureException(error)
|
||||
}
|
||||
}
|
||||
|
@ -115,6 +115,7 @@ function createScriptTasks({ browserPlatforms, livereload }) {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
// inpage must be built before contentscript
|
||||
// because inpage bundle result is included inside contentscript
|
||||
const contentscriptSubtask = createTask(
|
||||
@ -122,6 +123,12 @@ function createScriptTasks({ browserPlatforms, livereload }) {
|
||||
createTaskForBuildJsExtensionContentscript({ devMode, testing }),
|
||||
)
|
||||
|
||||
// this can run whenever
|
||||
const disableConsoleSubtask = createTask(
|
||||
`${taskPrefix}:disable-console`,
|
||||
createTaskForBuildJsExtensionDisableConsole({ devMode }),
|
||||
)
|
||||
|
||||
// task for initiating livereload
|
||||
const initiateLiveReload = async () => {
|
||||
if (devMode) {
|
||||
@ -142,6 +149,7 @@ function createScriptTasks({ browserPlatforms, livereload }) {
|
||||
const allSubtasks = [
|
||||
...standardSubtasks,
|
||||
contentscriptSubtask,
|
||||
disableConsoleSubtask,
|
||||
].map((subtask) => runInChildProcess(subtask))
|
||||
// const allSubtasks = [...standardSubtasks, contentscriptSubtask].map(subtask => (subtask))
|
||||
// make a parent task that runs each task in a child thread
|
||||
@ -165,6 +173,16 @@ function createScriptTasks({ browserPlatforms, livereload }) {
|
||||
})
|
||||
}
|
||||
|
||||
function createTaskForBuildJsExtensionDisableConsole({ devMode }) {
|
||||
const filename = 'disable-console'
|
||||
return bundleTask({
|
||||
label: filename,
|
||||
filename: `${filename}.js`,
|
||||
filepath: `./app/scripts/${filename}.js`,
|
||||
devMode,
|
||||
})
|
||||
}
|
||||
|
||||
function createTaskForBuildJsExtensionContentscript({ devMode, testing }) {
|
||||
const inpage = 'inpage'
|
||||
const contentscript = 'contentscript'
|
||||
|
@ -121,12 +121,14 @@ const EMPTY_INIT_STATE = {
|
||||
topAggId: null,
|
||||
routeState: '',
|
||||
swapsFeatureIsLive: false,
|
||||
swapsQuoteRefreshTime: 60000,
|
||||
},
|
||||
}
|
||||
|
||||
const sandbox = sinon.createSandbox()
|
||||
const fetchTradesInfoStub = sandbox.stub()
|
||||
const fetchSwapsFeatureLivenessStub = sandbox.stub()
|
||||
const fetchSwapsQuoteRefreshTimeStub = sandbox.stub()
|
||||
|
||||
describe('SwapsController', function () {
|
||||
let provider
|
||||
@ -140,6 +142,7 @@ describe('SwapsController', function () {
|
||||
tokenRatesStore: MOCK_TOKEN_RATES_STORE,
|
||||
fetchTradesInfo: fetchTradesInfoStub,
|
||||
fetchSwapsFeatureLiveness: fetchSwapsFeatureLivenessStub,
|
||||
fetchSwapsQuoteRefreshTime: fetchSwapsQuoteRefreshTimeStub,
|
||||
})
|
||||
}
|
||||
|
||||
@ -639,9 +642,9 @@ describe('SwapsController', function () {
|
||||
const quotes = await swapsController.fetchAndSetQuotes(undefined)
|
||||
assert.strictEqual(quotes, null)
|
||||
})
|
||||
|
||||
it('calls fetchTradesInfo with the given fetchParams and returns the correct quotes', async function () {
|
||||
fetchTradesInfoStub.resolves(getMockQuotes())
|
||||
fetchSwapsQuoteRefreshTimeStub.resolves(getMockQuoteRefreshTime())
|
||||
|
||||
// Make it so approval is not required
|
||||
sandbox
|
||||
@ -682,9 +685,9 @@ describe('SwapsController', function () {
|
||||
true,
|
||||
)
|
||||
})
|
||||
|
||||
it('performs the allowance check', async function () {
|
||||
fetchTradesInfoStub.resolves(getMockQuotes())
|
||||
fetchSwapsQuoteRefreshTimeStub.resolves(getMockQuoteRefreshTime())
|
||||
|
||||
// Make it so approval is not required
|
||||
const allowanceStub = sandbox
|
||||
@ -707,6 +710,7 @@ describe('SwapsController', function () {
|
||||
|
||||
it('gets the gas limit if approval is required', async function () {
|
||||
fetchTradesInfoStub.resolves(MOCK_QUOTES_APPROVAL_REQUIRED)
|
||||
fetchSwapsQuoteRefreshTimeStub.resolves(getMockQuoteRefreshTime())
|
||||
|
||||
// Ensure approval is required
|
||||
sandbox
|
||||
@ -732,6 +736,7 @@ describe('SwapsController', function () {
|
||||
|
||||
it('marks the best quote', async function () {
|
||||
fetchTradesInfoStub.resolves(getMockQuotes())
|
||||
fetchSwapsQuoteRefreshTimeStub.resolves(getMockQuoteRefreshTime())
|
||||
|
||||
// Make it so approval is not required
|
||||
sandbox
|
||||
@ -762,6 +767,7 @@ describe('SwapsController', function () {
|
||||
}
|
||||
const quotes = { ...getMockQuotes(), [bestAggId]: bestQuote }
|
||||
fetchTradesInfoStub.resolves(quotes)
|
||||
fetchSwapsQuoteRefreshTimeStub.resolves(getMockQuoteRefreshTime())
|
||||
|
||||
// Make it so approval is not required
|
||||
sandbox
|
||||
@ -779,6 +785,7 @@ describe('SwapsController', function () {
|
||||
|
||||
it('does not mark as best quote if no conversion rate exists for destination token', async function () {
|
||||
fetchTradesInfoStub.resolves(getMockQuotes())
|
||||
fetchSwapsQuoteRefreshTimeStub.resolves(getMockQuoteRefreshTime())
|
||||
|
||||
// Make it so approval is not required
|
||||
sandbox
|
||||
@ -805,6 +812,7 @@ describe('SwapsController', function () {
|
||||
assert.deepStrictEqual(swapsState, {
|
||||
...EMPTY_INIT_STATE.swapsState,
|
||||
tokens: old.tokens,
|
||||
swapsQuoteRefreshTime: old.swapsQuoteRefreshTime,
|
||||
})
|
||||
})
|
||||
|
||||
@ -850,8 +858,14 @@ describe('SwapsController', function () {
|
||||
const tokens = 'test'
|
||||
const fetchParams = 'test'
|
||||
const swapsFeatureIsLive = false
|
||||
const swapsQuoteRefreshTime = 0
|
||||
swapsController.store.updateState({
|
||||
swapsState: { tokens, fetchParams, swapsFeatureIsLive },
|
||||
swapsState: {
|
||||
tokens,
|
||||
fetchParams,
|
||||
swapsFeatureIsLive,
|
||||
swapsQuoteRefreshTime,
|
||||
},
|
||||
})
|
||||
|
||||
swapsController.resetPostFetchState()
|
||||
@ -862,6 +876,7 @@ describe('SwapsController', function () {
|
||||
tokens,
|
||||
fetchParams,
|
||||
swapsFeatureIsLive,
|
||||
swapsQuoteRefreshTime,
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1615,3 +1630,7 @@ function getTopQuoteAndSavingsBaseExpectedResults() {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function getMockQuoteRefreshTime() {
|
||||
return 45000
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ export default class SignatureRequestMessage extends PureComponent {
|
||||
this.renderNode(value)
|
||||
) : (
|
||||
<span className="signature-request-message--node-value">
|
||||
{value}
|
||||
{`${value}`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
@ -39,7 +39,6 @@ import { calcGasTotal } from '../../pages/send/send.utils'
|
||||
import {
|
||||
decimalToHex,
|
||||
getValueFromWeiHex,
|
||||
hexMax,
|
||||
decGWEIToHexWEI,
|
||||
hexToDecimal,
|
||||
hexWEIToDecGWEI,
|
||||
@ -67,6 +66,8 @@ const GAS_PRICES_LOADING_STATES = {
|
||||
COMPLETED: 'COMPLETED',
|
||||
}
|
||||
|
||||
export const FALLBACK_GAS_MULTIPLIER = 1.5
|
||||
|
||||
const initialState = {
|
||||
aggregatorMetadata: null,
|
||||
approveTxId: null,
|
||||
@ -223,6 +224,9 @@ const getSwapsState = (state) => state.metamask.swapsState
|
||||
export const getSwapsFeatureLiveness = (state) =>
|
||||
state.metamask.swapsState.swapsFeatureIsLive
|
||||
|
||||
export const getSwapsQuoteRefreshTime = (state) =>
|
||||
state.metamask.swapsState.swapsQuoteRefreshTime
|
||||
|
||||
export const getBackgroundSwapRouteState = (state) =>
|
||||
state.metamask.swapsState.routeState
|
||||
|
||||
@ -593,20 +597,16 @@ export const signAndSendTransactions = (history, metaMetricsEvent) => {
|
||||
const usedQuote = getUsedQuote(state)
|
||||
const usedTradeTxParams = usedQuote.trade
|
||||
|
||||
const estimatedGasLimit = new BigNumber(
|
||||
usedQuote?.gasEstimate || decimalToHex(usedQuote?.averageGas || 0),
|
||||
16,
|
||||
)
|
||||
const estimatedGasLimit = new BigNumber(usedQuote?.gasEstimate || `0x0`, 16)
|
||||
const estimatedGasLimitWithMultiplier = estimatedGasLimit
|
||||
.times(1.4, 10)
|
||||
.times(usedQuote?.gasMultiplier || FALLBACK_GAS_MULTIPLIER, 10)
|
||||
.round(0)
|
||||
.toString(16)
|
||||
const maxGasLimit =
|
||||
customSwapsGas ||
|
||||
hexMax(
|
||||
`0x${decimalToHex(usedQuote?.maxGas || 0)}`,
|
||||
estimatedGasLimitWithMultiplier,
|
||||
)
|
||||
(usedQuote?.gasEstimate
|
||||
? estimatedGasLimitWithMultiplier
|
||||
: usedQuote?.maxGas)
|
||||
|
||||
const usedGasPrice = getUsedSwapsGasPrice(state)
|
||||
usedTradeTxParams.gas = maxGasLimit
|
||||
|
@ -1,4 +1,3 @@
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { ETH, GWEI, WEI } from '../constants/common'
|
||||
import { addHexPrefix } from '../../../../app/scripts/lib/util'
|
||||
import {
|
||||
@ -163,16 +162,6 @@ export function hexWEIToDecETH(hexWEI) {
|
||||
})
|
||||
}
|
||||
|
||||
export function hexMax(...hexNumbers) {
|
||||
let max = hexNumbers[0]
|
||||
hexNumbers.slice(1).forEach((hexNumber) => {
|
||||
if (new BigNumber(hexNumber, 16).gt(max, 16)) {
|
||||
max = hexNumber
|
||||
}
|
||||
})
|
||||
return max
|
||||
}
|
||||
|
||||
export function addHexes(aHexWEI, bHexWEI) {
|
||||
return addCurrencies(aHexWEI, bHexWEI, {
|
||||
aBase: 16,
|
||||
|
@ -16,7 +16,6 @@ const fetchWithCache = async (
|
||||
fetchOptions.headers = new window.Headers(fetchOptions.headers)
|
||||
}
|
||||
if (
|
||||
fetchOptions.headers &&
|
||||
fetchOptions.headers.has('Content-Type') &&
|
||||
fetchOptions.headers.get('Content-Type') !== 'application/json'
|
||||
) {
|
||||
@ -24,8 +23,8 @@ const fetchWithCache = async (
|
||||
}
|
||||
|
||||
const currentTime = Date.now()
|
||||
const cachedFetch = (await getStorageItem('cachedFetch')) || {}
|
||||
const { cachedResponse, cachedTime } = cachedFetch[url] || {}
|
||||
const cacheKey = `cachedFetch:${url}`
|
||||
const { cachedResponse, cachedTime } = (await getStorageItem(cacheKey)) || {}
|
||||
if (cachedResponse && currentTime - cachedTime < cacheRefreshTime) {
|
||||
return cachedResponse
|
||||
}
|
||||
@ -48,8 +47,8 @@ const fetchWithCache = async (
|
||||
cachedResponse: responseJson,
|
||||
cachedTime: currentTime,
|
||||
}
|
||||
cachedFetch[url] = cacheEntry
|
||||
await setStorageItem('cachedFetch', cachedFetch)
|
||||
|
||||
await setStorageItem(cacheKey, cacheEntry)
|
||||
return responseJson
|
||||
}
|
||||
|
||||
|
@ -37,10 +37,8 @@ describe('Fetch with cache', function () {
|
||||
.reply(200, '{"average": 2}')
|
||||
|
||||
fakeStorage.getStorageItem.returns({
|
||||
'https://fetchwithcache.metamask.io/price': {
|
||||
cachedResponse: { average: 1 },
|
||||
cachedTime: Date.now(),
|
||||
},
|
||||
cachedResponse: { average: 1 },
|
||||
cachedTime: Date.now(),
|
||||
})
|
||||
|
||||
const response = await fetchWithCache(
|
||||
@ -57,10 +55,8 @@ describe('Fetch with cache', function () {
|
||||
.reply(200, '{"average": 3}')
|
||||
|
||||
fakeStorage.getStorageItem.returns({
|
||||
'https://fetchwithcache.metamask.io/cached': {
|
||||
cachedResponse: { average: 1 },
|
||||
cachedTime: Date.now() - 1000,
|
||||
},
|
||||
cachedResponse: { average: 1 },
|
||||
cachedTime: Date.now() - 1000,
|
||||
})
|
||||
|
||||
const response = await fetchWithCache(
|
||||
@ -135,4 +131,43 @@ describe('Fetch with cache', function () {
|
||||
{ message: 'fetchWithCache only supports JSON responses' },
|
||||
)
|
||||
})
|
||||
|
||||
it('should correctly cache responses from interwoven requests', async function () {
|
||||
nock('https://fetchwithcache.metamask.io')
|
||||
.get('/foo')
|
||||
.reply(200, '{"average": 9}')
|
||||
nock('https://fetchwithcache.metamask.io')
|
||||
.get('/bar')
|
||||
.reply(200, '{"average": 9}')
|
||||
|
||||
const testCache = {}
|
||||
fakeStorage.getStorageItem.callsFake((key) => testCache[key])
|
||||
fakeStorage.setStorageItem.callsFake((key, value) => {
|
||||
testCache[key] = value
|
||||
})
|
||||
|
||||
await Promise.all([
|
||||
fetchWithCache(
|
||||
'https://fetchwithcache.metamask.io/foo',
|
||||
{},
|
||||
{ cacheRefreshTime: 123 },
|
||||
),
|
||||
fetchWithCache(
|
||||
'https://fetchwithcache.metamask.io/bar',
|
||||
{},
|
||||
{ cacheRefreshTime: 123 },
|
||||
),
|
||||
])
|
||||
|
||||
assert.deepStrictEqual(
|
||||
testCache['cachedFetch:https://fetchwithcache.metamask.io/foo']
|
||||
.cachedResponse,
|
||||
{ average: 9 },
|
||||
)
|
||||
assert.deepStrictEqual(
|
||||
testCache['cachedFetch:https://fetchwithcache.metamask.io/bar']
|
||||
.cachedResponse,
|
||||
{ average: 9 },
|
||||
)
|
||||
})
|
||||
})
|
||||
|
@ -105,7 +105,10 @@ export default class MetaMetricsOptIn extends Component {
|
||||
await setParticipateInMetaMetrics(false)
|
||||
|
||||
try {
|
||||
if (participateInMetaMetrics === true) {
|
||||
if (
|
||||
participateInMetaMetrics === null ||
|
||||
participateInMetaMetrics === true
|
||||
) {
|
||||
await metricsEvent({
|
||||
eventOpts: {
|
||||
category: 'Onboarding',
|
||||
@ -128,7 +131,10 @@ export default class MetaMetricsOptIn extends Component {
|
||||
)
|
||||
try {
|
||||
const metrics = []
|
||||
if (participateInMetaMetrics === false) {
|
||||
if (
|
||||
participateInMetaMetrics === null ||
|
||||
participateInMetaMetrics === false
|
||||
) {
|
||||
metrics.push(
|
||||
metricsEvent({
|
||||
eventOpts: {
|
||||
|
@ -156,7 +156,7 @@ export default class SendTransactionScreen extends Component {
|
||||
|
||||
if (sendTokenAddress && prevTokenAddress !== sendTokenAddress) {
|
||||
this.updateSendToken()
|
||||
this.validate(sendTokenAddress)
|
||||
this.validate(this.state.query)
|
||||
updateGas = true
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
import React, { useState, useEffect, useContext, useRef } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import { Duration } from 'luxon'
|
||||
import { I18nContext } from '../../../contexts/i18n'
|
||||
import InfoTooltip from '../../../components/ui/info-tooltip'
|
||||
|
||||
const TIMER_BASE = 60000
|
||||
import { getSwapsQuoteRefreshTime } from '../../../ducks/swaps/swaps'
|
||||
|
||||
// Return the mm:ss start time of the countdown timer.
|
||||
// If time has elapsed between `timeStarted` the time current time,
|
||||
@ -31,7 +31,7 @@ function timeBelowWarningTime(timer, warningTime) {
|
||||
export default function CountdownTimer({
|
||||
timeStarted,
|
||||
timeOnly,
|
||||
timerBase = TIMER_BASE,
|
||||
timerBase,
|
||||
warningTime,
|
||||
labelKey,
|
||||
infoTooltipLabelKey,
|
||||
@ -40,9 +40,12 @@ export default function CountdownTimer({
|
||||
const intervalRef = useRef()
|
||||
const initialTimeStartedRef = useRef()
|
||||
|
||||
const swapsQuoteRefreshTime = useSelector(getSwapsQuoteRefreshTime)
|
||||
const timerStart = Number(timerBase) || swapsQuoteRefreshTime
|
||||
|
||||
const [currentTime, setCurrentTime] = useState(() => Date.now())
|
||||
const [timer, setTimer] = useState(() =>
|
||||
getNewTimer(currentTime, timeStarted, timerBase),
|
||||
getNewTimer(currentTime, timeStarted, timerStart),
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
@ -67,14 +70,14 @@ export default function CountdownTimer({
|
||||
initialTimeStartedRef.current = timeStarted
|
||||
const newCurrentTime = Date.now()
|
||||
setCurrentTime(newCurrentTime)
|
||||
setTimer(getNewTimer(newCurrentTime, timeStarted, timerBase))
|
||||
setTimer(getNewTimer(newCurrentTime, timeStarted, timerStart))
|
||||
|
||||
clearInterval(intervalRef.current)
|
||||
intervalRef.current = setInterval(() => {
|
||||
setTimer(decreaseTimerByOne)
|
||||
}, 1000)
|
||||
}
|
||||
}, [timeStarted, timer, timerBase])
|
||||
}, [timeStarted, timer, timerStart])
|
||||
|
||||
const formattedTimer = Duration.fromMillis(timer).toFormat('m:ss')
|
||||
let time
|
||||
|
@ -24,20 +24,24 @@ const TOKEN_TRANSFER_LOG_TOPIC_HASH =
|
||||
|
||||
const CACHE_REFRESH_ONE_HOUR = 3600000
|
||||
|
||||
const METASWAP_API_HOST = 'https://api.metaswap.codefi.network'
|
||||
|
||||
const getBaseApi = function (type) {
|
||||
switch (type) {
|
||||
case 'trade':
|
||||
return `https://api.metaswap.codefi.network/trades?`
|
||||
return `${METASWAP_API_HOST}/trades?`
|
||||
case 'tokens':
|
||||
return `https://api.metaswap.codefi.network/tokens`
|
||||
return `${METASWAP_API_HOST}/tokens`
|
||||
case 'topAssets':
|
||||
return `https://api.metaswap.codefi.network/topAssets`
|
||||
return `${METASWAP_API_HOST}/topAssets`
|
||||
case 'featureFlag':
|
||||
return `https://api.metaswap.codefi.network/featureFlag`
|
||||
return `${METASWAP_API_HOST}/featureFlag`
|
||||
case 'aggregatorMetadata':
|
||||
return `https://api.metaswap.codefi.network/aggregatorMetadata`
|
||||
return `${METASWAP_API_HOST}/aggregatorMetadata`
|
||||
case 'gasPrices':
|
||||
return `https://api.metaswap.codefi.network/gasPrices`
|
||||
return `${METASWAP_API_HOST}/gasPrices`
|
||||
case 'refreshTime':
|
||||
return `${METASWAP_API_HOST}/quoteRefreshRate`
|
||||
default:
|
||||
throw new Error('getBaseApi requires an api call type')
|
||||
}
|
||||
@ -112,6 +116,11 @@ const QUOTE_VALIDATORS = [
|
||||
property: 'maxGas',
|
||||
type: 'number',
|
||||
},
|
||||
{
|
||||
property: 'gasEstimate',
|
||||
type: 'number|undefined',
|
||||
validator: (gasEstimate) => gasEstimate === undefined || gasEstimate > 0,
|
||||
},
|
||||
]
|
||||
|
||||
const TOKEN_VALIDATORS = [
|
||||
@ -323,6 +332,23 @@ export async function fetchSwapsFeatureLiveness() {
|
||||
return status?.active
|
||||
}
|
||||
|
||||
export async function fetchSwapsQuoteRefreshTime() {
|
||||
const response = await fetchWithCache(
|
||||
getBaseApi('refreshTime'),
|
||||
{ method: 'GET' },
|
||||
{ cacheRefreshTime: 600000 },
|
||||
)
|
||||
|
||||
// We presently use milliseconds in the UI
|
||||
if (typeof response?.seconds === 'number' && response.seconds > 0) {
|
||||
return response.seconds * 1000
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`MetaMask - refreshTime provided invalid response: ${response}`,
|
||||
)
|
||||
}
|
||||
|
||||
export async function fetchTokenPrice(address) {
|
||||
const query = `contract_addresses=${address}&vs_currencies=eth`
|
||||
|
||||
|
@ -12,6 +12,7 @@ import { useSwapsEthToken } from '../../../hooks/useSwapsEthToken'
|
||||
import { MetaMetricsContext } from '../../../contexts/metametrics.new'
|
||||
import FeeCard from '../fee-card'
|
||||
import {
|
||||
FALLBACK_GAS_MULTIPLIER,
|
||||
getQuotes,
|
||||
getSelectedQuote,
|
||||
getApproveTxParams,
|
||||
@ -27,6 +28,7 @@ import {
|
||||
signAndSendTransactions,
|
||||
getBackgroundSwapRouteState,
|
||||
swapsQuoteSelected,
|
||||
getSwapsQuoteRefreshTime,
|
||||
} from '../../../ducks/swaps/swaps'
|
||||
import {
|
||||
conversionRateSelector,
|
||||
@ -57,7 +59,6 @@ import {
|
||||
} from '../../../helpers/utils/token-util'
|
||||
import {
|
||||
decimalToHex,
|
||||
hexMax,
|
||||
hexToDecimal,
|
||||
getValueFromWeiHex,
|
||||
} from '../../../helpers/utils/conversions.util'
|
||||
@ -115,6 +116,7 @@ export default function ViewQuote() {
|
||||
const topQuote = useSelector(getTopQuote)
|
||||
const usedQuote = selectedQuote || topQuote
|
||||
const tradeValue = usedQuote?.trade?.value ?? '0x0'
|
||||
const swapsQuoteRefreshTime = useSelector(getSwapsQuoteRefreshTime)
|
||||
|
||||
const { isBestQuote } = usedQuote
|
||||
const fetchParamsSourceToken = fetchParams?.sourceToken
|
||||
@ -123,18 +125,16 @@ export default function ViewQuote() {
|
||||
usedQuote?.gasEstimateWithRefund ||
|
||||
`0x${decimalToHex(usedQuote?.averageGas || 0)}`
|
||||
|
||||
const gasLimitForMax =
|
||||
usedQuote?.gasEstimate || `0x${decimalToHex(usedQuote?.averageGas || 0)}`
|
||||
const gasLimitForMax = usedQuote?.gasEstimate || `0x0`
|
||||
|
||||
const usedGasLimitWithMultiplier = new BigNumber(gasLimitForMax, 16)
|
||||
.times(1.4, 10)
|
||||
.times(usedQuote?.gasMultiplier || FALLBACK_GAS_MULTIPLIER, 10)
|
||||
.round(0)
|
||||
.toString(16)
|
||||
|
||||
const nonCustomMaxGasLimit = hexMax(
|
||||
`0x${decimalToHex(usedQuote?.maxGas || 0)}`,
|
||||
usedGasLimitWithMultiplier,
|
||||
)
|
||||
const nonCustomMaxGasLimit = usedQuote?.gasEstimate
|
||||
? usedGasLimitWithMultiplier
|
||||
: `0x${decimalToHex(usedQuote?.maxGas || 0)}`
|
||||
const maxGasLimit = customMaxGas || nonCustomMaxGasLimit
|
||||
|
||||
const gasTotalInWeiHex = calcGasTotal(maxGasLimit, gasPrice)
|
||||
@ -265,14 +265,23 @@ export default function ViewQuote() {
|
||||
useEffect(() => {
|
||||
const currentTime = Date.now()
|
||||
const timeSinceLastFetched = currentTime - quotesLastFetched
|
||||
if (timeSinceLastFetched > 60000 && !dispatchedSafeRefetch) {
|
||||
if (
|
||||
timeSinceLastFetched > swapsQuoteRefreshTime &&
|
||||
!dispatchedSafeRefetch
|
||||
) {
|
||||
setDispatchedSafeRefetch(true)
|
||||
dispatch(safeRefetchQuotes())
|
||||
} else if (timeSinceLastFetched > 60000) {
|
||||
} else if (timeSinceLastFetched > swapsQuoteRefreshTime) {
|
||||
dispatch(setSwapsErrorKey(QUOTES_EXPIRED_ERROR))
|
||||
history.push(SWAPS_ERROR_ROUTE)
|
||||
}
|
||||
}, [quotesLastFetched, dispatchedSafeRefetch, dispatch, history])
|
||||
}, [
|
||||
quotesLastFetched,
|
||||
dispatchedSafeRefetch,
|
||||
dispatch,
|
||||
history,
|
||||
swapsQuoteRefreshTime,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
if (!originalApproveAmount && approveAmount) {
|
||||
|
12
yarn.lock
12
yarn.lock
@ -24922,9 +24922,9 @@ tunnel@^0.0.6:
|
||||
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
|
||||
|
||||
tweetnacl-util@^0.15.0:
|
||||
version "0.15.0"
|
||||
resolved "https://registry.yarnpkg.com/tweetnacl-util/-/tweetnacl-util-0.15.0.tgz#4576c1cee5e2d63d207fee52f1ba02819480bc75"
|
||||
integrity sha1-RXbBzuXi1j0gf+5S8boCgZSAvHU=
|
||||
version "0.15.1"
|
||||
resolved "https://registry.yarnpkg.com/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz#b80fcdb5c97bcc508be18c44a4be50f022eea00b"
|
||||
integrity sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==
|
||||
|
||||
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
|
||||
version "0.14.5"
|
||||
@ -24932,9 +24932,9 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0:
|
||||
integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
|
||||
|
||||
tweetnacl@^1.0.0, tweetnacl@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.1.tgz#2594d42da73cd036bd0d2a54683dd35a6b55ca17"
|
||||
integrity sha512-kcoMoKTPYnoeS50tzoqjPY3Uv9axeuuFAZY9M/9zFnhoVvRfxz9K29IMPD7jGmt2c8SW7i3gT9WqDl2+nV7p4A==
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596"
|
||||
integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==
|
||||
|
||||
type-check@^0.4.0, type-check@~0.4.0:
|
||||
version "0.4.0"
|
||||
|
Loading…
Reference in New Issue
Block a user