commit
9a658ee53d
@ -0,0 +1,73 @@ |
||||
const ObservableStore = require('obs-store') |
||||
const extend = require('xtend') |
||||
|
||||
class AppStateController { |
||||
/** |
||||
* @constructor |
||||
* @param opts |
||||
*/ |
||||
constructor (opts = {}) { |
||||
const {initState, onInactiveTimeout, preferencesStore} = opts |
||||
const {preferences} = preferencesStore.getState() |
||||
|
||||
this.onInactiveTimeout = onInactiveTimeout || (() => {}) |
||||
this.store = new ObservableStore(extend({ |
||||
timeoutMinutes: 0, |
||||
}, initState)) |
||||
this.timer = null |
||||
|
||||
preferencesStore.subscribe(state => { |
||||
this._setInactiveTimeout(state.preferences.autoLogoutTimeLimit) |
||||
}) |
||||
|
||||
this._setInactiveTimeout(preferences.autoLogoutTimeLimit) |
||||
} |
||||
|
||||
/** |
||||
* Sets the last active time to the current time |
||||
* @return {void} |
||||
*/ |
||||
setLastActiveTime () { |
||||
this._resetTimer() |
||||
} |
||||
|
||||
/** |
||||
* Sets the inactive timeout for the app |
||||
* @param {number} timeoutMinutes the inactive timeout in minutes |
||||
* @return {void} |
||||
* @private |
||||
*/ |
||||
_setInactiveTimeout (timeoutMinutes) { |
||||
this.store.putState({ |
||||
timeoutMinutes, |
||||
}) |
||||
|
||||
this._resetTimer() |
||||
} |
||||
|
||||
/** |
||||
* Resets the internal inactive timer |
||||
* |
||||
* If the {@code timeoutMinutes} state is falsy (i.e., zero) then a new |
||||
* timer will not be created. |
||||
* |
||||
* @return {void} |
||||
* @private |
||||
*/ |
||||
_resetTimer () { |
||||
const {timeoutMinutes} = this.store.getState() |
||||
|
||||
if (this.timer) { |
||||
clearTimeout(this.timer) |
||||
} |
||||
|
||||
if (!timeoutMinutes) { |
||||
return |
||||
} |
||||
|
||||
this.timer = setTimeout(() => this.onInactiveTimeout(), timeoutMinutes * 60 * 1000) |
||||
} |
||||
} |
||||
|
||||
module.exports = AppStateController |
||||
|
@ -1,136 +0,0 @@ |
||||
const ObservableStore = require('obs-store') |
||||
const extend = require('xtend') |
||||
const PhishingDetector = require('eth-phishing-detect/src/detector') |
||||
const log = require('loglevel') |
||||
|
||||
// compute phishing lists
|
||||
const PHISHING_DETECTION_CONFIG = require('eth-phishing-detect/src/config.json') |
||||
// every four minutes
|
||||
const POLLING_INTERVAL = 4 * 60 * 1000 |
||||
|
||||
class BlacklistController { |
||||
|
||||
/** |
||||
* Responsible for polling for and storing an up to date 'eth-phishing-detect' config.json file, while |
||||
* exposing a method that can check whether a given url is a phishing attempt. The 'eth-phishing-detect' |
||||
* config.json file contains a fuzzylist, whitelist and blacklist. |
||||
* |
||||
* |
||||
* @typedef {Object} BlacklistController |
||||
* @param {object} opts Overrides the defaults for the initial state of this.store |
||||
* @property {object} store The the store of the current phishing config |
||||
* @property {object} store.phishing Contains fuzzylist, whitelist and blacklist arrays. @see |
||||
* {@link https://github.com/MetaMask/eth-phishing-detect/blob/master/src/config.json}
|
||||
* @property {object} _phishingDetector The PhishingDetector instantiated by passing store.phishing to |
||||
* PhishingDetector. |
||||
* @property {object} _phishingUpdateIntervalRef Id of the interval created to periodically update the blacklist |
||||
* |
||||
*/ |
||||
constructor (opts = {}) { |
||||
const initState = extend({ |
||||
phishing: PHISHING_DETECTION_CONFIG, |
||||
whitelist: [], |
||||
}, opts.initState) |
||||
this.store = new ObservableStore(initState) |
||||
// phishing detector
|
||||
this._phishingDetector = null |
||||
this._setupPhishingDetector(initState.phishing) |
||||
// polling references
|
||||
this._phishingUpdateIntervalRef = null |
||||
} |
||||
|
||||
/** |
||||
* Adds the given hostname to the runtime whitelist |
||||
* @param {string} hostname the hostname to whitelist |
||||
*/ |
||||
whitelistDomain (hostname) { |
||||
if (!hostname) { |
||||
return |
||||
} |
||||
|
||||
const { whitelist } = this.store.getState() |
||||
this.store.updateState({ |
||||
whitelist: [...new Set([hostname, ...whitelist])], |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* Given a url, returns the result of checking if that url is in the store.phishing blacklist |
||||
* |
||||
* @param {string} hostname The hostname portion of a url; the one that will be checked against the white and |
||||
* blacklists of store.phishing |
||||
* @returns {boolean} Whether or not the passed hostname is on our phishing blacklist |
||||
* |
||||
*/ |
||||
checkForPhishing (hostname) { |
||||
if (!hostname) return false |
||||
|
||||
const { whitelist } = this.store.getState() |
||||
if (whitelist.some((e) => e === hostname)) { |
||||
return false |
||||
} |
||||
|
||||
const { result } = this._phishingDetector.check(hostname) |
||||
return result |
||||
} |
||||
|
||||
/** |
||||
* Queries `https://api.infura.io/v2/blacklist` for an updated blacklist config. This is passed to this._phishingDetector |
||||
* to update our phishing detector instance, and is updated in the store. The new phishing config is returned |
||||
* |
||||
* |
||||
* @returns {Promise<object>} Promises the updated blacklist config for the phishingDetector |
||||
* |
||||
*/ |
||||
async updatePhishingList () { |
||||
// make request
|
||||
let response |
||||
try { |
||||
response = await fetch('https://api.infura.io/v2/blacklist') |
||||
} catch (err) { |
||||
log.error(new Error(`BlacklistController - failed to fetch blacklist:\n${err.stack}`)) |
||||
return |
||||
} |
||||
// parse response
|
||||
let rawResponse |
||||
let phishing |
||||
try { |
||||
const rawResponse = await response.text() |
||||
phishing = JSON.parse(rawResponse) |
||||
} catch (err) { |
||||
log.error(new Error(`BlacklistController - failed to parse blacklist:\n${rawResponse}`)) |
||||
return |
||||
} |
||||
// update current blacklist
|
||||
this.store.updateState({ phishing }) |
||||
this._setupPhishingDetector(phishing) |
||||
return phishing |
||||
} |
||||
|
||||
/** |
||||
* Initiates the updating of the local blacklist at a set interval. The update is done via this.updatePhishingList(). |
||||
* Also, this method store a reference to that interval at this._phishingUpdateIntervalRef |
||||
* |
||||
*/ |
||||
scheduleUpdates () { |
||||
if (this._phishingUpdateIntervalRef) return |
||||
this.updatePhishingList() |
||||
this._phishingUpdateIntervalRef = setInterval(() => { |
||||
this.updatePhishingList() |
||||
}, POLLING_INTERVAL) |
||||
} |
||||
|
||||
/** |
||||
* Sets this._phishingDetector to a new PhishingDetector instance. |
||||
* @see {@link https://github.com/MetaMask/eth-phishing-detect}
|
||||
* |
||||
* @private |
||||
* @param {object} config A config object like that found at {@link https://github.com/MetaMask/eth-phishing-detect/blob/master/src/config.json}
|
||||
* |
||||
*/ |
||||
_setupPhishingDetector (config) { |
||||
this._phishingDetector = new PhishingDetector(config) |
||||
} |
||||
} |
||||
|
||||
module.exports = BlacklistController |
@ -1,19 +0,0 @@ |
||||
const BlockTracker = require('eth-block-tracker') |
||||
|
||||
/** |
||||
* Creates a block tracker that sends platform events on success and failure |
||||
*/ |
||||
module.exports = function createBlockTracker (args, platform) { |
||||
const blockTracker = new BlockTracker(args) |
||||
blockTracker.on('latest', () => { |
||||
if (platform && platform.sendMessage) { |
||||
platform.sendMessage({ action: 'ethereum-ping-success' }) |
||||
} |
||||
}) |
||||
blockTracker.on('error', () => { |
||||
if (platform && platform.sendMessage) { |
||||
platform.sendMessage({ action: 'ethereum-ping-error' }) |
||||
} |
||||
}) |
||||
return blockTracker |
||||
} |
@ -1,180 +0,0 @@ |
||||
const ObservableStore = require('obs-store') |
||||
const extend = require('xtend') |
||||
const log = require('loglevel') |
||||
|
||||
// every three seconds when an incomplete tx is waiting
|
||||
const POLLING_INTERVAL = 3000 |
||||
|
||||
class ShapeshiftController { |
||||
|
||||
/** |
||||
* Controller responsible for managing the list of shapeshift transactions. On construction, it initiates a poll |
||||
* that queries a shapeshift.io API for updates to any pending shapeshift transactions |
||||
* |
||||
* @typedef {Object} ShapeshiftController |
||||
* @param {object} opts Overrides the defaults for the initial state of this.store |
||||
* @property {array} opts.initState initializes the the state of the ShapeshiftController. Can contain an |
||||
* shapeShiftTxList array. |
||||
* @property {array} shapeShiftTxList An array of ShapeShiftTx objects |
||||
* |
||||
*/ |
||||
constructor (opts = {}) { |
||||
const initState = extend({ |
||||
shapeShiftTxList: [], |
||||
}, opts.initState) |
||||
this.store = new ObservableStore(initState) |
||||
this.pollForUpdates() |
||||
} |
||||
|
||||
/** |
||||
* Represents, and contains data about, a single shapeshift transaction. |
||||
* @typedef {Object} ShapeShiftTx |
||||
* @property {string} depositAddress - An address at which to send a crypto deposit, so that eth can be sent to the |
||||
* user's Metamask account |
||||
* @property {string} depositType - An abbreviation of the type of crypto currency to be deposited. |
||||
* @property {string} key - The 'shapeshift' key differentiates this from other types of txs in Metamask |
||||
* @property {number} time - The time at which the tx was created |
||||
* @property {object} response - Initiated as an empty object, which will be replaced by a Response object. @see {@link |
||||
* https://developer.mozilla.org/en-US/docs/Web/API/Response}
|
||||
*/ |
||||
|
||||
//
|
||||
// PUBLIC METHODS
|
||||
//
|
||||
|
||||
/** |
||||
* A getter for the shapeShiftTxList property |
||||
* |
||||
* @returns {array<ShapeShiftTx>} |
||||
* |
||||
*/ |
||||
getShapeShiftTxList () { |
||||
const shapeShiftTxList = this.store.getState().shapeShiftTxList |
||||
return shapeShiftTxList |
||||
} |
||||
|
||||
/** |
||||
* A getter for all ShapeShiftTx in the shapeShiftTxList that have not successfully completed a deposit. |
||||
* |
||||
* @returns {array<ShapeShiftTx>} Only includes ShapeShiftTx which has a response property with a status !== complete |
||||
* |
||||
*/ |
||||
getPendingTxs () { |
||||
const txs = this.getShapeShiftTxList() |
||||
const pending = txs.filter(tx => tx.response && tx.response.status !== 'complete') |
||||
return pending |
||||
} |
||||
|
||||
/** |
||||
* A poll that exists as long as there are pending transactions. Each call attempts to update the data of any |
||||
* pendingTxs, and then calls itself again. If there are no pending txs, the recursive call is not made and |
||||
* the polling stops. |
||||
* |
||||
* this.updateTx is used to attempt the update to the pendingTxs in the ShapeShiftTxList, and that updated data |
||||
* is saved with saveTx. |
||||
* |
||||
*/ |
||||
pollForUpdates () { |
||||
const pendingTxs = this.getPendingTxs() |
||||
|
||||
if (pendingTxs.length === 0) { |
||||
return |
||||
} |
||||
|
||||
Promise.all(pendingTxs.map((tx) => { |
||||
return this.updateTx(tx) |
||||
})) |
||||
.then((results) => { |
||||
results.forEach(tx => this.saveTx(tx)) |
||||
this.timeout = setTimeout(this.pollForUpdates.bind(this), POLLING_INTERVAL) |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* Attempts to update a ShapeShiftTx with data from a shapeshift.io API. Both the response and time properties |
||||
* can be updated. The response property is updated with every call, but the time property is only updated when |
||||
* the response status updates to 'complete'. This will occur once the user makes a deposit as the ShapeShiftTx |
||||
* depositAddress |
||||
* |
||||
* @param {ShapeShiftTx} tx The tx to update |
||||
* |
||||
*/ |
||||
async updateTx (tx) { |
||||
try { |
||||
const url = `https://shapeshift.io/txStat/${tx.depositAddress}` |
||||
const response = await fetch(url) |
||||
const json = await response.json() |
||||
tx.response = json |
||||
if (tx.response.status === 'complete') { |
||||
tx.time = new Date().getTime() |
||||
} |
||||
return tx |
||||
} catch (err) { |
||||
log.warn(err) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Saves an updated to a ShapeShiftTx in the shapeShiftTxList. If the passed ShapeShiftTx is not in the |
||||
* shapeShiftTxList, nothing happens. |
||||
* |
||||
* @param {ShapeShiftTx} tx The updated tx to save, if it exists in the current shapeShiftTxList |
||||
* |
||||
*/ |
||||
saveTx (tx) { |
||||
const { shapeShiftTxList } = this.store.getState() |
||||
const index = shapeShiftTxList.indexOf(tx) |
||||
if (index !== -1) { |
||||
shapeShiftTxList[index] = tx |
||||
this.store.updateState({ shapeShiftTxList }) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Removes a ShapeShiftTx from the shapeShiftTxList |
||||
* |
||||
* @param {ShapeShiftTx} tx The tx to remove |
||||
* |
||||
*/ |
||||
removeShapeShiftTx (tx) { |
||||
const { shapeShiftTxList } = this.store.getState() |
||||
const index = shapeShiftTxList.indexOf(index) |
||||
if (index !== -1) { |
||||
shapeShiftTxList.splice(index, 1) |
||||
} |
||||
this.updateState({ shapeShiftTxList }) |
||||
} |
||||
|
||||
/** |
||||
* Creates a new ShapeShiftTx, adds it to the shapeShiftTxList, and initiates a new poll for updates of pending txs |
||||
* |
||||
* @param {string} depositAddress - An address at which to send a crypto deposit, so that eth can be sent to the |
||||
* user's Metamask account |
||||
* @param {string} depositType - An abbreviation of the type of crypto currency to be deposited. |
||||
* |
||||
*/ |
||||
createShapeShiftTx (depositAddress, depositType) { |
||||
const state = this.store.getState() |
||||
let { shapeShiftTxList } = state |
||||
|
||||
var shapeShiftTx = { |
||||
depositAddress, |
||||
depositType, |
||||
key: 'shapeshift', |
||||
time: new Date().getTime(), |
||||
response: {}, |
||||
} |
||||
|
||||
if (!shapeShiftTxList) { |
||||
shapeShiftTxList = [shapeShiftTx] |
||||
} else { |
||||
shapeShiftTxList.push(shapeShiftTx) |
||||
} |
||||
|
||||
this.store.updateState({ shapeShiftTxList }) |
||||
this.pollForUpdates() |
||||
} |
||||
|
||||
} |
||||
|
||||
module.exports = ShapeshiftController |
@ -1,161 +0,0 @@ |
||||
const EthQuery = require('ethjs-query') |
||||
const assert = require('assert') |
||||
const Mutex = require('await-semaphore').Mutex |
||||
/** |
||||
@param opts {Object} |
||||
@param {Object} opts.provider a ethereum provider |
||||
@param {Function} opts.getPendingTransactions a function that returns an array of txMeta |
||||
whosee status is `submitted` |
||||
@param {Function} opts.getConfirmedTransactions a function that returns an array of txMeta |
||||
whose status is `confirmed` |
||||
@class |
||||
*/ |
||||
class NonceTracker { |
||||
|
||||
constructor ({ provider, blockTracker, getPendingTransactions, getConfirmedTransactions }) { |
||||
this.provider = provider |
||||
this.blockTracker = blockTracker |
||||
this.ethQuery = new EthQuery(provider) |
||||
this.getPendingTransactions = getPendingTransactions |
||||
this.getConfirmedTransactions = getConfirmedTransactions |
||||
this.lockMap = {} |
||||
} |
||||
|
||||
/** |
||||
@returns {Promise<Object>} with the key releaseLock (the gloabl mutex) |
||||
*/ |
||||
async getGlobalLock () { |
||||
const globalMutex = this._lookupMutex('global') |
||||
// await global mutex free
|
||||
const releaseLock = await globalMutex.acquire() |
||||
return { releaseLock } |
||||
} |
||||
|
||||
/** |
||||
* @typedef NonceDetails |
||||
* @property {number} highestLocallyConfirmed - A hex string of the highest nonce on a confirmed transaction. |
||||
* @property {number} nextNetworkNonce - The next nonce suggested by the eth_getTransactionCount method. |
||||
* @property {number} highestSuggested - The maximum between the other two, the number returned. |
||||
*/ |
||||
|
||||
/** |
||||
this will return an object with the `nextNonce` `nonceDetails` of type NonceDetails, and the releaseLock |
||||
Note: releaseLock must be called after adding a signed tx to pending transactions (or discarding). |
||||
|
||||
@param address {string} the hex string for the address whose nonce we are calculating |
||||
@returns {Promise<NonceDetails>} |
||||
*/ |
||||
async getNonceLock (address) { |
||||
// await global mutex free
|
||||
await this._globalMutexFree() |
||||
// await lock free, then take lock
|
||||
const releaseLock = await this._takeMutex(address) |
||||
try { |
||||
// evaluate multiple nextNonce strategies
|
||||
const nonceDetails = {} |
||||
const networkNonceResult = await this._getNetworkNextNonce(address) |
||||
const highestLocallyConfirmed = this._getHighestLocallyConfirmed(address) |
||||
const nextNetworkNonce = networkNonceResult.nonce |
||||
const highestSuggested = Math.max(nextNetworkNonce, highestLocallyConfirmed) |
||||
|
||||
const pendingTxs = this.getPendingTransactions(address) |
||||
const localNonceResult = this._getHighestContinuousFrom(pendingTxs, highestSuggested) || 0 |
||||
|
||||
nonceDetails.params = { |
||||
highestLocallyConfirmed, |
||||
highestSuggested, |
||||
nextNetworkNonce, |
||||
} |
||||
nonceDetails.local = localNonceResult |
||||
nonceDetails.network = networkNonceResult |
||||
|
||||
const nextNonce = Math.max(networkNonceResult.nonce, localNonceResult.nonce) |
||||
assert(Number.isInteger(nextNonce), `nonce-tracker - nextNonce is not an integer - got: (${typeof nextNonce}) "${nextNonce}"`) |
||||
|
||||
// return nonce and release cb
|
||||
return { nextNonce, nonceDetails, releaseLock } |
||||
} catch (err) { |
||||
// release lock if we encounter an error
|
||||
releaseLock() |
||||
throw err |
||||
} |
||||
} |
||||
|
||||
async _globalMutexFree () { |
||||
const globalMutex = this._lookupMutex('global') |
||||
const releaseLock = await globalMutex.acquire() |
||||
releaseLock() |
||||
} |
||||
|
||||
async _takeMutex (lockId) { |
||||
const mutex = this._lookupMutex(lockId) |
||||
const releaseLock = await mutex.acquire() |
||||
return releaseLock |
||||
} |
||||
|
||||
_lookupMutex (lockId) { |
||||
let mutex = this.lockMap[lockId] |
||||
if (!mutex) { |
||||
mutex = new Mutex() |
||||
this.lockMap[lockId] = mutex |
||||
} |
||||
return mutex |
||||
} |
||||
|
||||
async _getNetworkNextNonce (address) { |
||||
// calculate next nonce
|
||||
// we need to make sure our base count
|
||||
// and pending count are from the same block
|
||||
const blockNumber = await this.blockTracker.getLatestBlock() |
||||
const baseCountBN = await this.ethQuery.getTransactionCount(address, blockNumber) |
||||
const baseCount = baseCountBN.toNumber() |
||||
assert(Number.isInteger(baseCount), `nonce-tracker - baseCount is not an integer - got: (${typeof baseCount}) "${baseCount}"`) |
||||
const nonceDetails = { blockNumber, baseCount } |
||||
return { name: 'network', nonce: baseCount, details: nonceDetails } |
||||
} |
||||
|
||||
_getHighestLocallyConfirmed (address) { |
||||
const confirmedTransactions = this.getConfirmedTransactions(address) |
||||
const highest = this._getHighestNonce(confirmedTransactions) |
||||
return Number.isInteger(highest) ? highest + 1 : 0 |
||||
} |
||||
|
||||
_getHighestNonce (txList) { |
||||
const nonces = txList.map((txMeta) => { |
||||
const nonce = txMeta.txParams.nonce |
||||
assert(typeof nonce, 'string', 'nonces should be hex strings') |
||||
return parseInt(nonce, 16) |
||||
}) |
||||
const highestNonce = Math.max.apply(null, nonces) |
||||
return highestNonce |
||||
} |
||||
|
||||
/** |
||||
@typedef {object} highestContinuousFrom |
||||
@property {string} - name the name for how the nonce was calculated based on the data used |
||||
@property {number} - nonce the next suggested nonce |
||||
@property {object} - details the provided starting nonce that was used (for debugging) |
||||
*/ |
||||
/** |
||||
@param txList {array} - list of txMeta's |
||||
@param startPoint {number} - the highest known locally confirmed nonce |
||||
@returns {highestContinuousFrom} |
||||
*/ |
||||
_getHighestContinuousFrom (txList, startPoint) { |
||||
const nonces = txList.map((txMeta) => { |
||||
const nonce = txMeta.txParams.nonce |
||||
assert(typeof nonce, 'string', 'nonces should be hex strings') |
||||
return parseInt(nonce, 16) |
||||
}) |
||||
|
||||
let highest = startPoint |
||||
while (nonces.includes(highest)) { |
||||
highest++ |
||||
} |
||||
|
||||
return { name: 'local', nonce: highest, details: { startPoint, highest } } |
||||
} |
||||
|
||||
} |
||||
|
||||
module.exports = NonceTracker |
@ -1,17 +0,0 @@ |
||||
const MessageManager = require('./lib/message-manager') |
||||
const PersonalMessageManager = require('./lib/personal-message-manager') |
||||
const TypedMessageManager = require('./lib/typed-message-manager') |
||||
|
||||
class UserActionController { |
||||
|
||||
constructor (opts = {}) { |
||||
|
||||
this.messageManager = new MessageManager() |
||||
this.personalMessageManager = new PersonalMessageManager() |
||||
this.typedMessageManager = new TypedMessageManager() |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
module.exports = UserActionController |
@ -0,0 +1,26 @@ |
||||
const { |
||||
getMetaMetricState, |
||||
} = require('../../../ui/app/selectors/selectors') |
||||
const { |
||||
sendMetaMetricsEvent, |
||||
} = require('../../../ui/app/helpers/utils/metametrics.util') |
||||
|
||||
const inDevelopment = process.env.NODE_ENV === 'development' |
||||
|
||||
const METAMETRICS_TRACKING_URL = inDevelopment |
||||
? 'http://www.metamask.io/metametrics' |
||||
: 'http://www.metamask.io/metametrics-prod' |
||||
|
||||
function backEndMetaMetricsEvent (metaMaskState, eventData) { |
||||
const stateEventData = getMetaMetricState({ metamask: metaMaskState }) |
||||
|
||||
if (stateEventData.participateInMetaMetrics) { |
||||
sendMetaMetricsEvent({ |
||||
...stateEventData, |
||||
...eventData, |
||||
url: METAMETRICS_TRACKING_URL + '/backend', |
||||
}) |
||||
} |
||||
} |
||||
|
||||
module.exports = backEndMetaMetricsEvent |
@ -0,0 +1,16 @@ |
||||
module.exports = createDnodeRemoteGetter |
||||
|
||||
function createDnodeRemoteGetter (dnode) { |
||||
let remote |
||||
|
||||
dnode.once('remote', (_remote) => { |
||||
remote = _remote |
||||
}) |
||||
|
||||
async function getRemote () { |
||||
if (remote) return remote |
||||
return await new Promise(resolve => dnode.once('remote', resolve)) |
||||
} |
||||
|
||||
return getRemote |
||||
} |
@ -1,16 +0,0 @@ |
||||
module.exports = createProviderMiddleware |
||||
|
||||
/** |
||||
* Forwards an HTTP request to the current Web3 provider |
||||
* |
||||
* @param {{ provider: Object }} config Configuration containing current Web3 provider |
||||
*/ |
||||
function createProviderMiddleware ({ provider }) { |
||||
return (req, res, next, end) => { |
||||
provider.sendAsync(req, (err, _res) => { |
||||
if (err) return end(err) |
||||
res.result = _res.result |
||||
end() |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,71 @@ |
||||
## Creating Metrics Events |
||||
|
||||
The `metricsEvent` method is made available to all components via context. This is done in `metamask-extension/ui/app/helpers/higher-order-components/metametrics/metametrics.provider.js`. As such, it can be called in all components by first adding it to the context proptypes: |
||||
|
||||
``` |
||||
static contextTypes = { |
||||
t: PropTypes.func, |
||||
metricsEvent: PropTypes.func, |
||||
} |
||||
``` |
||||
|
||||
and then accessing it on `this.context`. |
||||
|
||||
Below is an example of a metrics event call: |
||||
|
||||
``` |
||||
this.context.metricsEvent({ |
||||
eventOpts: { |
||||
category: 'Navigation', |
||||
action: 'Main Menu', |
||||
name: 'Switched Account', |
||||
}, |
||||
}) |
||||
``` |
||||
|
||||
### Base Schema |
||||
|
||||
Every `metricsEvent` call is passed an object that must have an `eventOpts` property. This property is an object that itself must have three properties: |
||||
- category: categorizes events according to the schema we have set up in our matomo.org instance |
||||
- action: usually describes the page on which the event takes place, or sometimes a significant subsections of a page |
||||
- name: a very specific descriptor of the event |
||||
|
||||
### Implicit properties |
||||
|
||||
All metrics events send the following data when called: |
||||
- network |
||||
- environmentType |
||||
- activeCurrency |
||||
- accountType |
||||
- numberOfTokens |
||||
- numberOfAccounts |
||||
|
||||
These are added to the metrics event via the metametrics provider. |
||||
|
||||
### Custom Variables |
||||
|
||||
Metrics events can include custom variables. These are included within the `customVariables` property that is a first-level property within first param passed to `metricsEvent`. |
||||
|
||||
For example: |
||||
``` |
||||
this.context.metricsEvent({ |
||||
eventOpts: { |
||||
category: 'Settings', |
||||
action: 'Custom RPC', |
||||
name: 'Error', |
||||
}, |
||||
customVariables: { |
||||
networkId: newRpc, |
||||
chainId, |
||||
}, |
||||
}) |
||||
``` |
||||
|
||||
Custom variables can have custom property names and values can be strings or numbers. |
||||
|
||||
**To include a custom variable, there are a set of necessary steps you must take.** |
||||
|
||||
1. First you must declare a constant equal to the desired name of the custom variable property in `metamask-extension/ui/app/helpers/utils/metametrics.util.js` under `//Custom Variable Declarations` |
||||
1. Then you must add that name to the `customVariableNameIdMap` declaration |
||||
1. The id must be between 1 and 5 |
||||
1. There can be no more than 5 custom variables assigned ids on a given url |
@ -0,0 +1,5 @@ |
||||
# MetaMask Design System |
||||
|
||||
A design system is a series of components that can be reused in different combinations. Design systems allow you to manage design at scale. |
||||
|
||||
Design System [Figma File](https://www.figma.com/file/aWgwMrzdAuv9VuPdtst64uuw/Style-Guide?node-id=211%3A0) |
@ -0,0 +1,5 @@ |
||||
# Google Chrome/Brave Limited Site Access for Extensions |
||||
|
||||
Problem: MetaMask doesn't work with limited site access enabled under Chrome's extensions. |
||||
|
||||
Solution: In addition to the site you wish to whitelist, you must add 'api.infura.io' as another domain, so the MetaMask extension is authorized to make RPC calls to Infura. |
@ -0,0 +1,9 @@ |
||||
#!/usr/bin/env bash |
||||
|
||||
set -e |
||||
set -u |
||||
set -o pipefail |
||||
|
||||
export PATH="$PATH:./node_modules/.bin" |
||||
|
||||
shell-parallel -s 'static-server test/web3 --port 8080' -x 'sleep 5 && mocha test/e2e/beta/web3.spec' |
@ -0,0 +1,365 @@ |
||||
const path = require('path') |
||||
const assert = require('assert') |
||||
const webdriver = require('selenium-webdriver') |
||||
const { By } = webdriver |
||||
const { |
||||
delay, |
||||
buildChromeWebDriver, |
||||
buildFirefoxWebdriver, |
||||
installWebExt, |
||||
getExtensionIdChrome, |
||||
getExtensionIdFirefox, |
||||
} = require('../func') |
||||
const { |
||||
checkBrowserForConsoleErrors, |
||||
closeAllWindowHandlesExcept, |
||||
findElement, |
||||
findElements, |
||||
openNewPage, |
||||
switchToWindowWithTitle, |
||||
verboseReportOnFailure, |
||||
waitUntilXWindowHandles, |
||||
} = require('./helpers') |
||||
const fetchMockResponses = require('./fetch-mocks.js') |
||||
|
||||
|
||||
describe('Using MetaMask with an existing account', function () { |
||||
let extensionId |
||||
let driver |
||||
|
||||
const testSeedPhrase = 'forum vessel pink push lonely enact gentle tail admit parrot grunt dress' |
||||
const regularDelayMs = 1000 |
||||
const largeDelayMs = regularDelayMs * 2 |
||||
|
||||
const button = async (x) => { |
||||
const buttoncheck = x |
||||
await buttoncheck.click() |
||||
await delay(largeDelayMs) |
||||
const [results] = await findElements(driver, By.css('#results')) |
||||
const resulttext = await results.getText() |
||||
var parsedData = JSON.parse(resulttext) |
||||
|
||||
return (parsedData) |
||||
|
||||
} |
||||
|
||||
this.timeout(0) |
||||
this.bail(true) |
||||
|
||||
before(async function () { |
||||
let extensionUrl |
||||
switch (process.env.SELENIUM_BROWSER) { |
||||
case 'chrome': { |
||||
const extensionPath = path.resolve('dist/chrome') |
||||
driver = buildChromeWebDriver(extensionPath) |
||||
extensionId = await getExtensionIdChrome(driver) |
||||
await delay(regularDelayMs) |
||||
extensionUrl = `chrome-extension://${extensionId}/home.html` |
||||
break |
||||
} |
||||
case 'firefox': { |
||||
const extensionPath = path.resolve('dist/firefox') |
||||
driver = buildFirefoxWebdriver() |
||||
await installWebExt(driver, extensionPath) |
||||
await delay(regularDelayMs) |
||||
extensionId = await getExtensionIdFirefox(driver) |
||||
extensionUrl = `moz-extension://${extensionId}/home.html` |
||||
break |
||||
} |
||||
} |
||||
// Depending on the state of the application built into the above directory (extPath) and the value of
|
||||
// METAMASK_DEBUG we will see different post-install behaviour and possibly some extra windows. Here we
|
||||
// are closing any extraneous windows to reset us to a single window before continuing.
|
||||
const [tab1] = await driver.getAllWindowHandles() |
||||
await closeAllWindowHandlesExcept(driver, [tab1]) |
||||
await driver.switchTo().window(tab1) |
||||
await driver.get(extensionUrl) |
||||
}) |
||||
|
||||
beforeEach(async function () { |
||||
await driver.executeScript( |
||||
'window.origFetch = window.fetch.bind(window);' + |
||||
'window.fetch = ' + |
||||
'(...args) => { ' + |
||||
'if (args[0] === "https://ethgasstation.info/json/ethgasAPI.json") { return ' + |
||||
'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.ethGasBasic + '\')) }); } else if ' + |
||||
'(args[0] === "https://ethgasstation.info/json/predictTable.json") { return ' + |
||||
'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.ethGasPredictTable + '\')) }); } else if ' + |
||||
'(args[0].match(/chromeextensionmm/)) { return ' + |
||||
'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.metametrics + '\')) }); } else if ' + |
||||
'(args[0] === "https://dev.blockscale.net/api/gasexpress.json") { return ' + |
||||
'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.gasExpress + '\')) }); } ' + |
||||
'return window.origFetch(...args); };' + |
||||
'function cancelInfuraRequest(requestDetails) {' + |
||||
'console.log("Canceling: " + requestDetails.url);' + |
||||
'return {' + |
||||
'cancel: true' + |
||||
'};' + |
||||
' }' + |
||||
'window.chrome && window.chrome.webRequest && window.chrome.webRequest.onBeforeRequest.addListener(' + |
||||
'cancelInfuraRequest,' + |
||||
'{urls: ["https://*.infura.io/*"]},' + |
||||
'["blocking"]' + |
||||
');' |
||||
) |
||||
}) |
||||
|
||||
afterEach(async function () { |
||||
if (process.env.SELENIUM_BROWSER === 'chrome') { |
||||
const errors = await checkBrowserForConsoleErrors(driver) |
||||
if (errors.length) { |
||||
const errorReports = errors.map(err => err.message) |
||||
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` |
||||
console.error(new Error(errorMessage)) |
||||
} |
||||
} |
||||
if (this.currentTest.state === 'failed') { |
||||
await verboseReportOnFailure(driver, this.currentTest) |
||||
} |
||||
}) |
||||
|
||||
after(async function () { |
||||
await driver.quit() |
||||
}) |
||||
|
||||
describe('First time flow starting from an existing seed phrase', () => { |
||||
it('clicks the continue button on the welcome screen', async () => { |
||||
await findElement(driver, By.css('.welcome-page__header')) |
||||
const welcomeScreenBtn = await findElement(driver, By.css('.first-time-flow__button')) |
||||
welcomeScreenBtn.click() |
||||
await delay(largeDelayMs) |
||||
}) |
||||
|
||||
it('clicks the "Import Wallet" option', async () => { |
||||
const customRpcButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Import Wallet')]`)) |
||||
customRpcButton.click() |
||||
await delay(largeDelayMs) |
||||
}) |
||||
|
||||
it('clicks the "No thanks" option on the metametrics opt-in screen', async () => { |
||||
const optOutButton = await findElement(driver, By.css('.btn-default')) |
||||
optOutButton.click() |
||||
await delay(largeDelayMs) |
||||
}) |
||||
|
||||
it('imports a seed phrase', async () => { |
||||
const [seedTextArea] = await findElements(driver, By.css('textarea.first-time-flow__textarea')) |
||||
await seedTextArea.sendKeys(testSeedPhrase) |
||||
await delay(regularDelayMs) |
||||
|
||||
const [password] = await findElements(driver, By.id('password')) |
||||
await password.sendKeys('correct horse battery staple') |
||||
const [confirmPassword] = await findElements(driver, By.id('confirm-password')) |
||||
confirmPassword.sendKeys('correct horse battery staple') |
||||
|
||||
const tosCheckBox = await findElement(driver, By.css('.first-time-flow__checkbox')) |
||||
await tosCheckBox.click() |
||||
|
||||
const [importButton] = await findElements(driver, By.xpath(`//button[contains(text(), 'Import')]`)) |
||||
await importButton.click() |
||||
await delay(regularDelayMs) |
||||
}) |
||||
|
||||
it('clicks through the success screen', async () => { |
||||
await findElement(driver, By.xpath(`//div[contains(text(), 'Congratulations')]`)) |
||||
const doneButton = await findElement(driver, By.css('button.first-time-flow__button')) |
||||
await doneButton.click() |
||||
await delay(regularDelayMs) |
||||
}) |
||||
}) |
||||
|
||||
|
||||
describe('opens dapp', () => { |
||||
|
||||
it('switches to mainnet', async () => { |
||||
const networkDropdown = await findElement(driver, By.css('.network-name')) |
||||
await networkDropdown.click() |
||||
await delay(regularDelayMs) |
||||
|
||||
const [mainnet] = await findElements(driver, By.xpath(`//span[contains(text(), 'Main Ethereum Network')]`)) |
||||
await mainnet.click() |
||||
await delay(largeDelayMs * 2) |
||||
}) |
||||
|
||||
it('', async () => { |
||||
await openNewPage(driver, 'http://127.0.0.1:8080/') |
||||
await delay(regularDelayMs) |
||||
|
||||
await waitUntilXWindowHandles(driver, 3) |
||||
const windowHandles = await driver.getAllWindowHandles() |
||||
|
||||
const extension = windowHandles[0] |
||||
const popup = await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles) |
||||
const dapp = windowHandles.find(handle => handle !== extension && handle !== popup) |
||||
|
||||
await delay(regularDelayMs) |
||||
const approveButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) |
||||
await approveButton.click() |
||||
|
||||
await driver.switchTo().window(dapp) |
||||
await delay(regularDelayMs) |
||||
|
||||
|
||||
}) |
||||
}) |
||||
|
||||
describe('testing web3 methods', async () => { |
||||
|
||||
|
||||
it('testing hexa methods', async () => { |
||||
|
||||
|
||||
var List = await driver.findElements(By.className('hexaNumberMethods')) |
||||
|
||||
for (let i = 0; i < List.length; i++) { |
||||
try { |
||||
|
||||
var parsedData = await button(List[i]) |
||||
console.log(parsedData) |
||||
var result = parseInt(parsedData.result, 16) |
||||
|
||||
assert.equal((typeof result === 'number'), true) |
||||
await delay(regularDelayMs) |
||||
} catch (err) { |
||||
console.log(err) |
||||
assert(false) |
||||
|
||||
} |
||||
} |
||||
}) |
||||
|
||||
it('testing booleanMethods', async () => { |
||||
|
||||
var List = await driver.findElements(By.className('booleanMethods')) |
||||
|
||||
for (let i = 0; i < List.length; i++) { |
||||
try { |
||||
|
||||
var parsedData = await button(List[i]) |
||||
console.log(parsedData) |
||||
var result = parsedData.result |
||||
|
||||
assert.equal(result, false) |
||||
await delay(regularDelayMs) |
||||
} catch (err) { |
||||
console.log(err) |
||||
assert(false) |
||||
|
||||
|
||||
} |
||||
} |
||||
|
||||
}) |
||||
|
||||
it('testing transactionMethods', async () => { |
||||
|
||||
var List = await driver.findElements(By.className('transactionMethods')) |
||||
|
||||
for (let i = 0; i < List.length; i++) { |
||||
try { |
||||
|
||||
var parsedData = await button(List[i]) |
||||
|
||||
console.log(parsedData.result.blockHash) |
||||
|
||||
var result = [] |
||||
result.push(parseInt(parsedData.result.blockHash, 16)) |
||||
result.push(parseInt(parsedData.result.blockNumber, 16)) |
||||
result.push(parseInt(parsedData.result.gas, 16)) |
||||
result.push(parseInt(parsedData.result.gasPrice, 16)) |
||||
result.push(parseInt(parsedData.result.hash, 16)) |
||||
result.push(parseInt(parsedData.result.input, 16)) |
||||
result.push(parseInt(parsedData.result.nonce, 16)) |
||||
result.push(parseInt(parsedData.result.r, 16)) |
||||
result.push(parseInt(parsedData.result.s, 16)) |
||||
result.push(parseInt(parsedData.result.v, 16)) |
||||
result.push(parseInt(parsedData.result.to, 16)) |
||||
result.push(parseInt(parsedData.result.value, 16)) |
||||
|
||||
|
||||
result.forEach((value) => { |
||||
assert.equal((typeof value === 'number'), true) |
||||
}) |
||||
|
||||
|
||||
} catch (err) { |
||||
|
||||
console.log(err) |
||||
assert(false) |
||||
|
||||
|
||||
} |
||||
} |
||||
|
||||
}) |
||||
|
||||
it('testing blockMethods', async () => { |
||||
|
||||
var List = await driver.findElements(By.className('blockMethods')) |
||||
|
||||
for (let i = 0; i < List.length; i++) { |
||||
try { |
||||
|
||||
var parsedData = await button(List[i]) |
||||
console.log(JSON.stringify(parsedData) + i) |
||||
|
||||
console.log(parsedData.result.parentHash) |
||||
|
||||
var result = parseInt(parsedData.result.parentHash, 16) |
||||
|
||||
assert.equal((typeof result === 'number'), true) |
||||
await delay(regularDelayMs) |
||||
} catch (err) { |
||||
|
||||
console.log(err) |
||||
assert(false) |
||||
|
||||
|
||||
} |
||||
} |
||||
}) |
||||
|
||||
it('testing methods', async () => { |
||||
|
||||
var List = await driver.findElements(By.className('methods')) |
||||
var parsedData |
||||
var result |
||||
|
||||
for (let i = 0; i < List.length; i++) { |
||||
try { |
||||
|
||||
if (i === 2) { |
||||
|
||||
parsedData = await button(List[i]) |
||||
console.log(parsedData.result.blockHash) |
||||
|
||||
result = parseInt(parsedData.result.blockHash, 16) |
||||
|
||||
assert.equal((typeof result === 'number' || (result === 0)), true) |
||||
await delay(regularDelayMs) |
||||
} else { |
||||
parsedData = await button(List[i]) |
||||
console.log(parsedData.result) |
||||
|
||||
result = parseInt(parsedData.result, 16) |
||||
|
||||
assert.equal((typeof result === 'number' || (result === 0)), true) |
||||
await delay(regularDelayMs) |
||||
} |
||||
|
||||
|
||||
} catch (err) { |
||||
|
||||
console.log(err) |
||||
assert(false) |
||||
|
||||
|
||||
} |
||||
} |
||||
}) |
||||
|
||||
|
||||
}) |
||||
|
||||
|
||||
}) |
@ -1,56 +0,0 @@ |
||||
const assert = require('assert') |
||||
const BlacklistController = require('../../../../app/scripts/controllers/blacklist') |
||||
|
||||
describe('blacklist controller', function () { |
||||
let blacklistController |
||||
|
||||
before(() => { |
||||
blacklistController = new BlacklistController() |
||||
}) |
||||
|
||||
describe('whitelistDomain', function () { |
||||
it('should add hostname to the runtime whitelist', function () { |
||||
blacklistController.whitelistDomain('foo.com') |
||||
assert.deepEqual(blacklistController.store.getState().whitelist, ['foo.com']) |
||||
|
||||
blacklistController.whitelistDomain('bar.com') |
||||
assert.deepEqual(blacklistController.store.getState().whitelist, ['bar.com', 'foo.com']) |
||||
}) |
||||
}) |
||||
|
||||
describe('checkForPhishing', function () { |
||||
it('should not flag whitelisted values', function () { |
||||
const result = blacklistController.checkForPhishing('www.metamask.io') |
||||
assert.equal(result, false) |
||||
}) |
||||
it('should flag explicit values', function () { |
||||
const result = blacklistController.checkForPhishing('metamask.com') |
||||
assert.equal(result, true) |
||||
}) |
||||
it('should flag levenshtein values', function () { |
||||
const result = blacklistController.checkForPhishing('metmask.io') |
||||
assert.equal(result, true) |
||||
}) |
||||
it('should not flag not-even-close values', function () { |
||||
const result = blacklistController.checkForPhishing('example.com') |
||||
assert.equal(result, false) |
||||
}) |
||||
it('should not flag the ropsten faucet domains', function () { |
||||
const result = blacklistController.checkForPhishing('faucet.metamask.io') |
||||
assert.equal(result, false) |
||||
}) |
||||
it('should not flag the mascara domain', function () { |
||||
const result = blacklistController.checkForPhishing('zero.metamask.io') |
||||
assert.equal(result, false) |
||||
}) |
||||
it('should not flag the mascara-faucet domain', function () { |
||||
const result = blacklistController.checkForPhishing('zero-faucet.metamask.io') |
||||
assert.equal(result, false) |
||||
}) |
||||
it('should not flag whitelisted domain', function () { |
||||
blacklistController.whitelistDomain('metamask.com') |
||||
const result = blacklistController.checkForPhishing('metamask.com') |
||||
assert.equal(result, false) |
||||
}) |
||||
}) |
||||
}) |
@ -1,238 +0,0 @@ |
||||
const assert = require('assert') |
||||
const NonceTracker = require('../../../../../app/scripts/controllers/transactions/nonce-tracker') |
||||
const MockTxGen = require('../../../../lib/mock-tx-gen') |
||||
const providerResultStub = {} |
||||
|
||||
describe('Nonce Tracker', function () { |
||||
let nonceTracker, pendingTxs, confirmedTxs |
||||
|
||||
describe('#getNonceLock', function () { |
||||
|
||||
describe('with 3 confirmed and 1 pending', function () { |
||||
beforeEach(function () { |
||||
const txGen = new MockTxGen() |
||||
confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 3 }) |
||||
pendingTxs = txGen.generate({ status: 'submitted' }, { count: 1 }) |
||||
nonceTracker = generateNonceTrackerWith(pendingTxs, confirmedTxs, '0x1') |
||||
}) |
||||
|
||||
it('should return 4', async function () { |
||||
this.timeout(15000) |
||||
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') |
||||
assert.equal(nonceLock.nextNonce, '4', `nonce should be 4 got ${nonceLock.nextNonce}`) |
||||
await nonceLock.releaseLock() |
||||
}) |
||||
|
||||
it('should use localNonce if network returns a nonce lower then a confirmed tx in state', async function () { |
||||
this.timeout(15000) |
||||
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') |
||||
assert.equal(nonceLock.nextNonce, '4', 'nonce should be 4') |
||||
await nonceLock.releaseLock() |
||||
}) |
||||
}) |
||||
|
||||
describe('sentry issue 476304902', function () { |
||||
beforeEach(function () { |
||||
const txGen = new MockTxGen() |
||||
pendingTxs = txGen.generate({ status: 'submitted' }, { |
||||
fromNonce: 3, |
||||
count: 29, |
||||
}) |
||||
nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x3') |
||||
}) |
||||
|
||||
it('should return 9', async function () { |
||||
this.timeout(15000) |
||||
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') |
||||
assert.equal(nonceLock.nextNonce, '32', `nonce should be 32 got ${nonceLock.nextNonce}`) |
||||
await nonceLock.releaseLock() |
||||
}) |
||||
}) |
||||
|
||||
describe('issue 3670', function () { |
||||
beforeEach(function () { |
||||
const txGen = new MockTxGen() |
||||
pendingTxs = txGen.generate({ status: 'submitted' }, { |
||||
fromNonce: 6, |
||||
count: 3, |
||||
}) |
||||
nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x6') |
||||
}) |
||||
|
||||
it('should return 9', async function () { |
||||
this.timeout(15000) |
||||
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') |
||||
assert.equal(nonceLock.nextNonce, '9', `nonce should be 9 got ${nonceLock.nextNonce}`) |
||||
await nonceLock.releaseLock() |
||||
}) |
||||
}) |
||||
|
||||
describe('with no previous txs', function () { |
||||
beforeEach(function () { |
||||
nonceTracker = generateNonceTrackerWith([], []) |
||||
}) |
||||
|
||||
it('should return 0', async function () { |
||||
this.timeout(15000) |
||||
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') |
||||
assert.equal(nonceLock.nextNonce, '0', `nonce should be 0 returned ${nonceLock.nextNonce}`) |
||||
await nonceLock.releaseLock() |
||||
}) |
||||
}) |
||||
|
||||
describe('with multiple previous txs with same nonce', function () { |
||||
beforeEach(function () { |
||||
const txGen = new MockTxGen() |
||||
confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 1 }) |
||||
pendingTxs = txGen.generate({ |
||||
status: 'submitted', |
||||
txParams: { nonce: '0x01' }, |
||||
}, { count: 5 }) |
||||
|
||||
nonceTracker = generateNonceTrackerWith(pendingTxs, confirmedTxs, '0x0') |
||||
}) |
||||
|
||||
it('should return nonce after those', async function () { |
||||
this.timeout(15000) |
||||
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') |
||||
assert.equal(nonceLock.nextNonce, '2', `nonce should be 2 got ${nonceLock.nextNonce}`) |
||||
await nonceLock.releaseLock() |
||||
}) |
||||
}) |
||||
|
||||
describe('when local confirmed count is higher than network nonce', function () { |
||||
beforeEach(function () { |
||||
const txGen = new MockTxGen() |
||||
confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 3 }) |
||||
nonceTracker = generateNonceTrackerWith([], confirmedTxs, '0x1') |
||||
}) |
||||
|
||||
it('should return nonce after those', async function () { |
||||
this.timeout(15000) |
||||
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') |
||||
assert.equal(nonceLock.nextNonce, '3', `nonce should be 3 got ${nonceLock.nextNonce}`) |
||||
await nonceLock.releaseLock() |
||||
}) |
||||
}) |
||||
|
||||
describe('when local pending count is higher than other metrics', function () { |
||||
beforeEach(function () { |
||||
const txGen = new MockTxGen() |
||||
pendingTxs = txGen.generate({ status: 'submitted' }, { count: 2 }) |
||||
nonceTracker = generateNonceTrackerWith(pendingTxs, []) |
||||
}) |
||||
|
||||
it('should return nonce after those', async function () { |
||||
this.timeout(15000) |
||||
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') |
||||
assert.equal(nonceLock.nextNonce, '2', `nonce should be 2 got ${nonceLock.nextNonce}`) |
||||
await nonceLock.releaseLock() |
||||
}) |
||||
}) |
||||
|
||||
describe('when provider nonce is higher than other metrics', function () { |
||||
beforeEach(function () { |
||||
const txGen = new MockTxGen() |
||||
pendingTxs = txGen.generate({ status: 'submitted' }, { count: 2 }) |
||||
nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x05') |
||||
}) |
||||
|
||||
it('should return nonce after those', async function () { |
||||
this.timeout(15000) |
||||
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') |
||||
assert.equal(nonceLock.nextNonce, '5', `nonce should be 5 got ${nonceLock.nextNonce}`) |
||||
await nonceLock.releaseLock() |
||||
}) |
||||
}) |
||||
|
||||
describe('when there are some pending nonces below the remote one and some over.', function () { |
||||
beforeEach(function () { |
||||
const txGen = new MockTxGen() |
||||
pendingTxs = txGen.generate({ status: 'submitted' }, { count: 5 }) |
||||
nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x03') |
||||
}) |
||||
|
||||
it('should return nonce after those', async function () { |
||||
this.timeout(15000) |
||||
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') |
||||
assert.equal(nonceLock.nextNonce, '5', `nonce should be 5 got ${nonceLock.nextNonce}`) |
||||
await nonceLock.releaseLock() |
||||
}) |
||||
}) |
||||
|
||||
describe('when there are pending nonces non sequentially over the network nonce.', function () { |
||||
beforeEach(function () { |
||||
const txGen = new MockTxGen() |
||||
txGen.generate({ status: 'submitted' }, { count: 5 }) |
||||
// 5 over that number
|
||||
pendingTxs = txGen.generate({ status: 'submitted' }, { count: 5 }) |
||||
nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x00') |
||||
}) |
||||
|
||||
it('should return nonce after network nonce', async function () { |
||||
this.timeout(15000) |
||||
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') |
||||
assert.equal(nonceLock.nextNonce, '0', `nonce should be 0 got ${nonceLock.nextNonce}`) |
||||
await nonceLock.releaseLock() |
||||
}) |
||||
}) |
||||
|
||||
describe('When all three return different values', function () { |
||||
beforeEach(function () { |
||||
const txGen = new MockTxGen() |
||||
confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 10 }) |
||||
pendingTxs = txGen.generate({ |
||||
status: 'submitted', |
||||
nonce: 100, |
||||
}, { count: 1 }) |
||||
// 0x32 is 50 in hex:
|
||||
nonceTracker = generateNonceTrackerWith(pendingTxs, confirmedTxs, '0x32') |
||||
}) |
||||
|
||||
it('should return nonce after network nonce', async function () { |
||||
this.timeout(15000) |
||||
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') |
||||
assert.equal(nonceLock.nextNonce, '50', `nonce should be 50 got ${nonceLock.nextNonce}`) |
||||
await nonceLock.releaseLock() |
||||
}) |
||||
}) |
||||
|
||||
describe('Faq issue 67', function () { |
||||
beforeEach(function () { |
||||
const txGen = new MockTxGen() |
||||
confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 64 }) |
||||
pendingTxs = txGen.generate({ |
||||
status: 'submitted', |
||||
}, { count: 10 }) |
||||
// 0x40 is 64 in hex:
|
||||
nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x40') |
||||
}) |
||||
|
||||
it('should return nonce after network nonce', async function () { |
||||
this.timeout(15000) |
||||
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') |
||||
assert.equal(nonceLock.nextNonce, '74', `nonce should be 74 got ${nonceLock.nextNonce}`) |
||||
await nonceLock.releaseLock() |
||||
}) |
||||
}) |
||||
}) |
||||
}) |
||||
|
||||
function generateNonceTrackerWith (pending, confirmed, providerStub = '0x0') { |
||||
const getPendingTransactions = () => pending |
||||
const getConfirmedTransactions = () => confirmed |
||||
providerResultStub.result = providerStub |
||||
const provider = { |
||||
sendAsync: (_, cb) => { cb(undefined, providerResultStub) }, |
||||
} |
||||
const blockTracker = { |
||||
getCurrentBlock: () => '0x11b568', |
||||
getLatestBlock: async () => '0x11b568', |
||||
} |
||||
return new NonceTracker({ |
||||
provider, |
||||
blockTracker, |
||||
getPendingTransactions, |
||||
getConfirmedTransactions, |
||||
}) |
||||
} |
@ -0,0 +1,105 @@ |
||||
<html> |
||||
<head> |
||||
<title>Web3 Test Dapp</title> |
||||
</head> |
||||
<body> |
||||
<div style="display: flex; flex-flow: column;"> |
||||
<div style="display: flex; font-size: 1.25rem;">hexaNumberMethods</div> |
||||
<div style="display: flex;"> |
||||
<button id="eth_blockNumber" class="hexaNumberMethods">eth_blockNumber</button> |
||||
|
||||
<button id="eth_gasPrice" class="hexaNumberMethods">eth_gasPrice</button> |
||||
<button id="eth_newBlockFilter" class="hexaNumberMethods">eth_newBlockFilter</button> |
||||
<button id="eth_newPendingTransactionFilter" class="hexaNumberMethods"> |
||||
eth_newPendingTransactionFilter |
||||
</button> |
||||
<button id="eth_getUncleCountByBlockHash" class="hexaNumberMethods"> |
||||
eth_getUncleCountByBlockHash |
||||
</button> |
||||
<button id="eth_getBlockTransactionCountByHash" class="hexaNumberMethods"> |
||||
getBlockTransactionCountByHash |
||||
</button> |
||||
</div> |
||||
<div style="display: flex ;"> |
||||
<button id="eth_getTransactionCount" class="hexaNumberMethods">eth_getTransactionCount</button> |
||||
<button id="eth_getBalance" class="hexaNumberMethods">eth_getBalance</button> |
||||
<button id="eth_estimateGas" class="hexaNumberMethods">eth_estimateGas</button> |
||||
</div> |
||||
<div style="display: flex ;"> |
||||
|
||||
<button id="eth_getUncleCountByBlockNumber" class="hexaNumberMethods"> |
||||
eth_getUncleCountByBlockNumber |
||||
</button> |
||||
<button id='eth_getBlockTransactionCountByNumber' class="hexaNumberMethods"> |
||||
eth_getBlockTransactionCountByNumber |
||||
</button> |
||||
<button id="eth_protocolVersion" class="hexaNumberMethods">eth_protocolVersion</button> |
||||
|
||||
<button id="eth_getCode" class="hexaNumberMethods">eth_getCode</button> |
||||
</div> |
||||
</div> |
||||
<div style="display: flex; flex-flow: column;"> |
||||
<div style="display: flex; font-size: 1.25rem;">booleanMethods</div> |
||||
<div style="display: flex ;"> |
||||
<button id="eth_uninstallFilter" class = 'booleanMethods'>eth_uninstallFilter</button> |
||||
<button id="eth_mining" class = 'booleanMethods'>eth_mining</button> |
||||
<button id="eth_syncing" class = 'booleanMethods'>eth_syncing</button> |
||||
</div> |
||||
</div> |
||||
<div style="display: flex; flex-flow: column;"> |
||||
<div style="display: flex; font-size: 1.25rem;" >transactionMethods</div> |
||||
<div style="display: flex ;"> |
||||
<button id="eth_getTransactionByHash" class='transactionMethods'>eth_getTransactionByHash</button> |
||||
<button id="eth_getTransactionByBlockHashAndIndex" class = 'transactionMethods'> |
||||
eth_getTransactionByBlockHashAndIndex |
||||
</button> |
||||
<button id="eth_getTransactionByBlockNumberAndIndex" class="transactionMethods"> |
||||
eth_getTransactionByBlockNumberAndIndex |
||||
</button> |
||||
|
||||
|
||||
</div> |
||||
</div> |
||||
|
||||
<div style="display: flex; flex-flow: column;"> |
||||
<div style="display: flex; font-size: 1.25rem;">blockMethods</div> |
||||
|
||||
<div style="display: flex ;"> |
||||
|
||||
|
||||
<button id="eth_getUncleByBlockHashAndIndex" class="blockMethods"> |
||||
eth_getUncleByBlockHashAndIndex |
||||
</button> |
||||
<button id="eth_getBlockByHash" class="blockMethods">eth_getBlockByHash</button> |
||||
</div> |
||||
<div style="display: flex ;"> |
||||
<button id="eth_getBlockByNumber" class="blockMethods">eth_getBlockByNumber</button> |
||||
|
||||
|
||||
</div> |
||||
</div> |
||||
|
||||
<div style="display: flex; flex-flow: column;"> |
||||
<div style="display: flex; font-size: 1.25rem;">Methods</div> |
||||
<div style="display: flex ;"> |
||||
<button id="eth_call" class = 'methods'>eth_call</button> |
||||
<button id="eth_getStorageAt" class="methods">eth_getStorageAt</button> |
||||
<button id="eth_getTransactionReceipt" class="methods"> |
||||
eth_getTransactionReceipt |
||||
</button> |
||||
|
||||
</div> |
||||
</div> |
||||
<div style="display: flex; flex-flow: column;"> |
||||
<div id='results'></div> |
||||
</div> |
||||
|
||||
|
||||
|
||||
|
||||
</div> |
||||
<script src="schema.js"></script> |
||||
<script src="web3.js"></script> |
||||
|
||||
</body> |
||||
</html> |
@ -0,0 +1,209 @@ |
||||
/* eslint no-unused-vars: 0 */ |
||||
|
||||
var params = { |
||||
// diffrent params used in the methods
|
||||
param: [], |
||||
blockHashParams: '0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35', |
||||
filterParams: ['0xfe704947a3cd3ca12541458a4321c869'], |
||||
transactionHashParams: [ |
||||
'0xbb3a336e3f823ec18197f1e13ee875700f08f03e2cab75f0d0b118dabb44cba0', |
||||
], |
||||
blockHashAndIndexParams: [ |
||||
'0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35', |
||||
'0x0', |
||||
], |
||||
uncleByBlockNumberAndIndexParams: ['0x29c', '0x0'], |
||||
blockParameterParams: '0x5bad55', |
||||
data: '0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675', |
||||
addressParams: '0xc94770007dda54cF92009BFF0dE90c06F603a09f', |
||||
getStorageAtParams: [ |
||||
'0x295a70b2de5e3953354a6a8344e616ed314d7251', |
||||
'0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9', |
||||
'0x65a8db', |
||||
], |
||||
getCodeParams: ['0x06012c8cf97bead5deae237070f9587f8e7a266d', '0x65a8db'], |
||||
estimateTransaction: { |
||||
from: '0xb60e8dd61c5d32be8058bb8eb970870f07233155', |
||||
to: '0xd46e8dd67c5d32be8058bb8eb970870f07244567', |
||||
gas: '0x76c0', |
||||
gasPrice: '0x9184e72a000', |
||||
value: '0x9184e72a', |
||||
data: '0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675', |
||||
}, |
||||
filterGetLogs: [{'blockHash': '0x7c5a35e9cb3e8ae0e221ab470abae9d446c3a5626ce6689fc777dcffcab52c70', 'topics': ['0x241ea03ca20251805084d27d4440371c34a0b85ff108f6bb5611248f73818b80']}], |
||||
block: { |
||||
__required: [], |
||||
number: 'Q', |
||||
hash: 'D32', |
||||
parentHash: 'D32', |
||||
nonce: 'D', |
||||
sha3Uncles: 'D', |
||||
logsBloom: 'D', |
||||
transactionsRoot: 'D', |
||||
stateRoot: 'D', |
||||
receiptsRoot: 'D', |
||||
miner: 'D', |
||||
difficulty: 'Q', |
||||
totalDifficulty: 'Q', |
||||
extraData: 'D', |
||||
size: 'Q', |
||||
gasLimit: 'Q', |
||||
gasUsed: 'Q', |
||||
timestamp: 'Q', |
||||
transactions: ['DATA|Transaction'], |
||||
uncles: ['D'], |
||||
}, |
||||
transaction: { |
||||
__required: [], |
||||
hash: 'D32', |
||||
nonce: 'Q', |
||||
blockHash: 'D32', |
||||
blockNumber: 'Q', |
||||
transactionIndex: 'Q', |
||||
from: 'D20', |
||||
to: 'D20', |
||||
value: 'Q', |
||||
gasPrice: 'Q', |
||||
gas: 'Q', |
||||
input: 'D', |
||||
}, |
||||
receipt: { |
||||
__required: [], |
||||
transactionHash: 'D32', |
||||
transactionIndex: 'Q', |
||||
blockHash: 'D32', |
||||
blockNumber: 'Q', |
||||
cumulativeGasUsed: 'Q', |
||||
gasUsed: 'Q', |
||||
contractAddress: 'D20', |
||||
logs: ['FilterChange'], |
||||
}, |
||||
|
||||
filterChange: { |
||||
__required: [], |
||||
removed: 'B', |
||||
logIndex: 'Q', |
||||
transactionIndex: 'Q', |
||||
transactionHash: 'D32', |
||||
blockHash: 'D32', |
||||
blockNumber: 'Q', |
||||
address: 'D20', |
||||
data: 'Array|DATA', |
||||
topics: ['D'], |
||||
}, |
||||
} |
||||
|
||||
var methods = { |
||||
hexaNumberMethods: { |
||||
// these are the methods which have output in the form of hexa decimal numbers
|
||||
eth_blockNumber: ['eth_blockNumber', params.param, 'Q'], |
||||
eth_gasPrice: ['eth_gasPrice', params.param, 'Q'], |
||||
eth_newBlockFilter: ['eth_newBlockFilter', params.param, 'Q'], |
||||
eth_newPendingTransactionFilter: [ |
||||
'eth_newPendingTransactionFilter', |
||||
params.param, |
||||
'Q', |
||||
], |
||||
eth_getUncleCountByBlockHash: [ |
||||
'eth_getUncleCountByBlockHash', |
||||
[params.blockHashParams], |
||||
'Q', |
||||
1, |
||||
], |
||||
eth_getBlockTransactionCountByHash: [ |
||||
'eth_getBlockTransactionCountByHash', |
||||
[params.blockHashParams], |
||||
'Q', |
||||
1, |
||||
], |
||||
eth_getTransactionCount: [ |
||||
'eth_getTransactionCount', |
||||
[params.addressParams, params.blockParameterParams], |
||||
'Q', |
||||
1, |
||||
2, |
||||
], |
||||
eth_getBalance: ['eth_getBalance', [params.addressParams, 'latest'], 'Q', 1, 2], |
||||
eth_estimateGas: ['eth_estimateGas', [params.estimateTransaction], 'Q', 1], |
||||
eth_getUncleCountByBlockNumber: [ |
||||
'eth_getUncleCountByBlockNumber', |
||||
[params.blockParameterParams], |
||||
'Q', |
||||
1, |
||||
], |
||||
eth_getBlockTransactionCountByNumber: [ |
||||
'eth_getBlockTransactionCountByNumber', |
||||
['latest'], |
||||
'Q', |
||||
1, |
||||
], |
||||
eth_protocolVersion: ['eth_protocolVersion', params.param, 'S'], |
||||
eth_getCode: ['eth_getCode', params.getCodeParams, 'D', 1, 2], |
||||
}, |
||||
booleanMethods: { |
||||
// these are the methods which have output in the form of boolean
|
||||
eth_uninstallFilter: ['eth_uninstallFilter', params.filterParams, 'B', 1], |
||||
eth_mining: ['eth_mining', params.param, 'B'], |
||||
eth_syncing: ['eth_syncing', params.param, 'B|EthSyncing'], |
||||
}, |
||||
transactionMethods: { |
||||
// these are the methods which have output in the form of transaction object
|
||||
eth_getTransactionByHash: [ |
||||
'eth_getTransactionByHash', |
||||
params.transactionHashParams, |
||||
params.transaction, |
||||
1, |
||||
], |
||||
eth_getTransactionByBlockHashAndIndex: [ |
||||
'eth_getTransactionByBlockHashAndIndex', |
||||
params.blockHashAndIndexParams, |
||||
params.transaction, |
||||
2, |
||||
], |
||||
eth_getTransactionByBlockNumberAndIndex: [ |
||||
'eth_getTransactionByBlockNumberAndIndex', |
||||
[params.blockParameterParams, '0x0'], |
||||
params.transaction, |
||||
2, |
||||
], |
||||
|
||||
}, |
||||
blockMethods: { |
||||
// these are the methods which have output in the form of a block
|
||||
|
||||
eth_getUncleByBlockNumberAndIndex: [ |
||||
'eth_getUncleByBlockNumberAndIndex', |
||||
params.uncleByBlockNumberAndIndexParams, |
||||
params.block, |
||||
2, |
||||
], |
||||
eth_getBlockByHash: [ |
||||
'eth_getBlockByHash', |
||||
[params.params, false], |
||||
params.block, |
||||
2, |
||||
], |
||||
eth_getBlockByNumber: [ |
||||
'eth_getBlockByNumber', |
||||
[params.blockParameterParams, false], |
||||
params.block, |
||||
2, |
||||
], |
||||
}, |
||||
|
||||
methods: { |
||||
// these are the methods which have output in the form of bytes data
|
||||
|
||||
eth_call: ['eth_call', [params.estimateTransaction, 'latest'], 'D', 1, 2], |
||||
eth_getStorageAt: ['eth_getStorageAt', params.getStorageAtParams, 'D', 2, 2], |
||||
eth_getTransactionReceipt: [ |
||||
'eth_getTransactionReceipt', |
||||
params.transactionHashParams, |
||||
params.receipt, |
||||
1, |
||||
], |
||||
|
||||
}, |
||||
|
||||
} |
||||
|
@ -0,0 +1,34 @@ |
||||
/* eslint no-undef: 0 */ |
||||
|
||||
var json = methods |
||||
|
||||
web3.currentProvider.enable().then(() => { |
||||
|
||||
Object.keys(json).forEach(methodGroupKey => { |
||||
|
||||
console.log(methodGroupKey) |
||||
const methodGroup = json[methodGroupKey] |
||||
console.log(methodGroup) |
||||
Object.keys(methodGroup).forEach(methodKey => { |
||||
|
||||
const methodButton = document.getElementById(methodKey) |
||||
methodButton.addEventListener('click', function () { |
||||
|
||||
window.ethereum.sendAsync({ |
||||
method: methodKey, |
||||
params: methodGroup[methodKey][1], |
||||
}, function (err, result) { |
||||
if (err) { |
||||
console.log(err) |
||||
console.log(methodKey) |
||||
} else { |
||||
document.getElementById('results').innerHTML = JSON.stringify(result) |
||||
} |
||||
}) |
||||
}) |
||||
|
||||
}) |
||||
|
||||
}) |
||||
}) |
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue