commit
be91171194
@ -1,52 +0,0 @@ |
||||
<html> |
||||
<head> |
||||
<title>MetaMask</title> |
||||
<style> |
||||
*{ |
||||
padding: 0; |
||||
margin: 0; |
||||
box-sizing: border-box; |
||||
} |
||||
img{ |
||||
display: block; |
||||
} |
||||
html, body{ |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
width: 100%; |
||||
height: 100%; |
||||
} |
||||
.app{ |
||||
position: relative; |
||||
width: 100%; |
||||
height: auto; |
||||
overflow: hidden; |
||||
} |
||||
img{ |
||||
display: block; |
||||
width: 100%; |
||||
height: auto; |
||||
} |
||||
h2{ |
||||
display: block; |
||||
width: 100%; |
||||
overflow: hidden; |
||||
position: absolute; |
||||
bottom: 20%; |
||||
left: 0; |
||||
color: #1b243d; |
||||
text-align: center; |
||||
} |
||||
h2 > a{ |
||||
color: #1b243d; |
||||
} |
||||
</style> |
||||
</head> |
||||
<body> |
||||
<div class="app"> |
||||
<img src="./images/404.png" alt=""> |
||||
<h2>Powered by <a href="https://www.portal.network/">Portal Network</a></h2> |
||||
</div> |
||||
</body> |
||||
</html> |
@ -1,79 +0,0 @@ |
||||
<html> |
||||
<head> |
||||
<title>MetaMask Error</title> |
||||
<link href="https://fonts.googleapis.com/css?family=Rokkitt" rel="stylesheet"> |
||||
<style> |
||||
*{ |
||||
padding: 0; |
||||
margin: 0; |
||||
box-sizing: border-box; |
||||
} |
||||
img{ |
||||
display: block; |
||||
} |
||||
html, body{ |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
width: 100%; |
||||
height: 100%; |
||||
} |
||||
@keyframes logoAmin{ |
||||
from {transform: scale(1);} |
||||
50%{transform: scale(1.1);} |
||||
to {transform: scale(1);} |
||||
} |
||||
.errorBox{ |
||||
width: 70%; |
||||
height: auto; |
||||
overflow: hidden; |
||||
background-image: url("./images/deadface.png"); |
||||
background-repeat: no-repeat; |
||||
background-position: 100% 50%; |
||||
background-size: auto 90%; |
||||
padding: 5px; |
||||
} |
||||
.errorBox > img{ |
||||
width: 100px; |
||||
height: auto; |
||||
margin-bottom: 25px; |
||||
animation: logoAmin 1s infinite linear; |
||||
} |
||||
.errorBox > h1, .errorBox > h2{ |
||||
letter-spacing: 2px; |
||||
} |
||||
.errorBox > h1{ |
||||
color: #9b9b9b; |
||||
font-size: 40px; |
||||
} |
||||
.errorBox > h2{ |
||||
color: #1b243d; |
||||
font-size: 20px; |
||||
padding-top: 5px; |
||||
} |
||||
.errorBox > h2 >a{ |
||||
color: #1b243d; |
||||
} |
||||
.errorBox > h2 >a:hover{ |
||||
color: #44588e; |
||||
} |
||||
|
||||
.errorBox > h1 > span{ |
||||
color: #33559f; |
||||
} |
||||
|
||||
</style> |
||||
</head> |
||||
<body> |
||||
<div class="errorBox"> |
||||
<img src="./images/logo.png" alt=""> |
||||
<h1><span id="name"></span> not found</h1> |
||||
<h2>Powered by <a href="https://www.portal.network/">Portal Network</a></h2> |
||||
</div> |
||||
<script> |
||||
let index = location.href.lastIndexOf("?name=") |
||||
let name = location.href.slice(index + 6) |
||||
document.getElementById("name").innerHTML = name |
||||
</script> |
||||
</body> |
||||
</html> |
After Width: | Height: | Size: 2.5 KiB |
@ -1,205 +0,0 @@ |
||||
const ObservableStore = require('obs-store') |
||||
const extend = require('xtend') |
||||
const log = require('loglevel') |
||||
|
||||
// every ten minutes
|
||||
const POLLING_INTERVAL = 600000 |
||||
|
||||
class CurrencyController { |
||||
|
||||
/** |
||||
* Controller responsible for managing data associated with the currently selected currency. |
||||
* |
||||
* @typedef {Object} CurrencyController |
||||
* @param {object} opts Overrides the defaults for the initial state of this.store |
||||
* @property {array} opts.initState initializes the the state of the CurrencyController. Can contain an |
||||
* currentCurrency, conversionRate and conversionDate properties |
||||
* @property {string} currentCurrency A 2-4 character shorthand that describes a specific currency, currently |
||||
* selected by the user |
||||
* @property {number} conversionRate The conversion rate from ETH to the selected currency. |
||||
* @property {string} conversionDate The date at which the conversion rate was set. Expressed in in milliseconds |
||||
* since midnight of January 1, 1970 |
||||
* @property {number} conversionInterval The id of the interval created by the scheduleConversionInterval method. |
||||
* Used to clear an existing interval on subsequent calls of that method. |
||||
* @property {string} nativeCurrency The ticker/symbol of the native chain currency |
||||
* |
||||
*/ |
||||
constructor (opts = {}) { |
||||
const initState = extend({ |
||||
currentCurrency: 'usd', |
||||
conversionRate: 0, |
||||
conversionDate: 'N/A', |
||||
nativeCurrency: 'ETH', |
||||
}, opts.initState) |
||||
this.store = new ObservableStore(initState) |
||||
} |
||||
|
||||
//
|
||||
// PUBLIC METHODS
|
||||
//
|
||||
|
||||
/** |
||||
* A getter for the nativeCurrency property |
||||
* |
||||
* @returns {string} A 2-4 character shorthand that describes the specific currency |
||||
* |
||||
*/ |
||||
getNativeCurrency () { |
||||
return this.store.getState().nativeCurrency |
||||
} |
||||
|
||||
/** |
||||
* A setter for the nativeCurrency property |
||||
* |
||||
* @param {string} nativeCurrency The new currency to set as the nativeCurrency in the store |
||||
* |
||||
*/ |
||||
setNativeCurrency (nativeCurrency) { |
||||
this.store.updateState({ |
||||
nativeCurrency, |
||||
ticker: nativeCurrency, |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* A getter for the currentCurrency property |
||||
* |
||||
* @returns {string} A 2-4 character shorthand that describes a specific currency, currently selected by the user |
||||
* |
||||
*/ |
||||
getCurrentCurrency () { |
||||
return this.store.getState().currentCurrency |
||||
} |
||||
|
||||
/** |
||||
* A setter for the currentCurrency property |
||||
* |
||||
* @param {string} currentCurrency The new currency to set as the currentCurrency in the store |
||||
* |
||||
*/ |
||||
setCurrentCurrency (currentCurrency) { |
||||
this.store.updateState({ currentCurrency }) |
||||
} |
||||
|
||||
/** |
||||
* A getter for the conversionRate property |
||||
* |
||||
* @returns {string} The conversion rate from ETH to the selected currency. |
||||
* |
||||
*/ |
||||
getConversionRate () { |
||||
return this.store.getState().conversionRate |
||||
} |
||||
|
||||
/** |
||||
* A setter for the conversionRate property |
||||
* |
||||
* @param {number} conversionRate The new rate to set as the conversionRate in the store |
||||
* |
||||
*/ |
||||
setConversionRate (conversionRate) { |
||||
this.store.updateState({ conversionRate }) |
||||
} |
||||
|
||||
/** |
||||
* A getter for the conversionDate property |
||||
* |
||||
* @returns {string} The date at which the conversion rate was set. Expressed in milliseconds since midnight of |
||||
* January 1, 1970 |
||||
* |
||||
*/ |
||||
getConversionDate () { |
||||
return this.store.getState().conversionDate |
||||
} |
||||
|
||||
/** |
||||
* A setter for the conversionDate property |
||||
* |
||||
* @param {number} conversionDate The date, expressed in milliseconds since midnight of January 1, 1970, that the |
||||
* conversionRate was set |
||||
* |
||||
*/ |
||||
setConversionDate (conversionDate) { |
||||
this.store.updateState({ conversionDate }) |
||||
} |
||||
|
||||
/** |
||||
* Updates the conversionRate and conversionDate properties associated with the currentCurrency. Updated info is |
||||
* fetched from an external API |
||||
* |
||||
*/ |
||||
async updateConversionRate () { |
||||
let currentCurrency, nativeCurrency |
||||
try { |
||||
currentCurrency = this.getCurrentCurrency() |
||||
nativeCurrency = this.getNativeCurrency() |
||||
// select api
|
||||
let apiUrl |
||||
if (nativeCurrency === 'ETH') { |
||||
// ETH
|
||||
apiUrl = `https://api.infura.io/v1/ticker/eth${currentCurrency.toLowerCase()}` |
||||
} else { |
||||
// ETC
|
||||
apiUrl = `https://min-api.cryptocompare.com/data/price?fsym=${nativeCurrency.toUpperCase()}&tsyms=${currentCurrency.toUpperCase()}` |
||||
} |
||||
// attempt request
|
||||
let response |
||||
try { |
||||
response = await fetch(apiUrl) |
||||
} catch (err) { |
||||
log.error(new Error(`CurrencyController - Failed to request currency from Infura:\n${err.stack}`)) |
||||
return |
||||
} |
||||
// parse response
|
||||
let rawResponse |
||||
let parsedResponse |
||||
try { |
||||
rawResponse = await response.text() |
||||
parsedResponse = JSON.parse(rawResponse) |
||||
} catch (err) { |
||||
log.error(new Error(`CurrencyController - Failed to parse response "${rawResponse}"`)) |
||||
return |
||||
} |
||||
// set conversion rate
|
||||
if (nativeCurrency === 'ETH') { |
||||
// ETH
|
||||
this.setConversionRate(Number(parsedResponse.bid)) |
||||
this.setConversionDate(Number(parsedResponse.timestamp)) |
||||
} else { |
||||
// ETC
|
||||
if (parsedResponse[currentCurrency.toUpperCase()]) { |
||||
this.setConversionRate(Number(parsedResponse[currentCurrency.toUpperCase()])) |
||||
this.setConversionDate(parseInt((new Date()).getTime() / 1000)) |
||||
} else { |
||||
this.setConversionRate(0) |
||||
this.setConversionDate('N/A') |
||||
} |
||||
} |
||||
} catch (err) { |
||||
// reset current conversion rate
|
||||
log.warn(`MetaMask - Failed to query currency conversion:`, nativeCurrency, currentCurrency, err) |
||||
this.setConversionRate(0) |
||||
this.setConversionDate('N/A') |
||||
// throw error
|
||||
log.error(new Error(`CurrencyController - Failed to query rate for currency "${currentCurrency}":\n${err.stack}`)) |
||||
return |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Creates a new poll, using setInterval, to periodically call updateConversionRate. The id of the interval is |
||||
* stored at the controller's conversionInterval property. If it is called and such an id already exists, the |
||||
* previous interval is clear and a new one is created. |
||||
* |
||||
*/ |
||||
scheduleConversionInterval () { |
||||
if (this.conversionInterval) { |
||||
clearInterval(this.conversionInterval) |
||||
} |
||||
this.conversionInterval = setInterval(() => { |
||||
this.updateConversionRate() |
||||
}, POLLING_INTERVAL) |
||||
} |
||||
} |
||||
|
||||
module.exports = CurrencyController |
@ -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,2 +1,2 @@ |
||||
module.exports = |
||||
[{'constant': true, 'inputs': [{'name': 'interfaceID', 'type': 'bytes4'}], 'name': 'supportsInterface', 'outputs': [{'name': '', 'type': 'bool'}], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'contentTypes', 'type': 'uint256'}], 'name': 'ABI', 'outputs': [{'name': 'contentType', 'type': 'uint256'}, {'name': 'data', 'type': 'bytes'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'x', 'type': 'bytes32'}, {'name': 'y', 'type': 'bytes32'}], 'name': 'setPubkey', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'content', 'outputs': [{'name': 'ret', 'type': 'bytes32'}], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'addr', 'outputs': [{'name': 'ret', 'type': 'address'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'contentType', 'type': 'uint256'}, {'name': 'data', 'type': 'bytes'}], 'name': 'setABI', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'name', 'outputs': [{'name': 'ret', 'type': 'string'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'name', 'type': 'string'}], 'name': 'setName', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'hash', 'type': 'bytes32'}], 'name': 'setContent', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'pubkey', 'outputs': [{'name': 'x', 'type': 'bytes32'}, {'name': 'y', 'type': 'bytes32'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'addr', 'type': 'address'}], 'name': 'setAddr', 'outputs': [], 'payable': false, 'type': 'function'}, {'inputs': [{'name': 'ensAddr', 'type': 'address'}], 'payable': false, 'type': 'constructor'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'a', 'type': 'address'}], 'name': 'AddrChanged', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'hash', 'type': 'bytes32'}], 'name': 'ContentChanged', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'name', 'type': 'string'}], 'name': 'NameChanged', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': true, 'name': 'contentType', 'type': 'uint256'}], 'name': 'ABIChanged', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'x', 'type': 'bytes32'}, {'indexed': false, 'name': 'y', 'type': 'bytes32'}], 'name': 'PubkeyChanged', 'type': 'event'}] |
||||
[{'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'hash', 'type': 'bytes32'}], 'name': 'setContent', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'content', 'outputs': [{'name': '', 'type': 'bytes32'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'interfaceID', 'type': 'bytes4'}], 'name': 'supportsInterface', 'outputs': [{'name': '', 'type': 'bool'}], 'payable': false, 'stateMutability': 'pure', 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'key', 'type': 'string'}, {'name': 'value', 'type': 'string'}], 'name': 'setText', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'contentTypes', 'type': 'uint256'}], 'name': 'ABI', 'outputs': [{'name': 'contentType', 'type': 'uint256'}, {'name': 'data', 'type': 'bytes'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'x', 'type': 'bytes32'}, {'name': 'y', 'type': 'bytes32'}], 'name': 'setPubkey', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'hash', 'type': 'bytes'}], 'name': 'setContenthash', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'addr', 'outputs': [{'name': '', 'type': 'address'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'key', 'type': 'string'}], 'name': 'text', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'contentType', 'type': 'uint256'}, {'name': 'data', 'type': 'bytes'}], 'name': 'setABI', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'name', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'name', 'type': 'string'}], 'name': 'setName', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'contenthash', 'outputs': [{'name': '', 'type': 'bytes'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'pubkey', 'outputs': [{'name': 'x', 'type': 'bytes32'}, {'name': 'y', 'type': 'bytes32'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'addr', 'type': 'address'}], 'name': 'setAddr', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'inputs': [{'name': 'ensAddr', 'type': 'address'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'constructor'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'a', 'type': 'address'}], 'name': 'AddrChanged', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'name', 'type': 'string'}], 'name': 'NameChanged', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': true, 'name': 'contentType', 'type': 'uint256'}], 'name': 'ABIChanged', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'x', 'type': 'bytes32'}, {'indexed': false, 'name': 'y', 'type': 'bytes32'}], 'name': 'PubkeyChanged', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'indexedKey', 'type': 'string'}, {'indexed': false, 'name': 'key', 'type': 'string'}], 'name': 'TextChanged', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'hash', 'type': 'bytes'}], 'name': 'ContenthashChanged', 'type': 'event'}] |
||||
|
@ -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 |
File diff suppressed because it is too large
Load Diff
@ -1,78 +0,0 @@ |
||||
const assert = require('assert') |
||||
const nock = require('nock') |
||||
const CurrencyController = require('../../../../app/scripts/controllers/currency') |
||||
|
||||
describe('currency-controller', function () { |
||||
var currencyController |
||||
|
||||
beforeEach(function () { |
||||
currencyController = new CurrencyController() |
||||
}) |
||||
|
||||
describe('currency conversions', function () { |
||||
describe('#setCurrentCurrency', function () { |
||||
it('should return USD as default', function () { |
||||
assert.equal(currencyController.getCurrentCurrency(), 'usd') |
||||
}) |
||||
|
||||
it('should be able to set to other currency', function () { |
||||
assert.equal(currencyController.getCurrentCurrency(), 'usd') |
||||
currencyController.setCurrentCurrency('JPY') |
||||
var result = currencyController.getCurrentCurrency() |
||||
assert.equal(result, 'JPY') |
||||
}) |
||||
}) |
||||
|
||||
describe('#getConversionRate', function () { |
||||
it('should return undefined if non-existent', function () { |
||||
var result = currencyController.getConversionRate() |
||||
assert.ok(!result) |
||||
}) |
||||
}) |
||||
|
||||
describe('#updateConversionRate', function () { |
||||
it('should retrieve an update for ETH to USD and set it in memory', function (done) { |
||||
this.timeout(15000) |
||||
nock('https://api.infura.io') |
||||
.get('/v1/ticker/ethusd') |
||||
.reply(200, '{"base": "ETH", "quote": "USD", "bid": 288.45, "ask": 288.46, "volume": 112888.17569277, "exchange": "bitfinex", "total_volume": 272175.00106721005, "num_exchanges": 8, "timestamp": 1506444677}') |
||||
|
||||
assert.equal(currencyController.getConversionRate(), 0) |
||||
currencyController.setCurrentCurrency('usd') |
||||
currencyController.updateConversionRate() |
||||
.then(function () { |
||||
var result = currencyController.getConversionRate() |
||||
assert.equal(typeof result, 'number') |
||||
done() |
||||
}).catch(function (err) { |
||||
done(err) |
||||
}) |
||||
}) |
||||
|
||||
it('should work for JPY as well.', function () { |
||||
this.timeout(15000) |
||||
assert.equal(currencyController.getConversionRate(), 0) |
||||
|
||||
nock('https://api.infura.io') |
||||
.get('/v1/ticker/ethjpy') |
||||
.reply(200, '{"base": "ETH", "quote": "JPY", "bid": 32300.0, "ask": 32400.0, "volume": 247.4616071, "exchange": "kraken", "total_volume": 247.4616071, "num_exchanges": 1, "timestamp": 1506444676}') |
||||
|
||||
|
||||
var promise = new Promise( |
||||
function (resolve) { |
||||
currencyController.setCurrentCurrency('jpy') |
||||
currencyController.updateConversionRate().then(function () { |
||||
resolve() |
||||
}) |
||||
}) |
||||
|
||||
promise.then(function () { |
||||
var result = currencyController.getConversionRate() |
||||
assert.equal(typeof result, 'number') |
||||
}).catch(function (done, err) { |
||||
done(err) |
||||
}) |
||||
}) |
||||
}) |
||||
}) |
||||
}) |
@ -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, |
||||
}) |
||||
} |
Loading…
Reference in new issue