aboutsummaryrefslogtreecommitdiffstats
path: root/ui/app/helpers/utils
diff options
context:
space:
mode:
Diffstat (limited to 'ui/app/helpers/utils')
-rw-r--r--ui/app/helpers/utils/fetch-with-cache.js28
-rw-r--r--ui/app/helpers/utils/fetch.js25
-rw-r--r--ui/app/helpers/utils/fetch.test.js54
-rw-r--r--ui/app/helpers/utils/transactions.util.js99
4 files changed, 163 insertions, 43 deletions
diff --git a/ui/app/helpers/utils/fetch-with-cache.js b/ui/app/helpers/utils/fetch-with-cache.js
new file mode 100644
index 000000000..ac641c3c4
--- /dev/null
+++ b/ui/app/helpers/utils/fetch-with-cache.js
@@ -0,0 +1,28 @@
+import {
+ loadLocalStorageData,
+ saveLocalStorageData,
+} from '../../../lib/local-storage-helpers'
+import http from './fetch'
+
+const fetch = http({
+ timeout: 30000,
+})
+
+export default function fetchWithCache (url, opts, cacheRefreshTime = 360000) {
+ const currentTime = Date.now()
+ const cachedFetch = loadLocalStorageData('cachedFetch') || {}
+ const { cachedUrl, cachedTime } = cachedFetch[url] || {}
+ if (cachedUrl && currentTime - cachedTime < cacheRefreshTime) {
+ return cachedFetch[url]
+ } else {
+ cachedFetch[url] = { cachedUrl: url, cachedTime: currentTime }
+ saveLocalStorageData(cachedFetch, 'cachedFetch')
+ return fetch(url, {
+ referrerPolicy: 'no-referrer-when-downgrade',
+ body: null,
+ method: 'GET',
+ mode: 'cors',
+ ...opts,
+ })
+ }
+}
diff --git a/ui/app/helpers/utils/fetch.js b/ui/app/helpers/utils/fetch.js
new file mode 100644
index 000000000..7bb483818
--- /dev/null
+++ b/ui/app/helpers/utils/fetch.js
@@ -0,0 +1,25 @@
+/* global AbortController */
+
+export default function ({ timeout = 120000 } = {}) {
+ return function _fetch (url, opts) {
+ return new Promise(async (resolve, reject) => {
+ const abortController = new AbortController()
+ const abortSignal = abortController.signal
+ const f = fetch(url, {
+ ...opts,
+ signal: abortSignal,
+ })
+
+ const timer = setTimeout(() => abortController.abort(), timeout)
+
+ try {
+ const res = await f
+ clearTimeout(timer)
+ return resolve(res)
+ } catch (e) {
+ clearTimeout(timer)
+ return reject(e)
+ }
+ })
+ }
+}
diff --git a/ui/app/helpers/utils/fetch.test.js b/ui/app/helpers/utils/fetch.test.js
new file mode 100644
index 000000000..12724525a
--- /dev/null
+++ b/ui/app/helpers/utils/fetch.test.js
@@ -0,0 +1,54 @@
+import assert from 'assert'
+import nock from 'nock'
+
+import http from './fetch'
+
+describe('custom fetch fn', () => {
+ it('fetches a url', async () => {
+ nock('https://api.infura.io')
+ .get('/money')
+ .reply(200, '{"hodl": false}')
+
+ const fetch = http()
+ const response = await (await fetch('https://api.infura.io/money')).json()
+ assert.deepEqual(response, {
+ hodl: false,
+ })
+ })
+
+ it('throws when the request hits a custom timeout', async () => {
+ nock('https://api.infura.io')
+ .get('/moon')
+ .delay(2000)
+ .reply(200, '{"moon": "2012-12-21T11:11:11Z"}')
+
+ const fetch = http({
+ timeout: 123,
+ })
+
+ try {
+ await fetch('https://api.infura.io/moon').then(r => r.json())
+ assert.fail('Request should throw')
+ } catch (e) {
+ assert.ok(e)
+ }
+ })
+
+ it('should abort the request when the custom timeout is hit', async () => {
+ nock('https://api.infura.io')
+ .get('/moon')
+ .delay(2000)
+ .reply(200, '{"moon": "2012-12-21T11:11:11Z"}')
+
+ const fetch = http({
+ timeout: 123,
+ })
+
+ try {
+ await fetch('https://api.infura.io/moon').then(r => r.json())
+ assert.fail('Request should be aborted')
+ } catch (e) {
+ assert.deepEqual(e.message, 'Aborted')
+ }
+ })
+})
diff --git a/ui/app/helpers/utils/transactions.util.js b/ui/app/helpers/utils/transactions.util.js
index 99ccc3478..c84053ec7 100644
--- a/ui/app/helpers/utils/transactions.util.js
+++ b/ui/app/helpers/utils/transactions.util.js
@@ -7,7 +7,7 @@ import {
TRANSACTION_STATUS_CONFIRMED,
} from '../../../../app/scripts/controllers/transactions/enums'
import prefixForNetwork from '../../../lib/etherscan-prefix-for-network'
-
+import fetchWithCache from './fetch-with-cache'
import {
TOKEN_METHOD_TRANSFER,
@@ -32,39 +32,57 @@ export function getTokenData (data = '') {
return abiDecoder.decodeMethod(data)
}
+async function getMethodFrom4Byte (fourBytePrefix) {
+ const fourByteResponse = (await fetchWithCache(`https://www.4byte.directory/api/v1/signatures/?hex_signature=${fourBytePrefix}`, {
+ referrerPolicy: 'no-referrer-when-downgrade',
+ body: null,
+ method: 'GET',
+ mode: 'cors',
+ }))
+
+ const fourByteJSON = await fourByteResponse.json()
+
+ if (fourByteJSON.count === 1) {
+ return fourByteJSON.results[0].text_signature
+ } else {
+ return null
+ }
+}
+
const registry = new MethodRegistry({ provider: global.ethereumProvider })
/**
- * Attempts to return the method data from the MethodRegistry library, if the method exists in the
- * registry. Otherwise, returns an empty object.
- * @param {string} data - The hex data (@code txParams.data) of a transaction
+ * Attempts to return the method data from the MethodRegistry library, the message registry library and the token abi, in that order of preference
+ * @param {string} fourBytePrefix - The prefix from the method code associated with the data
* @returns {Object}
*/
- export async function getMethodData (data = '') {
- const prefixedData = ethUtil.addHexPrefix(data)
- const fourBytePrefix = prefixedData.slice(0, 10)
-
- try {
- const sig = await registry.lookup(fourBytePrefix)
-
- if (!sig) {
- return {}
- }
-
- const parsedResult = registry.parse(sig)
-
- return {
- name: parsedResult.name,
- params: parsedResult.args,
- }
- } catch (error) {
- log.error(error)
- const contractData = getTokenData(data)
- const { name } = contractData || {}
- return { name }
+export async function getMethodDataAsync (fourBytePrefix) {
+ try {
+ const fourByteSig = getMethodFrom4Byte(fourBytePrefix).catch((e) => {
+ log.error(e)
+ return null
+ })
+
+ let sig = await registry.lookup(fourBytePrefix)
+
+ if (!sig) {
+ sig = await fourByteSig
}
+ if (!sig) {
+ return {}
+ }
+
+ const parsedResult = registry.parse(sig)
+ return {
+ name: parsedResult.name,
+ params: parsedResult.args,
+ }
+ } catch (error) {
+ log.error(error)
+ return {}
+ }
}
export function isConfirmDeployContract (txData = {}) {
@@ -87,11 +105,10 @@ export function getFourBytePrefix (data = '') {
/**
* Returns the action of a transaction as a key to be passed into the translator.
* @param {Object} transaction - txData object
- * @param {Object} methodData - Data returned from eth-method-registry
* @returns {string|undefined}
*/
-export async function getTransactionActionKey (transaction, methodData) {
- const { txParams: { data, to } = {}, msgParams, type } = transaction
+export function getTransactionActionKey (transaction) {
+ const { msgParams, type, transactionCategory } = transaction
if (type === 'cancel') {
return CANCEL_ATTEMPT_ACTION_KEY
@@ -105,27 +122,23 @@ export async function getTransactionActionKey (transaction, methodData) {
return DEPLOY_CONTRACT_ACTION_KEY
}
- if (data) {
- const toSmartContract = await isSmartContractAddress(to)
-
- if (!toSmartContract) {
- return SEND_ETHER_ACTION_KEY
- }
-
- const { name } = methodData
- const methodName = name && name.toLowerCase()
-
- if (!methodName) {
- return CONTRACT_INTERACTION_KEY
- }
+ const isTokenAction = [
+ TOKEN_METHOD_TRANSFER,
+ TOKEN_METHOD_APPROVE,
+ TOKEN_METHOD_TRANSFER_FROM,
+ ].find(actionName => actionName === transactionCategory)
+ const isNonTokenSmartContract = transactionCategory === CONTRACT_INTERACTION_KEY
- switch (methodName) {
+ if (isTokenAction || isNonTokenSmartContract) {
+ switch (transactionCategory) {
case TOKEN_METHOD_TRANSFER:
return SEND_TOKEN_ACTION_KEY
case TOKEN_METHOD_APPROVE:
return APPROVE_ACTION_KEY
case TOKEN_METHOD_TRANSFER_FROM:
return TRANSFER_FROM_ACTION_KEY
+ case CONTRACT_INTERACTION_KEY:
+ return CONTRACT_INTERACTION_KEY
default:
return undefined
}